From 3903a943415731d43f62187369b11c058f6e4d80 Mon Sep 17 00:00:00 2001 From: BitmapAsset Date: Sat, 10 Oct 2026 17:29:16 -0700 Subject: [PATCH] Release probbit 0.8.1 --- .github/workflows/bench.yml | 2 +- CHANGELOG.md | 6 +++--- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- README.md | 5 ++--- docs/CHEATSHEET.md | 2 +- docs/persona.md | 5 +++-- install.ps1 | 6 +++--- install.sh | 6 +++--- npm/package.json | 2 +- probbit-cli/Cargo.toml | 6 +++--- probbit-cli/tests/persona.rs | 12 ++++++++++-- probbit-decide/Cargo.toml | 4 ++-- probbit-ir/Cargo.toml | 2 +- scripts/bench/test_install_ps1.ps1 | 2 +- scripts/tests/test_install_contract.py | 4 ++-- 16 files changed, 42 insertions(+), 34 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 85cb328..bda4003 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -19,7 +19,7 @@ permissions: contents: read env: CARGO_TERM_COLOR: never - TAG: v0.8.0 + TAG: v0.8.1 jobs: bench: diff --git a/CHANGELOG.md b/CHANGELOG.md index 216e2e5..f2f9a53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). -## Unreleased +## 0.8.1 - 2026-10-10 ### Fixed - Reject invalid JSON number grammar and raw control characters, platform-sized integer overflow, overflowing linear-cap @@ -33,8 +33,8 @@ All notable changes to this project are documented here. The format follows - Independent enumeration/router oracles, actual WASM boundary tests, real Chromium interaction/layout checks, Node process contracts and cross-platform clean-prefix install tests (including Windows PowerShell 5.1/7 and npm 12). -These changes are a source candidate, not an update to the published 0.8.0 artifacts. Probability gate thresholds and frozen -benchmark corpora are unchanged. Passing finite tests is not exhaustive validation of every possible program or application. +This patch release hardens validation, lifecycle handling and the host-facing contracts. Probability gate thresholds and +frozen benchmark corpora are unchanged. Passing finite tests is not exhaustive validation of every possible program or application. ## 0.8.0 - 2026-10-09 diff --git a/Cargo.lock b/Cargo.lock index ded6842..2f28dbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "probbit-cli" -version = "0.8.0" +version = "0.8.1" dependencies = [ "probbit-core", "probbit-decide", @@ -13,11 +13,11 @@ dependencies = [ [[package]] name = "probbit-core" -version = "0.8.0" +version = "0.8.1" [[package]] name = "probbit-decide" -version = "0.8.0" +version = "0.8.1" dependencies = [ "probbit-core", "probbit-ir", @@ -25,14 +25,14 @@ dependencies = [ [[package]] name = "probbit-ir" -version = "0.8.0" +version = "0.8.1" dependencies = [ "probbit-core", ] [[package]] name = "probbit-wasm" -version = "0.8.0" +version = "0.8.1" dependencies = [ "probbit-core", "probbit-decide", diff --git a/Cargo.toml b/Cargo.toml index 58dde7e..e83f01c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["probbit-core", "probbit-ir", "probbit-decide", "probbit-cli", "probbit-wasm"] [workspace.package] -version = "0.8.0" +version = "0.8.1" edition = "2021" license = "Apache-2.0" rust-version = "1.78" diff --git a/README.md b/README.md index 9d52e8e..fcceb6a 100644 --- a/README.md +++ b/README.md @@ -105,8 +105,7 @@ modes"). The tables are in [BENCHMARKS.md](BENCHMARKS.md). ## Install -One line, no sudo, the archive's SHA-256 checked before anything is installed. The current release is v0.8.0; the one-liners -were verified from clean machines on macOS (Apple silicon) and Linux (x86_64). +One line, no sudo, the archive's SHA-256 checked before anything is installed. The current release is v0.8.1. | how | command | |---|---| @@ -534,7 +533,7 @@ anywhere ([docs/persona.md](docs/persona.md) ยง5.8). No strand yet? The tutor's ```sh probbit monitor --demo --open -probbit monitor --demo drives --open # source checkout: synthetic goals/drive demo, not yet in the 0.8.0 release +probbit monitor --demo drives --open # synthetic goals/drive demo (0.8.1) ``` ![probbit monitor: the tutor's week in the browser, the bars moving with every event](docs/probbit-monitor.gif) diff --git a/docs/CHEATSHEET.md b/docs/CHEATSHEET.md index b7e1462..1c09108 100644 --- a/docs/CHEATSHEET.md +++ b/docs/CHEATSHEET.md @@ -1,4 +1,4 @@ -# probbit cheat sheet (0.8.0) +# probbit cheat sheet (0.8.1) The CLI's own help and docs, shortened; `probbit --help` has every flag. diff --git a/docs/persona.md b/docs/persona.md index f5779df..6f2f5d2 100644 --- a/docs/persona.md +++ b/docs/persona.md @@ -698,7 +698,7 @@ credits every learned trait with the level it took, so a reward aimed at one tra demo below, praise for short answers also raised the light joke the tutor was making): learn the traits the feedback judges. **The strand.** `--strand FILE` logs the life. Line 1 is the header, compact JSON: -`{"probbit_strand":1,"engine":"probbit 0.8.0","persona":{"name","version","digest"},"seed":N,"state":{...},"document":{...}}`, +`{"probbit_strand":1,"engine":"probbit 0.8.1","persona":{"name","version","digest"},"seed":N,"state":{...},"document":{...}}`, with the initial state as canonical JSON (one individual has one header, whether it comes from `init` or from a state file) and the persona document in its own key order (the compiled program, and `engine.program` with it, follows the document's key order). Then one line per event, canonical JSON: `{"inputs":{...,"elapsed_hours":h},"n":k,"prev":"sha256:...","stance":"sha256:...", @@ -790,7 +790,8 @@ anchor. Historical measurement before this hardening, on an Apple M4: a 10,000-e bytes, 10 checkpoint lines) opens in `monitor --once` from its last checkpoint in 0.005 s (load 3.3), where a full `verify` takes 192 s. A replay from a checkpoint costs what the events after it cost: 500 events past the last one took 26 s to draw (load 6-13, about 50 ms an event for this persona), so K bounds the wait; a smaller K costs a few KB per checkpoint line. A strand without checkpoint or control lines (shorter than K events, or `--checkpoint-every 0`) is the -strand of 0.8.0, byte for byte; a binary before this one does not read the new lines. +same format as in 0.8.0. New strand headers record the current engine version, so their chain hashes differ; existing +0.8.0 strands remain replayable. Binaries before 0.8.0 do not read checkpoint or control lines. Exit codes: `live` 0 when every event got its stance; 2 for a bad persona, state or flag, and after a bad event (each bad event prints one `{"error"}` object on stdout, changes nothing and the run goes on); 4 when the writer lock is held by another writer diff --git a/install.ps1 b/install.ps1 index 1d34815..b77af05 100644 --- a/install.ps1 +++ b/install.ps1 @@ -2,10 +2,10 @@ # and installs probbit.exe. No administrator rights needed. # # irm https://raw.githubusercontent.com/BitmapAsset/probbit/main/install.ps1 | iex -# & ([scriptblock]::Create((irm https://raw.githubusercontent.com/BitmapAsset/probbit/main/install.ps1))) -Version v0.8.0 +# & ([scriptblock]::Create((irm https://raw.githubusercontent.com/BitmapAsset/probbit/main/install.ps1))) -Version v0.8.1 # # Parameters (each also read from the environment variable named after it): -# -Version PROBBIT_VERSION release tag, e.g. v0.8.0 (default: the latest release) +# -Version PROBBIT_VERSION release tag, e.g. v0.8.1 (default: the latest release) # -InstallDir PROBBIT_INSTALL_DIR where probbit.exe goes (default: $HOME\.local\bin) # -DownloadBase PROBBIT_DOWNLOAD_BASE the archive is fetched from //probbit--.zip # (default: https://github.com/BitmapAsset/probbit/releases/download); needs -Version @@ -37,7 +37,7 @@ $customBase = [bool]$DownloadBase if (-not $DownloadBase) { $DownloadBase = "https://github.com/$Repo/releases/download" } $DownloadBase = $DownloadBase.TrimEnd('/') if (-not $Version) { - if ($customBase) { throw 'probbit install: set -Version (or PROBBIT_VERSION), e.g. v0.8.0, together with -DownloadBase' } + if ($customBase) { throw 'probbit install: set -Version (or PROBBIT_VERSION), e.g. v0.8.1, together with -DownloadBase' } $Version = (Invoke-RestMethod -UseBasicParsing -Uri "https://api.github.com/repos/$Repo/releases/latest").tag_name if (-not $Version) { throw 'probbit install: no published release found (set -Version)' } } diff --git a/install.sh b/install.sh index df2bb36..da1911a 100755 --- a/install.sh +++ b/install.sh @@ -2,10 +2,10 @@ # probbit installer: fetches a prebuilt release archive, checks its SHA-256 and installs the `probbit` binary. Never uses sudo. # # curl -fsSL https://raw.githubusercontent.com/BitmapAsset/probbit/main/install.sh | sh -# curl -fsSL https://raw.githubusercontent.com/BitmapAsset/probbit/main/install.sh | PROBBIT_VERSION=v0.8.0 sh +# curl -fsSL https://raw.githubusercontent.com/BitmapAsset/probbit/main/install.sh | PROBBIT_VERSION=v0.8.1 sh # # Environment (all optional): -# PROBBIT_VERSION release tag, e.g. v0.8.0 (default: the latest release) +# PROBBIT_VERSION release tag, e.g. v0.8.1 (default: the latest release) # PROBBIT_INSTALL_DIR where `probbit` goes (default: ~/.local/bin; never overwrites a system-wide install implicitly) # PROBBIT_DOWNLOAD_BASE the archive is fetched from $PROBBIT_DOWNLOAD_BASE//probbit--.tar.gz # (default: https://github.com/BitmapAsset/probbit/releases/download); needs PROBBIT_VERSION @@ -90,7 +90,7 @@ main() { version="${PROBBIT_VERSION:-}" if [ -z "$version" ]; then - [ -z "${PROBBIT_DOWNLOAD_BASE:-}" ] || die "set PROBBIT_VERSION (e.g. v0.8.0) together with PROBBIT_DOWNLOAD_BASE" + [ -z "${PROBBIT_DOWNLOAD_BASE:-}" ] || die "set PROBBIT_VERSION (e.g. v0.8.1) together with PROBBIT_DOWNLOAD_BASE" version=$(latest_tag) || exit 1 [ -n "$version" ] || die "no published release found (set PROBBIT_VERSION)" fi diff --git a/npm/package.json b/npm/package.json index e3062c7..b2f3dc5 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "probbit", - "version": "0.8.0", + "version": "0.8.1", "description": "probbit, a virtual p-bit processor for joint decisions under hard rules: installs the prebuilt probbit CLI (JSON in, JSON out).", "license": "Apache-2.0", "repository": { diff --git a/probbit-cli/Cargo.toml b/probbit-cli/Cargo.toml index d9db486..89a0ff3 100644 --- a/probbit-cli/Cargo.toml +++ b/probbit-cli/Cargo.toml @@ -15,6 +15,6 @@ name = "probbit" path = "src/main.rs" [dependencies] -probbit-core = { path = "../probbit-core", version = "0.8.0" } -probbit-decide = { path = "../probbit-decide", version = "0.8.0" } -probbit-ir = { path = "../probbit-ir", version = "0.8.0" } +probbit-core = { path = "../probbit-core", version = "0.8.1" } +probbit-decide = { path = "../probbit-decide", version = "0.8.1" } +probbit-ir = { path = "../probbit-ir", version = "0.8.1" } diff --git a/probbit-cli/tests/persona.rs b/probbit-cli/tests/persona.rs index 42f2e39..1ac5969 100644 --- a/probbit-cli/tests/persona.rs +++ b/probbit-cli/tests/persona.rs @@ -650,8 +650,9 @@ fn src_events() -> Vec { e.push(format!(r#""src":"{}""#, ["self", "human:owner", "env:tests", "clock"][t % 4])); format!("{{{}}}", e.join(",")) }).collect() } /// A persona without `reward_from` gives 0.8.0's bytes with `src` in its events (an undeclared input, listed in `ignored`): the -/// trace, the strand and the final state of 300 events of the drives fixture (seed 4) are the ones the 0.8.0 binary writes, and -/// the run leaves no lock file behind. The same persona with `reward_from: [human, env]` refuses the rewards from `self` and the +/// trace, the continued 0.8.0 strand and the final state of 300 events of the drives fixture (seed 4) match the historical +/// digests. Fresh headers identify the current binary; the run leaves no lock file behind. The same persona with +/// `reward_from: [human, env]` refuses the rewards from `self` and the /// clock (exit 2, one error at `inputs.src` per refused event) and accepts the others. #[test] fn src_without_reward_from_gives_0_8_0_bytes() { @@ -661,8 +662,15 @@ fn src_without_reward_from_gives_0_8_0_bytes() { let (c, _, e) = probbit(&["persona", "replay", &fx, "--seed", "4", "--script", &script], ""); assert_eq!(c, 0, "{e}"); assert_eq!(e.trim(), "replay: 300 turns, trace sha256 ce8f247d1fe761ea651998f48360491030f15ba3c8530b0ed59d824868f71b71, final state sha256:52312b3cb8744360e7613e4db2fc5e3d326cb3c2204af7312e2a4196f9781551"); let (_, s0, _) = probbit(&["persona", "init", &fx, "--seed", "4"], ""); std::fs::write(&st, &s0).unwrap(); + let (c, _, e) = probbit(&["live", &fx, "--state", &st, "--clock", "fixed", "--strand", &sd], ""); assert_eq!(c, 0, "{e}"); + let header = read(&sd); assert_eq!(header.lines().count(), 1); + let current_engine = concat!("\"engine\":\"probbit ", env!("CARGO_PKG_VERSION"), "\""); + assert!(header.contains(current_engine), "{header}"); + // Continue a historical header: its version participates in every chained hash. Keep the 0.8.0 golden, not a new hash per release. + std::fs::write(&sd, header.replacen(current_engine, "\"engine\":\"probbit 0.8.0\"", 1)).unwrap(); let (c, out, e) = probbit(&["live", &fx, "--state", &st, "--clock", "fixed", "--strand", &sd], &(evs.join("\n") + "\n")); assert_eq!(c, 0, "{e}"); assert!(e.contains("strand head sha256:c37fea55abbec95d0c0f1f1703741e20c26cd5d66587132917772435a5e94655, final state sha256:52312b3cb8744360e7613e4db2fc5e3d326cb3c2204af7312e2a4196f9781551"), "{e}"); + let (c, verified, e) = probbit(&["live", "verify", &sd], ""); assert_eq!(c, 0, "{verified}{e}"); assert_eq!(out.lines().filter(|l| l.contains(r#""ignored":["src"]"#)).count(), 300); assert!(!std::path::Path::new(&format!("{sd}.lock")).exists(), "the lock is released"); // with reward_from: the rewards from self and the clock are refused, the others accepted diff --git a/probbit-decide/Cargo.toml b/probbit-decide/Cargo.toml index 301739e..d9caedc 100644 --- a/probbit-decide/Cargo.toml +++ b/probbit-decide/Cargo.toml @@ -11,5 +11,5 @@ keywords = ["p-bit", "assignment", "routing", "constraints", "probabilistic"] categories = ["algorithms", "science"] [dependencies] -probbit-core = { path = "../probbit-core", version = "0.8.0" } -probbit-ir = { path = "../probbit-ir", version = "0.8.0" } +probbit-core = { path = "../probbit-core", version = "0.8.1" } +probbit-ir = { path = "../probbit-ir", version = "0.8.1" } diff --git a/probbit-ir/Cargo.toml b/probbit-ir/Cargo.toml index e5e3df8..f6a1ff2 100644 --- a/probbit-ir/Cargo.toml +++ b/probbit-ir/Cargo.toml @@ -11,4 +11,4 @@ keywords = ["p-bit", "constraints", "gibbs-sampling", "probabilistic", "ising"] categories = ["algorithms", "science", "mathematics"] [dependencies] -probbit-core = { path = "../probbit-core", version = "0.8.0" } +probbit-core = { path = "../probbit-core", version = "0.8.1" } diff --git a/scripts/bench/test_install_ps1.ps1 b/scripts/bench/test_install_ps1.ps1 index 7466cfc..b10434b 100644 --- a/scripts/bench/test_install_ps1.ps1 +++ b/scripts/bench/test_install_ps1.ps1 @@ -3,7 +3,7 @@ # installs it with -DownloadBase, then the `irm | iex` form (environment variables), then the tampered copy (must fail). # pwsh -File scripts/bench/test_install_ps1.ps1 -Srv [-Tag v0.2.0] [-AddToPath] # powershell -ExecutionPolicy Bypass -File scripts/bench/test_install_ps1.ps1 -Srv -param([Parameter(Mandatory = $true)][string]$Srv, [string]$Tag = 'v0.8.0', [switch]$AddToPath) +param([Parameter(Mandatory = $true)][string]$Srv, [string]$Tag = 'v0.8.1', [switch]$AddToPath) $ErrorActionPreference = 'Stop' "PowerShell $($PSVersionTable.PSVersion) ($($PSVersionTable.PSEdition))" $oldPath = $env:Path diff --git a/scripts/tests/test_install_contract.py b/scripts/tests/test_install_contract.py index 45ee2f4..afe11b6 100644 --- a/scripts/tests/test_install_contract.py +++ b/scripts/tests/test_install_contract.py @@ -24,7 +24,7 @@ def run_fixture(self, os_name, arch, libc, **extra): script.chmod(0o755) env = dict(os.environ, PATH=str(root) + os.pathsep + os.environ["PATH"], FIXTURE_OS=os_name, FIXTURE_ARCH=arch, FIXTURE_LIBC=libc, - PROBBIT_VERSION="v0.8.0", PROBBIT_INSTALL_DIR=str(root / "bin")) + PROBBIT_VERSION="v0.8.1", PROBBIT_INSTALL_DIR=str(root / "bin")) for key in ["PROBBIT_TARGET", "PROBBIT_DOWNLOAD_BASE"]: env.pop(key, None) env.update(extra) @@ -45,7 +45,7 @@ def test_supported_target_selection(self): with self.subTest(target=target): output = self.run_fixture(os_name, arch, libc) self.assertIn("FIXTURE_FETCH", output) - self.assertIn("probbit-v0.8.0-" + target + ".tar.gz", output) + self.assertIn("probbit-v0.8.1-" + target + ".tar.gz", output) def test_unsupported_architecture_never_fetches(self): output = self.run_fixture("Linux", "riscv64", "glibc")