diff --git a/.github/workflows/README.md b/.github/workflows/README.md
index 927ed1e55..8098b9455 100755
--- a/.github/workflows/README.md
+++ b/.github/workflows/README.md
@@ -12,6 +12,7 @@ This directory contains all GitHub Actions workflows for the starter-gradle proj
| **Security** | `codeql-analysis.yml` | Security scanning with CodeQL | Push to main/minor, daily schedule |
| **Publishing** | `publish-release.yml` | Publish release (Maven, Cargo, npm, Docker) | Tag push `v*.*.*` |
| **Publishing** | `publish-snapshot.yml` | Publish snapshot versions | Manual, daily schedule |
+| **Publishing** | `publish-plugins.yml` | Build/sign/deploy runtime plugins catalog | Plugin tag push, manual dispatch |
| **Publishing** | `_publish.yml` | Reusable publish workflow | Called by other workflows |
| **Automation** | `auto-fix-lockfile.yml` | Auto-update lockfiles | Daily schedule, manual |
| **Automation** | `fix-renovate.yml` | Fix lockfiles for Renovate PRs | Comment `/fix-lock` on PR |
@@ -189,6 +190,34 @@ Calls the reusable `_publish.yml` workflow with:
---
+### `publish-plugins.yml` - Plugin Artifact Publishing
+
+**Purpose**: Builds, signs, verifies, and deploys runtime plugin artifacts and catalog metadata.
+
+**Triggers**:
+
+- Push tag matching `plugin//v`
+- Manual trigger (`workflow_dispatch`)
+
+**What it does**:
+
+1. Resolves target plugin from `package.metadata.corvus.plugin_id`
+2. Builds WASM artifact (`wasm32-wasip1`)
+3. Generates immutable artifact paths under `artifacts///`
+4. Upserts plugin entry into `catalog.json` and preserves existing entries
+5. Signs artifact with cosign (key-based or keyless)
+6. Verifies signature in CI
+7. Builds plugins catalog site
+8. Deploys to Cloudflare Pages (configurable)
+
+**Required secrets/vars for deployment**:
+
+- `CLOUDFLARE_API_TOKEN`
+- `CLOUDFLARE_ACCOUNT_ID`
+- `CLOUDFLARE_PAGES_PROJECT_NAME` (repository variable recommended)
+
+---
+
### `_publish.yml` - Reusable Publishing Workflow
**Purpose**: Internal reusable workflow for publishing release/snapshot artifacts.
diff --git a/.github/workflows/publish-plugins.yml b/.github/workflows/publish-plugins.yml
index 56c0de2b4..32a6079c2 100644
--- a/.github/workflows/publish-plugins.yml
+++ b/.github/workflows/publish-plugins.yml
@@ -2,30 +2,58 @@
name: publish-plugins
on:
+ push:
+ tags:
+ - "plugin/*/v*"
workflow_dispatch:
inputs:
+ plugin_id:
+ description: "Plugin ID (required for manual dispatch)"
+ required: false
+ default: ""
plugin_version:
- description: "Plugin version (semver)"
- required: true
- default: "0.1.0"
+ description: "Plugin version (required for manual dispatch; semver without 'v')"
+ required: false
+ default: ""
+ catalog_base_url:
+ description: "Catalog base URL for immutable artifact links"
+ required: false
+ default: "https://corvus.profiletailors.com"
oci_repository:
description: "OCI repository (example: ghcr.io/org/corvus-plugins/memory-surreal-graphs)"
required: false
+ default: ""
+ deploy_cloudflare_pages:
+ description: "Deploy plugins catalog app to Cloudflare Pages"
+ required: false
+ type: boolean
+ default: true
+ cloudflare_project_name:
+ description: "Cloudflare Pages project name"
+ required: false
+ default: ""
permissions:
contents: read
packages: write
id-token: write
+concurrency:
+ group: plugins-publish-global
+ cancel-in-progress: true
+
jobs:
- build-and-publish-plugin:
+ build-sign-deploy:
runs-on: ubuntu-latest
env:
- PLUGIN_ID: memory.surreal.graphs
- PLUGIN_DIR: clients/agent-runtime/plugins/memory-surreal-graphs
DIST_DIR: clients/agent-runtime/dist/plugins
- OCI_REPOSITORY: ${{ inputs.oci_repository }}
- PLUGIN_VERSION: ${{ inputs.plugin_version }}
+ INPUT_PLUGIN_ID: ${{ inputs.plugin_id }}
+ INPUT_PLUGIN_VERSION: ${{ inputs.plugin_version }}
+ INPUT_CATALOG_BASE_URL: ${{ inputs.catalog_base_url }}
+ INPUT_OCI_REPOSITORY: ${{ inputs.oci_repository }}
+ INPUT_DEPLOY_CF: ${{ inputs.deploy_cloudflare_pages }}
+ INPUT_CF_PROJECT_NAME: ${{ inputs.cloudflare_project_name }}
+ DEFAULT_CF_PROJECT_NAME: ${{ vars.CLOUDFLARE_PAGES_PROJECT_NAME }}
steps:
- name: ✈ Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
@@ -36,94 +64,334 @@ jobs:
toolchain: stable
targets: wasm32-wasip1
+ - name: 🧮 Resolve plugin release target
+ id: meta
+ shell: python
+ run: |
+ import json
+ import os
+ import pathlib
+ import re
+ import tomllib
+
+ event_name = os.environ["GITHUB_EVENT_NAME"]
+ ref_name = os.environ.get("GITHUB_REF_NAME", "")
+
+ input_plugin_id = os.environ.get("INPUT_PLUGIN_ID", "").strip()
+ input_plugin_version = os.environ.get("INPUT_PLUGIN_VERSION", "").strip()
+ input_catalog_base_url = (
+ os.environ.get("INPUT_CATALOG_BASE_URL", "https://corvus.profiletailors.com")
+ .strip()
+ .rstrip("/")
+ )
+ input_oci_repository = os.environ.get("INPUT_OCI_REPOSITORY", "").strip()
+ input_deploy_cf = os.environ.get("INPUT_DEPLOY_CF", "true").strip().lower()
+ input_cf_project_name = os.environ.get("INPUT_CF_PROJECT_NAME", "").strip()
+ default_cf_project_name = os.environ.get("DEFAULT_CF_PROJECT_NAME", "").strip()
+ effective_cf_project_name = input_cf_project_name or default_cf_project_name
+
+ if not input_catalog_base_url:
+ input_catalog_base_url = "https://corvus.profiletailors.com"
+
+ if not input_deploy_cf:
+ input_deploy_cf = "true"
+
+ if not input_catalog_base_url.startswith("https://"):
+ raise SystemExit("catalog_base_url must start with https://")
+
+ tag_plugin_id = ""
+ tag_version = ""
+ tag_match = re.fullmatch(r"plugin/(.+)/v([0-9]+\.[0-9]+\.[0-9]+(?:[-+][A-Za-z0-9_.-]+)?)", ref_name)
+ if tag_match:
+ tag_plugin_id = tag_match.group(1)
+ tag_version = tag_match.group(2)
+
+ if event_name == "push":
+ if not tag_match:
+ raise SystemExit(
+ "push trigger requires tag format plugin//v",
+ )
+ plugin_id = tag_plugin_id
+ plugin_version = tag_version
+ else:
+ if not input_plugin_id or not input_plugin_version:
+ raise SystemExit(
+ "workflow_dispatch requires plugin_id and plugin_version inputs",
+ )
+ plugin_id = input_plugin_id
+ plugin_version = input_plugin_version
+
+ if any(token in plugin_id for token in ["/", "\\", ".."]):
+ raise SystemExit(f"invalid plugin_id: {plugin_id!r}")
+
+ if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:[-+][A-Za-z0-9_.-]+)?", plugin_version):
+ raise SystemExit(
+ "plugin_version must be semver without leading 'v'",
+ )
+
+ plugin_manifests = sorted(
+ pathlib.Path("clients/agent-runtime/plugins").glob("*/Cargo.toml"),
+ )
+ selected_plugin_manifest = None
+ selected_plugin_data = None
+
+ for manifest_path in plugin_manifests:
+ data = tomllib.loads(manifest_path.read_text(encoding="utf-8"))
+ metadata = data.get("package", {}).get("metadata", {}).get("corvus", {})
+ manifest_plugin_id = metadata.get("plugin_id", "").strip()
+ if manifest_plugin_id == plugin_id:
+ selected_plugin_manifest = manifest_path
+ selected_plugin_data = data
+ break
+
+ if selected_plugin_manifest is None or selected_plugin_data is None:
+ raise SystemExit(
+ f"plugin_id {plugin_id!r} not found. Ensure package.metadata.corvus.plugin_id is set.",
+ )
+
+ package = selected_plugin_data.get("package", {})
+ plugin_dir = selected_plugin_manifest.parent
+ manifest_version = str(package.get("version", "")).strip()
+ if not manifest_version:
+ raise SystemExit("plugin Cargo.toml missing package.version")
+ if manifest_version != plugin_version:
+ raise SystemExit(
+ f"version mismatch: requested {plugin_version}, plugin Cargo.toml has {manifest_version}",
+ )
+
+ metadata = package.get("metadata", {}).get("corvus", {})
+ crate_name = str(package.get("name", "")).replace("-", "_")
+ if not crate_name:
+ raise SystemExit("plugin Cargo.toml missing package.name")
+
+ runtime_manifest = pathlib.Path("clients/agent-runtime/Cargo.toml")
+ runtime_data = tomllib.loads(runtime_manifest.read_text(encoding="utf-8"))
+ runtime_version = str(runtime_data.get("package", {}).get("version", "")).strip()
+ if not runtime_version:
+ raise SystemExit("clients/agent-runtime/Cargo.toml missing package.version")
+
+ publisher = str(metadata.get("publisher", "corvus-official")).strip() or "corvus-official"
+ runtime_api = str(metadata.get("runtime_api", "corvus-plugin/v1")).strip() or "corvus-plugin/v1"
+ capabilities = metadata.get("capabilities", ["memory"])
+ if not isinstance(capabilities, list) or not capabilities:
+ raise SystemExit("metadata.capabilities must be a non-empty array")
+ memory_entrypoint = str(metadata.get("memory_entrypoint", "memory_v1")).strip() or "memory_v1"
+ health_entrypoint = str(metadata.get("health_entrypoint", "health_v1")).strip() or "health_v1"
+ tools_entrypoints = metadata.get("tools_entrypoints", [])
+ if not isinstance(tools_entrypoints, list):
+ raise SystemExit("metadata.tools_entrypoints must be an array")
+ limits = {
+ "memory_mb": int(metadata.get("memory_mb", 256)),
+ "fuel": int(metadata.get("fuel", 1_000_000)),
+ "timeout_ms": int(metadata.get("timeout_ms", 10_000)),
+ "max_output_bytes": int(metadata.get("max_output_bytes", 262_144)),
+ }
+
+ artifact_relative_path = (
+ f"artifacts/{plugin_id}/{plugin_version}/{plugin_id}.wasm"
+ )
+ signature_relative_path = f"{artifact_relative_path}.sig"
+ certificate_relative_path = f"{artifact_relative_path}.pem"
+ manifest_relative_path = (
+ f"artifacts/{plugin_id}/{plugin_version}/plugin-manifest.json"
+ )
+ artifact_url = f"{input_catalog_base_url}/{artifact_relative_path}"
+ signature_url = f"{input_catalog_base_url}/{signature_relative_path}"
+ certificate_url = f"{input_catalog_base_url}/{certificate_relative_path}"
+ manifest_url = f"{input_catalog_base_url}/{manifest_relative_path}"
+
+ output = pathlib.Path(os.environ["GITHUB_OUTPUT"])
+ with output.open("a", encoding="utf-8") as fh:
+ fh.write(f"plugin_id={plugin_id}\n")
+ fh.write(f"plugin_version={plugin_version}\n")
+ fh.write(f"plugin_dir={plugin_dir.as_posix()}\n")
+ fh.write(f"wasm_binary_name={crate_name}.wasm\n")
+ fh.write(f"runtime_version={runtime_version}\n")
+ fh.write(f"catalog_base_url={input_catalog_base_url}\n")
+ fh.write(f"oci_repository={input_oci_repository}\n")
+ fh.write(f"deploy_cloudflare_pages={input_deploy_cf}\n")
+ fh.write(f"cloudflare_project_name={effective_cf_project_name}\n")
+ fh.write(f"artifact_relative_path={artifact_relative_path}\n")
+ fh.write(f"signature_relative_path={signature_relative_path}\n")
+ fh.write(f"certificate_relative_path={certificate_relative_path}\n")
+ fh.write(f"manifest_relative_path={manifest_relative_path}\n")
+ fh.write(f"artifact_url={artifact_url}\n")
+ fh.write(f"signature_url={signature_url}\n")
+ fh.write(f"certificate_url={certificate_url}\n")
+ fh.write(f"manifest_url={manifest_url}\n")
+
+ fh.write(f"publisher={publisher}\n")
+ fh.write(f"runtime_api={runtime_api}\n")
+ fh.write(f"memory_entrypoint={memory_entrypoint}\n")
+ fh.write(f"health_entrypoint={health_entrypoint}\n")
+
+ fh.write("capabilities_json< "$DIST_DIR/plugin-manifest.json" < "$DIST_DIR/catalog.json" < "$DIST_DIR/revocations.json" < cosign.sha256
sha256sum --check --strict cosign.sha256
mv "${COSIGN_BINARY}" cosign
chmod +x cosign
- ./cosign sign-blob \
- --yes \
- --key env://COSIGN_PRIVATE_KEY \
- --output-signature "$DIST_DIR/$PLUGIN_ID.wasm.sig" \
- --output-certificate "$DIST_DIR/$PLUGIN_ID.wasm.pem" \
- "$DIST_DIR/$PLUGIN_ID.wasm"
+
+ - name: ✍️ Sign plugin artifact (keyed or keyless)
+ id: sign
+ env:
+ COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
+ COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
+ DIST_DIR: ${{ env.DIST_DIR }}
+ ARTIFACT_RELATIVE_PATH: ${{ steps.meta.outputs.artifact_relative_path }}
+ run: |
+ set -euo pipefail
+
+ artifact_path="$DIST_DIR/$ARTIFACT_RELATIVE_PATH"
+ signature_path="${artifact_path}.sig"
+ certificate_path="${artifact_path}.pem"
+
+ if [ -n "${COSIGN_PRIVATE_KEY:-}" ]; then
+ ./cosign sign-blob \
+ --yes \
+ --key env://COSIGN_PRIVATE_KEY \
+ --output-signature "$signature_path" \
+ "$artifact_path"
+ echo "has_certificate=false" >> "$GITHUB_OUTPUT"
+ else
+ ./cosign sign-blob \
+ --yes \
+ --output-signature "$signature_path" \
+ --output-certificate "$certificate_path" \
+ "$artifact_path"
+ echo "has_certificate=true" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: ✅ Verify plugin signature
+ env:
+ COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
+ DIST_DIR: ${{ env.DIST_DIR }}
+ ARTIFACT_RELATIVE_PATH: ${{ steps.meta.outputs.artifact_relative_path }}
+ run: |
+ set -euo pipefail
+
+ artifact_path="$DIST_DIR/$ARTIFACT_RELATIVE_PATH"
+ signature_path="${artifact_path}.sig"
+ certificate_path="${artifact_path}.pem"
+
+ if [ -n "${COSIGN_PRIVATE_KEY:-}" ]; then
+ ./cosign public-key --key env://COSIGN_PRIVATE_KEY > cosign.pub
+ ./cosign verify-blob \
+ --key cosign.pub \
+ --signature "$signature_path" \
+ "$artifact_path"
+ else
+ ./cosign verify-blob \
+ --certificate "$certificate_path" \
+ --signature "$signature_path" \
+ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
+ --certificate-identity-regexp "https://github.com/${GITHUB_REPOSITORY}/.github/workflows/publish-plugins.yml@.*" \
+ "$artifact_path"
+ fi
- name: 🚀 Optional publish plugin artifact to OCI
- if: ${{ env.OCI_REPOSITORY != '' }}
+ if: ${{ steps.meta.outputs.oci_repository != '' }}
env:
+ OCI_REPOSITORY: ${{ steps.meta.outputs.oci_repository }}
OCI_USERNAME: ${{ secrets.OCI_USERNAME }}
OCI_PASSWORD: ${{ secrets.OCI_PASSWORD }}
+ PLUGIN_VERSION: ${{ steps.meta.outputs.plugin_version }}
+ DIST_DIR: ${{ env.DIST_DIR }}
+ ARTIFACT_RELATIVE_PATH: ${{ steps.meta.outputs.artifact_relative_path }}
+ MANIFEST_RELATIVE_PATH: ${{ steps.meta.outputs.manifest_relative_path }}
run: |
set -euo pipefail
ORAS_VERSION="1.2.3"
@@ -169,6 +494,7 @@ jobs:
echo "Could not find checksum for ${ORAS_TARBALL}"
exit 1
fi
+
printf '%s\n' "${oras_checksum_line}" > oras.sha256
sha256sum --check --strict oras.sha256
@@ -180,21 +506,134 @@ jobs:
./oras login "$registry" -u "$OCI_USERNAME" -p "$OCI_PASSWORD"
fi
- ./oras push "$OCI_REPOSITORY:$PLUGIN_VERSION" \
- "$DIST_DIR/$PLUGIN_ID.wasm:application/wasm" \
- "$DIST_DIR/plugin-manifest.json:application/json" \
- "$DIST_DIR/catalog.json:application/json" \
+ oras_args=(
+ "$DIST_DIR/$ARTIFACT_RELATIVE_PATH:application/wasm"
+ "$DIST_DIR/${ARTIFACT_RELATIVE_PATH}.sig:application/octet-stream"
+ "$DIST_DIR/$MANIFEST_RELATIVE_PATH:application/json"
+ "$DIST_DIR/catalog.json:application/json"
"$DIST_DIR/revocations.json:application/json"
+ )
+
+ certificate_path="$DIST_DIR/${ARTIFACT_RELATIVE_PATH}.pem"
+ if [ -f "$certificate_path" ]; then
+ oras_args+=("$certificate_path:application/x-pem-file")
+ fi
+
+ ./oras push "$OCI_REPOSITORY:$PLUGIN_VERSION" "${oras_args[@]}"
+
+ - name: 📦 Setup pnpm
+ uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
+ with:
+ version: 10
+
+ - name: 📦 Setup Node.js
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
+ with:
+ node-version: 24
+ cache: "pnpm"
+ cache-dependency-path: clients/web/pnpm-lock.yaml
+
+ - name: 🧩 Stage generated catalog assets
+ env:
+ DIST_DIR: ${{ env.DIST_DIR }}
+ ARTIFACT_RELATIVE_PATH: ${{ steps.meta.outputs.artifact_relative_path }}
+ SIGNATURE_RELATIVE_PATH: ${{ steps.meta.outputs.signature_relative_path }}
+ CERTIFICATE_RELATIVE_PATH: ${{ steps.meta.outputs.certificate_relative_path }}
+ MANIFEST_RELATIVE_PATH: ${{ steps.meta.outputs.manifest_relative_path }}
+ run: |
+ set -euo pipefail
+
+ catalog_public="clients/web/apps/plugins/public"
+
+ mkdir -p "$catalog_public/$(dirname "$ARTIFACT_RELATIVE_PATH")"
+ cp "$DIST_DIR/$ARTIFACT_RELATIVE_PATH" "$catalog_public/$ARTIFACT_RELATIVE_PATH"
+ cp "$DIST_DIR/$SIGNATURE_RELATIVE_PATH" "$catalog_public/$SIGNATURE_RELATIVE_PATH"
+ if [ -f "$DIST_DIR/$CERTIFICATE_RELATIVE_PATH" ]; then
+ cp "$DIST_DIR/$CERTIFICATE_RELATIVE_PATH" "$catalog_public/$CERTIFICATE_RELATIVE_PATH"
+ fi
+ cp "$DIST_DIR/$MANIFEST_RELATIVE_PATH" "$catalog_public/$MANIFEST_RELATIVE_PATH"
+ cp "$DIST_DIR/catalog.json" "$catalog_public/catalog.json"
+ cp "$DIST_DIR/revocations.json" "$catalog_public/revocations.json"
+
+ - name: 📥 Install web dependencies
+ working-directory: clients/web
+ run: pnpm install --frozen-lockfile --filter @corvus/plugins-catalog...
+
+ - name: 🏗️ Build plugins catalog site
+ working-directory: clients/web
+ env:
+ PLUGINS_URL: ${{ steps.meta.outputs.catalog_base_url }}
+ run: pnpm run build:plugins
+
+ - name: 🚀 Optional deploy plugins catalog to Cloudflare Pages
+ if: ${{ steps.meta.outputs.deploy_cloudflare_pages == 'true' }}
+ working-directory: clients/web/apps/plugins
+ env:
+ CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ CLOUDFLARE_PROJECT_NAME: ${{ steps.meta.outputs.cloudflare_project_name }}
+ PLUGIN_ID: ${{ steps.meta.outputs.plugin_id }}
+ PLUGIN_VERSION: ${{ steps.meta.outputs.plugin_version }}
+ run: |
+ set -euo pipefail
+
+ if [ -z "${CLOUDFLARE_API_TOKEN:-}" ] || [ -z "${CLOUDFLARE_ACCOUNT_ID:-}" ]; then
+ echo "Missing required Cloudflare secrets: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID"
+ exit 1
+ fi
+
+ if [ -z "${CLOUDFLARE_PROJECT_NAME:-}" ]; then
+ echo "Missing cloudflare_project_name input"
+ exit 1
+ fi
+
+ pnpm dlx wrangler@4.44.0 pages deploy ./dist \
+ --project-name "$CLOUDFLARE_PROJECT_NAME" \
+ --branch main \
+ --commit-hash "$GITHUB_SHA" \
+ --commit-message "publish plugin ${PLUGIN_ID} ${PLUGIN_VERSION}"
- name: ⬆ Upload plugin bundle artifacts
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
- name: plugin-bundle-${{ env.PLUGIN_ID }}-${{ env.PLUGIN_VERSION }}
+ name: plugin-bundle-${{ steps.meta.outputs.plugin_id }}-${{ steps.meta.outputs.plugin_version }}
path: |
- clients/agent-runtime/dist/plugins/${{ env.PLUGIN_ID }}.wasm
+ clients/agent-runtime/dist/plugins/${{ steps.meta.outputs.artifact_relative_path }}
+ clients/agent-runtime/dist/plugins/${{ steps.meta.outputs.signature_relative_path }}
+ clients/agent-runtime/dist/plugins/**/*.pem
+ clients/agent-runtime/dist/plugins/${{ steps.meta.outputs.manifest_relative_path }}
clients/agent-runtime/dist/plugins/plugin-manifest.json
clients/agent-runtime/dist/plugins/catalog.json
clients/agent-runtime/dist/plugins/revocations.json
- clients/agent-runtime/dist/plugins/${{ env.PLUGIN_ID }}.wasm.sig
- clients/agent-runtime/dist/plugins/${{ env.PLUGIN_ID }}.wasm.pem
- if-no-files-found: ignore
+ if-no-files-found: error
+
+ - name: ⬆ Upload plugins catalog site artifact
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ with:
+ name: plugins-catalog-site-${{ steps.meta.outputs.plugin_id }}-${{ steps.meta.outputs.plugin_version }}
+ path: clients/web/apps/plugins/dist
+ if-no-files-found: error
+
+ - name: 📝 Publish summary
+ env:
+ PLUGIN_ID: ${{ steps.meta.outputs.plugin_id }}
+ PLUGIN_VERSION: ${{ steps.meta.outputs.plugin_version }}
+ RUNTIME_VERSION: ${{ steps.meta.outputs.runtime_version }}
+ ARTIFACT_URL: ${{ steps.meta.outputs.artifact_url }}
+ MANIFEST_URL: ${{ steps.meta.outputs.manifest_url }}
+ CATALOG_URL: ${{ steps.meta.outputs.catalog_base_url }}/catalog.json
+ REVOCATIONS_URL: ${{ steps.meta.outputs.catalog_base_url }}/revocations.json
+ ARTIFACT_DIGEST: ${{ steps.bundle.outputs.artifact_digest }}
+ run: |
+ {
+ echo "## Plugin publish summary"
+ echo
+ echo "- Plugin ID: \`$PLUGIN_ID\`"
+ echo "- Plugin version: \`$PLUGIN_VERSION\`"
+ echo "- Min agent version: \`$RUNTIME_VERSION\`"
+ echo "- Artifact URL: \`$ARTIFACT_URL\`"
+ echo "- Manifest URL: \`$MANIFEST_URL\`"
+ echo "- Catalog URL: \`$CATALOG_URL\`"
+ echo "- Revocations URL: \`$REVOCATIONS_URL\`"
+ echo "- Artifact digest: \`$ARTIFACT_DIGEST\`"
+ } >> "$GITHUB_STEP_SUMMARY"
diff --git a/.lycheeignore b/.lycheeignore
index c75ca5d84..658da9b83 100644
--- a/.lycheeignore
+++ b/.lycheeignore
@@ -9,6 +9,9 @@
# Mend.io returns 403 Forbidden to bots
^https://www\.mend\.io/.*
+# GNU Make website is intermittently timing out from CI/hook network
+^https://www\.gnu\.org/software/make/?$
+
# Checkstyle header file contains regex patterns that look like URLs
gradle/configs/checkstyle/checkstyle-header-file\.txt
diff --git a/clients/agent-runtime/plugins/memory-surreal-graphs/Cargo.toml b/clients/agent-runtime/plugins/memory-surreal-graphs/Cargo.toml
index ac9ebf649..c228712e5 100644
--- a/clients/agent-runtime/plugins/memory-surreal-graphs/Cargo.toml
+++ b/clients/agent-runtime/plugins/memory-surreal-graphs/Cargo.toml
@@ -5,6 +5,19 @@ edition = "2021"
license = "Apache-2.0"
description = "Pilot WASM plugin artifact for Corvus surreal graph memory backend"
+[package.metadata.corvus]
+plugin_id = "memory.surreal.graphs"
+publisher = "corvus-official"
+runtime_api = "corvus-plugin/v1"
+capabilities = ["memory"]
+memory_entrypoint = "memory_v1"
+health_entrypoint = "health_v1"
+tools_entrypoints = []
+memory_mb = 256
+fuel = 1000000
+timeout_ms = 10000
+max_output_bytes = 262144
+
[lib]
crate-type = ["cdylib"]
diff --git a/clients/agent-runtime/src/onboard/wizard.rs b/clients/agent-runtime/src/onboard/wizard.rs
index 37138658e..455c43fc5 100755
--- a/clients/agent-runtime/src/onboard/wizard.rs
+++ b/clients/agent-runtime/src/onboard/wizard.rs
@@ -2769,10 +2769,15 @@ fn setup_memory() -> Result {
style("ℹ").cyan()
);
let mut bootstrap = plugin_bootstrap_config(backend)?;
- install_and_handle_surreal_graphs(&mut bootstrap, false)?;
+ install_and_handle_surreal_graphs(&mut bootstrap, true)?;
+ if bootstrap.memory.backend != backend {
+ let original_auto_save = config.auto_save;
+ config = bootstrap.memory;
+ config.auto_save = original_auto_save;
+ }
}
- if requires_surreal_memory_setup(backend) {
+ if requires_surreal_memory_setup(&config.backend) {
setup_surreal_memory_options(&mut config)?;
}
diff --git a/clients/web/apps/docs/src/content/docs/en/404.mdx b/clients/web/apps/docs/src/content/docs/en/404.mdx
index 14c14978f..d9b08e8b7 100644
--- a/clients/web/apps/docs/src/content/docs/en/404.mdx
+++ b/clients/web/apps/docs/src/content/docs/en/404.mdx
@@ -1,19 +1,19 @@
---
-title: '404'
+title: "404"
template: splash
editUrl: false
hero:
-title: '404'
-tagline: Page not found. The content may have moved during Corvus customization.
-actions:
-- text: Back to Home
-link: /
-icon: right-arrow
-variant: primary
-- text: View Documentation
-link: ../guides/getting-started/
-icon: open-book
-variant: minimal
+ title: "404"
+ tagline: Page not found. The content may have moved during Corvus customization.
+ actions:
+ - text: Back to Home
+ link: /
+ icon: right-arrow
+ variant: primary
+ - text: View Documentation
+ link: ../guides/getting-started/
+ icon: open-book
+ variant: minimal
---
## Looking for something?
diff --git a/clients/web/apps/docs/src/content/docs/en/guides/plugins.md b/clients/web/apps/docs/src/content/docs/en/guides/plugins.md
index 9056cec0a..b80cc4862 100644
--- a/clients/web/apps/docs/src/content/docs/en/guides/plugins.md
+++ b/clients/web/apps/docs/src/content/docs/en/guides/plugins.md
@@ -114,22 +114,45 @@ Workflow file:
Current workflow behavior:
-1. Build WASM plugin artifact.
-2. Assemble bundle metadata:
- - `plugin-manifest.json`
- - `catalog.json`
- - `revocations.json`
-3. Optionally sign with cosign key (if secret is present).
-4. Optionally push to OCI (if `oci_repository` input is provided).
-5. Upload bundle artifacts.
+1. Trigger automatically on plugin release tags: `plugin//v`.
+2. Resolve plugin directory dynamically from `package.metadata.corvus.plugin_id` in each
+ plugin `Cargo.toml`.
+3. Build WASM plugin artifact for `wasm32-wasip1`.
+4. Assemble immutable bundle metadata and artifacts:
+ - `artifacts///.wasm`
+ - `artifacts///plugin-manifest.json`
+ - root `catalog.json` (upsert plugin entry, keep others)
+ - root `revocations.json` (preserve list, refresh `updated_at`)
+5. Sign artifact with cosign (key-based when `COSIGN_PRIVATE_KEY` is set, otherwise keyless
+ OIDC).
+6. Verify signature in CI.
+7. Optionally push artifact bundle to OCI (`oci_repository`).
+8. Build plugins catalog app and deploy to Cloudflare Pages (enabled by default for release tags).
+9. Upload build + bundle artifacts as workflow artifacts for traceability.
:::important
-This workflow is currently configured for `memory.surreal.graphs` in job env defaults. For a new
-plugin, either:
+To onboard a new plugin into automated releases:
-1. Adapt env values for the new plugin, or
-2. Generalize workflow inputs/matrix so plugin ID/folder/artifact name are parameters.
- :::
+1. Create it under `clients/agent-runtime/plugins//`.
+2. Add `package.metadata.corvus.plugin_id` to its `Cargo.toml`.
+3. Set plugin limits/capabilities in `package.metadata.corvus`.
+4. Create a release tag: `plugin//v`.
+
+No workflow code changes are required for new plugins when metadata is present.
+:::
+
+Release example:
+
+```bash
+git tag plugin/memory.surreal.graphs/v0.1.0
+git push origin plugin/memory.surreal.graphs/v0.1.0
+```
+
+Cloudflare deployment configuration expected by the workflow:
+
+- Secret: `CLOUDFLARE_API_TOKEN`
+- Secret: `CLOUDFLARE_ACCOUNT_ID`
+- Repository variable: `CLOUDFLARE_PAGES_PROJECT_NAME`
## 6. Operator Commands (Runtime)
diff --git a/clients/web/apps/docs/src/content/docs/en/index.mdx b/clients/web/apps/docs/src/content/docs/en/index.mdx
index c00bf7687..10cd92ec4 100644
--- a/clients/web/apps/docs/src/content/docs/en/index.mdx
+++ b/clients/web/apps/docs/src/content/docs/en/index.mdx
@@ -3,15 +3,15 @@ title: Corvus
description: Reactive agent platform for always-on orchestration on the JVM.
template: splash
hero:
-tagline: Build long-running, proactive agents with Kotlin, Spring Boot, Neo4j, Rust sidecars, and a transparent control panel.
-actions:
-- text: Get Started
-link: guides/getting-started/
-icon: right-arrow
-- text: View on GitHub
-link: https://github.com/dallay/corvus
-icon: external
-variant: minimal
+ tagline: Build long-running, proactive agents with Kotlin, Spring Boot, Neo4j, Rust sidecars, and a transparent control panel.
+ actions:
+ - text: Get Started
+ link: guides/getting-started/
+ icon: right-arrow
+ - text: View on GitHub
+ link: https://github.com/dallay/corvus
+ icon: external
+ variant: minimal
---
diff --git a/clients/web/apps/docs/src/content/docs/es/404.mdx b/clients/web/apps/docs/src/content/docs/es/404.mdx
index 606fb3f22..60a72520b 100644
--- a/clients/web/apps/docs/src/content/docs/es/404.mdx
+++ b/clients/web/apps/docs/src/content/docs/es/404.mdx
@@ -1,19 +1,19 @@
---
-title: '404'
+title: "404"
template: splash
editUrl: false
hero:
-title: '404'
-tagline: Página no encontrada. El contenido pudo moverse durante la personalización de Corvus.
-actions:
-- text: Volver al Inicio
-link: /
-icon: right-arrow
-variant: primary
-- text: Ver Documentación
-link: ../guides/getting-started/
-icon: open-book
-variant: minimal
+ title: "404"
+ tagline: Página no encontrada. El contenido pudo moverse durante la personalización de Corvus.
+ actions:
+ - text: Volver al Inicio
+ link: /
+ icon: right-arrow
+ variant: primary
+ - text: Ver Documentación
+ link: ../guides/getting-started/
+ icon: open-book
+ variant: minimal
---
## ¿Buscas algo?
diff --git a/clients/web/apps/docs/src/content/docs/es/guides/plugins.md b/clients/web/apps/docs/src/content/docs/es/guides/plugins.md
index 016b0245e..b239c9d59 100644
--- a/clients/web/apps/docs/src/content/docs/es/guides/plugins.md
+++ b/clients/web/apps/docs/src/content/docs/es/guides/plugins.md
@@ -115,23 +115,46 @@ Archivo de workflow:
Comportamiento actual del workflow:
-1. Build del artefacto WASM del plugin.
-2. Ensamblar metadatos del bundle:
- - `plugin-manifest.json`
- - `catalog.json`
- - `revocations.json`
-3. Opcionalmente firmar con clave cosign (si el secret está presente).
-4. Opcionalmente push a OCI (si el input `oci_repository` es proporcionado).
-5. Upload de artefactos del bundle.
+1. Se dispara automáticamente con tags de release: `plugin//v`.
+2. Resuelve dinámicamente la carpeta del plugin usando `package.metadata.corvus.plugin_id` en
+ cada `Cargo.toml`.
+3. Hace build del artefacto WASM para `wasm32-wasip1`.
+4. Ensambla bundle inmutable con artefactos/metadatos:
+ - `artifacts///.wasm`
+ - `artifacts///plugin-manifest.json`
+ - `catalog.json` raíz (upsert del plugin, preservando los demás)
+ - `revocations.json` raíz (preserva la lista, actualiza `updated_at`)
+5. Firma el artefacto con cosign (con clave cuando existe `COSIGN_PRIVATE_KEY`, o bien keyless
+ OIDC si no existe).
+6. Verifica la firma en CI.
+7. Push opcional del bundle a OCI (`oci_repository`).
+8. Hace build de la app de catálogo y deploy a Cloudflare Pages (habilitado por defecto para tags de
+ release).
+9. Sube artefactos del build y del bundle para trazabilidad.
:::important
-Este workflow está actualmente configurado para `memory.surreal.graphs` en los defaults de env del
-job. Para un nuevo plugin, ya sea:
+Para integrar un nuevo plugin al release automático:
-1. Adaptar los valores de env para el nuevo plugin, o
-2. Generalizar los inputs/matrix del workflow para que plugin ID/folder/nombre de artefacto sean
- parámetros.
- :::
+1. Créalo en `clients/agent-runtime/plugins//`.
+2. Agrega `package.metadata.corvus.plugin_id` en su `Cargo.toml`.
+3. Define límites/capacidades en `package.metadata.corvus`.
+4. Crea un tag de release: `plugin//v`.
+
+Con metadata correcta, no hace falta cambiar el workflow para plugins nuevos.
+:::
+
+Ejemplo de release:
+
+```bash
+git tag plugin/memory.surreal.graphs/v0.1.0
+git push origin plugin/memory.surreal.graphs/v0.1.0
+```
+
+Configuración de Cloudflare esperada por el workflow:
+
+- Secret: `CLOUDFLARE_API_TOKEN`
+- Secret: `CLOUDFLARE_ACCOUNT_ID`
+- Variable de repositorio: `CLOUDFLARE_PAGES_PROJECT_NAME`
## 6. Comandos de Operador (Runtime)
diff --git a/clients/web/apps/docs/src/content/docs/es/index.mdx b/clients/web/apps/docs/src/content/docs/es/index.mdx
index 7630baeef..c012b0e43 100644
--- a/clients/web/apps/docs/src/content/docs/es/index.mdx
+++ b/clients/web/apps/docs/src/content/docs/es/index.mdx
@@ -3,15 +3,15 @@ title: Corvus
description: Plataforma reactiva de agentes siempre activos sobre JVM.
template: splash
hero:
-tagline: Construye agentes proactivos y de larga duración con Kotlin, Spring Boot, Neo4j, sidecars en Rust y un panel transparente.
-actions:
-- text: Primeros Pasos
-link: guides/getting-started/
-icon: right-arrow
-- text: Ver en GitHub
-link: https://github.com/dallay/corvus
-icon: external
-variant: minimal
+ tagline: Construye agentes proactivos y de larga duración con Kotlin, Spring Boot, Neo4j, sidecars en Rust y un panel transparente.
+ actions:
+ - text: Primeros Pasos
+ link: guides/getting-started/
+ icon: right-arrow
+ - text: Ver en GitHub
+ link: https://github.com/dallay/corvus
+ icon: external
+ variant: minimal
---
diff --git a/clients/web/apps/docs/src/pages/404.astro b/clients/web/apps/docs/src/pages/404.astro
deleted file mode 100644
index 416dcf8e4..000000000
--- a/clients/web/apps/docs/src/pages/404.astro
+++ /dev/null
@@ -1,273 +0,0 @@
----
-// biome-ignore lint/correctness/noUnusedImports: Used in Astro template below.
-import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro'
-// biome-ignore lint/correctness/noUnusedImports: Used in Astro template below.
-import { Icon } from '@astrojs/starlight/components'
-
-const baseUrl = import.meta.env.BASE_URL
-// biome-ignore lint/correctness/noUnusedVariables: Used in Astro template below.
-const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`
-
-// Detect locale from pathname (e.g., /corvus/es/...)
-const pathname = Astro.url.pathname
-const isEs = pathname.includes('/es/')
-const locale = isEs ? 'es' : 'en'
-
-const t = {
- en: {
- title: '404',
- tagline: "The page you're looking for has disappeared from the repository.",
- home: 'Back to Home',
- repo: 'View Repository',
- gettingStarted: 'Getting Started',
- structure: 'Project Structure',
- },
- es: {
- title: '404',
- tagline: 'La página que buscas ha desaparecido en el repositorio.',
- home: 'Volver al inicio',
- repo: 'Ver Repositorio',
- gettingStarted: 'Primeros pasos',
- structure: 'Estructura',
- },
-}
-
-// biome-ignore lint/correctness/noUnusedVariables: Used in Astro template below.
-const labels = t[locale]
----
-
-
-
-
-
-
404
-
-
{labels.tagline}
-
-
-
-
-
-
-
-
-
diff --git a/clients/web/apps/plugins/README.md b/clients/web/apps/plugins/README.md
index af2bd0dac..6cc8c1568 100644
--- a/clients/web/apps/plugins/README.md
+++ b/clients/web/apps/plugins/README.md
@@ -4,6 +4,7 @@ Dedicated site for publishing official runtime plugin metadata:
- `catalog.json`
- `revocations.json`
+- versioned immutable plugin artifacts under `artifacts///`
## Purpose
@@ -38,3 +39,32 @@ PLUGINS_URL=https://staging-catalog.profiletailors.com pnpm build:plugins
- Publish `catalog.json` and `revocations.json` at the site root.
- Keep `revocations.json` uncached (`no-store`) for immediate revocation propagation.
+- Publish WASM artifacts at immutable, versioned paths.
+ - Example: `/artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm`
+ - Use long-lived immutable cache headers for artifact paths.
+
+## Automated Plugin Release (Cloudflare Pages)
+
+Plugin publishing is automated by `.github/workflows/publish-plugins.yml`.
+
+- Automatic trigger (recommended): push a tag using format
+ `plugin//v`.
+- Manual trigger: workflow dispatch with `plugin_id` + `plugin_version`.
+
+Required plugin metadata in each plugin `Cargo.toml`:
+
+```toml
+[package.metadata.corvus]
+plugin_id = "memory.surreal.graphs"
+```
+
+Required repository secrets/variables for Cloudflare deployment:
+
+- Secret: `CLOUDFLARE_API_TOKEN`
+- Secret: `CLOUDFLARE_ACCOUNT_ID`
+- Variable (recommended): `CLOUDFLARE_PAGES_PROJECT_NAME`
+
+Optional signing/publishing secrets:
+
+- `COSIGN_PRIVATE_KEY` and `COSIGN_PASSWORD` (key-based signing)
+- `OCI_USERNAME` and `OCI_PASSWORD` (when publishing to OCI)
diff --git a/clients/web/apps/plugins/public/_headers b/clients/web/apps/plugins/public/_headers
index a9c1aab62..c628370c8 100644
--- a/clients/web/apps/plugins/public/_headers
+++ b/clients/web/apps/plugins/public/_headers
@@ -7,3 +7,43 @@
Content-Type: application/json; charset=utf-8
Cache-Control: no-store, max-age=0
X-Content-Type-Options: nosniff
+
+/artifacts/*.wasm
+ Content-Type: application/wasm
+ Cache-Control: public, max-age=31536000, immutable
+ Access-Control-Allow-Origin: *
+ Access-Control-Allow-Methods: GET, OPTIONS
+ Access-Control-Allow-Headers: Content-Type
+ X-Content-Type-Options: nosniff
+
+/artifacts/*.json
+ Content-Type: application/json; charset=utf-8
+ Cache-Control: public, max-age=31536000, immutable
+ Access-Control-Allow-Origin: *
+ Access-Control-Allow-Methods: GET, OPTIONS
+ Access-Control-Allow-Headers: Content-Type
+ X-Content-Type-Options: nosniff
+
+/artifacts/*.sig
+ Content-Type: application/octet-stream
+ Cache-Control: public, max-age=31536000, immutable
+ Access-Control-Allow-Origin: *
+ Access-Control-Allow-Methods: GET, OPTIONS
+ Access-Control-Allow-Headers: Content-Type
+ X-Content-Type-Options: nosniff
+
+/artifacts/*.pem
+ Content-Type: application/x-pem-file
+ Cache-Control: public, max-age=31536000, immutable
+ Access-Control-Allow-Origin: *
+ Access-Control-Allow-Methods: GET, OPTIONS
+ Access-Control-Allow-Headers: Content-Type
+ X-Content-Type-Options: nosniff
+
+/artifacts/*
+ Content-Type: application/octet-stream
+ Cache-Control: public, max-age=31536000, immutable
+ Access-Control-Allow-Origin: *
+ Access-Control-Allow-Methods: GET, OPTIONS
+ Access-Control-Allow-Headers: Content-Type
+ X-Content-Type-Options: nosniff
diff --git a/clients/web/apps/plugins/public/artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm b/clients/web/apps/plugins/public/artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm
new file mode 100755
index 000000000..02b7bdab6
Binary files /dev/null and b/clients/web/apps/plugins/public/artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm differ
diff --git a/clients/web/apps/plugins/public/artifacts/memory.surreal.graphs/0.1.0/plugin-manifest.json b/clients/web/apps/plugins/public/artifacts/memory.surreal.graphs/0.1.0/plugin-manifest.json
new file mode 100644
index 000000000..7cecf8982
--- /dev/null
+++ b/clients/web/apps/plugins/public/artifacts/memory.surreal.graphs/0.1.0/plugin-manifest.json
@@ -0,0 +1,28 @@
+{
+ "id": "memory.surreal.graphs",
+ "version": "0.1.0",
+ "digest": "sha256:eb7bc57182fece32470868b953df8330e35e64adf3e73c51714bfc91c98a8a61",
+ "publisher": "corvus-official",
+ "runtime_api": "corvus-plugin/v1",
+ "min_agent_version": "0.1.5",
+ "capabilities": ["memory"],
+ "entrypoints": {
+ "memory": "memory_v1",
+ "health": "health_v1",
+ "tools": []
+ },
+ "limits": {
+ "memory_mb": 256,
+ "fuel": 1000000,
+ "timeout_ms": 10000,
+ "max_output_bytes": 262144
+ },
+ "artifact": {
+ "url": "https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm",
+ "digest": "sha256:eb7bc57182fece32470868b953df8330e35e64adf3e73c51714bfc91c98a8a61"
+ },
+ "signature": {
+ "url": "https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm.sig",
+ "certificate_url": "https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm.pem"
+ }
+}
diff --git a/clients/web/apps/plugins/public/catalog.json b/clients/web/apps/plugins/public/catalog.json
index a5b818f53..fbdd21e2c 100644
--- a/clients/web/apps/plugins/public/catalog.json
+++ b/clients/web/apps/plugins/public/catalog.json
@@ -1,3 +1,32 @@
{
- "plugins": []
+ "plugins": [
+ {
+ "id": "memory.surreal.graphs",
+ "version": "0.1.0",
+ "digest": "sha256:eb7bc57182fece32470868b953df8330e35e64adf3e73c51714bfc91c98a8a61",
+ "publisher": "corvus-official",
+ "runtime_api": "corvus-plugin/v1",
+ "min_agent_version": "0.1.5",
+ "capabilities": ["memory"],
+ "entrypoints": {
+ "memory": "memory_v1",
+ "health": "health_v1",
+ "tools": []
+ },
+ "limits": {
+ "memory_mb": 256,
+ "fuel": 1000000,
+ "timeout_ms": 10000,
+ "max_output_bytes": 262144
+ },
+ "artifact": {
+ "url": "https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm",
+ "digest": "sha256:eb7bc57182fece32470868b953df8330e35e64adf3e73c51714bfc91c98a8a61"
+ },
+ "signature": {
+ "url": "https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm.sig",
+ "certificate_url": "https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm.pem"
+ }
+ }
+ ]
}
diff --git a/clients/web/apps/plugins/src/pages/index.astro b/clients/web/apps/plugins/src/pages/index.astro
index 87ed85bc3..523c010aa 100644
--- a/clients/web/apps/plugins/src/pages/index.astro
+++ b/clients/web/apps/plugins/src/pages/index.astro
@@ -24,9 +24,21 @@ import CatalogLayout from "../layouts/CatalogLayout.astro";
Revocations
/revocations.json
+
+ Immutable artifact
+ /artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm
+
+
+ Immutable manifest
+ /artifacts/memory.surreal.graphs/0.1.0/plugin-manifest.json
+
Runtime defaults point to these URLs and treat them as official metadata sources.
+
+ The artifact and manifest paths below are immutable versioned examples. For the latest
+ release metadata, always use /catalog.json.
+
@@ -36,6 +48,12 @@ import CatalogLayout from "../layouts/CatalogLayout.astro";
curl -fsSL https://corvus.profiletailors.com/revocations.json
+
+ curl -fsSL https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm -o memory.surreal.graphs.wasm
+
+
+ curl -fsSL https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/plugin-manifest.json
+