Skip to content

feat(v0.55.0): validate by default on shared memory; stop publishing empty releases - #399

Merged
avrabe merged 3 commits into
mainfrom
fix/395-release-window
Sep 7, 2026
Merged

feat(v0.55.0): validate by default on shared memory; stop publishing empty releases#399
avrabe merged 3 commits into
mainfrom
fix/395-release-window

Conversation

@avrabe

@avrabe avrabe commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Two things jess reported that I had not acted on, plus her deferred ask.

1. --memory shared validates its own output by default (#390, #391)

Her ask, deferred from #390 because it was a behaviour change rather than the defect. She declined to open it unilaterally, so it went onto #391 with her reasoning attached; this implements it.

The single-address-space paths now run the fused module through wasmparser before writing it. --no-validate opts out; --validate still forces it anywhere; the two conflict rather than resolving silently.

That is where meld rewrites most invasively, and where both recent defects surfaced — #390 emitted invalid wasm at exit 0, #393 emitted a module that validated and returned the wrong number. In her words: she would have caught the first months earlier had this been the default. Cost is one wasmparser pass over an artifact already in memory.

A module that fails validation is not written. Previously the invalid artifact reached disk and failed later, in whatever consumed it.

Potency, established by making it fire

Routing verified across five invocations. Then the control that matters — reintroduce #390 and disable the v0.53.0 wiring backstop, so an invalid module is actually produced and validation is the only guard left:

Validating output (implied by --memory shared; --no-validate to skip)...
Error: Validation failed
Caused by: type mismatch: expected f32, found i32 (at offset 0xea)
→ no file written

Worth recording that the first attempt at that control never reached validation: the wiring backstop caught the defect earlier. Defense in depth behaving correctly — and a reminder that a control passing for the wrong reason proves nothing.

2. Releases are no longer published before their artifacts exist (#395)

She caught v0.53.0 public with one asset — the compliance report — and fetched it in that state.

Cause was the documented process racing its own pipeline. release.yml creates the release itself; AGENTS.md told a human to run gh release create right after the tag push, which publishes immediately. compliance.yml triggers on release: published, so it fired at once and uploaded its report while release.yml was still queued behind a busy fleet. That is precisely the state she saw.

  • Process: create as a draft, let the pipeline upload into it, verify checksums and the cosign signature, then gh release edit --draft=false. Nothing public until complete and verified.
  • Gate: release-assets-gate.yml fails on publish if the four platform tarballs, SHA256SUMS.txt or the cosign bundle are missing.

The gate reports rather than prevents — GitHub has no pre-publish hook — and that limit is written into the workflow rather than left to be discovered. A documented step nobody verifies is the same inert-gate class this repo has now hit three times; the process fix alone would have been a fourth.

Traceability

SR-73 + SWV-85, advanced by rivet verify with evidence. The decision is extracted into a total function with its implication table enumerated — six cases, not a sample — which is the treatment meld#397 argues these seams deserve.

Note the release-readiness gate from #394 did its job: rivet release status v0.55.0 exited 1 with "no artifacts scoped", which is what forced this requirement to exist for a change small enough to have skipped it.

Gate

fmt clean, clippy -D warnings clean, 879 (--workspace) / 878 (--all-features), both exit 0. rivet validate PASS, v0.55.0 cuttable.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WrmMqf1iuTS1UBEes5TmdC

avrabe and others added 3 commits September 5, 2026 22:02
jess caught v0.53.0 sitting public with ONE asset — the compliance
report — and fetched it in that state. The assets arrived later, so the
symptom cleared on its own; the cause did not.

## Cause: the documented process races its own pipeline

`release.yml` creates the GitHub release itself when it does not already
exist. AGENTS.md told a human to run `gh release create` immediately
after the tag push, which PUBLISHES right away — so the release goes
public empty and the pipeline fills it minutes later.

`compliance.yml` triggers on `release: published`, so it fired at once
and uploaded its report while `release.yml` was still queued behind a
busy runner fleet. That is exactly the state she saw: one asset, and it
was not one of the ones anybody wants.

## Fix

- **Process**: create the release as a DRAFT, let the pipeline upload
  into it, verify checksums AND the cosign signature (each with a
  negative control), and only then `gh release edit --draft=false`.
  Nothing is public until it is complete and verified.
- **Gate**: `release-assets-gate.yml` runs on publish and fails if the
  four platform tarballs, SHA256SUMS or the cosign bundle are missing.

The gate reports rather than prevents — GitHub offers no pre-publish
hook — and that limit is written into the workflow rather than left for
someone to discover. A documented step nobody verifies is the same class
of inert gate this repo has now found three times; the process fix alone
would have been a fourth.

Refs: #395

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WrmMqf1iuTS1UBEes5TmdC
)

jess's ask, deferred from #390 because it was a behaviour change rather
than the defect. She declined to open it unilaterally, so it went on
#391 with her reasoning attached; this implements it.

The single-address-space paths now run the fused module through
wasmparser before writing it. `--no-validate` opts out; `--validate`
still forces it anywhere.

That is where meld rewrites most invasively — rebasing absolute
addresses and bridging calling conventions inside one address space —
and where both recent defects surfaced: #390 emitted invalid wasm at
exit 0, #393 emitted a module that validated and returned the wrong
number. Her words: she would have caught the first months earlier had
this been the default. The cost is one wasmparser pass over an artifact
already in memory.

A module that fails validation is NOT written. Previously the invalid
artifact reached disk and failed later, in whatever consumed it.

## Potency, established by making it fire

Routing verified across five cases (implied on shared; --no-validate
opts out; multi unchanged; --validate forces on multi; the two flags
conflict). Then the control that matters: reintroducing #390 and
disabling the v0.53.0 wiring backstop — so an invalid module is actually
produced and validation is the only thing left standing — gives

  Validating output (implied by --memory shared; --no-validate to skip)...
  Error: Validation failed
  Caused by: type mismatch: expected f32, found i32 (at offset 0xea)

with no file on disk. Worth recording that the FIRST attempt at this
control never reached validation: the wiring backstop caught the defect
earlier. Defense in depth behaving correctly, and a reminder that a
control which passes for the wrong reason proves nothing.

877 tests (--workspace), exit 0.

Refs: #390, #391

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WrmMqf1iuTS1UBEes5TmdC
Extracted the decision into `validation_is_implied`, a total function
over its two inputs, so it can be enumerated rather than reasoned about:
three strategies x two opt-out states is six cases, all written down.
meld#397 argues meld's own decision seams deserve exactly this, and this
is a small enough one to demonstrate it on.

Also asserts the opt-out wins on every strategy — otherwise
`--no-validate` silently does nothing on exactly the path someone would
reach for it.

SR-73 + SWV-85, advanced by `rivet verify` with evidence rather than by
editing the status field. Note the release-readiness gate did its job
here: `rivet release status v0.55.0` exited 1 with "no artifacts scoped",
which is what forced this requirement to exist for a change small enough
to have skipped it.

879 (--workspace) / 878 (--all-features), both exit 0.

Refs: #390, #391

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WrmMqf1iuTS1UBEes5TmdC
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

LS-N verification gate

59/59 approved LS entries verified

count
Passed (≥1 test, all green) 59
Failed (≥1 test failure) 0
Missing (no ls_*_NN_* test found) 0

Approved loss-scenarios.yaml entries are expected to have a
regression test named ls_<letter>_<num>_* (e.g. LS-A-11
ls_a_11_*). The gate runs each prefix via cargo test --lib --no-fail-fast and aggregates pass/fail/missing.

Failed LS entries

(none)

Missing regression tests

(none)

Updated automatically by tools/post_verification_comment.py.
Source of truth: safety/stpa/loss-scenarios.yaml.

@avrabe
avrabe merged commit 7ddbfb1 into main Sep 7, 2026
20 checks passed
@avrabe
avrabe deleted the fix/395-release-window branch September 7, 2026 04:28
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