Skip to content

feat: transcoding attestation protocol, Nix flake, SLSA L3 artifacts - #58

Merged
avrabe merged 1 commit into
mainfrom
feat/attestation-slsa-nix
Mar 18, 2026
Merged

avrabe merged 1 commit into
mainfrom
feat/attestation-slsa-nix

Conversation

@avrabe

@avrabe avrabe commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Phase 2.4: TranscodingPredicate (wsc.dev/transcoding/v1) — in-toto predicate for carrying cryptographic provenance across the WASM→native compilation boundary (synth integration). Builder pattern, create_transcoding_statement() helper, 16 tests.
  • Issue Add Nix flake for reproducible development and build environment #48 / FEAT-3: flake.nix with Rust 1.90.0 via rust-overlay, Bazel 8, cargo-fuzz/audit/deny, rust-toolchain.toml, .envrc for direnv. SLSA Level 3 prerequisite.
  • Rivet artifacts: FEAT-6, DD-5, REQ-12, CR-17, CD-17, CD-18, CV-25, CV-26, DF-16 for full traceability

New files

File Purpose
src/lib/src/transcoding.rs Transcoding attestation protocol (810 lines, 16 tests)
flake.nix Nix flake for reproducible dev environment
rust-toolchain.toml Pin Rust 1.90.0 + wasm32-wasip2 target
.envrc direnv integration

Test plan

  • cargo check — workspace compiles clean
  • cargo test -p wsc -- transcoding — 16/16 tests pass
  • nix flake check --no-build passes
  • nix develop provides rustc 1.90.0 with wasm32-wasip2
  • CI cargo + bazel builds pass
  • CI signing workflow passes

Closes #48

🤖 Generated with Claude Code

…tifacts

Phase 2.4: Implement TranscodingPredicate (wsc.dev/transcoding/v1) for
carrying cryptographic provenance across the WASM→native compilation
boundary. Builder pattern, in-toto Statement wrapper, 16 tests.

Issue #48 / FEAT-3: Add flake.nix with Rust 1.90.0 (rust-overlay),
Bazel 8, cargo-fuzz/audit/deny, direnv integration, and
rust-toolchain.toml for reproducible builds (SLSA L3 prerequisite).

Rivet artifacts: FEAT-6, DD-5, REQ-12, CR-17, CD-17, CD-18, CV-25,
CV-26, DF-16 for full traceability of new work.

Implements: FEAT-6, FEAT-3
Refs: CR-17, CD-17, CD-18

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@avrabe
avrabe merged commit 3f7a8ac into main Mar 18, 2026
12 checks passed
@avrabe
avrabe deleted the feat/attestation-slsa-nix branch March 18, 2026 06:22
avrabe added a commit that referenced this pull request Oct 21, 2026
…solve in CI

With #307 unfreezing the rivet binary the compliance job stops panicking in
Export and fails one step later, in Validate: `rivet validate` exited 1 on
22 errors, and the action's run_validate.sh (rivet #953) propagates that as
the step's exit. So the compliance evidence for v0.12.0 stayed unobtainable
even after the pin fix. This closes it: **Result: PASS, exit 0.**

Every one of the 22 was mechanical, and all were fixed with `rivet modify` /
`rivet link` rather than hand-edited YAML:

**11 x off-vocabulary `status: in-progress`.** `in-progress` was never in
rivet's canonical lifecycle (draft -> proposed -> approved -> implemented ->
verified -> released, plus accepted/deprecated/rejected). rivet gained
enforcement of that enum in REQ-162 / #352 — the value had been passing
silently before. Mapped by checking each artifact against the code, not by
picking whichever value validated:

  FEAT-2,6,7,8,9,10,11 -> implemented   the implementing code exists —
                                        format/mcuboot.rs, transcoding.rs +
                                        intoto.rs, container/digest.rs +
                                        bundle.rs, keyless/proof_cache.rs,
                                        container/referrer.rs,
                                        formal-verification.yml, src/component
  CV-20, CV-21, CV-22  -> approved      deliberately NOT `implemented`. These
                                        are the Verus and Rocq verification
                                        activities, and all three steps exit 1
                                        behind continue-on-error today (CV-22
                                        is a stub; audit C-1 records
                                        `assume(false)` in the Verus side).
                                        A verification that does not discharge
                                        is scoped, not implemented.
  REQ-14               -> approved      its proofs are the three above.

`draft` would have validated too, and for the coverage rules it is the
cheaper answer — a draft artifact is exempt from them. That is exactly why it
was not used: it would have bought a PASS by making the gate look away.
`approved` keeps all three CVs and REQ-14 inside the coverage rules, which is
where the 182 warnings come from.

**6 x `satisfies` declared with zero targets** (DD-8..DD-13). Linked to the
requirement each decision actually serves rather than deleting the empty key,
which would only have moved DD-10's failure to the design-decision coverage
rule:

  DD-8  -> REQ-17   HW-preferred verify with SW fallback <- the wsc:crypto
                    hardware-verify interface with curve negotiation
  DD-9  -> REQ-15   "a wasm component via meld/loom/synth, NOT a hand-carved
                    no_std staticlib" is the design choice FOR REQ-15
  DD-10 -> REQ-19   MAX_SLICE_LEN = 256MB exists to hold the large embedded
                    core-module section REQ-19 names
  DD-11 -> REQ-23   offline keyless verify: SET mandatory, Merkle not checked
  DD-12 -> REQ-23   offline verify cannot check SCTs (no CT key in the bundle)
  DD-13 -> REQ-24   DSSE extracted to wsc-dsse, not into wsc-verify-core

**3 x undeclared `related-to` link type** (H-36/37/38). No STPA link type
expresses hazard-to-hazard adjacency — `prevents` is constraint->hazard,
`leads-to-hazard` is uca->hazard — so the relation had been written with a
type the schema never declared, which made it unenforceable and invisible to
coverage rules. Declared in schemas/stpa-sec.yaml (sigil's own on-disk
schema) rather than deleted: that three hazards are variants of H-25, H-33/34
and H-14 is real analysis, and it is explicitly NOT `sub-hazard` — they are
peers, so neither inherits the other's constraints.

**1 x duplicate artifact id, which is the one real defect in the set.**
CV-25 was declared twice in the same file: "Transcoding attestation roundtrip
test" (#58) and "Mechanised sister logic for compositional attestation-chain
trust" (#94). rivet's own words: "the second definition silently overwrites
the first" — so since #94 the transcoding roundtrip verification has not been
in the trace at all, along with its `verifies` link. The newcomer is
renumbered to CV-35 (`rivet next-id`); the original keeps CV-25. Nothing else
referenced the id, so no links needed rewiring.

This is the one edit made by hand: rivet can *detect* a duplicate id but
offers no `--set-id` and no rename/renumber subcommand, so there is no
command to do it with. Filed as friction in rivet.

Both callers of the compliance action now clone synth before invoking it.
rivet.yaml declares `synth` as an external at ../synth, and ONE unresolvable
external makes rivet abandon cross-ref checking for all of them — reporting
"cross-refs NOT CHECKED — externals failed to load" and counting sigil's 9
valid synth links as broken. A single-repo checkout has no sibling, so CI saw
31 errors where a developer with ../synth checked out saw 22. #307's kiln
removal does nothing about that; this does. actions/checkout refuses to write
outside GITHUB_WORKSPACE, hence a direct `git clone`.

Pinned to v0.87.0 rather than main, deliberately: a compliance bundle is
audit evidence and re-running a release must re-derive the same report, which
a moving HEAD cannot promise. The cost is stated in the workflow comment — a
cross-ref sigil still claims could rot in synth's later commits without this
job noticing. That drift shows up in a developer's local run and when the pin
is next raised.

claims.yaml gains SYNTH-PIN-AGREES so the two callers cannot pin different
refs and validate the release-time report against a different store than the
on-demand one. Negative controls, each run:

  callers pin v0.86.0 vs v0.87.0   -> RED, names both files, exit 1
  one clone step deleted            -> RED, 1 < min-captures 2, exit 1
  claim's pattern has no group      -> RED, named, exit 1

Writing that claim found a real fault in `captures-agree` itself: a pattern
whose group 1 does not participate (easy with an alternation) yielded None
and raised TypeError inside the sort — which aborted main() and left **every
later claim unevaluated**. The run still exited non-zero, so no false green,
but the silently-skipped tail is the dangerous half: fixing the broken claim
later re-enables claims nobody has looked at since. Both shapes are now named
as claim defects and reported, deduped, with the suite running to the end.

Prerequisite for tagging v0.12.0: the release's compliance report is the
V-model evidence, and it has not been produced since 2026-09-08.

Refs #284, #300

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aR3Md1h46K9wAUWMQiESH
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.

Add Nix flake for reproducible development and build environment

1 participant