diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0a656ad..f461cf8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -393,6 +393,7 @@ jobs: permissions: contents: write id-token: write # keyless cosign over the bundle SHA256SUMS (v1.77) + packages: write # OCI push of the flight component to ghcr.io (v1.128) steps: # relay's MODULE.bazel local_path_override's the four pulseengine rules to # ../, so bazel needs relay in a subdir + the rules as siblings @@ -515,3 +516,58 @@ jobs: echo "::error::Release $TAG has NO falcon-flight-*.wasm asset — the #100/#140 regression. Failing." exit 1 fi + + + # ── Publish the flight component to ghcr.io as an OCI artifact (v1.128) ── + # An ADDITIVE distribution channel: the same verified `falcon:flight` + # component, pushed as an OCI 1.1 artifact with the Component-Model media + # type via `wkg`, so it is `wkg oci pull`-able and indexable by + # wasm.directory (a meta-registry over OCI registries). Cosign-signed by + # immutable DIGEST, like the release bundle. NON-BLOCKING + # (continue-on-error): the primary artifact is the cosign-signed GitHub + # Release above; a registry hiccup must never fail a release. Tighten to + # hard-fail once proven over a few tags. Ref: ghcr.io//falcon-flight. + - name: Publish flight component to ghcr.io (OCI, cosign-signed) + continue-on-error: true + working-directory: relay + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Bind the tag via env — never expand ${{ ... }} inside run: (the + # command-injection convention this file follows throughout). + INPUT_TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + TAG="${INPUT_TAG:-${GITHUB_REF_NAME}}" + # Validate the shape before it reaches any shell command: a release + # tag is falcon-vX.Y.Z (+ optional prerelease). Reject anything else. + if ! printf '%s' "$TAG" | grep -qE '^falcon-v[0-9]+\.[0-9]+\.[0-9]+([-.][A-Za-z0-9.]+)?$'; then + echo "::error::refusing to publish OCI for a malformed tag: $TAG"; exit 1 + fi + MM=$(printf '%s' "$TAG" | sed -E 's/^falcon-v([0-9]+\.[0-9]+).*/\1/') + FULL=$(printf '%s' "$TAG" | sed -E 's/^falcon-v//') + WASM="falcon-flight-v${MM}.wasm" + REPO="ghcr.io/${GITHUB_REPOSITORY_OWNER}/falcon-flight" + + # Pull the exact wasm the release just attached (single source of truth). + gh release download "$TAG" -p "$WASM" --clobber + + # wkg = the wasm-pkg-tools CLI. PINNED (supply-chain), bumped in + # lockstep. Pushes the wasm-component OCI media type wasm.directory + # expects — a plain `oras push` would tag it as a generic blob. + cargo install wkg --locked --version 0.15.1 + + echo "$GHCR_TOKEN" | docker login ghcr.io -u "${GITHUB_ACTOR}" --password-stdin + wkg oci push "${REPO}:${FULL}" "$WASM" | tee push.log + # :latest is a moving convenience pointer — pushed for discovery, + # but NEVER signed (only immutable refs get a signature). + wkg oci push "${REPO}:latest" "$WASM" + + # Sign the IMMUTABLE digest, not a mutable tag. Only if we resolved one. + DIGEST=$(grep -oE 'sha256:[a-f0-9]{64}' push.log | head -1 || true) + if [ -n "$DIGEST" ]; then + cosign sign --yes "${REPO}@${DIGEST}" + echo "::notice::Published + signed ${REPO}@${DIGEST} (tags ${FULL}, latest). Pull: wkg oci pull ${REPO}:${FULL}" + else + echo "::warning::pushed ${REPO}:${FULL} but could not resolve a digest to sign — left unsigned this run" + fi diff --git a/docs/OCI-DISTRIBUTION.md b/docs/OCI-DISTRIBUTION.md new file mode 100644 index 0000000..23e0dd9 --- /dev/null +++ b/docs/OCI-DISTRIBUTION.md @@ -0,0 +1,57 @@ +# OCI distribution + wasm.directory + +The verified flight component (`falcon:flight`, the `falcon-flight-vX.wasm` +artifact) is published to **ghcr.io as an OCI 1.1 artifact** on every tagged +release, in addition to the cosign-signed GitHub Release. This makes it +`wkg oci pull`-able and indexable by [wasm.directory](https://wasm.directory) — +a meta-registry that indexes OCI registries rather than hosting packages itself. + +## What the release does automatically + +`.github/workflows/release.yml` (flight-component job) pushes the exact wasm the +release attached: + +``` +ghcr.io/pulseengine/falcon-flight: # e.g. 1.127.0 +ghcr.io/pulseengine/falcon-flight:latest +``` + +pushed with `wkg` (wasm-pkg-tools) so it carries the Component-Model OCI media +type wasm.directory expects — not a generic blob — and cosign-signed keyless, +like the release bundle. The step is **non-blocking** (`continue-on-error`): a +registry hiccup must never fail the primary signed GitHub Release. It will be +tightened to a hard failure once it has proven itself across a few tags. + +Pull + verify: + +```sh +wkg oci pull ghcr.io/pulseengine/falcon-flight:1.127.0 -o falcon-flight.wasm +cosign verify ghcr.io/pulseengine/falcon-flight:1.127.0 \ + --certificate-identity-regexp \ + 'https://github.com/pulseengine/relay/.github/workflows/release.yml@.*' \ + --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' +``` + +## One-time manual steps (org owner) + +1. **Make the ghcr package public.** ghcr packages are created *private* by + default; wasm.directory can only index a public package. After the first + tagged release populates it, in the org's package settings set + `pulseengine/falcon-flight` visibility to **public** and link it to the + `relay` repo. (One-time; subsequent pushes inherit the setting.) +2. **Register the namespace with wasm.directory.** Per its publishing guide, + add the `pulseengine` namespace → `ghcr.io` mapping to the wasm.directory + registry config (or your account's namespace list), then it indexes the + package. wasm.directory is **alpha** ("indexed data may be incomplete or + reset without notice; don't depend on it in production") — treat the listing + as a visibility channel, not a dependency. The durable, signed artifacts + remain the GitHub Release and the ghcr OCI ref. + +## Scope note + +`falcon:flight`'s current world (`flight-demo`) exports two runnable smoke-test +functions (`run-stabilization`, `run-position-hold`) — it is a *runnable* +verified component, not yet a reusable library exposing a typed control +interface. Publishing it is a provenance/visibility play (a formally-verified +flight component, signed, in the public component ecosystem). A richer typed +`falcon:flight` interface others could import is a separate follow-on.