From 1d4a76515fb2b3c7e19441d3a3cc73e04d2f0665 Mon Sep 17 00:00:00 2001 From: master Date: Mon, 2 Nov 2026 07:33:17 +0000 Subject: [PATCH 01/11] docs(deploy): the runbook, units and scripts say cybernode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The binary and the verbs are cybernode's since the move from soft3 (proposal cybernode § 3.7); the operator reads cybernode everywhere. Kept on purpose: /etc/soft3, /var/lib/soft3, the SOFT3_* variables and the tmux session names, because running hosts carry them. The spacepussy-test files in deploy/ are the legacy chaosnet stand on 7780, now pointed at the cybernode binary and named as legacy in the chain README. --- deploy/chain/README.md | 26 ++++++++++++------- deploy/chain/cybernode-check | 2 +- deploy/chain/cybernode@.service | 4 +-- deploy/chain/nginx-chain-api.conf.template | 2 +- deploy/chain/peers.toml.example | 2 +- deploy/chain/ufw-chain.sh | 4 +-- deploy/deploy-cyberproxy.nu | 7 ++--- .../nginx-rpc.spacepussy-test.soft3.org.conf | 2 +- deploy/spacepussy-test.service | 4 +-- 9 files changed, 31 insertions(+), 22 deletions(-) diff --git a/deploy/chain/README.md b/deploy/chain/README.md index 1503bb2..a62fb88 100644 --- a/deploy/chain/README.md +++ b/deploy/chain/README.md @@ -1,6 +1,6 @@ # deploy/chain — the phase-1 root chain on two hosts -Runbook for running `soft3 node` as the proof-of-work root chain (decision +Runbook for running `cybernode node` as the proof-of-work root chain (decision 2026-10-15, cyber/launch.md; genesis 2026-11-05) on two hosts, cyberproxy and agent-a (owner decision 2026-10-31: no new hardware; deimos and jupiter are retired, see § E). Node-to-node over cyber-radio (QUIC/UDP), the HTTP API @@ -54,6 +54,14 @@ Machine facts behind cyberproxy: `apparatus/servers/cyberproxy/inventory-2026-10 | miner | none | `cybernode mine --backend cpu` when `SOFT3_MINE_ADDRESS` is set | 7780 stays with the 0.10.0 chaosnet node (`spacepussy-test.service`); it is untouched. +Its files are the legacy stand in `deploy/` (`spacepussy-test.service`, +`deploy-cyberproxy.nu`, `nginx-rpc.spacepussy-test.soft3.org.conf`): the +spacepussy-test chaosnet on cyberproxy, now run by the `cybernode` binary +(the `soft3` facade still forwards the verb). Nothing of the root chain lives there. + +Kept names (README § kept names): `/etc/soft3`, `/var/lib/soft3`, `~/.soft3`, +the `SOFT3_*` variables and the tmux session `soft3-` stay as they are +on the running hosts; only what an operator reads and types says cybernode. ## step by step @@ -61,9 +69,9 @@ Machine facts behind cyberproxy: `apparatus/servers/cyberproxy/inventory-2026-10 cyberproxy as below. agent-a: see the variant after step 8. -1. Binary: `cargo install soft3 --version --locked` as `cyber` - (or copy the release binary to `/home/cyber/.cargo/bin/soft3`). Confirm - `soft3 node --help` lists `--chain --radio --peers`; the unit will not work +1. Binary: `cargo install cybernode --version --locked` as `cyber` + (or copy the release binary to `/home/cyber/.cargo/bin/cybernode`). Confirm + `cybernode node --help` lists `--chain --radio --peers`; the unit will not work with the 0.10.0 flag set. 2. Directories: `sudo mkdir -p /etc/soft3 /var/log/cybernode && sudo chown cyber:adm /var/log/cybernode`. 3. Parameters: `sudo cp chain.env.example /etc/soft3/pussy.env`, edit bind/radio/moniker/MemoryMax @@ -78,7 +86,7 @@ cyberproxy as below. agent-a: see the variant after step 8. snippet (`sed` line in the template), include it in the 443 server block, `sudo nginx -t`. Do **not** reload yet: the upstream is down and the location would answer 502 — reload on genesis day. -7. Identity and peers: `soft3 node id --home /var/lib/soft3/pussy` prints the +7. Identity and peers: `cybernode node id --home /var/lib/soft3/pussy` prints the endpoint id (64 hex). Cyberproxy's file is `peers.toml.example` → `/etc/soft3/pussy.peers.toml` (cyberproxy listed, it skips itself; agent-a is never listed — no inbound address, an id without addrs is a parse @@ -113,7 +121,7 @@ cyberproxy as below. agent-a: see the variant after step 8. 60 s and agree on height; on the dev chain the script goes on to assert a late joiner's IBD, a transaction crossing the fleet, a depth-3 fork reorg and a kill-and-restart recovery, all on the consensus kernel with - `soft3 mine --backend cpu` processes doing the work (receipts: + `cybernode mine --backend cpu` processes doing the work (receipts: `audit/chain/net-2026-10-15.md`, `audit/chain/slice-2026-10-16.md`). 2. Dress rehearsal on the real two hosts with `SOFT3_CHAIN=pussy-rc` (the launch allow-list has it), ports 7783/7793: @@ -190,14 +198,14 @@ is a parameter or a line in one file above. | assumption | where it lands | |--|--| -| CLI: `soft3 node --chain --home DIR --bind HOST:PORT --radio HOST:UDP --peers FILE --moniker NAME`; `soft3 node id --home DIR` prints the radio endpoint id — **implemented on the kernel** (soft3#187, slice 2026-10-16): `--home` is the chain directory (`radio.key`, `genesis.tade`, `blocks/` = the kernel's store, `anchors`); `--genesis FILE` installs the genesis file into it (required for `pussy`/`bostrom` until the file is embedded). The node does not mine: miners run `soft3 mine --node http://BIND --address NEURON` against `/template` + `/submit` | unit `ExecStart`, harness probe, step A7 | -| chain ids in the launch set are `pussy`, `bostrom` (and `pussy-rc` for rehearsal) — from `crate/src/node/genesis.rs` on branch `launch/45-genesis-chain-allowlist`; protocol `soft3//v1`, engine `cybergraph+bbg` | env `SOFT3_CHAIN`, harness placeholder genesis | +| CLI: `cybernode node --chain --home DIR --bind HOST:PORT --radio HOST:UDP --peers FILE --moniker NAME`; `cybernode node id --home DIR` prints the radio endpoint id — **implemented on the kernel** (soft3#187, slice 2026-10-16): `--home` is the chain directory (`radio.key`, `genesis.tade`, `blocks/` = the kernel's store, `anchors`); `--genesis FILE` installs the genesis file into it (required for `pussy`/`bostrom` until the file is embedded). The node does not mine: miners run `cybernode mine --node http://BIND --address NEURON` against `/template` + `/submit` | unit `ExecStart`, harness probe, step A7 | +| chain ids in the launch set are `pussy`, `bostrom` (and `pussy-rc` for rehearsal) — from `rs/src/node/genesis.rs` on branch `launch/45-genesis-chain-allowlist`; protocol `soft3//v1`, engine `cybergraph+bbg` | env `SOFT3_CHAIN`, harness placeholder genesis | | the radio endpoint id is the node's ed25519 public key, printed as 64 lowercase hex (radio `iroh-base` `PublicKey: Display`) | peers format `id` | | radio listens on one UDP port given by `--radio`; direct dial (agent-a → cyberproxy), no relay | ufw rule, peers `relay = ""` | | HTTP API keeps `GET /health` and `GET /status` with `height` and the bbg root (cyb bodies poll `/status` every 15 s with a 10 s timeout, `cyb/shell/src/worlds/body/networks.rs`), bodies limited to 8 MiB (`specs/native-node.md`) | nginx snippet, check script | | block cadence: ASERT targets 30 s (chain.md §1.5, half-life 600 s); `SOFT3_STALE_SECS=600` = 20 missed blocks, keep unless launch hashrate says otherwise | env, check `progress` | | PoW timestamp rule tolerates clock error of seconds; timesyncd (SNTP, ms-level) is enough, chrony is optional | unit `time-sync.target`, check `clock` | | state is `$home/blocks/` (headers, bodies, undo journals) + `$home/genesis.tade` (a tade file, chain.md §4.1 — not `genesis.json`); a restart replays every main-chain body on the genesis state and verifies the roots (§3.6) — measured locally in seconds for tens of blocks; budget it for the real height before genesis day | state dir, `TimeoutStopSec`, step C T−15 | -| the genesis file is produced by `soft3 genesis build` (specs/genesis.md) and copied byte-identical; `scripts/chain-fleet.nu` generates a test genesis (`soft3 chain genesis-dev`: chain `dev`, floor target `POW_LIMIT_BITS`, genesis time two hours back) when none is given — never a launch chain | step C T−15 | +| the genesis file is produced by `cybernode genesis build` (specs/genesis.md) and copied byte-identical; `scripts/chain-fleet.nu` generates a test genesis (`cybernode chain genesis-dev`: chain `dev`, floor target `POW_LIMIT_BITS`, genesis time two hours back) when none is given — never a launch chain | step C T−15 | | agent-a sits behind NAT; it dials cyberproxy, inbound to it is not assumed; a two-node stand has one dialable node, so cyberproxy restarting leaves agent-a reconnecting every `DIAL_INTERVAL` (3 s, `discovery.rs`) | peers files (agent-a is never listed), step C order, § layout | | `parse_peers`: an entry with an empty `id` is skipped, one with an id but no parsable `addrs` is a hard error; a node skips itself; `relay` is ignored by the code | `peers.toml.example`, `agent-a.peers.toml.example` | diff --git a/deploy/chain/cybernode-check b/deploy/chain/cybernode-check index 909927c..49f21d6 100755 --- a/deploy/chain/cybernode-check +++ b/deploy/chain/cybernode-check @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# cybernode-check — health of one soft3 root-chain instance on this machine. +# cybernode-check — health of one cybernode root-chain instance on this machine. # # cybernode-check pussy # full check, one line per probe, exit 0 ok / 1 degraded / 2 down # cybernode-check --pre pussy # ExecStartPre gate: clock synchronized, disk, env sanity (no HTTP) diff --git a/deploy/chain/cybernode@.service b/deploy/chain/cybernode@.service index 699b9d2..8c424c9 100644 --- a/deploy/chain/cybernode@.service +++ b/deploy/chain/cybernode@.service @@ -1,10 +1,10 @@ -# soft3 phase-1 root chain — one instance per chain: cybernode@pussy, cybernode@bostrom. +# cybernode — the phase-1 root chain, one instance per chain: cybernode@pussy, cybernode@bostrom. # Parameters come from /etc/soft3/.env (template: chain.env.example). # Install: sudo cp cybernode@.service /etc/systemd/system/ && sudo systemctl daemon-reload # Start: sudo systemctl enable --now cybernode@pussy (genesis day only — see README.md) [Unit] Description=cybernode — root chain node (%i) — cybergraph + bbg + radio -Documentation=https://github.com/cyberia-to/soft3/tree/main/deploy/chain +Documentation=https://github.com/cyberia-to/cybernode/tree/master/deploy/chain After=network-online.target time-sync.target Wants=network-online.target time-sync.target # PoW timestamp rules assume a sane clock: refuse to start when the clock is not NTP-synchronized. diff --git a/deploy/chain/nginx-chain-api.conf.template b/deploy/chain/nginx-chain-api.conf.template index 83754d8..d0bfbdd 100644 --- a/deploy/chain/nginx-chain-api.conf.template +++ b/deploy/chain/nginx-chain-api.conf.template @@ -1,4 +1,4 @@ -# soft3 root-chain HTTP API behind nginx — one snippet per chain instance. +# cybernode root-chain HTTP API behind nginx — one snippet per chain instance. # Render: sed -e 's/@CHAIN@/pussy/g' -e 's/@BIND@/127.0.0.1:7781/g' nginx-chain-api.conf.template \ # | sudo tee /etc/nginx/snippets/cybernode-pussy.conf # Include inside the public 443 server block (cyb.ai on cyberproxy, deimos.cybernode.ai on deimos): diff --git a/deploy/chain/peers.toml.example b/deploy/chain/peers.toml.example index c4f319c..311518b 100644 --- a/deploy/chain/peers.toml.example +++ b/deploy/chain/peers.toml.example @@ -6,7 +6,7 @@ # [[peer]] # name = "cyberproxy" operator label, informational # id = "" radio endpoint id: the node's ed25519 public key, 64 lowercase hex chars, -# printed by `soft3 node id --home ` on that machine; empty string +# printed by `cybernode node id --home ` on that machine; empty string # is allowed ONLY in the local harness (trust on first dial) # addrs = ["ip:port", ...] direct UDP addresses; v4 and v6 may both be listed # relay = "" optional relay URL; the stand dials direct, leave empty diff --git a/deploy/chain/ufw-chain.sh b/deploy/chain/ufw-chain.sh index 9d3361a..b1e9295 100755 --- a/deploy/chain/ufw-chain.sh +++ b/deploy/chain/ufw-chain.sh @@ -1,12 +1,12 @@ #!/usr/bin/env bash # Open (or close) the radio QUIC/UDP port of one chain instance in ufw, idempotently, with a log line. # -# sudo ./ufw-chain.sh open pussy # reads SOFT3_RADIO from /etc/soft3/pussy.env +# sudo ./ufw-chain.sh open pussy # reads SOFT3_RADIO from /etc/soft3/pussy.env (kept path, see README § kept names) # sudo ./ufw-chain.sh close pussy # sudo ./ufw-chain.sh show pussy # # Only the UDP port is touched. The HTTP API stays on loopback behind nginx (80/443 are already open -# on every cybernode machine). jupiter has no passwordless sudo: run this there as root by hand. +# on every apparatus host). jupiter has no passwordless sudo: run this there as root by hand. set -euo pipefail action="${1:-}"; chain="${2:-}" diff --git a/deploy/deploy-cyberproxy.nu b/deploy/deploy-cyberproxy.nu index cded445..0386979 100644 --- a/deploy/deploy-cyberproxy.nu +++ b/deploy/deploy-cyberproxy.nu @@ -1,11 +1,12 @@ #!/usr/bin/env nu -# Deploy real soft3 node (cybergraph+bbg) to cyberproxy. +# Deploy the cybernode binary (spacepussy-test chaosnet, cybergraph+bbg) to cyberproxy. +# Legacy stand of the 0.10.0 chaosnet on 7780; the root chain is deploy/chain/. const HOST = "cyberproxy" def main [] { - print $"deploying soft3 node → ($HOST)" - ^ssh $HOST 'export PATH=$HOME/.cargo/bin:$PATH; cargo install soft3 --force' + print $"deploying cybernode → ($HOST)" + ^ssh $HOST 'export PATH=$HOME/.cargo/bin:$PATH; cargo install cybernode --force' ^scp "deploy/spacepussy-test.service" $"($HOST):/tmp/spacepussy-test.service" ^ssh $HOST 'sudo cp /tmp/spacepussy-test.service /etc/systemd/system/spacepussy-test.service' ^ssh $HOST 'sudo systemctl daemon-reload && sudo systemctl enable --now spacepussy-test && sudo systemctl restart spacepussy-test' diff --git a/deploy/nginx-rpc.spacepussy-test.soft3.org.conf b/deploy/nginx-rpc.spacepussy-test.soft3.org.conf index f10c028..12e7d7b 100644 --- a/deploy/nginx-rpc.spacepussy-test.soft3.org.conf +++ b/deploy/nginx-rpc.spacepussy-test.soft3.org.conf @@ -1,4 +1,4 @@ -# spacepussy-test product RPC — soft3 chaosnet +# spacepussy-test product RPC — the chaosnet served by cybernode (legacy stand on 7780) # DNS: rpc.spacepussy-test.soft3.org → cyberproxy (167.235.28.94) # Also serves bare Host/IP on :80 for bootstrap before TLS. diff --git a/deploy/spacepussy-test.service b/deploy/spacepussy-test.service index dd74cf4..a24d1d5 100644 --- a/deploy/spacepussy-test.service +++ b/deploy/spacepussy-test.service @@ -1,5 +1,5 @@ [Unit] -Description=spacepussy-test soft3 node (cybergraph+bbg) +Description=spacepussy-test cybernode (cybergraph+bbg chaosnet) After=network.target [Service] @@ -8,7 +8,7 @@ User=cyber Group=cyber Environment=HOME=/home/cyber Environment=PATH=/home/cyber/.cargo/bin:/usr/local/bin:/usr/bin -ExecStart=/home/cyber/.cargo/bin/soft3 node --home /home/cyber/spacepussy-test/data --bind 127.0.0.1:7780 --moniker cyberproxy-spt +ExecStart=/home/cyber/.cargo/bin/cybernode node --home /home/cyber/spacepussy-test/data --bind 127.0.0.1:7780 --moniker cyberproxy-spt Restart=on-failure RestartSec=3 LimitNOFILE=65536 From d6f586b9bfe4059583d09662c9015d2bdc6804a0 Mon Sep 17 00:00:00 2001 From: master Date: Mon, 2 Nov 2026 07:34:21 +0000 Subject: [PATCH 02/11] docs(rs): user-visible strings and comments name cybernode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Banners, help lines, error messages and doc comments say cybernode and its verbs (node, mine, genesis, conformance) instead of the soft3 binary that no longer owns them; the default moniker becomes "cybernode". Only string literals and comments change — no logic, no env variable names: SOFT3_CHAIN/BIND/RADIO/MONIKER/STALE_SECS/MIN_DISK_GIB, SOFT3_DEV_EPOCH, SOFT3_COST_* and SOFT3_CONFORMANCE_DIR stay, as do the wire identifiers soft3/…/v1 and the ~/.soft3 home. Not compiled here: the sibling pins (lens gpu, bbg fjall) do not resolve on this machine. --- rs/src/chain/conformance.rs | 6 +++--- rs/src/chain/economy/vectors.rs | 2 +- rs/src/chain/genesis.rs | 2 +- rs/src/chain/mine/bench.rs | 2 +- rs/src/chain/mine/mod.rs | 2 +- rs/src/chain/mine/run.rs | 6 +++--- rs/src/chain/mod.rs | 2 +- rs/src/cli.rs | 10 +++++----- rs/src/lib.rs | 2 +- rs/src/network.rs | 8 ++++---- rs/src/node.rs | 6 +++--- rs/src/node/chain_node.rs | 8 ++++---- rs/tests/chain_adversary.rs | 6 +++--- rs/tests/mine_e2e.rs | 2 +- 14 files changed, 32 insertions(+), 32 deletions(-) diff --git a/rs/src/chain/conformance.rs b/rs/src/chain/conformance.rs index af77e8f..bcf9aac 100644 --- a/rs/src/chain/conformance.rs +++ b/rs/src/chain/conformance.rs @@ -1,7 +1,7 @@ //! The blessed vectors of chain-conformance.md (§6 signed transactions, §8 //! state, §10 genesis, §12 the pool) as `conformance/chain.snap` entries, generated from -//! the reference implementation. `soft3 conformance bless chain` writes -//! the file; `soft3 conformance check chain` regenerates and compares. +//! the reference implementation. `cybernode conformance bless chain` writes +//! the file; `cybernode conformance check chain` regenerates and compares. //! //! Entries are `hemera` fingerprints of canonical bytes where the stack's //! snapshot discipline wants fingerprints, and the 32-byte value itself @@ -204,7 +204,7 @@ pub fn render(entries: &[Entry], date: &str) -> String { let label = |e: &Entry| format!("{} :: \"{}\"", e.key, e.scenario); let width = sorted.iter().map(|e| label(e).len()).max().unwrap_or(0); let mut out = String::new(); - out.push_str(&format!("# generated by `soft3 conformance bless chain` ({date}); do not edit by hand\n")); + out.push_str(&format!("# generated by `cybernode conformance bless chain` ({date}); do not edit by hand\n")); out.push_str("# chain.snap — chain-conformance.md blessed vectors: §6 signed txs (fingerprints), §8 state roots and ids, §10 genesis\n"); out.push_str("# (particle = chain id, block-0 root and id, genesis weights, phi*_0), §12 pool formats (frozen at genesis), §14 the focus economy (tru epoch engine e660d1e).\n"); out.push_str("# Roots move with bbg's DIMENSION_VERSION, economy entries with tru's engine; a moved entry is a consensus change.\n"); diff --git a/rs/src/chain/economy/vectors.rs b/rs/src/chain/economy/vectors.rs index a58bb6a..463c2f9 100644 --- a/rs/src/chain/economy/vectors.rs +++ b/rs/src/chain/economy/vectors.rs @@ -4,7 +4,7 @@ //! The scenarios run the block transition itself (`apply::transition`), //! not the miner: block ids are `hemera(D_BLOCK ‖ height ‖ prev ‖ root)`, //! there is no header and no proof of work, so the binary can bless them -//! (`soft3 conformance bless chain`) without the test-only PoW limit. Every +//! (`cybernode conformance bless chain`) without the test-only PoW limit. Every //! economy rule on the path — coinbase split, settlement at //! `boundary + D`, snapshots keyed by boundary id, the journal that a //! reorganization reverts — is the kernel's own code. diff --git a/rs/src/chain/genesis.rs b/rs/src/chain/genesis.rs index 0aee1d6..b981b46 100644 --- a/rs/src/chain/genesis.rs +++ b/rs/src/chain/genesis.rs @@ -196,7 +196,7 @@ pub(crate) mod tests { } /// chain-conformance §10 against the real pussy file when it is cached - /// (`soft3 genesis build` writes ~/.cache/cyber/genesis/pussy.tade); + /// (`cybernode genesis build` writes ~/.cache/cyber/genesis/pussy.tade); /// the release gate `stack-chain-genesis` runs the same comparison. #[test] fn pussy_genesis_matches_the_blessed_vectors_when_cached() { diff --git a/rs/src/chain/mine/bench.rs b/rs/src/chain/mine/bench.rs index 6245c9b..0437969 100644 --- a/rs/src/chain/mine/bench.rs +++ b/rs/src/chain/mine/bench.rs @@ -1,4 +1,4 @@ -//! `soft3 mine bench`: hashes per second of every backend that opens, and +//! `cybernode mine bench`: hashes per second of every backend that opens, and //! the conformance vector found by each (chain-conformance §5: the mined //! header at `GENESIS_BITS`, nonce 934,636). diff --git a/rs/src/chain/mine/mod.rs b/rs/src/chain/mine/mod.rs index 8ec693a..5cc2502 100644 --- a/rs/src/chain/mine/mod.rs +++ b/rs/src/chain/mine/mod.rs @@ -1,4 +1,4 @@ -//! `soft3 mine` — the reference miner of the phase-1 root chain +//! `cybernode mine` — the reference miner of the phase-1 root chain //! (specs/chain.md §1.1–1.3, §8; chain-wire.md §7). //! //! The miner takes a header template from a node, grinds the 16-byte diff --git a/rs/src/chain/mine/run.rs b/rs/src/chain/mine/run.rs index 0e230dd..fddfd9a 100644 --- a/rs/src/chain/mine/run.rs +++ b/rs/src/chain/mine/run.rs @@ -12,7 +12,7 @@ use super::{Backend, Job}; use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -/// `soft3 mine` settings. +/// `cybernode mine` settings. #[derive(Clone, Debug)] pub struct Settings { pub chain: String, @@ -207,7 +207,7 @@ fn leading_zeros(t: &[u64; 4]) -> u32 { z } -/// `soft3 mine`: the loop over templates. Returns only on an unrecoverable +/// `cybernode mine`: the loop over templates. Returns only on an unrecoverable /// error or when `stop` is raised. pub fn run( backend: &dyn Backend, @@ -218,7 +218,7 @@ pub fn run( let client = Client::new(&s.node, &s.address); let salt = salt(); log(&format!( - "soft3 mine · chain {} · node {} · {} · to {}", + "cybernode mine · chain {} · node {} · {} · to {}", s.chain, s.node, backend.name(), diff --git a/rs/src/chain/mod.rs b/rs/src/chain/mod.rs index e46560b..96ad311 100644 --- a/rs/src/chain/mod.rs +++ b/rs/src/chain/mod.rs @@ -4,7 +4,7 @@ //! //! Module map follows chain.md §9.1. Consensus modules are pure functions //! over bytes; [`api`] is the surface the network, miner and HTTP layers -//! code against; [`mine`] is the reference miner (`soft3 mine`); [`net`] is +//! code against; [`mine`] is the reference miner (`cybernode mine`); [`net`] is //! the wire (specs/chain-wire.md), running on the kernel through //! [`net::kernel::KernelChain`]. diff --git a/rs/src/cli.rs b/rs/src/cli.rs index c154b25..dcc11d0 100644 --- a/rs/src/cli.rs +++ b/rs/src/cli.rs @@ -616,7 +616,7 @@ fn hostname_fallback() -> String { fn hostname_fallback_from Result>(get: F) -> String { get("HOSTNAME") .or_else(|_| get("HOST")) - .unwrap_or_else(|_| "soft3-node".into()) + .unwrap_or_else(|_| "cybernode".into()) } fn print_network(n: Network) { @@ -661,10 +661,10 @@ fn print_help() { println!(" cybernode genesis build --chain pussy|bostrom|pussy-rc --snapshot DIR --out FILE"); println!(" [--cache DIR] [--export DIR] [--no-root] [--time UNIX]"); println!(" cybernode genesis verify FILE [--root]"); - println!(" soft3 manifesto"); - println!(" soft3 version"); + println!(" cybernode manifesto"); + println!(" cybernode version"); println!(); - println!("not soft3 networks (cosmos bootloader on cybernode):"); + println!("not soft3 networks (cosmos bootloader on apparatus hosts):"); println!(" space-pussy · bostrom"); println!(); println!("docs https://cyber.page/soft3/docs/launch"); @@ -844,7 +844,7 @@ mod tests { #[test] fn hostname_fallback_defaults_when_neither_env_is_set() { let got = hostname_fallback_from(|_: &str| Err(std::env::VarError::NotPresent)); - assert_eq!(got, "soft3-node"); + assert_eq!(got, "cybernode"); } fn v(items: &[&str]) -> Vec { diff --git a/rs/src/lib.rs b/rs/src/lib.rs index 296d659..4625cd5 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -1,7 +1,7 @@ //! cybernode — rung 7 of the soft3 ladder: the node. moved from the `soft3` crate on 2026-10-27 (soft3/proposals/cybernode.md); the code is unchanged, the wire identifiers (`soft3/chain/…/v1`) and the home directories keep their names. //! //! - [[network]] — spacepussy-test product network (soft3 chaosnet) -//! - [[node]] — real soft3-node over cybergraph + bbg +//! - [[node]] — the node itself over cybergraph + bbg //! - [[genesis]] — burial snapshot → genesis manifest → bbg root (specs/genesis.md) //! - [[wallet]] — sign and submit phase-1 root chain transactions (specs/chain.md) //! - [[chain]] — the phase-1 proof-of-work root chain kernel (specs/chain.md); diff --git a/rs/src/network.rs b/rs/src/network.rs index 63d891f..86894ce 100644 --- a/rs/src/network.rs +++ b/rs/src/network.rs @@ -1,7 +1,7 @@ //! Product networks for the soft3 stack. //! //! Default after install: **spacepussy-test** — the soft3 chaosnet, -//! hosted on cybernode (`rpc.spacepussy-test.soft3.org`). +//! hosted on cyberproxy, an apparatus host (`rpc.spacepussy-test.soft3.org`). //! //! This is not the cosmos-sdk chain named `space-pussy` on cybernode //! (rpc.space-pussy.cybernode.ai). That bootloader chain is a migration @@ -17,7 +17,7 @@ pub enum Network { } impl Network { - /// Product default after `cargo install soft3` / `true-cyber`. + /// Product default after `cargo install cybernode` / `true-cyber`. pub const DEFAULT: Network = Network::SpacePussyTest; pub fn parse(s: &str) -> Option { @@ -81,7 +81,7 @@ impl Network { } } - /// Local bind for `soft3 node` on the host (behind nginx on cyberproxy). + /// Local bind for `cybernode node` on the host (behind nginx on cyberproxy). pub fn local_bind(self) -> &'static str { match self { Self::SpacePussyTest => "127.0.0.1:7780", @@ -161,7 +161,7 @@ pub fn probe(network: Network) -> Result { } } Err(format!( - "no soft3 node at {} — spacepussy-test offline or unreachable", + "no cybernode at {} — spacepussy-test offline or unreachable", network.rpc() )) } diff --git a/rs/src/node.rs b/rs/src/node.rs index c0c1451..215f127 100644 --- a/rs/src/node.rs +++ b/rs/src/node.rs @@ -1,7 +1,7 @@ //! Local HTTP adapter over Cybergraph's durable native coordinator. //! //! Acceptance, economics and recovery are owned by Cybergraph and BBG. -//! See `soft3/specs/native-node.md` for the HTTP and storage lifecycle contract. +//! See `specs/native-node.md` for the HTTP and storage lifecycle contract. pub mod chain_node; mod chain_routes; @@ -168,7 +168,7 @@ pub fn run(home: PathBuf, bind: &str, moniker: &str) -> io::Result<()> { let node = Node::open(home.clone(), moniker.to_owned())?; let listener = TcpListener::bind(bind)?; eprintln!( - "soft3 node · {} · moniker={} · home={}", + "cybernode · {} · moniker={} · home={}", node.network.chain_id(), moniker, home.display() @@ -202,7 +202,7 @@ pub fn run_chain(opts: chain_node::ChainOpts, bind: &str) -> io::Result<()> { let node = chain_node::ChainNode::open(opts)?; let tip = node.chain.tip(); eprintln!( - "soft3 node · chain {} · moniker={} · home={}", + "cybernode · chain {} · moniker={} · home={}", node.chain_name, node.moniker, node.home.display() ); eprintln!(" endpoint {}", hex(node.net.id().as_bytes())); diff --git a/rs/src/node/chain_node.rs b/rs/src/node/chain_node.rs index 6cd4a77..b416ef4 100644 --- a/rs/src/node/chain_node.rs +++ b/rs/src/node/chain_node.rs @@ -1,9 +1,9 @@ //! One chain node: the consensus kernel behind [`ChainView`] //! ([`KernelChain`]), its mempool and the wire, served over the HTTP adapter -//! (chain_routes.rs). Mining is a separate process: `soft3 mine --node URL` +//! (chain_routes.rs). Mining is a separate process: `cybernode mine --node URL` //! speaks `/template` and `/submit` (chain-wire §7). -//! `soft3 node --chain --home DIR --bind HOST:PORT --radio HOST:UDP -//! --peers FILE --moniker NAME` (deploy/chain/soft3-chain@.service). +//! `cybernode node --chain --home DIR --bind HOST:PORT --radio HOST:UDP +//! --peers FILE --moniker NAME` (deploy/chain/cybernode@.service). use std::io; use std::net::SocketAddr; @@ -131,7 +131,7 @@ impl ChainNode { let text = std::fs::read_to_string(path)?; let (peers, skipped) = discovery::parse_peers(&text, &name).map_err(io::Error::other)?; for s in skipped { - eprintln!(" peers: {s:?} has no endpoint id — skipped (QUIC dials need the id; `soft3 node id`)"); + eprintln!(" peers: {s:?} has no endpoint id — skipped (QUIC dials need the id; `cybernode node id`)"); } peers } diff --git a/rs/tests/chain_adversary.rs b/rs/tests/chain_adversary.rs index e76d327..928a6ea 100644 --- a/rs/tests/chain_adversary.rs +++ b/rs/tests/chain_adversary.rs @@ -1,5 +1,5 @@ -//! Adversarial wire tests against a real `soft3 node --chain dev` process on -//! the consensus kernel, mined by a real `soft3 mine --backend cpu` process +//! Adversarial wire tests against a real `cybernode node --chain dev` process on +//! the consensus kernel, mined by a real `cybernode mine --backend cpu` process //! (chain-wire.md §5, chain-conformance.md §11): oversized and malformed //! frames, a stream without the dialect, a wrong-chain status, a body //! pushed on gossip, a PoW-invalid header (never followed by a body @@ -81,7 +81,7 @@ fn wait_until(secs: u64, mut f: impl FnMut() -> bool) -> bool { const MINER: &str = "6666666666666666666666666666666666666666666666666666666666666666"; /// One dev node in a scratch home, a test genesis at the floor target, and -/// a `soft3 mine` process working it. +/// a `cybernode mine` process working it. fn spawn_node(label: &str) -> (NodeProc, Vec) { let home = std::env::temp_dir().join(format!("chain-adversary-{label}-{}", std::process::id())); let _ = std::fs::remove_dir_all(&home); diff --git a/rs/tests/mine_e2e.rs b/rs/tests/mine_e2e.rs index 55d0e18..856f601 100644 --- a/rs/tests/mine_e2e.rs +++ b/rs/tests/mine_e2e.rs @@ -1,4 +1,4 @@ -//! `soft3 mine` against a mock chain node: `GET /template`, `GET /tip`, +//! `cybernode mine` against a mock chain node: `GET /template`, `GET /tip`, //! `POST /submit` (chain-wire §7 shapes). The mock serves a template at //! `POW_LIMIT` (2^236, about a million hashes), verifies every submitted //! header natively with `hemera::hash`, advances its tip on acceptance, From a48f41b2c91ae51d55b083b670b58c2805eb0e2a Mon Sep 17 00:00:00 2001 From: master Date: Mon, 2 Nov 2026 07:34:21 +0000 Subject: [PATCH 03/11] docs(specs): the contracts name cybernode verbs and rs/ paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit genesis build/verify, node --chain, mine and conformance bless/check are cybernode's verbs; the implementation paths point at rs/src/… in this repository instead of soft3/crate/src/…. 'cybernode chain' joins the chain spec's aliases ahead of 'soft3 chain', which stays so old links resolve. --- specs/chain-conformance.md | 6 +++--- specs/chain-economy.md | 2 +- specs/chain-wire.md | 2 +- specs/chain.md | 19 ++++++++++--------- specs/genesis.md | 8 ++++---- specs/routing.md | 2 +- 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/specs/chain-conformance.md b/specs/chain-conformance.md index 9b87307..16d7d78 100644 --- a/specs/chain-conformance.md +++ b/specs/chain-conformance.md @@ -286,7 +286,7 @@ Computed on 2026-10-16 against hemera 0.4 (`cyber-hemera` 0.4.0 at 0.3 values of the same day were re-blessed through the same code, the definitions did not move) through the hemera API alone — `Hasher::finalize_xof` for `W` (chain.md §11.1) and the tree node (§11.4), -`hemera::hash` for the narrow values — by `crate/src/chain/pool/` (tests +`hemera::hash` for the narrow values — by `rs/src/chain/pool/` (tests `chain::pool::tests`). They are blessed as `chain::pool@v1` entries of `conformance/chain.snap`; a snapshot line holds 32 bytes, so a wide value enters it as `hemera(value)`, its @@ -358,8 +358,8 @@ rule-set-1 decoder (§6). `cargo test -p soft3 --features chain-conformance` runs sections 1–9, 11, 12 and 14 on every commit; section 10 runs in the release train (`stack-chain-genesis` gate) because it needs the pinned snapshot inputs. Blessed values are -written by `soft3 conformance bless chain` into `conformance/chain.snap` -(and checked by `soft3 conformance check chain`); the genesis entries of +written by `cybernode conformance bless chain` into `conformance/chain.snap` +(and checked by `cybernode conformance check chain`); the genesis entries of §10 need the genesis file (`--genesis FILE`, default `~/.cache/cyber/genesis/pussy.tade`) and are kept as they are when it is absent. They are reviewed in the PR that moves them. The report of each run goes to `cybernode/audit/chain/conformance-.md` diff --git a/specs/chain-economy.md b/specs/chain-economy.md index 03b87fc..e2ddf8e 100644 --- a/specs/chain-economy.md +++ b/specs/chain-economy.md @@ -958,7 +958,7 @@ audit/epoch-settlement-2026-10.md §6). | owner | change | |---|---| -| soft3 `crate/src/chain` | `params.rs` (§0); `tx.rs` kinds 6, 7 and kind 2 with stake; `apply.rs` coinbase split, settlement step, conservation; `state.rs` card, stake-key and pool operations journaled for reorgs; new `economy/` (`epoch.rs` boundaries and snapshots, `weights.rs` genesis weights and §4.2, `settle.rs` §5 over tru, `emission.rs` §6); `api.rs` §11; `wallet/tx.rs` builders for kinds 6, 7 | +| cybernode `rs/src/chain` | `params.rs` (§0); `tx.rs` kinds 6, 7 and kind 2 with stake; `apply.rs` coinbase split, settlement step, conservation; `state.rs` card, stake-key and pool operations journaled for reorgs; new `economy/` (`epoch.rs` boundaries and snapshots, `weights.rs` genesis weights and §4.2, `settle.rs` §5 over tru, `emission.rs` §6); `api.rs` §11; `wallet/tx.rs` builders for kinds 6, 7 | | bbg | `CardRecord` + `author`, `valence`, `since`; `DIMENSION_VERSION` 4; genesis roots re-blessed | | tru | the epoch entry point `tru::focusing::epoch` (tru/specs/epoch.md): `phi` (canonical input §4.3, `PHI_STEPS`, cold start, 2⁻⁶⁰, J, the digest preimage), `path_gradient` (§5.1, `ATTR_NODES`, the ground of §4.4), `net_axons`, thread-count independent; tru/specs owns the operator transposes and the scale of the gradient (2^112 nats per weight unit) — tru#40 (1ea853e) and tru#41 (e660d1e, the ground and the path gradient) | | foculus | `phi.rs` and the VDF beacon retired from the root chain; specs/epoch.md superseded for the root chain by §2 here | diff --git a/specs/chain-wire.md b/specs/chain-wire.md index 5ec8945..1a22bab 100644 --- a/specs/chain-wire.md +++ b/specs/chain-wire.md @@ -283,7 +283,7 @@ POST /submit {"header":""} ``` A template is tied to its `prev`: a miner polls `GET /tip` (every 2 s in -the reference miner, `soft3 mine`, `crate/src/chain/mine/api.rs`) and +the reference miner, `cybernode mine`, `rs/src/chain/mine/api.rs`) and refreshes when `id` moves, and refreshes anyway after 30 s to pick up new transactions; a miner may check that `target` and `bits` agree. The 16-byte nonce (bytes 24..40) is the miner's alone and nothing else in the header may change between template and submit. diff --git a/specs/chain.md b/specs/chain.md index 24ef7f3..07e0d54 100644 --- a/specs/chain.md +++ b/specs/chain.md @@ -5,7 +5,7 @@ crystal-type: spec crystal-domain: cyber status: accepted date: 2026-10-15 -alias: chain spec, root chain, phase-1 chain, soft3 chain, pussy chain, bostrom chain +alias: chain spec, root chain, phase-1 chain, cybernode chain, soft3 chain, pussy chain, bostrom chain --- # chain — the phase-1 root chain @@ -668,8 +668,8 @@ Rules: carried as its own token instead), so S0 for pussy is the bank supply at halt, 1,099,389,810,279,114,036, and `SUBSIDY = 10,458,426,657`. -The pipeline is a deterministic program — `soft3 genesis build` in -`soft3/crate/src/genesis/` (`specs/genesis.md`; `chain/genesis.rs` consumes +The pipeline is a deterministic program — `cybernode genesis build` in +`rs/src/genesis/` (`specs/genesis.md`; `chain/genesis.rs` consumes its output through `soft3::genesis::state::load`) — whose output file particle is published on the launch page and in the binary; two independent runs must produce byte-identical files (chain-conformance §10). @@ -690,7 +690,7 @@ Genesis times (cyber/launch decisions 2026-09-18): pussy ### 4.4 two chains, one binary -`soft3 node --chain pussy | bostrom [--genesis path]`. The binary embeds the +`cybernode node --chain pussy | bostrom [--genesis path]`. The binary embeds the two chain ids and genesis block ids; `--genesis` is for rehearsal nets (`pussy-rc`) and must match the embedded id for the named chain. All protocol constants (§0) are identical on both chains; only the genesis file @@ -929,8 +929,9 @@ Threats and mitigations, in one list: ### 9.1 crate and placement -The chain is `soft3 node --chain ` in `soft3/crate` — the existing -node crate (rung 7 of the ladder: radio · foculus · tade), reusing `node.rs` +The chain is `cybernode node --chain ` in `cybernode/rs` — the node +crate (rung 7 of the ladder: radio · foculus · tade; moved from `soft3/crate` +on 2026-10-27, proposal cybernode), reusing `node.rs` and the HTTP adapter, with a new `chain` module tree. Reasons: the ladder places the network and its consensus at soft3, below cyb and cyber and above cybergraph/bbg; the HTTP contract cyb and true-cyber consume already lives @@ -944,7 +945,7 @@ only: `hemera` (commit), `bbg` and `cybergraph` (graph), `mudra` and them. ``` -soft3/crate/src/chain/ +cybernode/rs/src/chain/ mod.rs Chain: open(home, name) → tip, state, mempool; Event bus params.rs §0 constants, ACTIVATIONS, embedded chain ids and genesis ids header.rs Header encode/decode (168 B), block_id, bits codec (§1.1–1.3) @@ -993,7 +994,7 @@ Decisions this page could not settle alone, each with the default the implementation uses until the owner says otherwise. Owner decisions 2026-10-15 (recorded by the kernel implementation, -`crate/src/chain/`): every default below stands except item 2. Item 2 was +`rs/src/chain/`): every default below stands except item 2. Item 2 was decided "amount must be 0" on 2026-10-15 and is superseded by the order of 2026-10-17: a cyberlink with `amount > 0` locks native stake on its position, unlocked by kind 6 after `MIN_LOCK` (chain-economy §3); the @@ -1034,7 +1035,7 @@ pinned constants. The chain freezes hemera's profile #15 (x⁷ in every round, 6 + 6 full rounds, 48 partial; hemera 0.4, `release/0.4` `7237a23`); every value quoted is computed against it (re-blessed 2026-10-16 from the 0.3 values, with the genesis particle; the definitions did not change). -Reference implementation: `crate/src/chain/pool/`; vectors: +Reference implementation: `rs/src/chain/pool/`; vectors: chain-conformance §12. ### 11.1 wide digests and canonical limbs diff --git a/specs/genesis.md b/specs/genesis.md index 5aae486..ceb9308 100644 --- a/specs/genesis.md +++ b/specs/genesis.md @@ -3,7 +3,7 @@ title: genesis pipeline tags: cyber, soft3, spec, genesis, bostrom, pussy crystal-type: spec crystal-domain: cyber -alias: genesis pipeline, soft3 genesis, genesis builder +alias: genesis pipeline, cybernode genesis, soft3 genesis, genesis builder --- # genesis pipeline @@ -11,7 +11,7 @@ alias: genesis pipeline, soft3 genesis, genesis builder How the burial snapshots of the bootloader chains become the genesis file of the phase-1 PoW root chain. The file format, the derivation rules and block 0 are normative in [chain.md §4](chain.md#4-genesis); this page is the -pipeline that implements them (`crate/src/genesis/`, `soft3 genesis`), the +pipeline that implements them (`rs/src/genesis/`, `cybernode genesis`), the decisions it takes where §4 leaves room, and its debug export. The node (`chain/genesis.rs`) consumes the file through `soft3::genesis::state::load` and must reproduce the recorded block-0 root. Companion contracts: @@ -38,8 +38,8 @@ fallback. `state_export.json.gz` is not read. ## command ``` -soft3 genesis build --chain pussy|bostrom --snapshot DIR --out FILE [--cache DIR] [--export DIR] [--no-root] [--time UNIX] -soft3 genesis verify FILE [--root] +cybernode genesis build --chain pussy|bostrom --snapshot DIR --out FILE [--cache DIR] [--export DIR] [--no-root] [--time UNIX] +cybernode genesis verify FILE [--root] ``` `build` writes the genesis file to `FILE` and a report to `FILE.json` diff --git a/specs/routing.md b/specs/routing.md index 633cb54..c72b2f8 100644 --- a/specs/routing.md +++ b/specs/routing.md @@ -8,7 +8,7 @@ alias: routing, the wire obeys the graph, locus routing, greedy routing, hyperbo # routing — the wire obeys the graph -How a soft3 node decides whom to dial and whom to forward to. The rule is a +How a cybernode decides whom to dial and whom to forward to. The rule is a composition: the address record comes from [cybergraph](https://github.com/cyberia-to/cybergraph/blob/master/specs/address.md) (FOLLOW, ANTENNA, SOCKET, LOCUS), the coordinate from [tru](https://github.com/cyberia-to/tru/blob/master/specs/locus.md) (locus), From 24ba01ebe7819fa4f7e98e0849e9f879612c7f4b Mon Sep 17 00:00:00 2001 From: master Date: Mon, 2 Nov 2026 08:50:04 +0000 Subject: [PATCH 04/11] feat(scripts): chain-fleet.nu moves in from soft3 The local rehearsal of the root chain belongs with the node: three cybernode nodes on one machine, mined by cybernode mine, resolving the binary from --bin, $CYBERNODE_BIN or cybernode on PATH. Its citations of deploy/chain/README.md and specs/chain-wire.md now resolve in this repository. SOFT3_DEV_EPOCH is the node's own variable and stays. --- scripts/chain-fleet.nu | 371 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100755 scripts/chain-fleet.nu diff --git a/scripts/chain-fleet.nu b/scripts/chain-fleet.nu new file mode 100755 index 0000000..7ede691 --- /dev/null +++ b/scripts/chain-fleet.nu @@ -0,0 +1,371 @@ +#!/usr/bin/env nu +# chain-fleet — three cybernode root-chain nodes on one machine, each with its own home, +# HTTP bind, radio UDP port and bootstrap-peers file, on the consensus kernel, mined by +# `cybernode mine --backend cpu` processes against their /template + /submit; the local +# rehearsal of deploy/chain/README.md before anything touches the apparatus hosts. +# Modeled on cyb/harness/fleet.sh. Moved from soft3/scripts on 2026-11-02 (proposal cybernode § 5). +# +# nu scripts/chain-fleet.nu --check # validate the config and the binary, start nothing +# nu scripts/chain-fleet.nu # run the fleet and assert the chain-wire scenarios +# nu scripts/chain-fleet.nu --bin rs/target/release/cybernode --n 3 --secs 20 --threads 1 --keep +# nu scripts/chain-fleet.nu --chain pussy --genesis ./genesis.tade # a real genesis file (no dev switches) +# nu scripts/chain-fleet.nu --http-base 17880 --radio-base 17890 # another port range when the default one is taken +# nu scripts/chain-fleet.nu --epoch 6:3:9 --secs 60 # a short epoch clock: the economy scenario settles twice +# +# Scenarios asserted (specs/chain-wire.md): peers connect · headers/blocks gossip and the +# heights converge · a late-joining node IBDs to the tip · a tx submitted to node 1 is mined +# by node 2 and visible on node 3 · a fork of depth 3 resolves to the heavier chain · a +# killed node restarts and recovers. The dev genesis sits at the floor target (2^-20 per hash) +# with genesis time two hours back, so ASERT stays at the floor for the whole run. Only the +# processes this script spawned are killed, by pid (recorded in $work/pids). + +const REQUIRED_FLAGS = ["--chain" "--home" "--bind" "--radio" "--peers" "--moniker"] +const HTTP_BASE = 17780 # default --http-base +const RADIO_BASE = 17790 # default --radio-base +# the harness wallet: a fixed seed, funded in the dev genesis; never a launch-chain key +const WALLET_SEED = "1111111111111111111111111111111111111111111111111111111111111111" +const BOB = "7777777777777777777777777777777777777777777777777777777777777777" + +def main [ + --check # validate config + binary only + --bin: string # node binary; default $CYBERNODE_BIN, then `cybernode` on PATH + --n: int = 3 # nodes in the fleet (a fourth joins late) + --secs: int = 20 # how long the fleet mines before the convergence check + --threads: int = 1 # CPU threads per `cybernode mine` process (1 ≈ a block per 10 s at the floor target on hemera 0.4, ~100 kH/s) + --chain: string = "dev" # chain name; `dev` gets a generated genesis and the /dev switches + --genesis: string # genesis.tade copied into every home; default: generated dev genesis + --keep # keep the work dir on success + --http-base: int = 17780 # first HTTP port (node i binds http-base + i − 1) + --radio-base: int = 17790 # first radio UDP port + --epoch: string # dev epoch clock L:D:MIN_LOCK (SOFT3_DEV_EPOCH); adds the economy scenario +] { + if ($epoch | is-not-empty) { + if $chain != "dev" { fail 2 "--epoch is a dev-net clock; launch chains run chain-economy §0" } + $env.SOFT3_DEV_EPOCH = $epoch + } + let bin = (resolve-bin $bin) + print $"chain-fleet: ($n) nodes · chain ($chain) · binary ($bin)" + + # ── 1. the binary must exist and speak the phase-1 flags ─────────────────── + if not ($bin | path exists) { + fail 2 $"no node binary at ($bin). Build cybernode (cargo build --release in rs/) or set --bin / $env.CYBERNODE_BIN." + } + let help = (do { ^$bin node --help } | complete) + let help_text = ($help.stdout + $help.stderr) + let missing = ($REQUIRED_FLAGS | where {|f| not ($help_text | str contains $f) }) + if ($missing | is-not-empty) { + print $"chain-fleet: `($bin) node --help` lacks ($missing | str join ' ')" + print "chain-fleet: this binary is not the phase-1 root-chain node. Required: cybernode node --chain --home DIR --bind HOST:PORT --radio HOST:UDP --peers FILE --moniker NAME" + fail 2 "binary probe failed" + } + print " ok binary speaks the phase-1 flags" + + # ── 2. ports must be free ────────────────────────────────────────────────── + let plan = (0..$n | each {|i| + { i: ($i + 1), http: ($http_base + $i), radio: ($radio_base + $i), moniker: $"fleet-($i + 1)" } + }) + for node in $plan { + for port in [$node.http $node.radio] { + let busy = (do { ^lsof -nP -i $":($port)" } | complete | get exit_code) == 0 + if $busy { fail 2 $"port ($port) is in use; free it or pass --http-base / --radio-base" } + } + } + print $" ok ports free: http ($plan | get http | str join ',') radio ($plan | get radio | str join ',')" + + # ── 3. homes, genesis, identities, peers ─────────────────────────────────── + let work = (^mktemp -d /tmp/chain-fleet.XXXXXX | str trim) + let wallet = (^$bin chain key --seed $WALLET_SEED | str trim) + let genesis_file = $"($work)/genesis.tade" + if ($genesis | is-empty) { + if $chain != "dev" { fail 2 $"--chain ($chain) needs --genesis ; only `dev` gets a generated genesis" } + ^$bin chain genesis-dev --name $chain --fund $"($wallet)=1000000000000" --out $genesis_file | ignore + } else { + cp $genesis $genesis_file + } + # every node's endpoint id first (the key is created on first call), then the peers files + let plan = ($plan | each {|node| + let home = $"($work)/node-($node.i)" + mkdir $home + $node | insert home $home | insert id (^$bin node id --home $home | str trim) + }) + for node in $plan { + let others = ($plan | where i != $node.i) + peers-file $chain $others | save $"($node.home)/peers.toml" + } + print $" ok homes + genesis + peers under ($work); wallet ($wallet | str substring 0..8)…" + + if $check { + print "chain-fleet: --check passed; nothing started" + rm -rf $work + return + } + + # ── 4. launch: every node; `cybernode mine` works node 1 and node 2 (each its own coinbase address) + let plan = ($plan | each {|node| $node | insert miner ($"($node.i)" | fill -c ($node.i | into string) -w 64) | insert work $work }) + "" | save -f $"($work)/pids" + let pids = ($plan | first $n | each {|node| launch $bin $chain $genesis_file $node }) + print $"chain-fleet: launched pids ($pids | str join ' ')" + + let result = (do { + # ── 5. every node answers /status within 60 s ────────────────────────── + let fleet = ($plan | first $n) + if not (wait-for 60 {|| ($fleet | all {|node| (status-of $node.http) != null }) }) { + print $" FAIL not every node answered /status within 60 s; logs in ($work)" + return false + } + print $" ok all ($n) nodes answer /status" + let m1 = (miner-start $bin $chain ($fleet | get 0) $threads) + let m2 = (miner-start $bin $chain ($fleet | get 1) $threads) + print $"chain-fleet: cybernode mine --backend cpu --threads ($threads) on node-1 \(pid ($m1)\) and node-2 \(pid ($m2)\)" + + # peers connect + if not (wait-for 30 {|| ($fleet | all {|node| (peer-count $node.http) >= 1 }) }) { + print $" FAIL peers did not connect: ($fleet | each {|node| $'node-($node.i)=(peer-count $node.http)' } | str join ' ')" + return false + } + print $" ok peers connected: ($fleet | each {|node| $'node-($node.i)=(peer-count $node.http)' } | str join ' ')" + + # headers/blocks gossip: the fleet mines for --secs then agrees on height + sleep ($secs * 1sec) + if not (wait-for 30 {|| (converged $fleet) }) { + print $" FAIL heights differ: ($fleet | each {|node| $'node-($node.i)=(height-of (status-of $node.http))' } | str join ' ')" + return false + } + let h = (height-of (status-of ($fleet | first | get http))) + if $h < 3 { print $" FAIL only ($h) blocks mined in ($secs) s"; return false } + let b1 = (^curl -s -m 3 $"http://127.0.0.1:(($fleet | get 1).http)/block/1" | from json) + let ids = ($fleet | each {|node| ^curl -s -m 3 $"http://127.0.0.1:($node.http)/block/1" | from json | get id } | uniq) + if ($ids | length) != 1 { print $" FAIL block 1 differs across nodes: ($ids | str join ' ')"; return false } + print $" ok block 1 ($b1.id | str substring 0..12)… mined by ($b1.miner | str substring 0..8)… is block 1 on every node" + print $" ok heights agree at ($h) across ($n) nodes" + + if $chain != "dev" { + print "chain-fleet: launch chain — the dev scenarios (late join, tx, fork, restart) need /dev switches; stopping here" + return true + } + + # ── 6. a late-joining node IBDs to the tip ───────────────────────────── + let late = ($plan | get ($n)) + let late_pid = (launch $bin $chain $genesis_file $late) + print $"chain-fleet: late joiner node-($late.i) pid ($late_pid)" + let first = ($fleet | first) + if not (wait-for 60 {|| + let s = (status-of $late.http) + ($s != null) and (not (catching-up $s)) and ((height-of $s) >= ((height-of (status-of $first.http)) - 1)) and ((height-of $s) > 0) + }) { + print $" FAIL late joiner did not reach the tip: ((status-of $late.http) | default '' | lines | where $it =~ '^(height|best-header|catching|peers)' | str join ' ')" + return false + } + print $" ok late joiner synced to height (height-of (status-of $late.http)) \(IBD by ranges\)" + let everyone = ($fleet | append $late) + + # ── 7. a tx submitted to node 1 is mined by node 2, visible on node 3 ─── + miner-stop $m1 + sleep 2sec + let nonce = (^curl -s -m 3 $"http://127.0.0.1:($first.http)/nonce/($wallet)" | from json | get nonce) + let tx = (^$bin chain tx --seed $WALLET_SEED --genesis $genesis_file --nonce $nonce --to $BOB --amount 5 | str trim) + let txid = (^curl -s -m 3 -X POST -H "Content-Type: application/octet-stream" --data-binary $"($tx)" $"http://127.0.0.1:($first.http)/tx" | from json | get txid) + let third = ($fleet | get 2) + if not (wait-for 60 {|| (tx-height $third.http $txid) != null }) { + print $" FAIL tx ($txid) not seen mined on node-3: (^curl -s -m 3 $'http://127.0.0.1:($third.http)/tx/($txid)')" + return false + } + let mined_at = (tx-height $third.http $txid) + let miner = (^curl -s -m 3 $"http://127.0.0.1:($third.http)/block/($mined_at)" | from json | get miner) + let second = ($fleet | get 1) + if $miner != $second.miner { print $" FAIL block ($mined_at) paid ($miner), not node-2's miner ($second.miner)"; return false } + let bob = (^curl -s -m 3 $"http://127.0.0.1:($third.http)/balance/($BOB)" | parse -r 'balance: (?\d+)' | first | get b | into int) + if $bob != 5 { print $" FAIL bob holds ($bob) on node-3, expected 5"; return false } + print $" ok tx ($txid | str substring 0..8)… submitted to node-1, mined at ($mined_at) by node-2's miner ($miner | str substring 0..8)…, bob=5 on node-3" + + # ── 8. a fork of depth 3 resolves to the heavier chain ───────────────── + miner-stop $m2 + sleep 2sec + if not (wait-for 30 {|| (converged $everyone) }) { + print $" FAIL fleet did not settle before the fork: ($everyone | each {|node| $'node-($node.i)=(height-of (status-of $node.http))' } | str join ' ')" + return false + } + let base = (height-of (status-of $first.http)) + isolate $late.http true + let ml = (miner-start $bin $chain $late $threads) + if not (wait-for 180 {|| (height-of (status-of $late.http)) >= ($base + 5) }) { print " FAIL isolated node did not mine its fork"; return false } + miner-stop $ml + let mm = (miner-start $bin $chain $first $threads) + if not (wait-for 180 {|| (height-of (status-of $first.http)) >= ($base + 3) }) { print " FAIL main chain did not extend"; return false } + miner-stop $mm + sleep 2sec + let main_h = (height-of (status-of $first.http)) + let fork_h = (height-of (status-of $late.http)) + let fork_tip = (tip-of $late.http) + let main_work = (work-of $first.http) + let fork_work = (work-of $late.http) + if $fork_work <= $main_work { print $" FAIL fork \(($fork_h), work ($fork_work)\) is not heavier than main \(($main_h), work ($main_work)\)"; return false } + isolate $late.http false + let dial = ({ id: $first.id, addrs: [$"127.0.0.1:($first.radio)"] } | to json -r) + ^curl -s -m 10 -X POST -d $dial $"http://127.0.0.1:($late.http)/dev/dial" | ignore + if not (wait-for 120 {|| ($fleet | all {|node| (tip-of $node.http) == $fork_tip }) }) { + print $" FAIL fleet did not reorg to the heavier fork: ($everyone | each {|node| $'node-($node.i)=(height-of (status-of $node.http))' } | str join ' ')" + return false + } + print $" ok fork of depth ($main_h - $base) from ($base): main ($main_h) reorged to the heavier chain ($fork_h), tip ($fork_tip | str substring 0..8)… on every node" + + # ── 8b. the focus economy: stake settles identically everywhere ──────── + if ($epoch | is-not-empty) { + let clock = ($epoch | split row ":" | each {|x| $x | into int }) + let mstake = (miner-start $bin $chain $first $threads) + let base = (height-of (status-of $first.http)) + let nonce = (^curl -s -m 3 $"http://127.0.0.1:($first.http)/nonce/($wallet)" | from json | get nonce) + let p = "1111111111111111111111111111111111111111111111111111111111111111" + let q = "2222222222222222222222222222222222222222222222222222222222222222" + let lock = (^$bin chain tx --seed $WALLET_SEED --genesis $genesis_file --nonce $nonce --kind link --p $p --q $q --valence 1 --amount 1000000000 | str trim) + let lock_id = (^curl -s -m 3 -X POST -H "Content-Type: application/octet-stream" --data-binary $"($lock)" $"http://127.0.0.1:($first.http)/tx" | from json | get txid) + if not (wait-for 120 {|| (tx-height $first.http $lock_id) != null }) { print $" FAIL staked link ($lock_id) not mined"; return false } + let locked_at = (tx-height $first.http $lock_id) + # two settlements after the lock: settle(E) = E·L + D for the lock's epoch and the next + let e1 = ($locked_at + $clock.0 - 1) // $clock.0 + let target = (($e1 + 1) * $clock.0 + $clock.1) + if not (wait-for 600 {|| ($everyone | all {|node| (height-of (status-of $node.http)) >= $target }) }) { + print $" FAIL fleet did not reach height ($target): ($everyone | each {|node| $'node-($node.i)=(height-of (status-of $node.http))' } | str join ' ')" + return false + } + miner-stop $mstake + for e in [$e1 ($e1 + 1)] { + let digests = ($everyone | each {|node| ^curl -s -m 5 $"http://127.0.0.1:($node.http)/epoch/($e)" | from json | get digest } | uniq) + if ($digests | length) != 1 { print $" FAIL epoch ($e) records differ: ($digests | str join ' ')"; return false } + let rec = (^curl -s -m 5 $"http://127.0.0.1:($first.http)/epoch/($e)" | from json) + print $" ok epoch ($e) record ($rec.digest | str substring 0..12)… on every node: budget ($rec.budget), paid ($rec.paid), rewarded ($rec.rewarded), pool after ($rec.pool_after)" + } + let card = (^curl -s -m 3 $"http://127.0.0.1:($first.http)/cards/($wallet)" | from json | get cards | first) + print $" ok position ($card.card | str substring 0..12)… stake ($card.stake), valence ($card.valence), unlock at ($card.unlock_at)" + if not ((status-of $first.http) | str contains "stake-keys-match: true") { print " FAIL stake keys do not match the running total"; return false } + } + + # ── 9. a killed node restarts and recovers ───────────────────────────── + let victim = $third + let before = (height-of (status-of $victim.http)) + ^kill -9 ($pids | get 2) + sleep 1sec + let mr = (miner-start $bin $chain $first $threads) + let new_pid = (launch $bin $chain $genesis_file $victim) + print $"chain-fleet: node-3 killed and relaunched as pid ($new_pid)" + if not (wait-for 120 {|| + let s = (status-of $victim.http) + ($s != null) and ((height-of $s) > $before) and ((height-of $s) >= ((height-of (status-of $first.http)) - 1)) + }) { + print $" FAIL node-3 did not recover: before ($before), now ((status-of $victim.http) | default '' | parse -r 'height: (?\d+)')" + return false + } + miner-stop $mr + let after = (height-of (status-of $victim.http)) + let conserved = ((status-of $victim.http) | str contains "conserved: true") + if not $conserved { print " FAIL conservation check failed after restart"; return false } + print $" ok node-3 recovered from disk: height ($before) before the kill, ($after) after restart \(replayed, then synced\), conserved" + true + }) + + # ── cleanup — every pid this script spawned (nodes, the late joiner, the relaunch, miners), by pid only + let spawned = (open $"($work)/pids" | lines | where $it != "" | into int) + for pid in $spawned { do -i { ^kill $pid } | complete | ignore } + sleep 1sec + for pid in $spawned { do -i { ^kill -9 $pid } | complete | ignore } + if $result and (not $keep) { rm -rf $work } else { print $"chain-fleet: artifacts kept in ($work)" } + if $result { print "chain-fleet: GREEN" } else { fail 1 "chain-fleet: RED" } +} + +def launch [bin: string, chain: string, genesis: string, node: record] { + let cmd = $"exec ($bin) node --chain ($chain) --home ($node.home) --bind 127.0.0.1:($node.http) --radio 127.0.0.1:($node.radio) --peers ($node.home)/peers.toml --genesis ($genesis) --moniker ($node.moniker) --log-net >> ($node.home)/stdout.log 2>&1 & echo $!" + let pid = (^bash -c $cmd | str trim | into int) + $"($pid)\n" | save -a $"($node.work)/pids" + $pid +} + +# `cybernode mine --backend cpu` against one node's /template + /submit, paying the node's miner address +def miner-start [bin: string, chain: string, node: record, threads: int] { + let cmd = $"exec ($bin) mine --chain ($chain) --node http://127.0.0.1:($node.http) --address ($node.miner) --backend cpu --threads ($threads) >> ($node.home)/mine.log 2>&1 & echo $!" + let pid = (^bash -c $cmd | str trim | into int) + $"($pid)\n" | save -a $"($node.work)/pids" + $pid +} + +def miner-stop [pid: int] { + do -i { ^kill $pid } +} + +# chainwork of a node's tip (decimal) +def work-of [port: int] { + ^curl -s -m 3 $"http://127.0.0.1:($port)/tip" | from json | get chainwork | into int +} + +def peers-file [chain: string, others: list] { + [$"version = 1" $"chain = \"($chain)\"" ""] + | append ($others | each {|o| + [ "[[peer]]" $"name = \"($o.moniker)\"" $"id = \"($o.id)\"" $"addrs = [\"127.0.0.1:($o.radio)\"]" "relay = \"\"" "" ] + } | flatten) + | str join "\n" +} + +# poll `cond` every half second for up to `secs` +def wait-for [secs: int, cond: closure] { + let deadline = ((date now) + ($secs * 1sec)) + mut ok = false + while (not $ok) and ((date now) < $deadline) { + $ok = (do $cond) + if not $ok { sleep 500ms } + } + $ok +} + +# the fleet converges when every node reports the same height +def converged [nodes: list] { + let heights = ($nodes | each {|node| height-of (status-of $node.http) } | uniq) + (($heights | length) == 1) and (($heights | first) != null) +} + +def status-of [port: int] { + let r = (do { ^curl -s -m 3 $"http://127.0.0.1:($port)/status" } | complete) + if $r.exit_code == 0 and ($r.stdout | is-not-empty) { $r.stdout } else { null } +} + +def height-of [status] { + if $status == null { return null } + let m = ($status | parse -r '(?m)^"?height"?[ :=]+(?\d+)') + if ($m | is-empty) { null } else { $m | first | get h | into int } +} + +def catching-up [status] { + ($status | str contains "catching-up: true") +} + +def tip-of [port: int] { + let r = (do { ^curl -s -m 3 $"http://127.0.0.1:($port)/tip" } | complete) + if $r.exit_code == 0 and ($r.stdout | is-not-empty) { $r.stdout | from json | get id } else { null } +} + +def peer-count [port: int] { + let r = (do { ^curl -s -m 3 $"http://127.0.0.1:($port)/peers" } | complete) + if $r.exit_code == 0 and ($r.stdout | is-not-empty) { $r.stdout | from json | get peers | length } else { 0 } +} + +def tx-height [port: int, txid: string] { + let r = (do { ^curl -s -m 3 $"http://127.0.0.1:($port)/tx/($txid)" } | complete) + if $r.exit_code != 0 or ($r.stdout | is-empty) { return null } + let v = ($r.stdout | from json) + let h = ($v | get -o height) + if ($h == null) or ($h == "mempool") { null } else { $h } +} + +def isolate [port: int, on: bool] { + ^curl -s -m 3 -X POST -d ({ on: $on } | to json -r) $"http://127.0.0.1:($port)/dev/isolate" | ignore +} + +def resolve-bin [bin] { + if ($bin | is-not-empty) { return ($bin | path expand) } + if ("CYBERNODE_BIN" in $env) { return ($env.CYBERNODE_BIN | path expand) } + let found = (which cybernode) + if ($found | is-empty) { "cybernode" } else { $found | first | get path } +} + +def fail [code: int, msg: string] { + print $"chain-fleet: ($msg)" + exit $code +} From 22b4f233a99c4a7d34e46ebc0f1e97ef798dcc38 Mon Sep 17 00:00:00 2001 From: master Date: Mon, 2 Nov 2026 08:50:04 +0000 Subject: [PATCH 05/11] docs: launch.md moves in from soft3 as the node operator guide Same structure as soft3/docs/launch.md: install is cargo install cybernode, the node runs as cybernode node, the public host uses the cybernode@ unit of deploy/chain instead of an inline unit running the soft3 binary, and the network itself is the cybernode. --- docs/launch.md | 181 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 docs/launch.md diff --git a/docs/launch.md b/docs/launch.md new file mode 100644 index 0000000..a3ceb7b --- /dev/null +++ b/docs/launch.md @@ -0,0 +1,181 @@ +--- +title: launch spacepussy-test +tags: cyber, soft3, cybernode, guide, launch +crystal-type: guide +crystal-domain: cyber +alias: launch network, cybernode guide, node operator guide, start testnet, spacepussy-test guide +--- +# launch spacepussy-test + +moved from `soft3/docs/launch.md` on 2026-11-02 (proposal cybernode § 5); the node operator guide of [[cybernode]]. + +how to run the **real soft3 network** with cybernode — cybergraph + bbg as the chaosnet **spacepussy-test**. the phase-1 root chain (`pussy`, `bostrom`) runs from the same binary with `--chain`; its runbook is `deploy/chain/README.md`. + +this is not a status stub. the node processes cyberlinks into authenticated state, advances height on finalize, and serves product RPC. + +## network identity + +| field | value | +|-------|-------| +| name | spacepussy-test | +| chain_id | `spacepussy-test` | +| engine | cybergraph + bbg | +| protocol | `soft3/spacepussy-test/v1` (a kept wire identifier) | +| public rpc | `https://cyb.ai/spacepussy-test` | +| host | cyberproxy (an apparatus host, the public edge) | +| local bind | `127.0.0.1:7780` | +| denom | `testpussy` | + +cosmos **space-pussy** / **bostrom** on the apparatus hosts are bootloader chains (go-cyber). product tools reject those names. + +## install + +```bash +rustup update stable # need rustc ≥ 1.85 (edition 2024 deps) +cargo install cybernode # operator / node (or: cargo install --path rs) +cargo install true-cyber # product face (thin client) +``` + +## run a node (local) + +```bash +cybernode node --home ~/.spacepussy-test --bind 127.0.0.1:7780 --moniker dev-1 +``` + +what starts: + +- cybergraph processor (intend/seal/link lifecycle) +- bbg authenticated state +- durable log at `$home/log` + block height at `$home/blocks` +- HTTP RPC on the bind address + +the root chain instead: `cybernode node --chain pussy --home ~/.soft3/pussy --bind 127.0.0.1:7781 --radio 0.0.0.0:7791 --peers peers.toml` (the home directory keeps its name; see README § kept names). + +### RPC + +| method | path | role | +|--------|------|------| +| GET | `/status` | chain_id, moniker, height, bbg_root, signals, particles | +| GET | `/health` | `ok` | +| GET | `/root` | BBG root hex | +| GET | `/stats` | height, root, graph counts | +| POST | `/v1/link` | submit cyberlink JSON | +| POST | `/v1/finalize` | close block (advance height) | + +### submit a cyberlink + +labels are hemera-hashed; pure hex (≤64 chars) is left-padded identity. + +```bash +curl -sS -X POST http://127.0.0.1:7780/v1/link \ + -H 'content-type: application/json' \ + -d '{"neuron":"01","from":"0a","to":"0b","amount":1,"valence":0,"finalize":true}' +``` + +response: + +```json +{"ok": true, "height": 1, "root": "…", "signals": 1} +``` + +`finalize: true` (default) calls `bbg.finalize_block` after the link so height advances. + +### probe + +```bash +cybernode sync +# or, against local bind: +# cybernode does not yet take --rpc; use curl for local-only, public edge via cybernode sync +curl -sS http://127.0.0.1:7780/status | head +cyber sync # product client → public edge +``` + +## run the public chaosnet (cyberproxy) + +host: **cyberproxy** (edge for cyb.ai). + +```bash +# on an operator machine +cargo install cybernode --force + +# on cyberproxy +ssh cyberproxy +export PATH="$HOME/.cargo/bin:$PATH" +cargo install cybernode --force +``` + +the unit is `deploy/chain/cybernode@.service`, one instance per network +(`cybernode@pussy`, `cybernode@bostrom`), parameters in `/etc/soft3/.env` +(template `deploy/chain/chain.env.example`, a kept path), the health gate +`cybernode-check` as `ExecStartPre`: + +```bash +sudo install -m 0755 deploy/chain/cybernode-check /usr/local/bin/ +sudo cp deploy/chain/cybernode@.service /etc/systemd/system/ +sudo cp deploy/chain/chain.env.example /etc/soft3/pussy.env # edit bind, radio, moniker +sudo systemctl daemon-reload +sudo systemctl enable --now cybernode@pussy +sudo systemctl status cybernode@pussy --no-pager | head -20 +curl -sS http://127.0.0.1:7781/status | head +``` + +the chaosnet on 7780 keeps its legacy unit `deploy/spacepussy-test.service`, +which now starts the cybernode binary; `deploy/deploy-cyberproxy.nu` installs +and restarts it. step by step for genesis day: `deploy/chain/README.md`. + +nginx (already on cyberproxy): `cyb.ai/spacepussy-test/` → `127.0.0.1:7780`; the +root chain snippet is `deploy/chain/nginx-chain-api.conf.template`. + +```bash +# public +curl -sS https://cyb.ai/spacepussy-test/status | head +cyber sync +``` + +redeploy after a cybernode upgrade: + +```bash +ssh cyberproxy 'export PATH=$HOME/.cargo/bin:$PATH; cargo install cybernode --force; sudo systemctl restart cybernode@pussy' +``` + +## multi-node (current limits) + +the chaosnet v1 is **single-node cybergraph**. each `cybernode node` without `--chain` is an independent local processor with its own store. the root chain (`--chain`) gossips over radio and syncs (`specs/chain-wire.md`); three nodes on one machine: `nu scripts/chain-fleet.nu`. + +not yet on the public chaosnet: + +| capability | status | +|------------|--------| +| cybergraph link + bbg state | **live** | +| height / root / stats RPC | **live** | +| durable log | **live** | +| radio gossip between nodes | live on the root chain (`--chain`), open (S4) on the chaosnet | +| foculus multi-node φ* finality | open (S5) | +| seal STARK binding at commit | open (S3) | + +next work is wire [[radio]] + [[foculus]] so two cybernodes exchange signals and share one tip — see [[cyber/launch]] S3–S6. + +until then: one public node on cyberproxy is the product spacepussy-test; clients sync to it with `cyber sync` / `cybernode sync`. + +## product client + +```bash +cargo install true-cyber --force +export PATH="$HOME/.cargo/bin:$PATH" +cyber version # cyber … (true-cyber) +cyber sync # probes the cybernode on cyberproxy +``` + +true-cyber is a thin product face over the soft3 network. the network itself is the cybernode. + +## not soft3 + +| name | substrate | role | +|------|-----------|------| +| spacepussy-test | soft3 cybergraph+bbg, run by cybernode | product chaosnet | +| space-pussy | cosmos go-cyber | bootloader experimental | +| bostrom | cosmos go-cyber | bootloader mainnet history | + +## related + +- [[cybernode]] · [[soft3]] · [[soft3/status]] · [[install]] · [[bootloader]] · [[cyber/launch]] · `deploy/chain/README.md` From 8978792e689ff1386836339e3d70aceab6fa5919 Mon Sep 17 00:00:00 2001 From: master Date: Mon, 2 Nov 2026 08:50:04 +0000 Subject: [PATCH 06/11] docs(audit): block-relation and proof-auth receipts move in from soft3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unchanged except the first body line naming the move (proposal cybernode § 5); the receipts of the chain belong beside its code. --- audit/chain/block-relation-2026-11.md | 349 ++++++++++++++++++++++++++ audit/chain/proof-auth-2026-11.md | 271 ++++++++++++++++++++ 2 files changed, 620 insertions(+) create mode 100644 audit/chain/block-relation-2026-11.md create mode 100644 audit/chain/proof-auth-2026-11.md diff --git a/audit/chain/block-relation-2026-11.md b/audit/chain/block-relation-2026-11.md new file mode 100644 index 0000000..22d7143 --- /dev/null +++ b/audit/chain/block-relation-2026-11.md @@ -0,0 +1,349 @@ +--- +tags: soft3, chain, audit, zheng, proven-present, pussy +crystal-type: audit +crystal-domain: crypto +date: 2026-11-01 +serves: release/director/plan.md row P8 +--- +moved from soft3/audit/chain on 2026-11-02 (proposal cybernode §5) + +# the block relation for rule set 1 — counted on the native node, 2026-11-01 + +Plan row P8: the block relation (proven-present S1) for rule set 1, written +against `specs/chain.md` §5, and what a block proof costs with and without +the economy boundary. This page counts what the relation has to prove. It +does not build the relation and does not measure a proof. Every figure is +labelled measured (a counter the node ran), derived (computed from a counter +and a formula in source) or extrapolated. + +## 1. what was measured + +- Run [41311666610](https://github.com/cyberia-to/soft3/actions/runs/41311666610), + workflow "Diagnostic P8 census" (`.github/workflows/diag-p8-census.yml`), + branch `diag/p8-block-census`, commit `afdfcbc` (never merged, never in the + stand), green. Log `census.log`, sha256 + `1ec1f6e7c268645aa9b97e7f1704edd2442e71dd2fe8ed1a676c219fd2656f31`, the + digest the run printed. 1,389 lines. +- Command in the run: `cargo test --locked --release --features opcount -p + soft3 --lib census -- --nocapture --test-threads=2`, on `ubuntu-24.04`. + Test `chain::tests::census::census` (`crate/src/chain/tests/census.rs`). +- Stand (`snapshot/sources.json`, candidate `candidate-20261101.1`, + `rehearsal: true`): soft3 `afdfcbc`, bbg `2d7873c`, hemera `8559b52`, mudra + `33f8029`, nox `41ba24f`, zheng `166a14e`, lens `d6dcc3f`, tru `464104c`, + foculus `0551e3b`, cybergraph `fe115b3`, plus file, rune, radio, vault, + simulation, tok (16 pins). +- The scenario is **not the live pussy-rc chain.** It is a deterministic range + mined on the test harness (`Net::with_funds`, splitmix64 seed 8): 24 + accounts, 24 particles, 400 heights with 0–16 generated transactions per + block (mix: transfers, bare and staked links, payouts), one block at + `MAX_BLOCK_TXS` (height 150), the spec's clock (epoch 240, settle delay + 120), so the first boundary is height 240 and the first settlement 360. + A second scenario, a short clock (L = 10, D = 5, 45 heights, 3 accounts), + reaches kinds 3, 6 and 7 and four settlements. Every block is validated by + `accept_block` on a second fresh chain with a cold signature cache + (`clear_verified_cache`, census.rs:256), so counts are a validator's. +- 445 blocks: 400 main (397 ordinary, 3 special: 150 max, 240 boundary, 360 + settlement) and 45 short-clock. Native validation time on the runner: + median 4 ms, max 229 ms (the 1,024-transaction block), 1,848 ms for the 400 + main blocks. +- Scripts: `/tmp/p8-scripts/{load,a1,a2,a3,a4,a5}.py` on the director host, + run as `python3 -I aN.py` (stdlib). They parse the 445 `P8` lines, 70 + `P8-JOB`, 5 `P8-OUTCOME`, `P8-MAX`, `P8-SUMMARY`. Not in the repo. sha256 + prefixes: load `783ce7fa`, a1 `a629956e`, a2 `d8181a1f`, a3 `9b5b6f85`, a4 + `889af94f`, a5 `e2513cbe`. a1 is stats and fit, a2 by purpose and special + blocks, a3 jobs and outcomes, a4 boundary comparison, per kind and the + mismatch check, a5 split fits and the adjacency bracket. + +What a counter means (`crate/src/chain/opcount.rs`): + +- `hash..perms` is hemera permutations of every hash soft3 computes, + `len/56 + 1` per input (opcount.rs:63, replayed against `hemera::hash` for + lengths 0..400 by the test at opcount.rs:176). `calls` counts hashes. + `uniq_*` counts once per distinct `(purpose, input)` inside one block's + validation (opcount.rs:19-38). Unique is therefore the count if a relation + shares every repeated hash; executed is the count if it recomputes them. +- `journal` hashes are undo before-images, not validity (opcount.rs:26); the + unique totals exclude them. `sig_aux` is derived: one 33-byte hemera of + `neuron_of(pubkey)` per secp256k1 verification actually run (sigs.rs:61-69). +- `derived.*` are the permutations bbg performs inside its tries, computed in + `crate/src/chain/bbg_state/census.rs` from the touched keys and bbg + `2d7873c` source: leaf = `2 + ceil(limbs/4)` per dirty leaf, branch = 2 per + dirty branch, root fold 14, stats commit 3 (census.rs:89-138). Derived, not + run inside bbg. + +## 2. the relation, written against chain.md §5.5 + +| §5.5 step | what the relation proves | primitives (counter) | +|---|---|---| +| 1 syntax (§1.1, §1.3) | 168 bytes, version, chain, `bits` canonical | compact-target decode 5, encode 6 per block (`u256.target_decode`, `u256.target_encode`, header.rs:162-181), integer | +| 2 parent | `prev` known, height + 1 | none hashed here; the parent's own proof is S2, not S1 | +| 3 time (§1.4) | MTP rule, drift rule | integer compares, no counter; MTP needs 11 ancestor times as witness (open) | +| 4 target (§1.5) | `bits` equals ASERT(prev) | 1 ASERT evaluation (`u256.asert`, daa.rs:50): integer, cubic factor, i128 | +| 5 work (§1.2) | `block_id < target` | `hash.header`: 4 permutations unique (1 hash); the node computes it 6 times (24 executed); 1 compare, 1 work, 1 chainwork | +| 6 body (§1.6, §1.7) | counts, sizes, `tx_root` recomputed, coinbase position | `hash.tx_root`: leaf per tx plus node per pair (calls = 2n − 1) | +| 7 transactions (§2.7, §3.4) | decode, `txid`, NSIG1 over the statement, nonce, balances, kind rules, coinbase = subsidy + fees | `hash.txid`, `hash.sig_statement`, `sig.verify` (secp256k1), `hash.sig_aux`, `hash.key` (balance key, axon id, card id, stake key, neuron id); state reads and writes per dimension | +| 8 state (§3.3) | apply on the parent state, `state_root` equal | dirty leaves and branches per bbg dimension (balances, neurons, cards, time, coins, axons_out, axons_in), root fold 14, stats commit 3 | +| economy (chain-economy §2.4, §5.7) | snapshot at the boundary, settlement at `settle(E)` | section 4 | + +Per validity step the transaction kinds differ little. Per transaction, +measured over all 400 main blocks (`per_kind_all_blocks`, unique permutations): + +| kind | txs | bytes | txid | tx_root | sig_statement | key | sig verifies | +|---|---|---|---|---|---|---|---| +| 0 coinbase | 400 | 82 | 3 | 2 | 0 | 3 | 0 | +| 1 transfer | 2,507 | 224 | 5 | 5 | 4 | 1.89 | 1 | +| 2 cyberlink | 1,502 | 257 | 6 | 5 | 4 | 4.95 | 1 | +| 7 payout | 282 | 248 | 6 | 5 | 4 | 4.38 | 1 | +| 3 claim | 1 | 140 | 4 | 3 | 2 | 10 | 1 | + +Kind 6 (unlock) is in the short-clock run only (2 txs, 4 sig_statement and 5 +tx_root each). Kind 3 has 1 sample in each run: thin. + +## 3. an ordinary block (397 blocks, measured) + +Ordinary means not 150, 240 or 360. Permutations are unique unless marked. + +| primitive | min | median | mean | max | +|---|---|---|---|---| +| transactions (incl. coinbase) | 1 | 9 | 9.2 | 18 | +| body bytes | 86 | 2,010 | 2,052.9 | 4,074 | +| NSIG1 verifications (`sig.verify`) | 0 | 8 | 8.2 | 17 | +| header | 4 | 4 | 4.0 | 4 | +| txid | 3 | 47 | 48.1 | 95 | +| tx_root | 2 | 58 | 59.5 | 119 | +| sig_statement | 0 | 32 | 32.9 | 66 | +| sig_aux | 0 | 7 | 6.7 | 16 | +| key | 3 | 36 | 35.8 | 80 | +| trie leaf (derived, all dimensions) | 6 | 110 | 110.8 | 243 | +| trie branch (derived, incl. adjacency inner) | 16 | 264 | 263.7 | 572 | +| root fold + stats commit (derived) | 17 | 17 | 17.0 | 17 | +| bbg axon id, member digest, card stake key (derived) | 0 | 20 | 21.3 | 63 | +| state, derived total | 39 | 406 | 412.8 | 873 | +| total, unique, no journal | 51 | 601 | 599.9 | 1,213 | +| total, executed (incl. journal) | 71 | 672 | 670.1 | 1,337 | +| ASERT / compare / work / chainwork | 1 each | 1 | 1 | 1 | + +Executed against unique: header 24 against 4, key 71.4 against 35.8 (mean), +journal 13.1 (excluded). The state part is 69 % of the unique total (412.8 of +599.9 mean). Of the state part, branches are 64 %, leaves 27 %. + +Fits over the 397 ordinary blocks (a1.py, a5.py, recomputed from the per-block +lines; the summary's numbers reproduce): + +| quantity y against txs x | intercept | slope per tx | R squared | +|---|---|---|---| +| unique permutations, total | 74.58 | 56.96 | 0.883 | +| unique, non-state (header, txid, tx_root, sig, key) | −4.69 | 20.79 | 0.994 | +| unique, state derived | 79.28 | 36.17 | 0.776 | +| NSIG1 verifications | −1 | 1.000 | 1.000 | +| unique total with the adjacency lower bracket (section 6) | 71.40 | 49.30 | 0.924 | + +The total fit matches the summary (74.58, 56.96, 397 points). R squared 0.88 +says the line is a summary of a noisy cloud (largest residual 425), not a law. +Executed permutations fit 85.05 + 63.44 per tx. One NSIG1 verification per +non-coinbase transaction is exact. + +## 4. the economy boundary + +"Without" below is measured where the counter is attributed to a kind +(snapshot = kind 253, settlement step = kind 254, `opcount.rs:56-62`) and +otherwise taken from ordinary blocks of the same transaction count. + +| block | txs | sig verifies | unique perms | executed perms | state derived | of which boundary work (hash) | +|---|---|---|---|---|---|---| +| ordinary, 1 tx (16 blocks) | 1 | 0 | median 74 (51–78) | 94 | median 62 | 0 | +| ordinary, 6 txs (23 blocks) | 6 | 5 | median 444 (283–624) | 490 | median 321 | 0 | +| ordinary median block (9 txs) | 9 | 8 | 601 | 672 | 406 | 0 | +| 240, boundary (kind 253) | 6 | 5 | **839** | 893 | 338 | 382 unique (383 executed) | +| 360, settlement (kind 254) | 1 | 0 | **1,094** | 1,165 | 286 | 796 unique (847 executed) | +| 150, max-count (section 5) | 1,024 | 1,023 | 19,992 | 29,207 | 3,018 | 0 | + +Block 240 against a 6-transaction ordinary block: +395 permutations on the +median (+89 %). Its hash work minus the snapshot is 457, within the ordinary +6-transaction range. The snapshot is 383 card-key hashes (382 unique), 383 raw +balance reads, a scan of 383 cards, 383 axon-edge reads, a scan of 0 marks +(`snapshot.positions` 383, `snapshot.marks` 0). It writes nothing into a +committed dimension: the state-derived part (338) is the ordinary part. + +Block 360 against a 1-transaction block: 1,094 against a median 74, **+1,020 +permutations (14.8 times)**. The 796 of the settlement step are 622 unique key +hashes (673 executed) and 174 economy permutations (`hash.economy`, 86 calls: +consistent with 43 payout leaves, 42 payout nodes and one epoch record digest, +derived from `settle.rs:379`, `economy/mod.rs:164`; the split is not logged). +It reads 588 raw balances, scans 586 cards, writes 44 balances (20 distinct +keys: 60 leaf and 200 branch permutations), writes 270 marks and the one `time` +entry (4 leaf permutations). `time` and marks: `kernel.marks.write` 270 are +node-side kernel state (`bbg_state.rs:27`), not a committed dimension, so no +trie permutation is derived for them. Whether marks need authentication is +open (section 7). Outcome (`P8-OUTCOME`, height 360, epoch 1): 24 particles, +187 axons, 43 payouts of 1,315,592 from a budget of 1,369,860. + +Short-clock settlements (4, same code): 20 economy permutations over 6 calls +for the one that pays 1, and 8 unique snapshot key hashes. Epochs 3 and 4 pay +0. + +The settlement engine calls, measured off the block path (`P8-JOB`, 70 lines; +`opcount::solve`, opcount.rs:77; `economy/run.rs:132-165`). Per settlement, +on each of the two chains (miner and validator): + +| scenario, epoch | graph | calls | solve ms (validator / miner) | job ms | hash.economy in the job | +|---|---|---|---|---|---| +| main, 1 | 24 particles, 187 edges, 383 positions, 0 prior marks | 1 focus, 4 gradient | 729 / 739 | 781 / 788 | 4 perms (φ digest) | +| short, 1–4 | 4 particles, 2 edges | 1 focus, 4 gradient | 548–579 | 551–598 | 1 perm | + +Main-range calls: focus 75–82 ms, gradient 142–194 ms each. The short graph's +gradients run 118–129 ms on 2 edges, so a fixed cost dominates at this size +(48 `PHI_STEPS`); these times say nothing about pussy-scale graphs +(`economy-cost-2026-10-17.md`: 15,967 particles, 29,112 links, 24 steps, 0.82 s +for one forward solve there, one core). A settlement is 1 + 4 engine calls here +(each gradient call is one forward and one adjoint, chain-economy §5.1: 9 +solves). + +What a block relation would take. Proposal, not measured: + +- Proven inside the block relation at 360: applying a given settlement result. + That is the 174 economy permutations, the balance writes with their trie + paths, the `time` record, the pool debit and conservation. About 1,094 + permutations and 0 signatures for this block. +- Taken as a separate proof: that the result is the φ* of the two snapshots. + This is the engine work above (`focus`, 4 `gradient`) and 586 position + reads, none of which is a hash count. The block relation takes the result's + digest (the epoch record) as a public input and the φ* proof, a different + relation, binds it. Whether that proof exists is not in this census. +- At 240 the relation proves that the 383 positions frozen are the cards + under the block's `state_root` (383 reads against the root, unpriced here), + because settlement reads nothing else (chain-economy §2.4). + +## 5. the maximum block + +P8-MAX, height 150: 1,024 transactions (1 coinbase, 829 transfers, 194 +cyberlinks), 235,640 body bytes (under `MAX_BLOCK_BYTES` 262,144), measured +by the same run, validated in 229 ms. + +| | measured at 1,024 txs | extrapolated from the ordinary fit | +|---|---|---| +| NSIG1 verifications | 1,023 | 1,023 (exact fit) | +| unique permutations | 19,992 | **58,403** (74.58 + 56.96 × 1,024) | +| of which non-state | 16,974 | 21,289 (−4.69 + 20.79 × 1,024) | +| of which state derived | 3,018 | 37,114 (79.28 + 36.17 × 1,024) | +| executed permutations | 29,207 | 65,046 (85.05 + 63.44 × 1,024) | + +The extrapolation overshoots the measurement 2.9 times, and the whole error is +in the state part (12 times). The reason is the workload: 24 accounts and 24 +particles, so 1,023 transactions touch the same few trie paths and keys +(unique key hashes 379 against 7,799 executed, sig_aux unique 24 against 1,023 +executed). The 150 block also uses only transfers and bare links +(`simple`, census.rs:67). Read the extrapolation as the cost of a block of 1,024 +transactions between distinct accounts with no shared paths (an upper reading), +and the measurement as the cost with full sharing. A mainnet-like block sits +between; this census cannot say where. The non-state part is structural: +per transaction txid + tx_root + sig_statement is 14 (transfer) to 15 +permutations, and one NSIG1 verification. + +## 6. pricing + +P5 (`proof-auth-2026-11.md`) gives no cost per hemera permutation in rows or +constraints, and no cost of an NSIG1 verification: the secp256k1 circuit does +not exist (P5 §5: trident `secp256k1.tri` `8f5c1a7` is a stub). What P5 gives +is whole relations, none of them a block: + +| P5 figure | value | status | +|---|---|---| +| one veil proof of a 4-limb hash, rows / entries | 34 / 8,633 | measured (P5 §1) | +| AUTH1 (tagged key, 5 secret atoms), rows / entries | 66 / 16,861 | measured (P5 §7) | +| proof bytes of those, linux x86_64 | 98,797 / 129,693 | measured | +| verify, cached key | 12.7–23.1 ms | measured | + +So no count in this page is converted to rows. The counts stay counts: +**601 permutations and 8 NSIG1 verifications for the median ordinary block; +839 permutations and 5 verifications for 240; 1,094 and 0 for 360.** A price +needs two numbers P5 does not have: rows per hemera permutation in zheng's +relation compiler, and rows for a secp256k1 verification. Both are open +(section 8). + +Measured on the native node, for scale only: 4 ms median validation, 8 NSIG1 +verifications per ordinary block, 1,023 in the largest. + +## 7. caveat: the adjacency counts are an upper bound + +`pending_mismatch_blocks_total = 575` in the summary. Verified here (a4.py): + +- 575 is the sum of the per-block, per-dimension mismatches + (`state.pending_mismatch`, census.rs:130): 286 on `axons_out` and 289 on + `axons_in`. The name says blocks, but the number counts (block, dimension) + pairs: **292 distinct blocks** (291 main, 1 short-clock), 283 of them in + both dimensions. +- In every one of them census-touched is larger than bbg's pending (286 and + 289 blocks, no block the other way). +- Totals over the 445 blocks: `axons_out` bbg pending 451 against census + touched 1,242; `axons_in` 449 against 1,226. Balances (4,839), neurons + (2,712), cards (940), time (5) and coins (445) agree exactly, block by block. + +The cause is visible in code, not directly counted. `census::link` inserts the +particle into `touched.out` and `touched.inn` on every link, unconditionally +(census.rs:62-63). bbg `AxonIndex::add` returns before `touched()` when the +axon is already in the particle's set (`keyed.rs:417-421` at `2d7873c`; +`Trie::set` returns false on an equal digest, `trie.rs:290-298`), so a +repeated `(p, q)` link leaves the particle clean and pending does not count +it. The harness draws `(p, q)` from 24 particles (552 ordered pairs), so +repeats are certain. Indirect evidence: 965 adds by height 240, and 187 +distinct axons in the snapshot at 240 (`P8-OUTCOME`). The census does not log +`add`'s return value, so the exact repeat count is **not measured**. The +census still charges, for a repeated link: the member digest (correct, bbg +computes it before `set`, keyed.rs:419), and the particle's dirty leaf, outer +branches and inner branches (not paid by bbg). Whether this accounts for the whole +gap (791 and 777 excess touches per dimension over 445 blocks) is not shown: +the size of the gap is open. + +Brackets (a5.py). The upper bound is the table in section 3: adjacency +leaf, branch and inner-branch permutations as derived from census touched. The +lower bracket scales those three parts, per block and per dimension, by +bbg pending over census touched; member digests and axon ids stay (bbg pays +them). Scaling is proportional, branches do not shrink linearly, so the lower +figure is a bracket by proportion, not a proven bound. + +| | upper (census touched) | lower (scaled by bbg pending) | +|---|---|---| +| adjacency permutations, 445 blocks | 45,004 (axons_out 22,452, axons_in 22,552) | 15,487 (axons_out 7,709, axons_in 7,778) | +| unique total, 397 ordinary blocks (sum) | 238,145 | 208,852 (−12.3 %) | +| unique total, all 445 blocks | 262,662 | 233,145 (−11.2 %) | +| ordinary median block | 601 | 541 | +| ordinary fit | 74.58 + 56.96 per tx | 71.40 + 49.30 per tx | +| block 240 | 839 | 721 | +| block 360 | 1,094 | 1,094 (no adjacency touched) | +| block 150 | 19,992 | 19,902 | + +Adjacency is 17.1 % of the +upper total over the 445 blocks. Everything else in this page does not depend +on the bracket. + +## 8. what remains + +1. Row cost per hemera permutation and per secp256k1 verification: unmeasured. + A relation of N chained permutations in zheng (rows, prove ms, proof bytes) + would price sections 3–5. P5 §5 says the NSIG1 circuit needs to be written + first. +2. The census counts the recomputation of dirty leaves and branches. A + relation also needs the reads: balances, neurons and cards read per block + (`state.balances.read` median 8, `neurons.read` 8, `cards.read` 4) and the + old values under the parent root, as authentication paths. Not counted; + they add to every figure here. +3. The adjacency gap (section 7): log the return value of `add`, rerun, and + replace the bracket by one number. Check that `axons_*` outer and inner + work for repeated links is what bbg really hashes. +4. The snapshot at 240 (383 positions and 0 marks here) and the 270 marks + written at 360 are kernel state outside the 14 committed leaves. Whether + the block relation proves them, and against which root, is a spec question + (proven-present S1 asks every dimension authenticated by `R`). +5. The φ* relation (focus, gradient) is not in P8. At pussy scale one forward + solve is 0.82 s on one core; a settlement is 9 solves per chain-economy + §5.1. Its proof is the load behind "with the economy boundary". +6. Workload realism: 24 accounts and 24 particles saturate the tries; the max + block shows it (section 5). A second run with many accounts, or with the + mined pussy-rc range, would separate the extrapolated and measured + columns. Kinds 3 and 6 have 1 and 2 samples. +7. MTP and drift (step 3) need 11 ancestor times as a witness; not counted. +8. Per-transaction NSIG1 in the relation is the dominant unpriced term (1,023 + in a full block). P5's proof-authorized kind would replace it with a + 96–131 KB envelope per transaction, so aggregation is this relation's job + (P5 §4, option 2). diff --git a/audit/chain/proof-auth-2026-11.md b/audit/chain/proof-auth-2026-11.md new file mode 100644 index 0000000..d5a1a90 --- /dev/null +++ b/audit/chain/proof-auth-2026-11.md @@ -0,0 +1,271 @@ +--- +tags: soft3, chain, audit, zheng, veil, signatures, pussy +crystal-type: audit +crystal-domain: crypto +date: 2026-11-01 +serves: release/director/plan.md row P5 (decision D16) +--- +moved from soft3/audit/chain on 2026-11-02 (proposal cybernode §5) + +# transactions without signatures — the measurement, 2026-11-01 + +The owner, 2026-11-01 (plan D16): pussy's tru and foculus must be "fully on +proofs, without signatures". Today every non-coinbase transaction carries +NSIG1 (`specs/chain.md` §2.7: `"NSIG1" ‖ pubkey[33] ‖ ecdsa[64]`, 102 bytes, +secp256k1). This page measures the replacement the plan names: a +zero-knowledge proof that the sender knows the secret behind the account's +key, bound to the transaction it authorizes. It measures and names. It does +not change the spec. The §2.7 amendment is the owner's hand (D6) and is +drafted separately with this page as its receipt. + +## 1. what was measured + +- **Stand:** zheng `diag/p5-proof-auth` at + [`a2f6641`](https://github.com/cyberia-to/zheng/commit/a2f664189f7114f49ae9c317ac0b20a12bfa3333), + cut from the stand's zheng `166a14e`. It was never merged and is never in + `release/pussy-stand.toml` (D16 (3)). Siblings are pinned by SHA to the + stand: hemera `8559b52` (0.4, profile #15), lens `d6dcc3f`, nox `41ba24f`. + strata, tade and honeycrisp are at their 2026-11-01 heads. +- **Test:** `rs/tests/proof_auth.rs`, + `proof_authorized_transaction_binds_the_digest_and_is_measured`, `--release`. +- **Statement:** "I know `k = (k0, k1, k2, k3)`, four Goldilocks limbs, with + `hemera(k0, k1, k2, k3, 0, 0, 0, 0) = K`." + - `K` (the account's key) is the only public output. + - The proof is veil (envelope profile 2, `prove_zk`). + - The transaction digest `hemera(tx_bytes)` rides in the envelope + `context`. `prove_zk` absorbs the context into the transcript + (`veil::prove_bound` over `zk_statement_bytes`, zheng + `rs/src/envelope/mod.rs:174-197`), so the binding needs no new + construction. + - The formula is checked three ways: the secret-input program, the same + hash over public inputs (`certify_execution`), and native nox. +- **Asserted on every leg:** + - the same proof fails against another transaction's digest, cold and with + a cached verifying key; + - forging any one limb of `K` fails; + - of 64 spread single-bit flips of the envelope, 0 are accepted; + - no limb of `k` appears in the statement bytes or the envelope bytes. +- **Gate:** zheng run + [41289914355](https://github.com/cyberia-to/zheng/actions/runs/41289914355), + 5 of 5 legs `success`, each `test result: ok. 1 passed; 0 failed`. The logs + are on agent-a in `~/director/state/zheng-run-41289914355//`. + +The proof parameters, from `veil::report`, are identical on all legs: + +| vars | log m | degree | matrices | committed entries | k | data rows | log n | queries | opening bits | IOP bits | +|---|---|---|---|---|---|---|---|---|---|---| +| 13 | 12 | 8 | 5 | 8,633 | 256 | 34 | 14 | 123 | 128.24 | 184.86 | + +Proven security is the minimum of the two: **128.2 bits**. + +## 2. results + +The proof size is the median of 11 proofs (min … max in parentheses). The +envelope is one proof. Prove time is the median of 11. Verify times are +medians of 21: "cold" is `Envelope::verify`, and "cached" is +`veil::verify_bound` with a cached `VerifyingKey`. + +| leg | backend | proof bytes | envelope bytes | prove ms | verify cold ms | verify cached ms | flips accepted | log sha256 | +|---|---|---|---|---|---|---|---|---| +| linux x86_64 (`ubuntu-24.04`) | cpu | 98,797 (96,989 … 99,053) | 99,044 | 672.5 | 88.5 | 23.1 | 0/64 | `cc76a8140e00be06…` | +| linux aarch64 (`ubuntu-24.04-arm`) | cpu | 97,997 (97,381 … 99,213) | 98,244 | 223.3 | 47.5 | 12.7 | 0/64 | `76f7fa6a4682566e…` | +| macOS aarch64, all threads | cpu | 98,149 (97,477 … 99,213) | 98,396 | 299.6 | 67.4 | 13.7 | 0/64 | `7650887a21b3a3f1…` | +| macOS aarch64, `ZHENG_THREADS=1` | metal | 98,533 (97,437 … 99,693) | 98,780 | 314.9 | 93.9 | 17.9 | 0/64 | `c20bcfbc2e457748…` | +| macOS aarch64, all threads | metal | 98,221 (97,477 … 99,309) | 98,468 | 208.4 | 67.8 | 15.7 | 0/64 | `c8f3052961caf702…` | + +**The Metal rows show the path runs, not Metal's speed.** + +- The device was `Apple Paravirtual device`, the runner VM's adapter (as in + plan row G16a). +- A zheng test proves on the CPU backend unless a backend is installed (lens + `rspcs/src/backend.rs:627`). The Metal legs therefore install one + explicitly (`ZHENG_BACKEND=metal`), and the log line `backend: metal` + confirms it. +- Derived from the code, not measured: the encode's `coset_ntt` reaches the + backend (n = 16,384 ≥ `PAR_MIN`). Its rows of 512 coefficients are below + Metal's `MIN_NTT` 2^12, so they stay on the CPU. The Merkle leaves and the + grinding reach the device. Verification runs on the CPU only. +- A bare-metal Apple Silicon number needs the owner's machine or build-a. No + such number is claimed here. + +## 3. against the 2026-10-09 baseline + +zheng `audit/zk-profile-2026-10.md` §3 measured veil on hash.tri's hash with +**one** secret limb, `hemera(x, 0⁷)`: + +| | 10-09 baseline | this run | +|---|---|---| +| committed entries | 2,417 | 8,633 (3.6×) | +| commitment `k`, data rows, `log n` | 128, 19, 13 | 256, 34, 14 | +| proven bits | 128.2 | 128.2 | +| proof bytes, median | 63,861 | 97,997 – 98,797 (1.55×) | +| prove | 142.9 ms | 208.4 – 672.5 ms | +| verify, cached key | 4.1 ms | 12.7 – 23.1 ms | + +- **Sizes compare directly.** Four secret limbs and a bound context grow the + masked relation 3.6×, and the hiding commitment moves to `k = 256`. The + proof grows 1.55×. +- **Times do not compare.** The baseline ran on an Apple M4 Max shared with + other agents (load 22–27). This run used GitHub-hosted runners. + +## 4. what this means for §2.7 + +The figures below come from `specs/chain.md` §0 and §1.6 (rule set 1) and the +table above. + +| | NSIG1 today | proof-authorized, as measured | +|---|---|---| +| authorization bytes per tx | 102 | ~99,044 (envelope) | +| ratio | 1× | 971× NSIG1; 1,544× a bare 64-byte ECDSA signature | +| a transfer's bytes (2 + body 120 + authorization) | 224 | ~99,166 | +| against `MAX_TX_BYTES` 512 | fits | **193× over** | +| transactions per block under `MAX_BLOCK_BYTES` 262,144 (header 168, count 4, coinbase 82) | 1,023 (the `MAX_BLOCK_TXS` cap binds) | **2** (261,890 / 99,166 = 2.64) | +| sustained rate at T = 30 s | ~34 tx/s (1,023 / 30 s; §1.6's rationale rounds to 40) | **0.067 tx/s** | +| fee at `MIN_FEE_PER_BYTE` 1 | 224 units | ~99,166 units (443×) | +| verifier work per tx (cached key) | one ECDSA verify | 12.7 – 23.1 ms | + +So per-transaction proofs at today's size cannot ride rule set 1's envelope. +The §2.7 draft has to choose among these options. Each is named with what is +measured and what is not: + +1. **Raise the limits for the proof-authorized kind.** This is a spec change + only. Throughput stays at 2 transactions per 256 KiB block (0.067 tx/s). + That is still above bostrom's historical rate (≈ 0.02 tx/s, §1.6), but + §1.6's 40 tx/s design rationale no longer holds. +2. **One proof per block over all of its authorizations.** The aggregation + is recursive: each sender's proof folds into a block proof. That is + proven-present S1's territory (plan row P8). + - The relation is not measured. + - The nearest receipt is zheng's recursion chain: `c_wrap_chain`'s final + proof is 74,525 B (plan row L3b, zheng run 40272079660), over + `specs/recursion.md`'s hard 64 KiB gate. + - Per-transaction bytes would fall to the body. The block would carry + one proof of that order. +3. **A smaller per-transaction proof.** The proof is 96 % opening (§3 of the + baseline: opening 61,693 of 64,309 B). Fewer queries or a different + commitment trade proven bits or prover time for bytes. Not measured. + +## 5. legacy accounts — the claim's options + +A preimage proof authenticates nothing for a legacy account. In §2.8, the id +of every snapshot account with a known key is +`neuron_of(pubkey) = hemera(compressed pubkey)`, and that **pubkey is public**: + +- `pubkeys.csv` lists all 61,675 bostrom accounts; +- the ones that ever signed a transaction carry a key. + +Anyone can prove knowledge of a preimage that is already public. For those +accounts, and for kind 3's address-only accounts (8,390 wallets; §2.8: +`ripemd160(sha256(sig.pubkey)) == account_id`), authorization has to prove +knowledge of the **secp256k1 secret key**. The options: + +| option | what it costs | measured? | +|---|---|---| +| (a) secp256k1 proven in-circuit (ECDSA verify, or knowledge of `sk` with `sk·G = pubkey`, over Goldilocks with non-native 256-bit arithmetic) | no circuit exists to measure: trident `lib/std/crypto/secp256k1.tri` (`master` `8f5c1a7`) is a stub (`scalar_mul` returns the identity, `verify_ecdsa` returns false, modular inversion TODO), and zheng `release/0.4` (`ecf2491`) and nox `release/0.4` (`41ba24f`) have no secp256k1 file in their trees | **no** — a cost needs the circuit first | +| (b) NSIG1 kept for the one-time move only: a legacy-keyed or address-only holder signs **one** transaction (an NSIG1 transfer, or kind 3) that moves everything to a native, proof-authorized neuron, and from then on that neuron uses proofs | 102 B once per account that moves. secp256k1 stays in the rule set as a classical hole (§8: "no post-quantum claim") for the balances not yet moved | the NSIG1 path is rule set 1 as it runs today (conformance, soft3-tests) | + +Option (b) is the only one buildable before 2026-11-05. Option (a) is a work +item of its own: write the circuit, then measure it. + +## 6. not done + +- Bare-metal Metal and the owner's M-series numbers (§2). +- Options 4.2 and 4.3, and 5 (a): unmeasured, as stated in their rows. +- The 518,720-flip exhaustive scan the baseline ran. This run does 64 + spread flips per leg. +- ~~The §2.7 amendment itself.~~ Drafted as + [soft3#572](https://github.com/cyberia-to/soft3/pull/572) (§2.9 AUTH1, + `decision`). Its relation was re-measured as written in §7. + +## 7. AUTH1 as written — the re-measurement, 2026-11-01 (session 471) + +§1–§5 measured `hemera(k0, k1, k2, k3, 0⁴)` over four raw Goldilocks limbs +with no domain tag. The §2.9 draft (#572 `962e439`) asked for +`auth_key(s) = hemera("soft3/chain/auth-key/v1\0" ‖ s)` over a 32-byte +secret, and said to re-measure before a verifying key is fixed. + +**What can be proven.** nox has one hash pattern, 15. It takes the input +noun's structural digest (`hash_leaf` of each atom's 8 LE bytes, +`hash_node` with `FLAG_PARENT` for each pair; nox `41ba24f` +`rs/data/hash.rs`) and then runs one plain permutation +(`rs/patterns/hash.rs`). nox has no pattern for hemera's byte sponge over +an arbitrary string. So `hemera(tag ‖ s)` as drafted cannot be proven +without a VM change. zheng's relation compiler constrains every +permutation of the structural digest as wires. It hashes only constant +subtrees natively (zheng `166a14e` `rs/src/execution/hash.rs:1-6, 151-190`), +so a constant tag costs no witness. + +**Decision** (fable #1 of 2026-11-01 recommended it; the director agrees, +and the owner decides on #572): option (A). `auth_key(s)` is pattern 15 +of the fixed noun + +``` +[[t0 [t1 [t2 t3]]] [s0 [s1 [s2 [s3 s4]]]]] +t_i = LE(tag[7i .. 7i+7]), tag = "soft3/chain/auth-key/v1\0" (24 bytes, 4 limbs) +s_i = LE(s[7i .. 7i+7]), s = 32 secret bytes (5 limbs, the last 4 bytes) +``` + +Every limb is below 2^56, so the encoding is canonical and needs no +rejection. The rejected options: (B) a byte-sponge pattern in nox would be +a VM change one day before the 2026-11-02 freeze, with new conformance +work. (C) a hand-built sponge relation outside nox would have no +conformance coverage. Fable's three conditions are met. The shape is +fixed by the program, the tag is constant and the five atoms are secrets. +The proof is bound to `auth_statement` through the envelope context. And +`from` is the particle id of the key noun, so publishing that noun +publishes the key. That last point goes into §2.9. + +**The run.** zheng `diag/p5-proof-auth` `b866673` (from `a2f6641`; never +merged, never in `release/pussy-stand.toml`, D16 (3)). A sonnet subagent +wrote it and the director reviewed it. `rs/tests/proof_auth.rs` +`auth1_tagged_key_binds_the_statement_and_is_measured` checks the key +three ways: the secret program's public output, `certify_execution` over +public limbs, and a native structural digest plus one `StepSponge` +permutation. The context is `hemera("soft3/chain/auth/v1\0" ‖ chain ‖ +tx_core)` with `chain = hemera("pussy")`. Run +[41291667421](https://github.com/cyberia-to/zheng/actions/runs/41291667421): +**5/5 legs `success`, 2 passed 0 failed on each**, every binding assert +held (another `tx_core` fails cold and cached, each forged key limb +fails), flips accepted **0/64** on every leg, and no secret limb or +7-byte window of `s` appears in the statement or the envelope. Logs are +in agent-a `~/director/state/zheng-run-41291667421/`. + +| leg (job) | log sha256 | proof B med (min, max) | envelope B | prove ms | verify ms cold / cached | the §2 relation, same run: proof B / prove ms | +|---|---|---|---|---|---|---| +| linux x86_64 (124804011019) | `0b696026…` | 129,693 (128,389, 130,333) | 129,971 | 564.1 | 71.0 / 20.8 | 98,381 / 395.0 | +| linux aarch64 (124804011039) | `ba404d58…` | 129,949 (129,261, 130,973) | 130,227 | 328.1 | 54.6 / 15.9 | 98,477 / 226.3 | +| macOS aarch64 CPU (124804010936) | `29a47646…` | 130,301 (129,589, 130,685) | 130,579 | 524.0 | 82.4 / 19.6 | 98,373 / 373.8 | +| macOS aarch64 Metal, 1 thread (124804010981) | `c8b0773c…` | 129,941 (129,133, 130,589) | 130,219 | 246.1 | 67.2 / 15.4 | 98,173 / 197.1 | +| macOS aarch64 Metal, all threads (124804010781) | `46744b4c…` | 130,397 (129,789, 130,877) | 130,675 | 339.5 | 98.0 / 20.0 | 97,965 / 229.5 | + +The Metal device is `Apple Paravirtual device` again (§2), so the Metal +rows show that the path runs and binds, not how fast Metal is. + +**Why it grew 32 %.** `veil::report` (linux x86_64): AUTH1 has +`rows 66, log_m 13, entries 16,861`, against `rows 34, log_m 12, +entries 8,633` for the §2 relation. `k 256`, `log_n 14`, 123 queries and +opening 128.2 bits are the same for both. Each secret atom costs a +64-bit decomposition and two permutations, and each pair one more. The +opening carries twice the rows, which costs 31,312–32,432 B depending on the leg. + +**What it does to §2.9's numbers.** A version-2 transfer is 2 + 120 + 4 + +envelope bytes. The envelope is the proof plus 278 B on every leg. + +| | the draft (`962e439`, §2 relation) | AUTH1 as written | +|---|---|---| +| envelope, largest median / largest seen | 99,044 / 99,940 (99,693 + 247) | 130,675 / **131,251** (130,973 + 278) | +| against `MAX_AUTH_BYTES` 131,072 | fits | **an honest proof can exceed it** (131,251) | +| transfers per block (261,890 B of body) | 2 | **1 guaranteed; 2 only when both envelopes are ≤ 130,819 B** | + +So the draft's `MAX_AUTH_BYTES` must rise. The director proposes 196,608 +on #572 (50 % over the largest envelope seen, which is room for +randomness and for a later relation of the same order). Throughput at +T = 30 s is 0.033–0.067 tx/s. That makes the owner's §10 row 13 (block +bytes) sharper, and leaves P8's block relation as the way out. + +**Still not done.** The verifying key is not fixed (it is fixed when an +activation entry is written). Conformance vectors for version 2 come after +the owner's verdict on #572. A range check `s_i < 2^56` inside the +circuit is not added. It is not needed for soundness, because a noun with +any other limb is a different noun with a different digest. It only makes +the wallet's encoding canonical, and that is enforced off-chain. From 9f483211dc5306808a9518043aaffede68093c8f Mon Sep 17 00:00:00 2001 From: master Date: Mon, 2 Nov 2026 08:51:44 +0000 Subject: [PATCH 07/11] docs: kept names, the operator pointer and the train's gates README records the decision of 2026-11-02: wire identifiers, genesis files, home directories and the SOFT3_* variable names stay for the running hosts; everything a person reads says cybernode. The ladder sentence stays rung 7; scripts/ and docs/ join the table and operators are pointed at docs/launch.md. AGENTS.md names the five gates the train runs for this repository (soft3/release/train_gates.py, director plan). --- AGENTS.md | 14 +++++++++++++- README.md | 12 ++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 50f3af6..d91881b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,4 +2,16 @@ > the doctrine of the home is `~/cyber/AGENTS.md`; it applies here first. Claude Code receives both files at session start through `~/.config/cyber/agents-md.nu`; other agents read them in order. -cybernode is rung 7 of the soft3 ladder: the node. `rs/` is the crate and the binary `cybernode`; `specs/` the chain, wire, conformance, genesis, native-node and routing contracts; `deploy/` the units of a running instance; `audit/` the receipts. the wire identifiers (`soft3/chain/…/v1`), the genesis files and the home directories are unchanged by the move from soft3 (2026-10-27): a change to any of them is a consensus change and follows specs/chain.md § 7. the operations of cyberia's own instance live in the private repository `apparatus`. +cybernode is rung 7 of the soft3 ladder: the node. `rs/` is the crate and the binary `cybernode`; `specs/` the chain, wire, conformance, genesis, native-node and routing contracts; `deploy/` the units of a running instance; `audit/` the receipts. the wire identifiers (`soft3/chain/…/v1`), the genesis files and the home directories are unchanged by the move from soft3 (2026-10-27): a change to any of them is a consensus change and follows specs/chain.md § 7. the operations of cyberia's own instance live in the private repository `apparatus`. the kept names (`SOFT3_*` variables, `/etc/soft3`, `~/.soft3`, the wire identifiers) are listed in README § kept names: never rename them in code; everything a person reads says cybernode. + +## gates + +the release train (`soft3/release/train_gates.py`, listed for this repository in `soft3/release/director/plan.md` on `release/phase1`) runs these gates over cybernode; a red gate ships red with its evidence, never hidden: + +- `cybernode-tests` — the node's `cargo test` in `rs/` against the pinned siblings +- `cybernode-release` — the release executable of this crate, one per platform +- `node-status` — boots the release binary with `node --home DIR --bind ADDR` and expects `GET /status` 200 with `height` and `chain` within 20 s +- `stack-chain-genesis` — chain-conformance § 10: the pussy genesis file (`SOFT3_GENESIS_DIR`, default `~/.cache/cyber/genesis`) reproduces the blessed particle, state root and block-0 id of `conformance/chain.snap`; blocked, never green, without the file +- `stack-pussy-identity` — `rs/tests/pussy_identity.rs`: the blessed pussy file boots when cached (plan row P4a) + +local checks before a commit: `bash -n` on every script under `deploy/`, `nu --ide-check 0 scripts/chain-fleet.nu`, `deploy/chain/tests/run.sh`. diff --git a/README.md b/README.md index 367c450..27373a9 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,10 @@ decided and executed from [[soft3/proposals/cybernode|the cybernode proposal]] ( |---|---| | `rs/` | the crate and binary `cybernode`: `chain/` the kernel, `genesis/`, `node/`, `wallet/`, `network.rs`; the verbs `run` (`node`) · `mine` · `sync` (`status`) · `genesis` · `chain` · `conformance` · `network` | | `specs/` | [[cybernode/specs/chain|chain]] · [[cybernode/specs/chain-wire|chain-wire]] · [[cybernode/specs/chain-conformance|chain-conformance]] · [[cybernode/specs/chain-economy|chain-economy]] · [[cybernode/specs/genesis|genesis]] · [[cybernode/specs/native-node|native-node]] · [[cybernode/specs/routing|routing]] | -| `deploy/` | `cybernode@.service`, `cybernode-check`, journald, logrotate, nginx and ufw templates of an instance | -| `audit/` | the receipts: conformance, economy, mining, the net, the requirement map of the chain | +| `deploy/` | `cybernode@.service`, `cybernode-check`, journald, logrotate, nginx and ufw templates of an instance; `deploy/chain/README.md` is the runbook of the two apparatus hosts | +| `scripts/` | `chain-fleet.nu`, three nodes on one machine — the local rehearsal of the runbook | +| `docs/` | [[cybernode/docs/launch|launch]], the node operator guide: install, run, probe, the public host | +| `audit/` | the receipts: conformance, economy, mining, the net, the requirement map of the chain, block relation and proof authentication | ``` cargo install --path rs # or: cargo install cybernode, once published @@ -24,6 +26,12 @@ cybernode mine --address NEURON --node http://127.0.0.1:8545 cybernode conformance check chain ``` +operators start at [[cybernode/docs/launch|docs/launch.md]]; the root chain on the apparatus hosts follows `deploy/chain/README.md`. + +## kept names + +decided 2026-11-02 while closing the proposal (§ 5): the wire identifiers (`soft3/chain/…/v1`, `soft3/spacepussy-test/v1`), the genesis files, the home directories (`~/.soft3/`, `/etc/soft3`, `/var/lib/soft3`) and the environment variable names `SOFT3_CHAIN`, `SOFT3_BIND`, `SOFT3_RADIO`, `SOFT3_MONIKER`, `SOFT3_STALE_SECS`, `SOFT3_MIN_DISK_GIB`, `SOFT3_DEV_EPOCH`, `SOFT3_COST_*`, `SOFT3_CONFORMANCE_DIR` keep their names, for compatibility with the running hosts and every genesis hash. everything a person reads — strings, docs, unit names, scripts, specs — says cybernode. a change to a kept name is a consensus or an operations change and goes through specs/chain.md § 7 or the apparatus runbooks. + the `soft3` crate remains the stack facade: it depends on cybernode, re-exports its modules for one release and forwards the operational verbs, so nothing that built against `soft3::chain` breaks today. the soft3 repository keeps the ladder, the contracts of the stack, the release train and the SDKs; the private repository `apparatus` keeps cyberia's own hosts, secrets and runbooks. what the node must still grow, so that cyberia runs all of its execution on it, is the capability table of the proposal § 4: proving workers and block aggregation, the index over bbg, snapshots and tip proofs, particles over radio blobs, operator keys through vault. each lands here as a spec, code and a receipt. From 2b4a29a44f8de43f2c06f3abd174aaf62da4aa36 Mon Sep 17 00:00:00 2001 From: master Date: Mon, 2 Nov 2026 08:51:58 +0000 Subject: [PATCH 08/11] docs: the kept-names note lists every SOFT3_ variable the code and the check read --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 27373a9..b9abed2 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ operators start at [[cybernode/docs/launch|docs/launch.md]]; the root chain on t ## kept names -decided 2026-11-02 while closing the proposal (§ 5): the wire identifiers (`soft3/chain/…/v1`, `soft3/spacepussy-test/v1`), the genesis files, the home directories (`~/.soft3/`, `/etc/soft3`, `/var/lib/soft3`) and the environment variable names `SOFT3_CHAIN`, `SOFT3_BIND`, `SOFT3_RADIO`, `SOFT3_MONIKER`, `SOFT3_STALE_SECS`, `SOFT3_MIN_DISK_GIB`, `SOFT3_DEV_EPOCH`, `SOFT3_COST_*`, `SOFT3_CONFORMANCE_DIR` keep their names, for compatibility with the running hosts and every genesis hash. everything a person reads — strings, docs, unit names, scripts, specs — says cybernode. a change to a kept name is a consensus or an operations change and goes through specs/chain.md § 7 or the apparatus runbooks. +decided 2026-11-02 while closing the proposal (§ 5): the wire identifiers (`soft3/chain/…/v1`, `soft3/spacepussy-test/v1`), the genesis files, the home directories (`~/.soft3/`, `/etc/soft3`, `/var/lib/soft3`) and the environment variable names `SOFT3_CHAIN`, `SOFT3_BIND`, `SOFT3_RADIO`, `SOFT3_MONIKER`, `SOFT3_STALE_SECS`, `SOFT3_MIN_DISK_GIB`, `SOFT3_DEV_EPOCH`, `SOFT3_COST_*`, `SOFT3_CONFORMANCE_DIR`, `SOFT3_GENESIS_DIR`, `SOFT3_GENESIS_REQUIRED` (and the check's `SOFT3_ETC`, `SOFT3_STATE`, `SOFT3_PIDFILE`, `SOFT3_MINE_*`) keep their names, for compatibility with the running hosts and every genesis hash. everything a person reads — strings, docs, unit names, scripts, specs — says cybernode. a change to a kept name is a consensus or an operations change and goes through specs/chain.md § 7 or the apparatus runbooks. the `soft3` crate remains the stack facade: it depends on cybernode, re-exports its modules for one release and forwards the operational verbs, so nothing that built against `soft3::chain` breaks today. the soft3 repository keeps the ladder, the contracts of the stack, the release train and the SDKs; the private repository `apparatus` keeps cyberia's own hosts, secrets and runbooks. From 9e03d9d6f5d3cf33f654da208ac0cae024e398a4 Mon Sep 17 00:00:00 2001 From: master Date: Mon, 2 Nov 2026 08:59:46 +0000 Subject: [PATCH 09/11] =?UTF-8?q?fix(deploy):=20install=20the=20node=20fro?= =?UTF-8?q?m=20its=20repository=20at=20a=20pinned=20revision=20=E2=80=94?= =?UTF-8?q?=20the=20crates.io=20name=20is=20unpublished;=20kept=20names=20?= =?UTF-8?q?list=20the=20runner's=20two=20variables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/chain/README.md | 2 +- deploy/deploy-cyberproxy.nu | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/chain/README.md b/deploy/chain/README.md index a62fb88..07f9571 100644 --- a/deploy/chain/README.md +++ b/deploy/chain/README.md @@ -69,7 +69,7 @@ on the running hosts; only what an operator reads and types says cybernode. cyberproxy as below. agent-a: see the variant after step 8. -1. Binary: `cargo install cybernode --version --locked` as `cyber` +1. Binary: `cargo install --git https://github.com/cyberia-to/cybernode cybernode --rev --locked` as `cyber` (the crates.io name is not published yet; never install an unpinned, unpublished name) (or copy the release binary to `/home/cyber/.cargo/bin/cybernode`). Confirm `cybernode node --help` lists `--chain --radio --peers`; the unit will not work with the 0.10.0 flag set. diff --git a/deploy/deploy-cyberproxy.nu b/deploy/deploy-cyberproxy.nu index 0386979..7a0282b 100644 --- a/deploy/deploy-cyberproxy.nu +++ b/deploy/deploy-cyberproxy.nu @@ -6,7 +6,7 @@ const HOST = "cyberproxy" def main [] { print $"deploying cybernode → ($HOST)" - ^ssh $HOST 'export PATH=$HOME/.cargo/bin:$PATH; cargo install cybernode --force' + ^ssh $HOST 'export PATH=$HOME/.cargo/bin:$PATH; cargo install --git https://github.com/cyberia-to/cybernode --rev $REV cybernode --force' ^scp "deploy/spacepussy-test.service" $"($HOST):/tmp/spacepussy-test.service" ^ssh $HOST 'sudo cp /tmp/spacepussy-test.service /etc/systemd/system/spacepussy-test.service' ^ssh $HOST 'sudo systemctl daemon-reload && sudo systemctl enable --now spacepussy-test && sudo systemctl restart spacepussy-test' From 66543ebaee120f5547c25798e9df238fb4abf05e Mon Sep 17 00:00:00 2001 From: master Date: Mon, 2 Nov 2026 09:01:05 +0000 Subject: [PATCH 10/11] fix(deploy): the install takes a pinned revision; launch guide and runner variables follow --- README.md | 2 +- deploy/deploy-cyberproxy.nu | 7 ++++--- docs/launch.md | 8 ++++---- rs/src/network.rs | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b9abed2..f4dd9b4 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ operators start at [[cybernode/docs/launch|docs/launch.md]]; the root chain on t ## kept names -decided 2026-11-02 while closing the proposal (§ 5): the wire identifiers (`soft3/chain/…/v1`, `soft3/spacepussy-test/v1`), the genesis files, the home directories (`~/.soft3/`, `/etc/soft3`, `/var/lib/soft3`) and the environment variable names `SOFT3_CHAIN`, `SOFT3_BIND`, `SOFT3_RADIO`, `SOFT3_MONIKER`, `SOFT3_STALE_SECS`, `SOFT3_MIN_DISK_GIB`, `SOFT3_DEV_EPOCH`, `SOFT3_COST_*`, `SOFT3_CONFORMANCE_DIR`, `SOFT3_GENESIS_DIR`, `SOFT3_GENESIS_REQUIRED` (and the check's `SOFT3_ETC`, `SOFT3_STATE`, `SOFT3_PIDFILE`, `SOFT3_MINE_*`) keep their names, for compatibility with the running hosts and every genesis hash. everything a person reads — strings, docs, unit names, scripts, specs — says cybernode. a change to a kept name is a consensus or an operations change and goes through specs/chain.md § 7 or the apparatus runbooks. +decided 2026-11-02 while closing the proposal (§ 5): the wire identifiers (`soft3/chain/…/v1`, `soft3/spacepussy-test/v1`), the genesis files, the home directories (`~/.soft3/`, `/etc/soft3`, `/var/lib/soft3`) and the environment variable names `SOFT3_CHAIN`, `SOFT3_BIND`, `SOFT3_RADIO`, `SOFT3_MONIKER`, `SOFT3_STALE_SECS`, `SOFT3_MIN_DISK_GIB`, `SOFT3_DEV_EPOCH`, `SOFT3_COST_*`, `SOFT3_CONFORMANCE_DIR`, `SOFT3_GENESIS_DIR`, `SOFT3_GENESIS_REQUIRED` (and the check's `SOFT3_ETC`, `SOFT3_STATE`, `SOFT3_PIDFILE`, `SOFT3_MINE_*`; the agent-a runner's `SOFT3_CHECK`, `SOFT3_LOG_NET`) keep their names, for compatibility with the running hosts and every genesis hash. everything a person reads — strings, docs, unit names, scripts, specs — says cybernode. a change to a kept name is a consensus or an operations change and goes through specs/chain.md § 7 or the apparatus runbooks. the `soft3` crate remains the stack facade: it depends on cybernode, re-exports its modules for one release and forwards the operational verbs, so nothing that built against `soft3::chain` breaks today. the soft3 repository keeps the ladder, the contracts of the stack, the release train and the SDKs; the private repository `apparatus` keeps cyberia's own hosts, secrets and runbooks. diff --git a/deploy/deploy-cyberproxy.nu b/deploy/deploy-cyberproxy.nu index 7a0282b..259e761 100644 --- a/deploy/deploy-cyberproxy.nu +++ b/deploy/deploy-cyberproxy.nu @@ -4,9 +4,10 @@ const HOST = "cyberproxy" -def main [] { - print $"deploying cybernode → ($HOST)" - ^ssh $HOST 'export PATH=$HOME/.cargo/bin:$PATH; cargo install --git https://github.com/cyberia-to/cybernode --rev $REV cybernode --force' +# rev: the revision to install (the phase-1 pin of cybernode); the crates.io name is not published, so never an unpinned install +def main [rev: string] { + print $"deploying cybernode ($rev) → ($HOST)" + ^ssh $HOST $"export PATH=$HOME/.cargo/bin:$PATH; cargo install --git https://github.com/cyberia-to/cybernode --rev ($rev) cybernode --force --locked" ^scp "deploy/spacepussy-test.service" $"($HOST):/tmp/spacepussy-test.service" ^ssh $HOST 'sudo cp /tmp/spacepussy-test.service /etc/systemd/system/spacepussy-test.service' ^ssh $HOST 'sudo systemctl daemon-reload && sudo systemctl enable --now spacepussy-test && sudo systemctl restart spacepussy-test' diff --git a/docs/launch.md b/docs/launch.md index a3ceb7b..7e415eb 100644 --- a/docs/launch.md +++ b/docs/launch.md @@ -32,7 +32,7 @@ cosmos **space-pussy** / **bostrom** on the apparatus hosts are bootloader chain ```bash rustup update stable # need rustc ≥ 1.85 (edition 2024 deps) -cargo install cybernode # operator / node (or: cargo install --path rs) +cargo install --git https://github.com/cyberia-to/cybernode cybernode # operator / node (or: cargo install --path rs); the crates.io name is not published yet cargo install true-cyber # product face (thin client) ``` @@ -96,12 +96,12 @@ host: **cyberproxy** (edge for cyb.ai). ```bash # on an operator machine -cargo install cybernode --force +cargo install --git https://github.com/cyberia-to/cybernode cybernode --force # on cyberproxy ssh cyberproxy export PATH="$HOME/.cargo/bin:$PATH" -cargo install cybernode --force +cargo install --git https://github.com/cyberia-to/cybernode cybernode --force ``` the unit is `deploy/chain/cybernode@.service`, one instance per network @@ -135,7 +135,7 @@ cyber sync redeploy after a cybernode upgrade: ```bash -ssh cyberproxy 'export PATH=$HOME/.cargo/bin:$PATH; cargo install cybernode --force; sudo systemctl restart cybernode@pussy' +ssh cyberproxy 'export PATH=$HOME/.cargo/bin:$PATH; cargo install --git https://github.com/cyberia-to/cybernode cybernode --force; sudo systemctl restart cybernode@pussy' ``` ## multi-node (current limits) diff --git a/rs/src/network.rs b/rs/src/network.rs index 86894ce..96a6d6b 100644 --- a/rs/src/network.rs +++ b/rs/src/network.rs @@ -17,7 +17,7 @@ pub enum Network { } impl Network { - /// Product default after `cargo install cybernode` / `true-cyber`. + /// Product default after installing `cybernode` / `true-cyber`. pub const DEFAULT: Network = Network::SpacePussyTest; pub fn parse(s: &str) -> Option { From eefdf26fa3b28b89c865529526469dc69ffa9f9f Mon Sep 17 00:00:00 2001 From: master Date: Mon, 2 Nov 2026 09:07:56 +0000 Subject: [PATCH 11/11] =?UTF-8?q?docs(launch):=20every=20install=20of=20th?= =?UTF-8?q?e=20node=20is=20pinned=20=E2=80=94=20a=20revision=20or=20the=20?= =?UTF-8?q?release=20branch,=20--locked?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/launch.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/launch.md b/docs/launch.md index 7e415eb..96963fa 100644 --- a/docs/launch.md +++ b/docs/launch.md @@ -32,7 +32,7 @@ cosmos **space-pussy** / **bostrom** on the apparatus hosts are bootloader chain ```bash rustup update stable # need rustc ≥ 1.85 (edition 2024 deps) -cargo install --git https://github.com/cyberia-to/cybernode cybernode # operator / node (or: cargo install --path rs); the crates.io name is not published yet +cargo install --git https://github.com/cyberia-to/cybernode cybernode --branch release/phase1 --locked # operator / node (or: cargo install --path rs); the crates.io name is not published yet, so never an unpinned install cargo install true-cyber # product face (thin client) ``` @@ -96,12 +96,12 @@ host: **cyberproxy** (edge for cyb.ai). ```bash # on an operator machine -cargo install --git https://github.com/cyberia-to/cybernode cybernode --force +cargo install --git https://github.com/cyberia-to/cybernode cybernode --rev --force --locked # on cyberproxy ssh cyberproxy export PATH="$HOME/.cargo/bin:$PATH" -cargo install --git https://github.com/cyberia-to/cybernode cybernode --force +cargo install --git https://github.com/cyberia-to/cybernode cybernode --rev --force --locked ``` the unit is `deploy/chain/cybernode@.service`, one instance per network @@ -135,7 +135,7 @@ cyber sync redeploy after a cybernode upgrade: ```bash -ssh cyberproxy 'export PATH=$HOME/.cargo/bin:$PATH; cargo install --git https://github.com/cyberia-to/cybernode cybernode --force; sudo systemctl restart cybernode@pussy' +ssh cyberproxy 'export PATH=$HOME/.cargo/bin:$PATH; cargo install --git https://github.com/cyberia-to/cybernode cybernode --rev --force --locked; sudo systemctl restart cybernode@pussy' ``` ## multi-node (current limits)