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
7 changes: 7 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@ jobs:
echo "::notice::Toolchain SBOM written to release-assets/varve-${BARE}.cdx.json"
ls -la release-assets/

- name: Stage onboarding assets (trust root + canonical realms)
run: |
set -euo pipefail
cp trust-roots/rolling.pub release-assets/rolling.pub
cp varve-realms.toml release-assets/varve-realms.toml
ls -la release-assets/

- name: Generate SHA256 checksums
run: |
set -euo pipefail
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## v0.13.1 — 2026-08-08

Cold-start onboarding (REQ-ONBOARD-001) — fixes pulseengine/varve#34,
found by an external consumer cold-starting from the tarball.

- The trust root (`rolling.pub`) and a canonical `varve-realms.toml` now
ship as **release assets**, and the README has a **Getting started**
section — a consumer with only the binary can reach a verified install
- The no-trust-root error now names the **zero-config realm path** (the
stronger mechanism) and where the key is published, instead of steering
to a bare `VARVE_TRUST_ROOT` with nowhere to get the key
- `varve install` **auto-caches** a line-status carried in the installed
layout, so `varve status` works with no `--from-file` step; the status
error describes the real path. Registry-side line-status distribution is
tracked as REQ-STATUS-DIST-001 (v0.14.0)

## v0.13.0 — 2026-08-08

Adversarial inputs (REQ-FUZZ-001 + REQ-PROP-001 + REQ-MATRIX-001 verified).
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/varve", "crates/varve-core"]

[workspace.package]
version = "0.13.0"
version = "0.13.1"
edition = "2024"
license = "Apache-2.0"
repository = "https://github.com/pulseengine/varve"
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,37 @@
> promise. The release plan lives in rivet (`rivet release status`); see
> [SECURITY.md](SECURITY.md) for the current trust posture and its limits.

## Getting started

Install one PulseEngine toolchain layer, verified, and dispatch its tools —
the zero-config path uses a **realm**, so no environment variable is needed:

```sh
# 1. In your project, pin a layer and name the realm:
cat > varve.toml <<'PIN'
manifest-version = 1
[toolchain]
realm = "pulseengine"
channel = "rolling"
layer = "2026.08.2"
PIN

# 2. Drop in the canonical realm definitions (registry + trust root).
# Ships as `varve-realms.toml` with every release, and lives at
# trust-roots/ in this repo:
curl -LO https://github.com/pulseengine/varve/releases/latest/download/varve-realms.toml

# 3. Install (verified against the realm's root — no env var), then shim:
varve install
varve shim install # then: . "$HOME/.varve/env"
rivet --version # dispatched from the pinned layer
```

Without a realm, point `VARVE_TRUST_ROOT` at the published root key
(`rolling.pub`, a release asset) and pass `--from oci://ghcr.io/pulseengine/varve/layers`.
The rolling channel is provisional and makes no qualification promise — see
[SECURITY.md](SECURITY.md).

## The problem

Three consumers, two toolchains, one afternoon:
Expand Down
42 changes: 42 additions & 0 deletions artifacts/requirements.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1252,3 +1252,45 @@ artifacts:
fields:
priority: should
category: non-functional

- id: REQ-ONBOARD-001
type: requirement
title: A cold-start consumer can reach a verified install from the tarball alone
status: verified
release: v0.13.1
description: >
A new consumer with only a release binary shall be able to get to a
verified install: the trust root (rolling.pub) and the canonical
varve-realms.toml ship as release assets and are documented in a README
Getting-started section; the no-trust-root error names the zero-config
realm path and where the key is published (not just "set the env var");
and `varve install` auto-caches a line-status carried in the installed
layout so `varve status` works with no extra step. Found by an external
cold-start (pulseengine/varve#34): the error steered to the weaker
mechanism and the strong one was documented nowhere user-facing.
tags: [ergonomics]
links:
- type: satisfies
target: REQ-REALM-001
fields:
priority: must
category: non-functional

- id: REQ-STATUS-DIST-001
type: requirement
title: Line-status is distributed with the layer over the registry
status: approved
release: v0.14.0
description: >
The deposit pipeline shall sign and attach a baseline line-status to
each layer, and the registry source shall fetch it, so `varve status`
works for a registry (oci://) install without a local layout — closing
the second half of pulseengine/varve#34 (v0.13.1 auto-caches only a
locally-carried layout's status).
tags: [core]
links:
- type: satisfies
target: REQ-KP-001
fields:
priority: should
category: functional
14 changes: 14 additions & 0 deletions artifacts/verification.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -407,3 +407,17 @@ artifacts:
links:
- type: verifies
target: REQ-MATRIX-001

- id: VER-ONBOARD-001
type: verification
title: Cold-start reaches verified install; status auto-works; error guides
status: verified
release: v0.13.1
fields:
method: automated-test
steps:
- run: cargo test -p varve install_auto_caches_a_layout_carried_line_status_so_status_just_works
- run: cargo test -p varve the_trust_root_error_points_to_the_realm_path
links:
- type: verifies
target: REQ-ONBOARD-001
1 change: 1 addition & 0 deletions crates/varve/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ varve-core.workspace = true
assert_cmd = "2.2.2"
hex = "0.4.3"
predicates = "3.1.4"
serde_json = "1.0.151"
tempfile = "3.27.0"
wsc-attestation = "0.10.0"
50 changes: 47 additions & 3 deletions crates/varve/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,16 @@ fn status(store: &Store, from_file: Option<&std::path::Path>) -> anyhow::Result<

let Some(doc) = cache.load(&line, &root_pk)? else {
bail!(
"no line-status document cached for line {line} — ingest one with `varve status --from-file <envelope>`"
"no line-status document cached for line {line}.\n\
\n\
Line-status carries the support window, known problems, and yank state \
for the line. `varve install` caches it automatically when the installed \
layer's oci-layout carries one; otherwise ingest a signed envelope \
explicitly:\n\
\n varve status --from-file <line-status.dsse.json>\n\
\n\
Distribution of line-status via the registry is tracked in \
REQ-STATUS-DIST-001 (pulseengine/varve#34)."
);
};
let report = doc.report_for(&pin.layer);
Expand Down Expand Up @@ -775,8 +784,17 @@ fn trust_root() -> anyhow::Result<varve_core::PinnedKeyVerifier> {
fn trust_root_bytes() -> anyhow::Result<Vec<u8>> {
let Some(path) = std::env::var_os("VARVE_TRUST_ROOT") else {
bail!(
"no trust root configured — set VARVE_TRUST_ROOT to the file holding the \
hex-encoded PulseEngine root public key"
"no trust root configured.\n\
\n\
The zero-config path is a realm: add `realm = \"pulseengine\"` to your \
varve.toml and commit a varve-realms.toml naming the registry and trust \
root — then no environment variable is needed and the realm's root is \
authoritative. The canonical file ships as `varve-realms.toml` with each \
release (and at pulseengine/varve/trust-roots/).\n\
\n\
Or, without a realm, point VARVE_TRUST_ROOT at the published root key \
(`rolling.pub`, a release asset). See the Getting started section of the \
README."
);
};
let path = PathBuf::from(path);
Expand Down Expand Up @@ -868,6 +886,32 @@ fn install(store: &Store, from: Option<&str>, platform: Option<String>) -> anyho
outcome.layer
);
}

// Auto-cache a line-status the source's oci-layout carries (DD-008), so
// `varve status` works with zero extra steps (varve#34). Verified against
// the same realm/trust root; a bad or stale one is a warning, never fatal
// to an otherwise-good install.
if std::path::Path::new(from).join("index.json").is_file() {
let line = pin.layer.line().clone();
match varve_core::read_status_from_layout(std::path::Path::new(from), &line) {
Ok(Some(envelope)) => {
let root_pk = ctx_root_bytes(&ctx)?;
match varve_core::LineStatus::verify_and_parse(&envelope, &root_pk) {
Ok(doc) => {
let cache = varve_core::StatusCache::at_root(store.root());
if let Err(e) = cache.update(&line, &envelope, &doc) {
eprintln!(
"note: layer carried a line-status but it was not cached: {e}"
);
}
}
Err(e) => eprintln!("note: layer carried an unverifiable line-status: {e}"),
}
}
Ok(None) => {}
Err(e) => eprintln!("note: could not read the layer's line-status: {e}"),
}
}
Ok(())
}

Expand Down
83 changes: 83 additions & 0 deletions crates/varve/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,89 @@ arg-prefix = "--wasi-arg"
);
}

// rivet: verifies REQ-ONBOARD-001
#[test]
fn install_auto_caches_a_layout_carried_line_status_so_status_just_works() {
let fx = fixture(Some(PIN_JULY), &[]);
let parent = fx.project.parent().unwrap();
let (sk, pk) = varve_core::generate_root_keypair();
let trust = parent.join("root.pub");
std::fs::write(&trust, hex::encode(&pk)).unwrap();

// Deposit a layer, then attach a signed line-status to its oci-layout.
let sk_path = parent.join("root.key");
std::fs::write(&sk_path, hex::encode(&sk)).unwrap();
let tool = parent.join("t");
std::fs::write(&tool, b"toolbytes").unwrap();
let spec = parent.join("d.toml");
let host = varve_core::host_platform();
std::fs::write(
&spec,
format!(
"layer = \"2026.07.0\"\nchannel = \"qualified\"\ncounter = 1\n\n[[tool]]\nname = \"synth\"\nversion = \"1\"\nplatform = \"{host}\"\npath = \"{}\"\n",
tool.display()
),
)
.unwrap();
let layout = parent.join("layout");
varve(&fx)
.args(["deposit", "--spec"])
.arg(&spec)
.args(["--issued-at", "2026-08-07T00:00:00Z", "--key"])
.arg(&sk_path)
.args(["--out"])
.arg(&layout)
.assert()
.success();

// Sign a line-status and attach it to the layout via the library.
let status_json = r#"{"line":"2026.07","counter":1,"issued-at":"2026-08-07T00:00:00Z","support-until":"2028-07-31","yanked":{},"known-problems":[]}"#;
let doc: varve_core::LineStatus = serde_json::from_str(status_json).unwrap();
let envelope = doc
.sign(
&hex::decode(std::fs::read_to_string(&sk_path).unwrap().trim()).unwrap(),
"k",
)
.unwrap();
let line = "2026.07.0"
.parse::<varve_core::LayerId>()
.unwrap()
.line()
.clone();
varve_core::attach_status_to_layout(&layout, &line, envelope.as_bytes()).unwrap();

// Install — and then `varve status` works with NO --from-file (varve#34).
varve(&fx)
.env("VARVE_TRUST_ROOT", &trust)
.args(["install", "--from"])
.arg(&layout)
.assert()
.success();
varve(&fx)
.env("VARVE_TRUST_ROOT", &trust)
.arg("status")
.assert()
.success()
.stdout(predicate::str::contains("supported until 2028-07-31"));
}

// rivet: verifies REQ-ONBOARD-001
#[test]
fn the_trust_root_error_points_to_the_realm_path() {
let fx = fixture(Some(PIN_JULY), &[]);
let signed = signed_layer_fixture(&fx, "2026.07.0", 1);
varve(&fx)
.args(["install", "--from"])
.arg(&signed.archive)
.assert()
.failure()
.stderr(
predicate::str::contains("realm")
.and(predicate::str::contains("rolling.pub"))
.and(predicate::str::contains("Getting started")),
);
}

// rivet: verifies REQ-COEXIST-001
#[test]
fn list_with_an_empty_core_succeeds_and_says_so() {
Expand Down
11 changes: 11 additions & 0 deletions varve-realms.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Canonical realm definitions for the PulseEngine toolchain.
# Commit this beside your varve.toml (or above it); `realm = "pulseengine"`
# in the pin then needs no environment variable — the realm supplies both
# the registry and the trust root, and the realm's root is authoritative.
#
# The rolling root is PROVISIONAL until the v1.0 ceremony (see SECURITY.md);
# the rolling channel makes no qualification promise.

[realm.pulseengine]
registry = "oci://ghcr.io/pulseengine/varve/layers"
trust-root = "4e771dc62a08be89e3450f8cd807da58ff70af4a4e124ebf2d2b71684cfd9973"
Loading