Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 32 additions & 19 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ jobs:
# (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/<owner>/falcon-flight.
- name: Publish flight component to ghcr.io (OCI, cosign-signed)
- name: Publish cascade components to ghcr.io (OCI, cosign-signed)
continue-on-error: true
working-directory: relay
env:
Expand All @@ -546,28 +546,41 @@ jobs:
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
# Per-component OCI publish (jess#167 decision 5): each fine-grained
# verified cascade stage is its own cosign-signed OCI entity, so jess
# can `wkg oci pull` and fuse/lower/place per core. The bundle dir was
# produced WASI-free (--no-default-features) by the build step above;
# push straight from it (no re-download). `flight` is the runnable
# demo; the rest are the composable stages.
SLUGS="flight iekf position attitude rate mixer"
BUNDLE="dist/falcon-components-v${MM}"

# 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

published=0
for slug in $SLUGS; do
WASM="${BUNDLE}/falcon-${slug}-v${MM}.wasm"
REPO="ghcr.io/${GITHUB_REPOSITORY_OWNER}/falcon-${slug}"
if [ ! -f "$WASM" ]; then
echo "::warning::missing ${WASM} — skipping falcon-${slug}"; continue
fi
wkg oci push "${REPO}:${FULL}" "$WASM" | tee "push-${slug}.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-${slug}.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}"
published=$((published + 1))
else
echo "::warning::pushed ${REPO}:${FULL} but could not resolve a digest to sign — left unsigned this run"
fi
done
echo "::notice::per-component OCI publish: ${published} components signed + pushed"
27 changes: 18 additions & 9 deletions docs/OCI-DISTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,28 @@ 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:
`.github/workflows/release.yml` (flight-component job) publishes **each verified
cascade stage as its own cosign-signed OCI entity** (jess#167 decision 5) — the
fine-grained components jess `wkg oci pull`s and fuses/lowers/places per core:

```
ghcr.io/pulseengine/falcon-flight:<full-version> # e.g. 1.127.0
ghcr.io/pulseengine/falcon-flight:latest
ghcr.io/pulseengine/falcon-flight:<full-version> # runnable demo (sealed loop)
ghcr.io/pulseengine/falcon-iekf:<full-version> # invariant-EKF estimator
ghcr.io/pulseengine/falcon-position:<full-version> # position/velocity controller
ghcr.io/pulseengine/falcon-attitude:<full-version> # geometric SO(3) attitude
ghcr.io/pulseengine/falcon-rate:<full-version> # body-rate PID
ghcr.io/pulseengine/falcon-mixer:<full-version> # control allocator
# ...each also tagged :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.
Each is built **WASI-free** (`--no-default-features`, so it lowers to bare metal
— see `wasm/cm/*`) and 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 by immutable digest, 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. Every published component carries the full
embedded metadata contract (below) so its wasm.directory listing is real.

Pull + verify:

Expand Down
7 changes: 6 additions & 1 deletion wasm/cm/attitude/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
# workspace (generated bindings module won't build under plain cargo).
[package]
name = "falcon-attitude-cm"
description = "Falcon v0.7 — attitude as a Component Model component (wac cascade)"
description = "Formally-verified geometric SO(3) attitude controller as a WebAssembly Component — attitude setpoint + vehicle state → body-rate setpoint."
homepage = "https://pulseengine.eu"
documentation = "https://github.com/pulseengine/relay/blob/main/docs/OCI-DISTRIBUTION.md"
authors = ["PulseEngine"]
keywords = ["flight-control", "verified", "drone", "embedded", "wasm"]
categories = ["aerospace", "science::robotics"]
version = "0.1.0"
edition = "2024"
license = "Apache-2.0"
Expand Down
7 changes: 6 additions & 1 deletion wasm/cm/falcon-mixer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
# via `cargo component build`.
[package]
name = "falcon-mixer-cm"
description = "Falcon v0.7 — mixer as a Component Model component (wac cascade)"
description = "Formally-verified control allocator (mixer) as a WebAssembly Component — body torque + thrust → per-motor commands (airframe-parameterizable)."
homepage = "https://pulseengine.eu"
documentation = "https://github.com/pulseengine/relay/blob/main/docs/OCI-DISTRIBUTION.md"
authors = ["PulseEngine"]
keywords = ["flight-control", "verified", "drone", "embedded", "wasm"]
categories = ["aerospace", "science::robotics"]
version = "0.1.0"
edition = "2024"
license = "Apache-2.0"
Expand Down
7 changes: 6 additions & 1 deletion wasm/cm/iekf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
# workspace (generated bindings module won't build under plain cargo).
[package]
name = "falcon-iekf-cm"
description = "Falcon v1.4 — IEKF (SE₂(3)) as a Component Model component, exporting the falcon:cascade/ekf interface"
description = "Formally-verified invariant-EKF (SE₂(3)) navigation estimator as a WebAssembly Component — fuses IMU/GNSS/mag/baro into a 16-state attitude/velocity/position/bias estimate."
homepage = "https://pulseengine.eu"
documentation = "https://github.com/pulseengine/relay/blob/main/docs/OCI-DISTRIBUTION.md"
authors = ["PulseEngine"]
keywords = ["flight-control", "verified", "drone", "embedded", "wasm"]
categories = ["aerospace", "science::robotics"]
version = "0.1.0"
edition = "2024"
license = "Apache-2.0"
Expand Down
7 changes: 6 additions & 1 deletion wasm/cm/position/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
# workspace (generated bindings module won't build under plain cargo).
[package]
name = "falcon-position-cm"
description = "Falcon v0.7 — position as a Component Model component (wac cascade)"
description = "Formally-verified position/velocity controller as a WebAssembly Component — NED waypoint + vehicle state → attitude setpoint."
homepage = "https://pulseengine.eu"
documentation = "https://github.com/pulseengine/relay/blob/main/docs/OCI-DISTRIBUTION.md"
authors = ["PulseEngine"]
keywords = ["flight-control", "verified", "drone", "embedded", "wasm"]
categories = ["aerospace", "science::robotics"]
version = "0.1.0"
edition = "2024"
license = "Apache-2.0"
Expand Down
7 changes: 6 additions & 1 deletion wasm/cm/rate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
# workspace (generated bindings module won't build under plain cargo).
[package]
name = "falcon-rate-cm"
description = "Falcon v0.7 — rate as a Component Model component (wac cascade)"
description = "Formally-verified body-rate PID controller as a WebAssembly Component — body-rate setpoint + gyro → body torque. Airframe-agnostic; part of the falcon:cascade control loop."
homepage = "https://pulseengine.eu"
documentation = "https://github.com/pulseengine/relay/blob/main/docs/OCI-DISTRIBUTION.md"
authors = ["PulseEngine"]
keywords = ["flight-control", "verified", "drone", "embedded", "wasm"]
categories = ["aerospace", "science::robotics"]
version = "0.1.0"
edition = "2024"
license = "Apache-2.0"
Expand Down
Loading