Skip to content

plan: toolchain becomes a v0.12.0 prerequisite; scope v0.13.0 (REQ-32..38) - #308

Merged
avrabe merged 2 commits into
mainfrom
plan/v0.13.0
Oct 16, 2026
Merged

avrabe merged 2 commits into
mainfrom
plan/v0.13.0

Conversation

@avrabe

@avrabe avrabe commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Lands the release plan in rivet per the scoping decision: ship v0.12.0 clean, which makes the toolchain bump a current-release prerequisite rather than next-release work.

v0.12.0 — scope change

REQ-31 moved v0.13.0 → v0.12.0. wasmtime 47.x carries RUSTSEC-2026-0327 (9.3 CRITICAL) plus -0326 and -0316, fixed only in 48.0.4+/49.0.2+ which need rustc 1.95/1.96. Shipping clean therefore requires the toolchain bump before the tag. The alternative — excepting a 9.3 on the grounds that wasmtime is optional and absent from default builds — was considered and declined.

REQ-31 carries the obligation to record each masked gate's status before/after the bump — kani format/merkle (tolerate_failure), Verus (assume(false), audit C-1), Rocq (stub), Bazel test (audit C-7) — so a mask cannot hide a toolchain-induced regression.

v0.13.0 — newly triaged

REQ Issue Note
REQ-32 #303 Sign the WASM preamble — breaking, needs SIGNATURE_VERSION bump
REQ-33 #304 Stop retaining stale signed_hashes_set entries (rollback)
REQ-34 #288 Device certs must bind the device's public key
REQ-35 #280 CycloneDX VEX per release — promoted by this cycle: 12 advisories in one week, and a workflow --ignore carries its rationale only in a YAML comment. A VEX says "not affected, because the dep is behind a non-default feature" machine-readably.
REQ-36 #289 Honour serial_number; also records that ProvisioningSession writes a serial that never reaches the cert, and that fixing REQ-34 makes the collision live on the HSM path
REQ-37 #279 musl builds — the glibc-2.39 floor excludes Alpine/older distros
REQ-38 #268 Scope what wsc still owes varve for v1.0. This line was varve-driven throughout and their reports twice surfaced real wrong-accepts, so their consolidated ask is the highest-signal roadmap input. Folds in #272.

Note on the burn-down

v0.12.0 still lists REQ-28/29 here because this branch is cut from main; #300 reassigns them to v0.13.0. After both merge: v0.12.0 = REQ-26/27/30 (verified) + REQ-31; v0.13.0 = nine requirements.

Merge after #300 (both touch artifacts/dev/features.yaml, different regions).

🤖 Generated with Claude Code

https://claude.ai/code/session_012aR3Md1h46K9wAUWMQiESH

avrabe and others added 2 commits October 14, 2026 21:14
…s surfaced

Triages the findings and blockers that came out of the v0.12.0 pre-release
Mythos pass and the advisory sweep into a v0.13.0 scope, so none of them sit as
untriaged issues invisible to the plan.

  REQ-31 / #302  Toolchain 1.94 -> 1.96 + re-qualify every formal-verification
                 gate. SECURITY-FORCED: wasmtime 47.x carries RUSTSEC-2026-0327
                 (9.3 CRITICAL) plus two more, fixed ONLY in 48.0.4+/49.0.2+
                 which need rustc 1.95/1.96. Carries the obligation to record
                 each MASKED gate's status before/after, so the mask cannot hide
                 a toolchain-induced regression.
  REQ-32 / #303  Sign the WASM preamble. The signature does not bind the
                 artifact bytes today, so a signed core module relabels to a
                 component and still verifies. BREAKING — needs a
                 SIGNATURE_VERSION bump and a migration decision.
  REQ-33 / #304  Stop retaining stale signed_hashes_set entries on re-sign, so
                 v1 content cannot verify under a v2 header. Explicitly NOT
                 fixed by v0.12.0's terminal-hash pinning.
  REQ-34 / #288  Device certificates must bind the device's public key — the
                 HSM/provisioning path certifies a throwaway ephemeral key
                 because ed25519_to_pem reads only the secret half.

REQ-28 (#231 SigstoreBundle::verify + DER acceptance) and REQ-29 (#259 Ed25519
Rekor-v2 SET path) are already reassigned to v0.13.0 in the v0.12.0 release PR
(#300), so v0.13.0's full scope is six requirements once that merges.

Still untriaged on purpose, pending a scoping call rather than a silent guess:
#289 (serial_number ignored / collisions), #280 (CycloneDX VEX per release —
directly relevant now that advisories gate the release), #279 (musl builds),
#272/#268 (varve's consolidated v1.0 ask).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aR3Md1h46K9wAUWMQiESH
…four issues

Per the scoping decision: ship v0.12.0 CLEAN rather than with a documented
exception, so the toolchain bump moves from next-release work into the current
release's critical path.

  REQ-31  v0.13.0 -> v0.12.0.  wasmtime 47.x carries RUSTSEC-2026-0327 (9.3
          CRITICAL) plus -0326 and -0316, fixed ONLY in 48.0.4+/49.0.2+ which
          need rustc 1.95/1.96. Shipping clean therefore requires the toolchain
          bump BEFORE the tag, not after it. The alternative — excepting a 9.3
          on the grounds that wasmtime is optional and absent from default
          builds — was considered and declined.

Newly triaged into v0.13.0, so they stop being untriaged issues invisible to
the plan:

  REQ-35 / #280  CycloneDX VEX per release, re-issued when advisories land.
                 Promoted by this cycle's experience: 12 advisories appeared in
                 one week and a workflow --ignore line carries its rationale
                 only in a YAML comment. A VEX states "not affected, because the
                 dependency is behind a non-default feature" machine-readably.
  REQ-36 / #289  Honour CertificateConfig::serial_number. Also records that
                 ProvisioningSession writes a serial into its result that never
                 reaches the certificate, and that fixing REQ-34 makes the
                 serial collision live on the HSM path too.
  REQ-37 / #279  musl Linux builds — the glibc-2.39 floor excludes Alpine and
                 older distros, which matters for a verification CLI meant to
                 run in arbitrary CI and air-gapped environments.
  REQ-38 / #268  Scope what wsc still owes varve for keyless at v1.0. This whole
                 release line was varve-driven and their reports twice surfaced
                 real wrong-accepts, so their consolidated ask is the
                 highest-signal roadmap input available. Folds in #272.

Note v0.12.0 still lists REQ-28/29 here because this branch is cut from main;
#300 reassigns them to v0.13.0, after which v0.12.0's scope is
REQ-26/27/30 (verified) + REQ-31, and v0.13.0's is nine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aR3Md1h46K9wAUWMQiESH
@avrabe avrabe changed the title plan(v0.13.0): scope the security + toolchain backlog the v0.12.0 pass surfaced plan: toolchain becomes a v0.12.0 prerequisite; scope v0.13.0 (REQ-32..38) Oct 14, 2026
@avrabe
avrabe merged commit 74f48c9 into main Oct 16, 2026
1 check passed
@avrabe
avrabe deleted the plan/v0.13.0 branch October 16, 2026 21:43
avrabe added a commit that referenced this pull request Nov 1, 2026
Audited 2026-10-21: main's `required_status_checks.contexts` is `[]` and the
repository has no rulesets. Every check in this repo is advisory. #305, #308,
#292, #306, #310 and #297 all merged over a board nothing enforced, and the
release that is being cut right now was gated by discipline rather than by CI.

Requiring the individual contexts is the obvious fix and a trap in both
directions:

  * rust.yml, formal-verification.yml and supply-chain.yml carry
    `on: pull_request: paths:`, so a PR touching none of their paths never gets
    a report from them — and a required context that never reports blocks the
    PR forever;
  * a job skipped INSIDE a workflow reports `skipped`, and on GitHub a skipped
    required context SATISFIES the merge button.

So a paths filter is a gate-disabler either way. This adds one unfiltered
workflow whose single job is the only context to require, and which judges what
the other workflows actually concluded.

Rules, all four failing closed:

  1. every completed check-run must conclude in {success, skipped, neutral} —
     ONE predicate, an allowlist, so a conclusion GitHub invents tomorrow is
     rejected rather than inherited as green;
  2. `Documentation claims hold` must be PRESENT. claim-check.yml has no paths
     filter, so its absence means it stopped running — the canary that keeps
     rule 1 from reading "everything was skipped" as mergeable;
  3. everything must be finished; hitting the deadline FAILS, because a gate
     that gives up and passes is worse than no gate;
  4. an API error, bad JSON or missing token exits non-zero. The audit's own
     finding is that a classifier's error path is usually its permissive one.

What it deliberately does NOT do: re-derive from the path globs which workflows
*should* have run. That would copy three filter lists into a fourth place, and
a copy that drifts is how this class of bug starts. The honest cost is stated in
the source — a job skipped by an in-workflow filter is taken at its word, with
rule 2 as the floor.

## The decision table found a hole in itself

`--self-test` drives 14 cases through the pure `classify()` function. Running
mutations against the FIRST draft, two of six survived:

  BLOCKING = set()                 -> 14/14 still held
  "failure" added to OK            -> 14/14 still held

Both for the same reason: a failure was rejected twice, once by a blocklist and
once by falling outside an allowlist, so either alone sufficed and neither could
be isolated. That is the same redundant-guard pattern just reported in
matrix.rs — reproduced from scratch in a new file, which is a decent argument
for how easily it happens.

Collapsed to the single allowlist, with the blocklist demoted to wording the
error message. Re-running every mutation:

  allowlist gains "failure"            13/14   caught
  allowlist accepts everything         10/14   caught
  conclusion check removed              9/14   caught
  canary presence defeated             11/14   caught
  never waits for in-progress          11/14   caught
  waits on itself (deadlock)           12/14   caught

6 of 6. The workflow runs `--self-test` as its first step, so the gate proves
itself before it judges anything.

NOT wired into branch protection by this commit. A context that has never
reported cannot be required without blocking every open PR, including the four
in this release's stack. Protection gets set after this lands and reports once.

Refs #316

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aR3Md1h46K9wAUWMQiESH
avrabe added a commit that referenced this pull request Nov 1, 2026
…#317)

Audited 2026-10-21: main's `required_status_checks.contexts` is `[]` and the
repository has no rulesets. Every check in this repo is advisory. #305, #308,
#292, #306, #310 and #297 all merged over a board nothing enforced, and the
release that is being cut right now was gated by discipline rather than by CI.

Requiring the individual contexts is the obvious fix and a trap in both
directions:

  * rust.yml, formal-verification.yml and supply-chain.yml carry
    `on: pull_request: paths:`, so a PR touching none of their paths never gets
    a report from them — and a required context that never reports blocks the
    PR forever;
  * a job skipped INSIDE a workflow reports `skipped`, and on GitHub a skipped
    required context SATISFIES the merge button.

So a paths filter is a gate-disabler either way. This adds one unfiltered
workflow whose single job is the only context to require, and which judges what
the other workflows actually concluded.

Rules, all four failing closed:

  1. every completed check-run must conclude in {success, skipped, neutral} —
     ONE predicate, an allowlist, so a conclusion GitHub invents tomorrow is
     rejected rather than inherited as green;
  2. `Documentation claims hold` must be PRESENT. claim-check.yml has no paths
     filter, so its absence means it stopped running — the canary that keeps
     rule 1 from reading "everything was skipped" as mergeable;
  3. everything must be finished; hitting the deadline FAILS, because a gate
     that gives up and passes is worse than no gate;
  4. an API error, bad JSON or missing token exits non-zero. The audit's own
     finding is that a classifier's error path is usually its permissive one.

What it deliberately does NOT do: re-derive from the path globs which workflows
*should* have run. That would copy three filter lists into a fourth place, and
a copy that drifts is how this class of bug starts. The honest cost is stated in
the source — a job skipped by an in-workflow filter is taken at its word, with
rule 2 as the floor.

## The decision table found a hole in itself

`--self-test` drives 14 cases through the pure `classify()` function. Running
mutations against the FIRST draft, two of six survived:

  BLOCKING = set()                 -> 14/14 still held
  "failure" added to OK            -> 14/14 still held

Both for the same reason: a failure was rejected twice, once by a blocklist and
once by falling outside an allowlist, so either alone sufficed and neither could
be isolated. That is the same redundant-guard pattern just reported in
matrix.rs — reproduced from scratch in a new file, which is a decent argument
for how easily it happens.

Collapsed to the single allowlist, with the blocklist demoted to wording the
error message. Re-running every mutation:

  allowlist gains "failure"            13/14   caught
  allowlist accepts everything         10/14   caught
  conclusion check removed              9/14   caught
  canary presence defeated             11/14   caught
  never waits for in-progress          11/14   caught
  waits on itself (deadlock)           12/14   caught

6 of 6. The workflow runs `--self-test` as its first step, so the gate proves
itself before it judges anything.

NOT wired into branch protection by this commit. A context that has never
reported cannot be required without blocking every open PR, including the four
in this release's stack. Protection gets set after this lands and reports once.

Refs #316


Claude-Session: https://claude.ai/code/session_012aR3Md1h46K9wAUWMQiESH

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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