portal-rng: 9-cell cross-impl stream matches independent Python baseline
Closes tickets 0001 (portal-rng) and 0002 (os-entropy-seed). Ticket 0001 goal 4 called for proof that seeding with k, drawing N, saving, clearing, resuming in any impl, and drawing M more produces a full stream matching a single-process Python baseline bit-for-bit. Prior tests/portal-cross-test.sh exercised producer-consumer agreement but used a producer-side self-computed baseline; it did not compare against an independent Python run that never saves or resumes. tests/portal-rng-cross-test.sh computes a single-process Python baseline once (seed=42, N+M=10 draws, no portal), then runs all 9 producer x consumer cells (Python, C, asm each side) and checks that producer's first N plus consumer's M equals the independent baseline. All 12 assertions pass. Wired into make test-all. Ticket status updated to resolved on both 0001 and 0002 with dated one-line resolution notes.
This commit is contained in:
parent
9dee0d02ee
commit
d2866f486d
4 changed files with 156 additions and 5 deletions
9
Makefile
9
Makefile
|
|
@ -99,9 +99,12 @@ functional-test: c-build
|
|||
@echo "── C ──"
|
||||
@./c/lumbda tests/functional.lsp | tail -3
|
||||
|
||||
test-all: test c-test asm-test functional-test
|
||||
portal-rng-cross-test: c-build asm-build
|
||||
@bash tests/portal-rng-cross-test.sh
|
||||
|
||||
test-all: test c-test asm-test functional-test portal-rng-cross-test
|
||||
@echo "════════════════════════════════════════════════════"
|
||||
@echo "All tests passed (Python + C + Assembly + functional)"
|
||||
@echo "All tests passed (Python + C + Assembly + functional + portal-rng-cross)"
|
||||
|
||||
# ─── Benchmarks (reproducible; referenced in whitepaper §6–§11) ───
|
||||
|
||||
|
|
@ -237,6 +240,6 @@ clean-all: clean clean-whitepaper clean-docs c-clean asm-clean
|
|||
.PHONY: all test test-verbose bench bench-verbose repl lint \
|
||||
c-build c-test c-bench c-repl c-clean \
|
||||
asm-build asm-test asm-repl asm-clean \
|
||||
test-all bench-all examples friction functional-test \
|
||||
test-all bench-all examples friction functional-test portal-rng-cross-test \
|
||||
bench-3way bench-portal bench-portal-cross bench-web bench-rpc-chain bench-proof \
|
||||
docs whitepaper clean clean-whitepaper clean-docs clean-all
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
# 0001 — Portal preserves RNG state across processes
|
||||
|
||||
**Status:** open
|
||||
**Status:** resolved
|
||||
**Reporter:** Zoe (via fox)
|
||||
**Implementer:** blackops
|
||||
**Opened:** 2026-04-20
|
||||
**Resolved:** 2026-04-24 — xoshiro256\*\* shipped in Python, C, asm; portal round-trips RNG state; `tests/portal-rng-cross-test.sh` proves all 9 producer×consumer cells reproduce the independent single-process Python baseline bit-for-bit. Wired into `make test-all`.
|
||||
|
||||
## Problem
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
# 0002 — `(random-seed-from-os!)` pulls kernel entropy
|
||||
|
||||
**Status:** open
|
||||
**Status:** resolved
|
||||
**Reporter:** fox (response to 0001 follow-up)
|
||||
**Implementer:** blackops
|
||||
**Opened:** 2026-04-20
|
||||
**Resolved:** 2026-04-24 — `(random-seed-from-os!)` shipped in all three impls, reads 8 LE bytes from `/dev/urandom`, fails loud on short read. Entropic + replay checks in `tests/functional.lsp` and `asm/test.sh`; OS-seeded state plugs cleanly into the 0001 portal path.
|
||||
|
||||
## Problem
|
||||
|
||||
|
|
|
|||
146
tests/portal-rng-cross-test.sh
Executable file
146
tests/portal-rng-cross-test.sh
Executable file
|
|
@ -0,0 +1,146 @@
|
|||
#!/bin/bash
|
||||
# portal-rng-cross-test.sh — ticket 0001 deliverable (goal 4):
|
||||
#
|
||||
# "seed k, draw N values, save, clear, resume in any impl, draw M more —
|
||||
# full stream matches a single-process Python baseline."
|
||||
#
|
||||
# This script proves that every (producer, consumer) pair across
|
||||
# Python / C / asm reproduces the exact same N+M stream that a single
|
||||
# Python process produces with no save/resume at all. That is the
|
||||
# stronger claim than portal-cross-test.sh's producer-side self-
|
||||
# consistency: here the truth is computed independently, once, upfront,
|
||||
# by a Python run that never touches the portal. Every cell compares
|
||||
# against the same independent baseline.
|
||||
#
|
||||
# Matrix: 3 producers × 3 consumers = 9 cells. K=42, N=5, M=5.
|
||||
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
PY="python3 lumbda.py --fast"
|
||||
C="./c/lumbda"
|
||||
ASM="./asm/lumbda"
|
||||
|
||||
K=42 # seed
|
||||
N=5 # draws before save
|
||||
M=5 # draws after resume
|
||||
RANGE=1000000 # random-int upper bound
|
||||
|
||||
# ─── Compute the independent single-process Python baseline (N+M draws) ───
|
||||
BASELINE_LSP=$(mktemp --suffix=.lsp)
|
||||
trap 'rm -f "$BASELINE_LSP" /tmp/rng-xstream.sexp' EXIT INT TERM
|
||||
|
||||
cat > "$BASELINE_LSP" <<EOF
|
||||
(random-seed! $K)
|
||||
;; for-each returns a non-printing value across Python/C/asm, so no
|
||||
;; trailing token leaks into stdout. Iteration count is carried by the
|
||||
;; length of the dummy list below (shell-expanded once).
|
||||
(for-each (lambda (_) (display (random-int $RANGE)) (newline))
|
||||
'($(seq 1 $((N + M)) | tr '\n' ' ')))
|
||||
EOF
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
BASELINE=$($PY "$BASELINE_LSP")
|
||||
BASELINE_COUNT=$(printf "%s\n" "$BASELINE" | wc -l)
|
||||
|
||||
if [ "$BASELINE_COUNT" -ne $((N + M)) ]; then
|
||||
echo "FAIL: baseline produced $BASELINE_COUNT draws, expected $((N + M))"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Split baseline into first-N and next-M.
|
||||
BASELINE_N=$(printf "%s\n" "$BASELINE" | head -n $N)
|
||||
BASELINE_M=$(printf "%s\n" "$BASELINE" | tail -n $M)
|
||||
|
||||
echo "═══════════════════════════════════════════════════════"
|
||||
echo "RNG portal cross-impl round-trip — independent Python baseline"
|
||||
echo "Ticket: docs/tickets/0001-portal-rng.md (goal 4)"
|
||||
echo " seed=$K, save after N=$N draws, resume for M=$M draws"
|
||||
echo "═══════════════════════════════════════════════════════"
|
||||
echo
|
||||
echo "single-process Python baseline (N+M=$((N + M)) draws):"
|
||||
printf "%s\n" "$BASELINE" | sed 's/^/ /'
|
||||
echo
|
||||
|
||||
# ─── Producer script (generated): seed, draw N, emit them to stdout, save state ───
|
||||
PROD_LSP=$(mktemp --suffix=.lsp)
|
||||
cat > "$PROD_LSP" <<EOF
|
||||
(random-seed! $K)
|
||||
(for-each (lambda (_) (display (random-int $RANGE)) (newline))
|
||||
'($(seq 1 $N | tr '\n' ' ')))
|
||||
(define snapshot (random-state))
|
||||
(define port (open-output-file "/tmp/rng-xstream.sexp"))
|
||||
(display "(random-state! '" port) (write snapshot port) (display ")\n" port)
|
||||
(close-port port)
|
||||
EOF
|
||||
|
||||
# ─── Consumer script (generated): load state, draw M, emit them to stdout ───
|
||||
CONS_LSP=$(mktemp --suffix=.lsp)
|
||||
cat > "$CONS_LSP" <<EOF
|
||||
(load "/tmp/rng-xstream.sexp")
|
||||
(for-each (lambda (_) (display (random-int $RANGE)) (newline))
|
||||
'($(seq 1 $M | tr '\n' ' ')))
|
||||
EOF
|
||||
|
||||
# shellcheck disable=SC2064
|
||||
trap "rm -f '$BASELINE_LSP' '$PROD_LSP' '$CONS_LSP' /tmp/rng-xstream.sexp" EXIT INT TERM
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
run_producer() {
|
||||
local impl="$1"
|
||||
case "$impl" in
|
||||
Python) $PY "$PROD_LSP" ;;
|
||||
C) $C "$PROD_LSP" ;;
|
||||
Asm) $ASM < "$PROD_LSP" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
run_consumer() {
|
||||
local impl="$1"
|
||||
case "$impl" in
|
||||
Python) $PY "$CONS_LSP" ;;
|
||||
C) $C "$CONS_LSP" ;;
|
||||
Asm) $ASM < "$CONS_LSP" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
verify_stream() {
|
||||
local label="$1" expected="$2" actual="$3"
|
||||
if [ "$expected" = "$actual" ]; then
|
||||
PASS=$((PASS + 1))
|
||||
printf " %-22s OK\n" "$label"
|
||||
else
|
||||
FAIL=$((FAIL + 1))
|
||||
printf " %-22s FAIL\n" "$label"
|
||||
echo " expected:"
|
||||
printf "%s\n" "$expected" | sed 's/^/ /'
|
||||
echo " actual:"
|
||||
printf "%s\n" "$actual" | sed 's/^/ /'
|
||||
fi
|
||||
}
|
||||
|
||||
for P_NAME in Python C Asm; do
|
||||
echo "── producer: $P_NAME ──"
|
||||
rm -f /tmp/rng-xstream.sexp
|
||||
PROD_OUT=$(run_producer "$P_NAME")
|
||||
|
||||
# Producer's first N must equal the Python baseline's first N.
|
||||
verify_stream "$P_NAME: first $N draws" "$BASELINE_N" "$PROD_OUT"
|
||||
|
||||
for C_NAME in Python C Asm; do
|
||||
CONS_OUT=$(run_consumer "$C_NAME")
|
||||
# Consumer's M draws after resume must equal the Python baseline's last M.
|
||||
verify_stream "$P_NAME -> $C_NAME (next $M)" "$BASELINE_M" "$CONS_OUT"
|
||||
done
|
||||
done
|
||||
|
||||
echo
|
||||
echo "═══════════════════════════════════════════════════════"
|
||||
echo "Cross-impl RNG round-trip vs Python baseline: $PASS passed, $FAIL failed"
|
||||
echo "═══════════════════════════════════════════════════════"
|
||||
|
||||
if [ $FAIL -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
Loading…
Add table
Add a link
Reference in a new issue