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
12 changes: 7 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@ jobs:
run: |
cargo install just --version 1.57.0 --locked
cargo install wasm-tools --version 1.252.0 --locked
cargo install b3sum --version 1.8.3 --locked
- name: Build checksum-verified zstd 1.5.7
run: |
curl --proto '=https' --tlsv1.2 -fsSLO https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz
echo 'eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-1.5.7.tar.gz' | sha256sum --check --strict
tar -xzf zstd-1.5.7.tar.gz
make -C zstd-1.5.7 -j2 zstd
echo "$PWD/zstd-1.5.7/programs" >> "$GITHUB_PATH"
- name: Build twice with identical canonical bytes
- name: Build twice with identical package and release identity
run: |
just dist
first="$(sha256sum dist/*.sigil-plugin.tar.zst)"
just dist
test "$first" = "$(sha256sum dist/*.sigil-plugin.tar.zst)"
source_commit=0000000000000000000000000000000000000000
just release-dist "$source_commit"
first="$(sha256sum dist/*.sigil-plugin.tar.zst dist/SHA256SUMS dist/release-manifest.json)"
just release-dist "$source_commit"
test "$first" = "$(sha256sum dist/*.sigil-plugin.tar.zst dist/SHA256SUMS dist/release-manifest.json)"
Original file line number Diff line number Diff line change
@@ -1,45 +1,40 @@
name: release
name: prepare-release

on:
push:
tags: ['v*']
workflow_dispatch:

permissions:
contents: read

jobs:
release:
assets:
runs-on: ubuntu-24.04
environment: release
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
with:
fetch-depth: 0
persist-credentials: false
- name: Install pinned build tools
run: |
cargo install just --version 1.57.0 --locked
cargo install wasm-tools --version 1.252.0 --locked
cargo install b3sum --version 1.8.3 --locked
- name: Build checksum-verified zstd 1.5.7
run: |
curl --proto '=https' --tlsv1.2 -fsSLO https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz
echo 'eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-1.5.7.tar.gz' | sha256sum --check --strict
tar -xzf zstd-1.5.7.tar.gz
make -C zstd-1.5.7 -j2 zstd
echo "$PWD/zstd-1.5.7/programs" >> "$GITHUB_PATH"
- name: Verify tag and build canonical assets
- name: Build exact package and canonical release identity
run: |
version="$(sed -n 's/^version = "\([^"]*\)"$/\1/p' plugin.toml)"
test "${GITHUB_REF_NAME#v}" = "$version"
just dist
- name: Publish immutable release
env:
GH_TOKEN: ${{ github.token }}
run: |
version="${GITHUB_REF_NAME#v}"
name="$(sed -n 's/^name = "\([^"]*\)"$/\1/p' plugin.toml)"
gh release create "$GITHUB_REF_NAME" \
"dist/$name-$version.sigil-plugin.tar.zst" \
dist/SHA256SUMS \
--verify-tag --title "$name $version" --generate-notes
set -euo pipefail
test "$GITHUB_REF" = refs/heads/main
test "$(sed -n 's/^source = "\([^"]*\)"$/\1/p' plugin.toml)" = "github:${GITHUB_REPOSITORY}"
just release-dist "$GITHUB_SHA"
test "$(find dist -mindepth 1 -maxdepth 1 -type f | wc -l)" -eq 3
test "$(find dist -mindepth 1 -maxdepth 1 | wc -l)" -eq 3
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: release-candidate-assets
path: dist/
if-no-files-found: error
254 changes: 254 additions & 0 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
name: publish-release

on:
workflow_dispatch:
inputs:
version:
description: Exact approved SemVer without v prefix
required: true
type: string
source_commit:
description: Exact approved 40-hex main commit
required: true
type: string
candidate_run_id:
description: Exact first-attempt prepare-release run
required: true
type: string
package_sha256:
description: Exact approved package SHA-256
required: true
type: string
checksums_sha256:
description: Exact approved SHA256SUMS SHA-256
required: true
type: string
release_manifest_sha256:
description: Exact approved release-manifest.json SHA-256
required: true
type: string

permissions: {}

jobs:
publish:
runs-on: ubuntu-24.04
environment: release
permissions:
actions: read
artifact-metadata: write
attestations: write
contents: write
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
with:
persist-credentials: false
- name: Verify dispatch and candidate build identity
env:
GH_TOKEN: ${{ github.token }}
CANDIDATE_RUN_ID: ${{ inputs.candidate_run_id }}
SOURCE_COMMIT: ${{ inputs.source_commit }}
run: |
set -euo pipefail
[[ "$CANDIDATE_RUN_ID" =~ ^[1-9][0-9]*$ ]]
[[ "$SOURCE_COMMIT" =~ ^[0-9a-f]{40}$ ]]
test "$GITHUB_REF" = refs/heads/main
test "$GITHUB_SHA" = "$SOURCE_COMMIT"
test "$(git rev-parse HEAD)" = "$SOURCE_COMMIT"
run_json="$(mktemp)"
gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${CANDIDATE_RUN_ID}" > "$run_json"
jq -e --arg repository "$GITHUB_REPOSITORY" --arg commit "$SOURCE_COMMIT" '
.repository.full_name == $repository and
.path == ".github/workflows/prepare-release.yml" and
.event == "workflow_dispatch" and
.head_branch == "main" and
.head_sha == $commit and
.status == "completed" and
.conclusion == "success" and
.run_attempt == 1
' "$run_json" >/dev/null
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: release-candidate-assets
path: dist
github-token: ${{ github.token }}
run-id: ${{ inputs.candidate_run_id }}
- name: Verify exact approved unpublished bytes
id: release
env:
VERSION: ${{ inputs.version }}
SOURCE_COMMIT: ${{ inputs.source_commit }}
PACKAGE_SHA256: ${{ inputs.package_sha256 }}
CHECKSUMS_SHA256: ${{ inputs.checksums_sha256 }}
RELEASE_MANIFEST_SHA256: ${{ inputs.release_manifest_sha256 }}
run: |
set -euo pipefail
[[ "$VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]
[[ "$SOURCE_COMMIT" =~ ^[0-9a-f]{40}$ ]]
[[ "$PACKAGE_SHA256" =~ ^[0-9a-f]{64}$ ]]
[[ "$CHECKSUMS_SHA256" =~ ^[0-9a-f]{64}$ ]]
[[ "$RELEASE_MANIFEST_SHA256" =~ ^[0-9a-f]{64}$ ]]
name="$(sed -n 's/^name = "\([^"]*\)"$/\1/p' plugin.toml)"
source="$(sed -n 's/^source = "\([^"]*\)"$/\1/p' plugin.toml)"
test "$source" = "github:${GITHUB_REPOSITORY}"
test "$(sed -n 's/^version = "\([^"]*\)"$/\1/p' plugin.toml)" = "$VERSION"
package="${name}-${VERSION}.sigil-plugin.tar.zst"
test -f "dist/$package" && test ! -L "dist/$package"
test -f dist/SHA256SUMS && test ! -L dist/SHA256SUMS
test -f dist/release-manifest.json && test ! -L dist/release-manifest.json
test "$(find dist -mindepth 1 -maxdepth 1 -type f | wc -l)" -eq 3
test "$(find dist -mindepth 1 -maxdepth 1 | wc -l)" -eq 3
printf '%s %s\n' "$PACKAGE_SHA256" "dist/$package" | sha256sum --check --strict
printf '%s %s\n' "$CHECKSUMS_SHA256" dist/SHA256SUMS | sha256sum --check --strict
printf '%s %s\n' "$RELEASE_MANIFEST_SHA256" dist/release-manifest.json | sha256sum --check --strict
test "$(cat dist/SHA256SUMS)" = "$PACKAGE_SHA256 $package"
python3 -I - "$name" "$VERSION" "$SOURCE_COMMIT" "$source" "$package" "$PACKAGE_SHA256" <<'PY'
import json
from pathlib import Path
import re
import sys

name, version, commit, source, package, package_sha256 = sys.argv[1:]
path = Path("dist/release-manifest.json")
raw = path.read_bytes()
value = json.loads(raw)
assert set(value) == {
"schema_version", "source", "source_commit", "name", "version",
"asset_name", "package_sha256", "package_blake3",
"manifest_blake3", "component_blake3",
}
assert value["schema_version"] == 1
assert value["source"] == source
assert value["source_commit"] == commit
assert value["name"] == name
assert value["version"] == version
assert value["asset_name"] == package
assert value["package_sha256"] == f"sha256:{package_sha256}"
for field in ("package_blake3", "manifest_blake3", "component_blake3"):
assert re.fullmatch(r"blake3:[0-9a-f]{64}", value[field])
canonical = json.dumps(
value, allow_nan=False, ensure_ascii=True, sort_keys=True,
separators=(",", ":"),
).encode("ascii")
assert raw == canonical
PY
echo "package_name=$package" >> "$GITHUB_OUTPUT"
echo "subject_digest=sha256:$PACKAGE_SHA256" >> "$GITHUB_OUTPUT"
- name: Stage and read back the exact draft
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
SOURCE_COMMIT: ${{ inputs.source_commit }}
PACKAGE_SHA256: ${{ inputs.package_sha256 }}
CHECKSUMS_SHA256: ${{ inputs.checksums_sha256 }}
RELEASE_MANIFEST_SHA256: ${{ inputs.release_manifest_sha256 }}
PACKAGE_NAME: ${{ steps.release.outputs.package_name }}
run: |
set -euo pipefail
tag="v${VERSION}"
if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
echo "existing tag burns ${tag}; refusing publication" >&2
exit 1
fi
if gh release view "$tag" >/dev/null 2>&1; then
echo "existing release burns ${tag}; refusing publication" >&2
exit 1
fi
status="$(curl --proto '=https' --tlsv1.2 --silent --show-error \
--output "${RUNNER_TEMP}/preexisting-attestations.json" --write-out '%{http_code}' \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/attestations/sha256:${PACKAGE_SHA256}?predicate_type=provenance&per_page=30")"
test "$status" = 404
gh release create "$tag" \
--target "$SOURCE_COMMIT" \
--title "${PACKAGE_NAME%.sigil-plugin.tar.zst}" \
--notes "Immutable keyless-provenance Sigil plugin ${VERSION}." \
--draft \
"dist/$PACKAGE_NAME" dist/SHA256SUMS dist/release-manifest.json
release_json="$(mktemp)"
gh release view "$tag" --json isDraft,isPrerelease,targetCommitish,assets > "$release_json"
jq -e --arg commit "$SOURCE_COMMIT" --arg package "$PACKAGE_NAME" '
.isDraft == true and .isPrerelease == false and .targetCommitish == $commit and
([.assets[].name] | sort) == (["SHA256SUMS", "release-manifest.json", $package] | sort)
' "$release_json" >/dev/null
readback="$(mktemp -d)"
gh release download "$tag" --dir "$readback"
test "$(find "$readback" -mindepth 1 -maxdepth 1 -type f | wc -l)" -eq 3
test "$(find "$readback" -mindepth 1 -maxdepth 1 | wc -l)" -eq 3
test "$(sha256sum "$readback/$PACKAGE_NAME" | cut -d' ' -f1)" = "$PACKAGE_SHA256"
test "$(sha256sum "$readback/SHA256SUMS" | cut -d' ' -f1)" = "$CHECKSUMS_SHA256"
test "$(sha256sum "$readback/release-manifest.json" | cut -d' ' -f1)" = "$RELEASE_MANIFEST_SHA256"
test "$(cat "$readback/SHA256SUMS")" = "$PACKAGE_SHA256 $PACKAGE_NAME"
- name: Create GitHub OIDC build provenance for the exact package
id: attest
uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1
with:
subject-name: ${{ steps.release.outputs.package_name }}
subject-digest: ${{ steps.release.outputs.subject_digest }}
show-summary: false
- name: Publish once and verify immutable public state
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
SOURCE_COMMIT: ${{ inputs.source_commit }}
PACKAGE_SHA256: ${{ inputs.package_sha256 }}
CHECKSUMS_SHA256: ${{ inputs.checksums_sha256 }}
RELEASE_MANIFEST_SHA256: ${{ inputs.release_manifest_sha256 }}
PACKAGE_NAME: ${{ steps.release.outputs.package_name }}
ATTESTATION_ID: ${{ steps.attest.outputs.attestation-id }}
BUNDLE_PATH: ${{ steps.attest.outputs.bundle-path }}
run: |
set -euo pipefail
test -n "$ATTESTATION_ID"
test -f "$BUNDLE_PATH" && test ! -L "$BUNDLE_PATH"
attestation_found=false
for _ in 1 2 3 4 5 6; do
status="$(curl --proto '=https' --tlsv1.2 --silent --show-error \
--output "${RUNNER_TEMP}/attestations.json" --write-out '%{http_code}' \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/attestations/sha256:${PACKAGE_SHA256}?predicate_type=provenance&per_page=30")"
if test "$status" = 200 && jq -e --argjson repository_id "$GITHUB_REPOSITORY_ID" '
(.attestations | length) == 1 and
.attestations[0].repository_id == $repository_id and
.attestations[0].initiator == "user"
' "${RUNNER_TEMP}/attestations.json" >/dev/null; then
attestation_found=true
break
fi
sleep 5
done
test "$attestation_found" = true
tag="v${VERSION}"
test "$(gh release view "$tag" --json isDraft,targetCommitish --jq '.isDraft')" = true
test "$(gh release view "$tag" --json isDraft,targetCommitish --jq '.targetCommitish')" = "$SOURCE_COMMIT"
if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
echo "tag appeared after preflight; ${tag} is burned" >&2
exit 1
fi
gh api --method POST "repos/${GITHUB_REPOSITORY}/git/refs" \
-f ref="refs/tags/${tag}" -f sha="$SOURCE_COMMIT" >/dev/null
git fetch --force origin "refs/tags/$tag:refs/tags/$tag"
test "$(git rev-list -n 1 "$tag")" = "$SOURCE_COMMIT"
gh release edit "$tag" --draft=false --latest=false
immutable=false
for _ in 1 2 3 4 5 6; do
if test "$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${tag}" --jq '.immutable')" = true; then
immutable=true
break
fi
sleep 5
done
test "$immutable" = true
test "$(gh release view "$tag" --json isDraft --jq '.isDraft')" = false
readback="$(mktemp -d)"
gh release download "$tag" --dir "$readback"
test "$(find "$readback" -mindepth 1 -maxdepth 1 -type f | wc -l)" -eq 3
test "$(find "$readback" -mindepth 1 -maxdepth 1 | wc -l)" -eq 3
test "$(sha256sum "$readback/$PACKAGE_NAME" | cut -d' ' -f1)" = "$PACKAGE_SHA256"
test "$(sha256sum "$readback/SHA256SUMS" | cut -d' ' -f1)" = "$CHECKSUMS_SHA256"
test "$(sha256sum "$readback/release-manifest.json" | cut -d' ' -f1)" = "$RELEASE_MANIFEST_SHA256"
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/build/
/dist/
/plugin.wasm

__pycache__/
4 changes: 4 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ sigil-check: check
dist: check
mkdir -p dist
{{python}} scripts/pack.py plugin.toml dist

release-dist source_commit: check
mkdir -p dist
{{python}} scripts/pack.py plugin.toml dist --source-commit "{{source_commit}}"
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,22 @@ expect(codec["echo-u32"](42) == 42)

The repository contains the complete WIT and core-WAT source. The component
build is pinned to `wasm-tools 1.252.0`; CI uses a pinned zstd 1.5.7
compatibility packer that emits byte-identical Sigil P3 archives. The release
workflow publishes only the canonical package plus `SHA256SUMS`, and Sigil
performs complete validation at installation.
compatibility packer that emits the same package and canonical release manifest
as Sigil. The protected, manually dispatched publisher accepts only exact
approved candidate digests, creates one GitHub OIDC/Sigstore attestation for
the package, and publishes the package, `SHA256SUMS`, and
`release-manifest.json` as an immutable release. It never uses a signing key.

```bash
just check
just dist
just sigil-check
sigil plugin install [email protected].0
sigil plugin install [email protected].1
sigil run examples/visible.lua
```

Installation records a bootstrap digest acquisition, not a signature, grant,
or project lock. P3 supports this plugin for visible local authoring only;
evaluation remains fail-closed until P4 supplies an exact checked-in lock.
Version 1.1.1 is the first keyless-provenance release. Sigil verifies and pins
its exact repository, workflow, source commit, protected environment, package,
manifest, and public transparency proof before installation or evaluation.
Earlier releases remain historical bootstrap-digest evidence and are never
promoted in place.
2 changes: 1 addition & 1 deletion plugin.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema_version = 1
name = "codec"
version = "1.1.0"
version = "1.1.1"
description = "capability-free reference codec for Sigil plugins"
license = "MIT"

Expand Down
Loading