mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-08-29 19:01:52 +00:00
* feat(forge-core): author module for writing signed RVF containers rvf-forge-core could verify containers but not produce them, so every test and fixture had to hand-assemble bytes through testkit. The author module makes writing a first-class operation: ContainerBuilder assembles segments, computes per-segment digests, and emits a signed root manifest that this crate's own verifier accepts. Segment kind decides signing policy rather than the caller: a .wasm payload becomes an executable WASM segment and is signed individually, anything else becomes an opaque VEC segment. That keeps rule 3 of the loading contract — unsigned executable segments are rejected by default — a property of the writer, not something each caller has to remember to ask for. The parity fixture generator now builds its input through this module instead of a bespoke byte layout, so the TypeScript and Rust sides are compared against a shared definition of what a valid container is. 138 tests, clippy clean. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01ParP55bZs2iTGEGvpnUecx * feat(rvforge): add the create command that writes a signed agent.rvf Closes the gap that made the published 0.1.0 unusable end to end: init printed "Next: rvforge pack <agent.rvf>" while creating no such file, so a first-time user's next command failed with FORGE_E_IO and there was no supported way to produce the input every other command needs. The only valid .rvf in the repo lived in tests/fixtures, which is not in the published tarball. create reads project metadata and declared capabilities from rvforge.json and signs with the key init --keygen recorded, so the common case takes no arguments. With no --from it writes a minimal but complete skeleton — a META segment declaring the requested capability classes and a signed root MANIFEST — which is enough for validate, test, pack, publish and build to run. Walking the whole pipeline before you have a model to put in it is the point. --from <dir> adds files as segments in sorted order, so the same input directory produces the same bytes. init's next-step line now points at create rather than at a file it does not write. Verified from an empty directory against the built CLI: init, create, validate --deep and test all exit 0 on a self-authored artifact. 253 tests. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01ParP55bZs2iTGEGvpnUecx * feat(reader): install, library and update flows over verified artifacts Takes rvforge-reader from a verification surface to one that manages installed agents: install, a library of what is installed, and update with rollback. Each flow re-verifies rather than trusting the step before it — an artifact that verified at download is verified again at install and again at load, because the file on disk between those points is not the same object the check covered. The dock bridge keeps the trust boundary the Dock exists to enforce. Chrome the system owns — trust badge, network indicator, pause — is populated from SystemOwnedStatus only, and agent-supplied text stays in AgentProvidedStatus and is sanitized before display. A hostile agent cannot forge an approved badge or claim it has stopped while running, because the types do not give it a channel to those fields. Update binds to lineage: an update whose base identity does not match the installed artifact is refused rather than applied, and rollback restores the previous version with its state capsule intact. 189 tests, clippy clean. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01ParP55bZs2iTGEGvpnUecx * fix(deps): bump rkyv 0.8.16 to 0.8.18 for RUSTSEC-2026-0233/0234/0235 Three advisories published against rkyv 0.8.16: a use-after-free during deserialization of crafted archives (RUSTSEC-2026-0233), and out-of-bounds reads from insufficient archive validation for Rc/Arc (0235) and hash tables (0234). rkyv is a workspace-wide dependency of ruvector-core, ruvector-graph, ruvector-router-core and ruvector-sparse-inference. All three advisories are deserialization-side, which is where untrusted bytes arrive, so an ignore entry would be the wrong call even though the existing audit.toml has that mechanism — .cargo/audit.toml states the policy directly: anything fixable is fixed via a dependency bump rather than ignored. Lockfile only, no manifest change. cargo audit exits 0 and the four dependent crates check clean. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01ParP55bZs2iTGEGvpnUecx
136 lines
5.6 KiB
Bash
Executable file
136 lines
5.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# CLI ↔ Rust parity check, in two halves.
|
|
#
|
|
# `npm/packages/rvforge` (TypeScript) writes `.rvf` containers and a local
|
|
# registry; `crates/rvf-forge-core` and `crates/rvforge-registry` (Rust) read
|
|
# them. Both sides implement the same specs and were built independently — so
|
|
# "both pass their own tests" says nothing about whether they interoperate.
|
|
#
|
|
# **Container parity.** `rvforge create` writes a real, signed `.rvf`, and
|
|
# `rvforge-rvf-check` inspects and verifies it through `rvf-forge-core`'s public
|
|
# API, with the publisher's public key supplied so the Ed25519 signatures are
|
|
# actually checked rather than skipped. Nothing is shared between the two
|
|
# implementations but the format itself: the segment layout, the SHAKE-256
|
|
# content hashes, the signature footers, and the Level-0 root manifest page all
|
|
# have to agree byte for byte or this fails.
|
|
#
|
|
# **Registry parity.** The run then drives pack → publish against that same
|
|
# artifact, publishes a *second* release so it exercises lineage rather than a
|
|
# single object, and hands the directory to `rvforge-registry-check`, which
|
|
# re-derives every content address, recomputes the Merkle log, re-applies the
|
|
# publication rules, and walks the witness chains.
|
|
#
|
|
# Prints `PARITY OK` and exits 0 when the Rust readers accept what the CLI
|
|
# wrote; otherwise prints the violation list and exits non-zero.
|
|
#
|
|
# Usage: scripts/rvforge-parity-check.sh [--keep]
|
|
# --keep leave the temporary registry on disk and print its path
|
|
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
CLI_DIR="$REPO_ROOT/npm/packages/rvforge"
|
|
CRATE_DIR="$REPO_ROOT/crates/rvforge-registry"
|
|
|
|
KEEP=0
|
|
[[ "${1:-}" == "--keep" ]] && KEEP=1
|
|
|
|
for required in "$CLI_DIR/package.json" "$CRATE_DIR/Cargo.toml"; do
|
|
if [[ ! -f "$required" ]]; then
|
|
echo "parity check needs $required; skipping" >&2
|
|
exit 0
|
|
fi
|
|
done
|
|
|
|
WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/rvforge-parity.XXXXXX")"
|
|
cleanup() {
|
|
if [[ "$KEEP" -eq 1 ]]; then
|
|
echo "kept: $WORKDIR"
|
|
else
|
|
rm -rf "$WORKDIR"
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
PROJECT_DIR="$WORKDIR/project"
|
|
REGISTRY_DIR="$WORKDIR/registry"
|
|
mkdir -p "$PROJECT_DIR"
|
|
|
|
echo "==> building the CLI"
|
|
# --workspaces=false: the package sits inside the npm/ workspace root, and a
|
|
# plain install resolves the whole workspace and fails EBADPLATFORM on
|
|
# platform-pinned siblings.
|
|
(cd "$CLI_DIR" && npm install --workspaces=false --silent && npm run build --silent)
|
|
|
|
CLI="$CLI_DIR/dist/cli.js"
|
|
KEY_FILE="$PROJECT_DIR/rvforge-publisher.key"
|
|
|
|
echo "==> rvforge init --keygen"
|
|
# init scaffolds rvforge.json too, so it runs first; the fixture generator then
|
|
# replaces that scaffold with a project that passes every pack check.
|
|
(cd "$PROJECT_DIR" && node "$CLI" init --keygen --quiet >/dev/null)
|
|
node "$REPO_ROOT/scripts/rvforge-parity-fixture.cjs" "$CLI_DIR/dist" "$PROJECT_DIR" >/dev/null
|
|
|
|
echo "==> rvforge create agent.rvf"
|
|
# The fixture no longer ships a synthetic .rvf: the CLI writes the artifact
|
|
# under test. A .wasm payload makes it carry an executable segment, so the
|
|
# run covers the signed-executable path rather than metadata alone.
|
|
mkdir -p "$PROJECT_DIR/payload"
|
|
printf '\0asm\1\0\0\0' > "$PROJECT_DIR/payload/agent.wasm"
|
|
(cd "$PROJECT_DIR" && node "$CLI" create agent.rvf \
|
|
--from payload --key-file "$KEY_FILE" --quiet >/dev/null)
|
|
|
|
echo "==> rvforge-rvf-check (Rust reads the container the CLI wrote)"
|
|
# The public key is passed as hex so the Rust side needs no base64 decoder;
|
|
# without it every signature check would record "skipped", which would prove
|
|
# nothing about whether the CLI's signatures verify.
|
|
PUBLIC_KEY_HEX="$(node -e '
|
|
const { readFileSync } = require("node:fs");
|
|
const key = JSON.parse(readFileSync(process.argv[1], "utf8"));
|
|
process.stdout.write(Buffer.from(key.publicKey, "base64").toString("hex"));
|
|
' "$KEY_FILE")"
|
|
|
|
if ! (cd "$REPO_ROOT" && cargo run --quiet -p rvf-forge-core \
|
|
--bin rvforge-rvf-check -- "$PROJECT_DIR/agent.rvf" \
|
|
--public-key "$PUBLIC_KEY_HEX"); then
|
|
echo
|
|
echo "PARITY FAILED — crates/rvf-forge-core rejected the .rvf that" >&2
|
|
echo "npm/packages/rvforge wrote; the two disagree about the RVF format" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> rvforge pack agent.rvf"
|
|
(cd "$PROJECT_DIR" && node "$CLI" pack agent.rvf --quiet >/dev/null)
|
|
|
|
echo "==> rvforge publish agent.rvf (1.0.0)"
|
|
(cd "$PROJECT_DIR" && node "$CLI" publish agent.rvf \
|
|
--registry "$REGISTRY_DIR" --key-file "$KEY_FILE" --quiet >/dev/null)
|
|
|
|
# A second release is what makes the check meaningful: it exercises the
|
|
# predecessor link, the release index as a chain, a two-leaf Merkle tree, and a
|
|
# witness receipt that has to name the previous one.
|
|
echo "==> rvforge publish agent.rvf (1.1.0)"
|
|
node -e '
|
|
const { readFileSync, writeFileSync } = require("node:fs");
|
|
const path = process.argv[1];
|
|
const project = JSON.parse(readFileSync(path, "utf8"));
|
|
project.version = "1.1.0";
|
|
writeFileSync(path, `${JSON.stringify(project, null, 2)}\n`);
|
|
' "$PROJECT_DIR/rvforge.json"
|
|
(cd "$PROJECT_DIR" && node "$CLI" publish agent.rvf \
|
|
--registry "$REGISTRY_DIR" --key-file "$KEY_FILE" --quiet >/dev/null)
|
|
|
|
echo "==> rvforge-registry-check (Rust)"
|
|
if (cd "$REPO_ROOT" && cargo run --quiet -p rvforge-registry \
|
|
--bin rvforge-registry-check -- "$REGISTRY_DIR"); then
|
|
echo
|
|
echo "PARITY OK — the Rust side reads everything the CLI wrote:"
|
|
echo " container rvf-forge-core inspected and verified agent.rvf"
|
|
echo " registry rvforge-registry replayed the whole publication log"
|
|
exit 0
|
|
fi
|
|
|
|
echo
|
|
echo "PARITY FAILED — the violations above are places the CLI and" >&2
|
|
echo "crates/rvforge-registry disagree about registry-model.md" >&2
|
|
exit 1
|