feat(stdlib): finish Crypto (CSPRNG + base64) and add Uuid.* library (#19 #16)
All checks were successful
docs / build-and-deploy (push) Successful in 2s

Crypto (#19): add the OS cryptographically-secure random surface
(random_bytes/random_hex/random_u32, reading /dev/urandom) and a standard
base64 encoder, completing the library alongside the existing SHA-256/
HMAC-SHA256/verify_hmac/hex/ct_equal. All pure integer IR, C-free.

Uuid (#16): a new namespace for stable, collision-free IDs — v4 (random) and
v7 (time-ordered) generation, plus parse/is_valid/to_text/equals/nil. UUIDs are
canonical lowercase 36-char strings; v4 and v7's random tail draw from the
crypto CSPRNG, so both carry the documented determinism caveat (mint at the
edges, never inside lockstep simulation). Reuses the crypto prelude's
fn_secure_bytes / fn_hex_encode.

- examples/library/{crypto,uuid}.ludic: known-answer vectors (SHA-256, HMAC,
  base64 per RFC 4231/4648) and structural invariants (uuid version/variant
  bits, parse/equals), wired into `x test` (now 51 passed).
- docs: per-symbol pages for every new method + a new Uuid section; inventory
  and impl-vs-docs coverage check pass.
- seed regenerated; `x bootstrap-cfree` fixpoint holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-30 21:40:51 +03:00
parent a422ef4375
commit 2ddf830f0b
26 changed files with 12185 additions and 10720 deletions

View file

@ -0,0 +1,13 @@
---
id: uuid
title: Uuid
order: 6
---
Universally-unique identifiers — stable IDs that don't collide, generated locally with no central authority handing out numbers. Reach for a UUID whenever something needs an identity that survives being saved, shared, or sent over a network: players and sessions in multiplayer, user-created content (levels, items, mods) that has to merge cleanly across installs, or a per-install / per-run ID for analytics and bug reports.
Two versions are provided. <a href="uuid-new"><code>Uuid.new</code></a> makes a **v4** (random) UUID — 122 bits of entropy, effectively never colliding. <a href="uuid-new_v7"><code>Uuid.new_v7</code></a> makes a **v7** (time-ordered) UUID whose leading bits are a millisecond timestamp, so a batch of v7 IDs sorts by creation time — friendly to database indexes and append logs. Both set the RFC 4122 version and variant bits correctly.
A UUID is represented as its canonical lowercase 36-character text form (<code>8-4-4-4-12</code>), the same shape you store, print, send, and compare — so there is no conversion at each boundary. Validate untrusted input with <a href="uuid-is_valid"><code>Uuid.is_valid</code></a> or normalise it with <a href="uuid-parse"><code>Uuid.parse</code></a>, and compare with <a href="uuid-equals"><code>Uuid.equals</code></a>, which ignores case.
**Determinism caveat.** v4 and the random tail of v7 come from the OS cryptographically-secure RNG (<a href="ns-Crypto"><code>Crypto</code></a>), which is non-deterministic by design. Minting a UUID inside the lockstep simulation will desync replays and networked peers — generate IDs at the edges (on connect, on save, on spawn-from-input), never per tick in reproducible gameplay code.

View file

@ -0,0 +1,27 @@
---
id: uuid-equals
name: Uuid.equals
category: uuid
kind: namespace-method
tokens: Uuid.equals
sig: Uuid.equals(a, b) -> bool
tip: Case-insensitive UUID equality.
order: 8
ns: Uuid
member: equals
---
Compares two UUIDs for equality, ignoring case. UUIDs generated by this library are always lowercase, so a plain <code>==</code> works between them — but a UUID that arrived from another system may be upper- or mixed-case, and <code>equals</code> matches it correctly without you having to normalise first. Values of different length are never equal.
Parameters:
- `a`, `b` — the UUID strings to compare
```ludic
program Eq {
entry {
let lo = "550e8400-e29b-41d4-a716-446655440000"
let up = "550E8400-E29B-41D4-A716-446655440000"
if Uuid.equals(lo, up) { print(1) } # case-insensitive
}
}
```

View file

@ -0,0 +1,28 @@
---
id: uuid-is_valid
name: Uuid.is_valid
category: uuid
kind: namespace-method
tokens: Uuid.is_valid
sig: Uuid.is_valid(s) -> bool
tip: Is s a well-formed UUID string?
order: 6
ns: Uuid
member: is_valid
---
Reports whether <code>s</code> is a well-formed UUID: exactly 36 characters, hyphens at positions 8, 13, 18 and 23, and hexadecimal digits (either case) everywhere else. This is the guard to run on any UUID that came from outside your program — a save file, a network message, a mod, a command-line flag — before you trust it as an identity.
It checks *shape*, not version: both v4 and v7 IDs (and any other conforming UUID) pass. To also fold a malformed value into a safe default in one step, use <a href="uuid-parse"><code>Uuid.parse</code></a> instead.
Parameters:
- `s` — the string to check
```ludic
program Valid {
entry {
if Uuid.is_valid("550e8400-e29b-41d4-a716-446655440000") { print(1) }
if not Uuid.is_valid("not-a-uuid") { print(2) }
}
}
```

View file

@ -0,0 +1,25 @@
---
id: uuid-new
name: Uuid.new
category: uuid
kind: namespace-method
tokens: Uuid.new
sig: Uuid.new() -> string
tip: A new random (v4) UUID as a canonical 36-char string.
order: 1
ns: Uuid
member: new
---
Generates a new **v4** (random) UUID and returns it in canonical lowercase text form, e.g. <code>550e8400-e29b-41d4-a716-446655440000</code>. A v4 UUID carries 122 bits of entropy drawn from the OS secure random generator, so two independently-generated IDs colliding is not something you will ever observe in practice — which is exactly what makes it a good identity for a player, a session, a networked entity, or a piece of user-created content that has no central authority to number it.
This is the default UUID constructor; <a href="uuid-v4"><code>Uuid.v4</code></a> is an explicit alias. Because the value is random it is **non-deterministic** — do not mint UUIDs inside the lockstep simulation, or replays and peers will diverge.
```ludic
program NewId {
entry {
let id = Uuid.new()
print(len(id)) # 36
}
}
```

View file

@ -0,0 +1,25 @@
---
id: uuid-new_v7
name: Uuid.new_v7
category: uuid
kind: namespace-method
tokens: Uuid.new_v7
sig: Uuid.new_v7() -> string
tip: A new time-ordered (v7) UUID; sorts by creation time.
order: 3
ns: Uuid
member: new_v7
---
Generates a new **v7** (time-ordered) UUID. Its leading 48 bits are a Unix-millisecond timestamp, so a batch of v7 IDs sorts lexicographically by creation time — which keeps database indexes and append-only logs tidy in a way random v4 IDs do not. The remaining bits are secure random, so IDs minted in the same millisecond are still distinct, and the RFC 4122 version (7) and variant bits are set.
Sub-second resolution is derived from the wall clock in seconds (multiplied to milliseconds), so ordering is guaranteed at one-second granularity with the random tail breaking ties within a second. Like v4 this reads the non-deterministic wall clock and CSPRNG — generate at the edges, never inside reproducible simulation. <a href="uuid-v7"><code>Uuid.v7</code></a> is an alias.
```ludic
program NewV7 {
entry {
let id = Uuid.new_v7()
print(id[14..15]) # 7 — the version nibble
}
}
```

View file

@ -0,0 +1,23 @@
---
id: uuid-nil
name: Uuid.nil
category: uuid
kind: namespace-method
tokens: Uuid.nil
sig: Uuid.nil() -> string
tip: The all-zero UUID.
order: 9
ns: Uuid
member: nil
---
Returns the nil UUID — <code>00000000-0000-0000-0000-000000000000</code> — the reserved all-zero value that means "no UUID". Use it as a sentinel for an unset or absent identity, and as the value <a href="uuid-parse"><code>Uuid.parse</code></a> returns when it is handed something that is not a valid UUID. It is a well-formed UUID string, so it passes <a href="uuid-is_valid"><code>Uuid.is_valid</code></a>; test for "no id" by comparing against <code>Uuid.nil()</code> explicitly.
```ludic
program Nil {
entry {
let none = Uuid.nil()
print(none) # 00000000-0000-0000-0000-000000000000
}
}
```

View file

@ -0,0 +1,29 @@
---
id: uuid-parse
name: Uuid.parse
category: uuid
kind: namespace-method
tokens: Uuid.parse
sig: Uuid.parse(s) -> string
tip: Normalise an untrusted string to a lowercase UUID, or the nil UUID.
order: 5
ns: Uuid
member: parse
---
Normalises an untrusted string: if <code>s</code> is a well-formed UUID it returns the same value in canonical lowercase form; if it is not, it returns the <a href="uuid-nil"><code>nil</code></a> UUID. This never faults on garbage, so it is safe to call on data that arrived from a file, a network peer, or a mod. When you need to *reject* bad input rather than fold it to nil, gate on <a href="uuid-is_valid"><code>Uuid.is_valid</code></a> first.
Parsing accepts either case and yields the lowercase canonical form, which is what the rest of the library produces — so a parsed ID compares equal (with <code>==</code>) to a freshly generated one of the same value.
Parameters:
- `s` — the string to validate and normalise
```ludic
program Parse {
entry {
let id = Uuid.parse("550E8400-E29B-41D4-A716-446655440000")
print(id[0..8]) # 550e8400 — lowercased
if Uuid.parse("nope") == Uuid.nil() { print(1) }
}
}
```

View file

@ -0,0 +1,26 @@
---
id: uuid-to_text
name: Uuid.to_text
category: uuid
kind: namespace-method
tokens: Uuid.to_text
sig: Uuid.to_text(id) -> string
tip: The canonical text form of a UUID.
order: 7
ns: Uuid
member: to_text
---
Returns the canonical 36-character text form of <code>id</code>. Because this library already represents every UUID as its canonical lowercase text, <code>to_text</code> is the identity function — it exists so intent is explicit at the point where a UUID is turned into a string for display, storage, or a wire payload, and so code stays correct if the internal representation ever changes to a packed 128-bit value.
Parameters:
- `id` — the UUID to render
```ludic
program ToText {
entry {
let id = Uuid.new()
print(len(Uuid.to_text(id))) # 36
}
}
```

View file

@ -0,0 +1,24 @@
---
id: uuid-v4
name: Uuid.v4
category: uuid
kind: namespace-method
tokens: Uuid.v4
sig: Uuid.v4() -> string
tip: Explicit alias for Uuid.new — a random (v4) UUID.
order: 2
ns: Uuid
member: v4
---
An explicit alias for <a href="uuid-new"><code>Uuid.new</code></a>: generates a **v4** (random) UUID in canonical lowercase text form. Use this spelling when the surrounding code also uses <a href="uuid-v7"><code>Uuid.v7</code></a> and naming both by version reads more clearly than <code>new</code> versus <code>new_v7</code>. The behaviour, entropy, and non-determinism caveat are identical to <code>Uuid.new</code>.
```ludic
program V4 {
entry {
let a = Uuid.v4()
let b = Uuid.v4()
if a != b { print(1) } # two draws differ
}
}
```

View file

@ -0,0 +1,23 @@
---
id: uuid-v7
name: Uuid.v7
category: uuid
kind: namespace-method
tokens: Uuid.v7
sig: Uuid.v7() -> string
tip: Explicit alias for Uuid.new_v7 — a time-ordered UUID.
order: 4
ns: Uuid
member: v7
---
An explicit alias for <a href="uuid-new_v7"><code>Uuid.new_v7</code></a>: generates a **v7** (time-ordered) UUID whose leading bits are a millisecond timestamp so the IDs sort by creation time. Use this spelling to sit symmetrically beside <a href="uuid-v4"><code>Uuid.v4</code></a>. Behaviour, timestamp resolution, and the determinism caveat are identical to <code>Uuid.new_v7</code>.
```ludic
program V7 {
entry {
let id = Uuid.v7()
if Uuid.is_valid(id) { print(1) }
}
}
```