# 0002 — `(random-seed-from-os!)` pulls kernel entropy

**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

Ticket 0001 gave us xoshiro256\*\* with deterministic startup seed `k = 0`
and explicit `(random-seed! k)`. Two processes running the same program
produce the same stream. That is the point — it is what lets a portal
round-trip reproduce state across machines.

But determinism has a dual cost: a program that wants a *different*
stream per run must seed itself from outside. Without a kernel-entropy
hook, users have to hand-roll one by opening `/dev/urandom` and folding
bytes into an integer. That is defect-prone (wrong endianness, short
read not handled, FD left open) and duplicates in every caller.

fox's note: *"a random seed from os on at least one side is very
important"* — because the common real-world flow is:

1. Machine A seeds from kernel entropy (`random-seed-from-os!`).
2. Machine A runs a stochastic simulation for a while.
3. Machine A portal-saves — state captured, including xoshiro256\*\* state.
4. Machine B portal-resumes — continues the *same* stream bit-for-bit.

Step 1 is missing today. This ticket adds it.

## Goals

1. One builtin, all three impls: `(random-seed-from-os!)`.
2. Reads 8 bytes from `/dev/urandom`, interprets as little-endian `u64`,
   calls existing `rng_seed`. No new PRNG, just a different seed source.
3. Returns void; user captures `(random-state)` if they want to replay.
4. Fail-loud on I/O error (missing `/dev/urandom`, short read) — we do
   not silently fall back to a weak seed.

## Non-goals

- Blocking `/dev/random` (entropy-exhaustion semantics). `urandom` is the
  right default; `man 4 urandom` confirms it is cryptographically strong
  after boot.
- `getrandom(2)` syscall. Portable to older kernels matters more than
  saving one open/read/close.
- Any silent use of OS entropy on startup. The existing `k = 0` default
  stays; determinism remains the default, kernel entropy is opt-in.
- `/dev/urandom` emulation on non-Linux. Lumbda targets Linux x86_64;
  other platforms earn their own ticket.

## API

```
(random-seed-from-os!) → void
```

Reads exactly 8 bytes from `/dev/urandom`, little-endian, passes the
resulting `u64` to the same `rng_seed` path as `(random-seed!)`. After
return, `(random-state)` reflects the new state, and the portal system
captures it unchanged.

Errors (fail-loud):
- `/dev/urandom` cannot be opened → error + exit(1) in asm; `LispErr`
  in Python + C.
- Short read (fewer than 8 bytes) → same treatment.

## Implementation

Python (~3 lines):

```python
def _rng_seed_from_os():
    with open('/dev/urandom', 'rb') as f:
        b = f.read(8)
    if len(b) != 8: raise LispErr('random-seed-from-os!: short read')
    _rng_seed(int.from_bytes(b, 'little', signed=False))
```

C (~10 lines): `open` / `read` / `close` on `/dev/urandom`, then call
existing `rng_seed(uint64_t)`.

Asm (~30 lines): `SYS_OPEN`, `SYS_READ`, `SYS_CLOSE` with an 8-byte
stack slot, then `call rng_seed`. New data:
`s_dev_urandom: .asciz "/dev/urandom"`. Error message follows the
existing `err_rng_*` pattern.

## Test plan

1. **Unit** — `(random-seed-from-os!)` runs without error in each tier.
2. **Entropic** — two consecutive `(random-seed-from-os!)` calls in
   the *same* process produce different streams (with overwhelming
   probability). Asserted via `(not (equal? state-a state-b))`.
3. **Determinism preserved** — `(random-seed-from-os!)` + capture via
   `(random-state)` + reseed with `(random-state! saved)` produces the
   original stream. This proves the OS seed plugs cleanly into the
   portal path from 0001.

## Deliverables

- [ ] `docs/tickets/0002-os-entropy-seed.md` (this ticket)
- [ ] Python: `lumbda.py` — helper + builtin binding
- [ ] C: `c/portal.c` — helper + builtin + `register_portal_builtins`
- [ ] Asm: `asm/lumbda.s` — new `BI_RANDOMSEEDFROMOS`, dispatch,
       `bi_random_seed_from_os`, data strings
- [ ] `tests/functional.lsp` — 2 shared assertions
- [ ] `asm/test.sh` — 2 asm-local checks
- [ ] Whitepaper §7.5 — one sentence noting the OS-seed option
- [ ] `make test-all` green
- [ ] `unmoad` clean on changed files
