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
118 changes: 118 additions & 0 deletions .github/workflows/gustos-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Build, verify, sign + publish gale's gust:os provider components
# (DD-OS-DELIVERY-001, gale#223/#224).
#
# gust's OS-as-wasm-component-model made distributable: each gust:os provider is a
# wasm COMPONENT that exports a gust:os capability interface and imports only
# gust:hal. The `verify` job gates that invariant on every PR (export gust:os,
# import only gust:hal — no leaking scheduler/env/heap). On a release tag the
# `publish` job additionally cosign-signs (keyless OIDC) and publishes to BOTH
# channels a consumer pulls from — a cosign-signed OCI registry (ghcr) and
# wasm.directory — so ANY downstream that imports gust:os pulls the same signed
# component and fuses it for its target.
#
# First demonstrator: the time provider (exports gust:os/time, residual = gust:hal).
# The full fused gale-nano runtime component is gale#224.
name: gustos publish

on:
push:
branches: [main]
paths:
- "benches/gust/drivers/*-provider/**"
- "benches/gust/drivers/wit-os/**"
- "benches/gust/drivers/build-gustos-components.sh"
- ".github/workflows/gustos-publish.yml"
tags: ["v*"]
pull_request:
branches: [main]
paths:
- "benches/gust/drivers/*-provider/**"
- "benches/gust/drivers/wit-os/**"
- "benches/gust/drivers/build-gustos-components.sh"
- ".github/workflows/gustos-publish.yml"
workflow_dispatch:
inputs:
version:
description: "component version tag (e.g. 0.5.1)"
required: true

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
verify:
name: "gust:os component invariant (export gust:os, import only gust:hal)"
runs-on: ubuntu-22.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Install wasm-tools
run: cargo install wasm-tools --version 1.245.1 --locked
- name: Build + verify the gust:os provider components
run: bash benches/gust/drivers/build-gustos-components.sh

publish:
name: "sign + publish gust:os components (OCI + wasm.directory)"
# Only on a release tag or an explicit dispatch — never on PR.
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
needs: verify
runs-on: ubuntu-22.04
timeout-minutes: 20
permissions:
contents: read
packages: write # ghcr OCI push
id-token: write # cosign keyless OIDC
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Install wasm-tools + wkg
run: |
cargo install wasm-tools --version 1.245.1 --locked
cargo install wkg --locked
- uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v2.4.1'
- name: Build + verify components
run: bash benches/gust/drivers/build-gustos-components.sh
- name: Resolve version
id: v
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
REF: ${{ github.ref }}
run: |
if [ -n "$INPUT_VERSION" ]; then V="$INPUT_VERSION"; else V="${REF#refs/tags/v}"; fi
echo "version=$V" >> "$GITHUB_OUTPUT"
- name: Publish signed to ghcr OCI + wasm.directory
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WASM_DIRECTORY_TOKEN: ${{ secrets.WASM_DIRECTORY_TOKEN }}
VERSION: ${{ steps.v.outputs.version }}
OWNER: ${{ github.repository_owner }}
run: |
set -euo pipefail
echo "$GH_TOKEN" | wkg oci login ghcr.io -u "$OWNER" --password-stdin
for comp in benches/gust/drivers/gustos-components/*.component.wasm; do
base="$(basename "$comp" .component.wasm)" # time-provider
pkg="gale-nano-${base%-provider}" # gale-nano-time
ref="ghcr.io/${OWNER}/${pkg}:${VERSION}"
echo "== $pkg → $ref =="
wkg oci push "$ref" "$comp"
cosign sign --yes "$ref"
echo "::notice::published + signed $ref"
if [ -n "${WASM_DIRECTORY_TOKEN:-}" ]; then
WKG_REGISTRY_TOKEN="$WASM_DIRECTORY_TOKEN" \
wkg publish "$comp" --package "pulseengine:${pkg}@${VERSION}" --registry wasm.directory \
|| echo "::warning::wasm.directory publish failed for $pkg"
else
echo "::warning::WASM_DIRECTORY_TOKEN unset — skipping wasm.directory publish for $pkg"
fi
done
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ MODULE.bazel.lock
# Agent working state (per-package; not source — see issue-hunt skill)
.claude/pulseengine/
**/.claude/pulseengine/
benches/gust/drivers/gustos-components/
57 changes: 57 additions & 0 deletions benches/gust/drivers/build-gustos-components.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Build gale's gust:os PROVIDER components — the consumable, publishable unit of
# gust's OS-as-wasm-component-model (DD-OS-DELIVERY-001, gale#223/#224).
#
# Each provider is a wasm COMPONENT that EXPORTS a gust:os capability interface and
# imports ONLY the gust:hal seam — the exact delivery invariant: a downstream that
# imports gust:os pulls the signed component and fuses it for its target; the only
# native residual is gust:hal. This is the first, minimal demonstrator (the time
# provider) ahead of the full fused gale-nano runtime component (gale#224).
#
# bash benches/gust/drivers/build-gustos-components.sh # build + verify
# OUT=/some/dir bash .../build-gustos-components.sh # choose output dir
#
# Verifies (the oracle for the DD invariant): every built component EXPORTS at least
# one gust:os/* interface and imports NOTHING outside gust:hal/*. A leaking scheduler
# / env / heap import (the core-module shape the release tarball ships) fails here.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
OUT="${OUT:-$HERE/gustos-components}"
WT="${WASM_TOOLS:-wasm-tools}"
mkdir -p "$OUT"

# gust:os provider crates → the wasm cdylib each emits. Start with the time provider
# (0-SRAM, exports gust:os/time); extend to log/spawn/timer as they harden into the
# published set (the full fused runtime is gale#224).
PROVIDERS=(
"time-provider:gust_time_provider"
)

fail=""
for entry in "${PROVIDERS[@]}"; do
crate="${entry%%:*}"; wasm_name="${entry##*:}"
printf '== %s ==\n' "$crate"
( cd "$HERE/$crate" && cargo build --release --target wasm32-unknown-unknown >/dev/null 2>&1 )
core="$(find "$HERE/$crate/target/wasm32-unknown-unknown/release" -maxdepth 1 -name "$wasm_name.wasm" | head -1)"
comp="$OUT/$crate.component.wasm"
"$WT" component new "$core" -o "$comp"

wit="$("$WT" component wit "$comp")"
exports_gustos="$(printf '%s' "$wit" | grep -cE '^\s*export gust:os/' || true)"
# imports outside gust:hal/* (the leak check): any `import X` whose target isn't gust:hal/
bad_imports="$(printf '%s' "$wit" | grep -E '^\s*import ' | grep -vE 'import gust:hal/' || true)"

if [ "$exports_gustos" -lt 1 ]; then
echo " FAIL: exports no gust:os/* interface"; fail="$fail $crate"
elif [ -n "$bad_imports" ]; then
echo " FAIL: imports outside gust:hal:"; printf '%s\n' "$bad_imports" | sed 's/^/ /'; fail="$fail $crate"
else
echo " ok: exports gust:os ($exports_gustos iface), residual import = gust:hal only → $comp"
fi
done

if [ -n "$fail" ]; then
echo ""; echo "gust:os component invariant FAILED:$fail"; exit 1
fi
echo ""
echo "gust:os provider components built + invariant held (export gust:os, import only gust:hal). Output: $OUT"
Loading