Skip to content

test(signing): wsc e2e in CI — pinned v0.9.0, sha256-verified, hard-fails on missing wsc#140

Merged
avrabe merged 2 commits into
mainfrom
feat/wsc-ci-end-to-end
May 25, 2026
Merged

test(signing): wsc e2e in CI — pinned v0.9.0, sha256-verified, hard-fails on missing wsc#140
avrabe merged 2 commits into
mainfrom
feat/wsc-ci-end-to-end

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 24, 2026

Summary

Closes the v0.6.0 honest blocker: Phase 5's synth compile --sign-output was unit-tested against sigil's CLI surface but never end-to-end. This PR wires wsc into CI via release-download (pinned v0.9.0 + sha256), adds three test cases, and runs the script on every PR touching the sign path plus every v* tag.

Notable finding — sigil v0.9.0 doesn't yet support keyless ELF

While building the e2e the agent discovered: sigil v0.9.0's wsc sign --keyless --format elf explicitly rejects with "Keyless signing is currently supported only for WASM format. Use key-based signing for ELF and MCUboot artifacts." Source: pulseengine/sigil src/cli/main.rs:770-779 at v0.9.0 (clean-room verified). PR #135's argv is forward-compatible with the future shape, but the path can't be a positive ELF-signature test today.

The test is therefore structured as: the gap exists and synth handles it correctly (exit non-zero, surface wsc's stderr verbatim, leave the unsigned ELF intact, clean up .signing.tmp). When sigil ships keyless ELF, the script auto-flips to a positive assertion. Documented in docs/sigil-integration.md.

Also corrected: --cert-identity-regexp does not exist in wsc v0.9.0 (only literal --cert-identity). Verified at sigil src/cli/main.rs:247-258.

Pinned values (clean-room verified against live sigil release)

  • wsc v0.9.0 Linux x86_64: 9054b4b066e2b0a954110851a43266ff0e9ef12b4e1ecc03c333943fd52cecb6
  • wsc v0.9.0 macOS aarch64 (not used in CI, on file): c7e4fddceed68512400d3f4bdc9a3741a5d7221d9afb0a9e1b8302755764ffd8

Test cases (tests/wsc_sign_e2e.sh)

  1. Positive WASM round-tripwsc sign --keyless --format wasm + wsc verify --keyless --format wasm. Validates the wsc binary, Fulcio + Rekor reachability, OIDC flow.
  2. Synth contract (currently negative)synth compile … --sign-output exits non-zero, surfaces the keyless-ELF rejection, preserves the unsigned ELF, cleans up tmp.
  3. Tamper-negative — flip a byte in the case-1 signed WASM; wsc verify must reject.

Script hard-fails when wsc is missing — no silent skip (verified via clean-room: [[ -x "$WSC" ]] || fail "...").

Workflow

  • .github/workflows/signing-e2e.yml: permissions: id-token: write, contents: read. ubuntu-latest. Triggers on path-filtered PRs (sign.rs, main.rs, the script, the WAT fixture, the workflow itself) and on v* tag pushes.
  • No MODULE.bazel / crates/BUILD.bazel / Cargo.toml changes (no new deps; rules_wasm_component is already declared but not needed here).
  • PR feat(cli): sign synth's output ELF binaries via sigil (release Phase 5) #135's sign.rs is unchanged — the argv pinning is still load-bearing.

Clean-room verification

A clean-room subagent independently verified (no inherited context): 9 of 10 claims CONFIRMED with file:line citations and live-fetched evidence (sha256 sidecars from sigil release, sigil source at v0.9.0). 1 CANNOT-VERIFY (the agent's local-run output — but the script logic matches what the agent reported).

Test plan

  • CI green (the new workflow runs)
  • Tamper-negative case actually rejects (the test would fail if it didn't)
  • When sigil ships keyless ELF: case 2 auto-flips to positive, no PR needed

🤖 Generated with Claude Code

Closes the "end-to-end signing was not validated" gap explicitly flagged
when Phase 5 (`synth compile --sign-output`, PR #135) shipped. The
implementing subagent honestly noted that `wsc` was not on PATH in its
environment, so only argv-shape unit tests existed; the actual Fulcio +
Rekor round-trip was unverified.

What this lands:

- `.github/workflows/signing-e2e.yml`: downloads a sha256-pinned `wsc`
  from sigil's GitHub releases (v0.9.0, sha256
  9054b4b066e2b0a954110851a43266ff0e9ef12b4e1ecc03c333943fd52cecb6 for
  the linux-x86_64 binary), builds synth-cli, and runs the e2e test.
  Triggered on PRs that touch `crates/synth-cli/src/sign.rs|main.rs`,
  on push to main, and on `v*` tag pushes. `id-token: write` is granted
  so wsc's keyless flow can obtain a GitHub OIDC token.

- `tests/wsc_sign_e2e.sh`: three load-bearing cases. (1) WASM keyless
  sign + verify round-trip — proves wsc is healthy and the keyless code
  path that sigil supports today works end-to-end. (2) Synth's actual
  contract: `synth compile --sign-output` against ELF. As of sigil v0.9.0
  this returns the explicit usage error "Keyless signing is currently
  supported only for WASM format" (sigil's `src/cli/main.rs:770-779`).
  The script asserts synth surfaces that error verbatim, exits non-zero,
  preserves the unsigned ELF, and cleans up the `.signing.tmp`. When
  sigil eventually ships keyless-ELF support, the script auto-detects
  the flip via a GitHub `::notice::` annotation and switches case 2
  into a positive "signed ELF" assertion. (3) Tamper-negative on the
  case-1 signed WASM — flips a byte, runs `wsc verify --keyless`, and
  fails if it accepts the tampered module.

  The script does NOT silently skip when wsc is missing; that would
  recreate the exact gap this workstream closes.

- `docs/sigil-integration.md`: replaces the "End-to-end signing +
  verification was not validated by the implementing agent" stanza
  with the actual CI status. Adds a "Verifying a signed WASM module
  locally" recipe (the path that actually works today) and corrects
  the verification-flag documentation: sigil v0.9.0 accepts the
  literal `--cert-identity`, not `--cert-identity-regexp`.

- `docs/release-process.md`: Phase 5 status updated from
  "compiler-side implemented" to "compiler-side implemented +
  CI-validated end-to-end".

Approach: release-download with sha256 pin, not Bazel. `rules_wasm_component`
is already in MODULE.bazel but it ships WASM-component toolchains, not the
`wsc` CLI distribution; sigil's own release binaries ship per-asset .sha256
sidecars, so pinning by version + sha256 in one place (the workflow file)
gives an auditable trust anchor without the complexity of a custom binary-
import Bazel rule.

Locally validated against sigil v0.9.0's macOS-aarch64 wsc + a debug
synth build (same source tree as this commit): case 2 exits rc=1, surfaces
the "Keyless signing is currently supported only for WASM" error verbatim,
preserves the 741-byte unsigned ELF, and leaves no stale .signing.tmp.
Cases 1 and 3 require an OIDC token (GitHub Actions only) and are exercised
in CI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 24, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…pered WASM)

The signing-e2e workflow's case 3 (tamper-negative on signed WASM) ran
on commit 7f1a9c9 against wsc v0.9.0 and surfaced a real sigil-side
gap: 'wsc verify --keyless' returned exit 0 on a WASM whose signed
payload (byte at offset 64, inside the module — well before the
~15kB trailing signature section) had been flipped.

Filed upstream as pulseengine/sigil#135 with full repro evidence
(CI run, exact wsc version + sha256, the byte-flip script).

This commit converts case 3 to an xfail with an explicit reference
to sigil#135. The test stays in place:
- If wsc continues to (incorrectly) accept the tampered file, case 3
  XFAILs as expected (workflow green) and notes the gap in CI output.
- If sigil#135 is fixed and wsc starts rejecting, case 3 XPASSes and
  emits a 'flip this back to a hard check' note to maintainers.

Also documents the gap in docs/sigil-integration.md (Status section)
and adds a CHANGELOG [Unreleased] entry tracking the upstream issue.

Until sigil#135 is fixed, 'wsc verify --keyless' should be treated as
proving signature-blob well-formedness, NOT module integrity. PR #135's
synth-cli signing path is unchanged; the gap is purely on the verify
side, but it affects what downstream consumers can claim about a
synth-emitted signed artifact.
@avrabe avrabe merged commit 6051678 into main May 25, 2026
9 checks passed
@avrabe avrabe deleted the feat/wsc-ci-end-to-end branch May 25, 2026 04:53
avrabe added a commit that referenced this pull request May 25, 2026
Bumps workspace version to 0.7.0 and promotes the [Unreleased]
entries (f64 ARM VFP-D codegen from #141, signing-e2e workflow
from #140) to a v0.7.0 heading.

Adds a falsification statement to the v0.7.0 notes per PulseEngine
methodology: the release is wrong if (a) a covered f64 op
miscompiles or fails to link on Cortex-M7DP, or (b) signing-e2e
goes red on a clean v0.7.0 checkout. Case 3 (tamper-negative) is
xfail until sigil#135 ships a fix; cases 1 and 2 must stay hard
green.

PRs included:
  #140 test(signing): wsc e2e in CI — pinned v0.9.0, sha256-verified
  #141 feat(backend): f64 codegen on Cortex-M7DP (non-optimized path)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant