docs: update ARCHITECTURE.md for v0.6.0 gene mutation system

This commit is contained in:
thakares committed 2026-05-29 15:55:33 +05:30
1 parent 4a64a57347
commit b2835f454f
1 file changed
+177 -34
+177 -34
View File
@@ -1,6 +1,6 @@
# ChronoSeal — Architecture # ChronoSeal — Architecture
> Note (v0.6.0): Synthetic Gene Mutation flow and mutation handshake updates are documented in [REFRACTORING-v0.6.0.md](REFRACTORING-v0.6.0.md) and [API.md](API.md). > Note (v0.6.0): Synthetic Gene Mutation flow and mutation handshake updates are documented in [REFRACTORING-v0.6.0.md](https://github.com/thakares/chronoseal-rs/blob/main/docs/REFRACTORING-v0.6.0.md) and [API.md](https://github.com/thakares/chronoseal-rs/blob/main/docs/API.md).
## Overview ## Overview
@@ -23,8 +23,7 @@ is stored in SQLite keyed on `session_id`. Every HTTP request is independently
verifiable. verifiable.
**Silent failure.** Validation failures never return an error status or an **Silent failure.** Validation failures never return an error status or an
error body. The server always responds `{"status":"ok"}` and simply omits error body. The server always responds `{"status":"ok"}` and simply omits `next_salt`. The client degrades gracefully. Attackers cannot enumerate
`next_salt`. The client degrades gracefully. Attackers cannot enumerate
validation rules by probing error responses. validation rules by probing error responses.
**Private key isolation.** The Ed25519 signing key is generated inside the **Private key isolation.** The Ed25519 signing key is generated inside the
@@ -41,19 +40,25 @@ activity, correct WASM execution, chain state synchronisation, and a valid
Ed25519 signature over a time-windowed payload. For an automated client, the Ed25519 signature over a time-windowed payload. For an automated client, the
synchronisation burden alone makes scaled operation expensive. synchronisation burden alone makes scaled operation expensive.
**Deterministic mutation parity (v0.6.0).** Each heartbeat additionally carries
a `mutation_step` and `gene_commitment` derived from a server-issued mutation
program. Server and WASM execute the same shared opcode engine
(`shared/src/vm_extensions.rs`), and the server rejects any heartbeat where
the recomputed commitment does not match the client-supplied value.
### High-Level Design ### High-Level Design
- **Core**: Rust + Axum (async web framework) - **Core**: Rust + Axum (async web framework)
- **Storage**: `db_type` selectable (`sqlite-in-memory`, `sqlite-in-disk`, `valkey` compatibility mode) - **Storage**: `db_type` selectable (`sqlite-in-memory`, `sqlite-in-disk`, `valkey` compatibility mode)
- **Client**: WASM + Rust (runs in browser for proof generation) - **Client**: WASM + Rust (runs in browser for proof generation)
- **Security Model**: Behavioral analysis + hash chaining + entropy scoring - **Security Model**: Behavioral analysis + hash chaining + entropy scoring + deterministic gene mutation parity
- **Deployment**: Static musl binary, systemd service, optional Docker - **Deployment**: Static musl binary, systemd service, optional Docker
### Key Components ### Key Components
- `shared/` — Types, constants, crypto primitives used by server and WASM - `shared/` — Types, constants, crypto primitives, gene model, and mutation engine used by server and WASM
- `server/` — Axum routes, session management, trust engine, rate limiting, cleanup tasks - `server/` — Axum routes, session management, trust engine, rate limiting, cleanup tasks
- `wasm/` — Client-side proof generation - `wasm/` — Client-side proof generation, mutation preview/commit lifecycle
- `frontend/` — Static assets served by the application - `frontend/` — Static assets served by the application
### Unix-Native Design Decisions ### Unix-Native Design Decisions
@@ -62,12 +67,11 @@ synchronisation burden alone makes scaled operation expensive.
- All state is either in-memory or in standard locations (`/run/`, `/var/log/`, `/etc/`) - All state is either in-memory or in standard locations (`/run/`, `/var/log/`, `/etc/`)
- Graceful shutdown and reload support via signals - Graceful shutdown and reload support via signals
- Logging designed for `journalctl` and structured parsing - Logging designed for `journalctl` and structured parsing
- Configuration will be fully runtime (no recompile needed) - Configuration is fully runtime (no recompile needed)
### Design Goal ### Design Goal
ChronoSeal should feel as natural to use as `nginx` or `redis-server` on a Linux system. ## ChronoSeal should feel as natural to use as `nginx` or `redis-server` on a Linux system.
---
## Component Map ## Component Map
@@ -89,7 +93,10 @@ ChronoSeal should feel as natural to use as `nginx` or `redis-server` on a Linux
│ │ ├ generate_keypair() │ │ │ │ ├ generate_keypair() │ │
│ │ ├ sign_message() │ │ │ │ ├ sign_message() │ │
│ │ ├ compute_next_hash() │ │ │ │ ├ compute_next_hash() │ │
│ │ └ run_program() │ │ │ │ ├ run_program() │ │
│ │ vm_extensions.rs │ │
│ │ ├ preview_mutation() │ │
│ │ └ commit_mutation() │ │
│ └──────────────────────────────┘ │ │ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────┘
│ HTTPS │ HTTPS
@@ -103,6 +110,7 @@ ChronoSeal should feel as natural to use as `nginx` or `redis-server` on a Linux
│ session.rs │ │ session.rs │
│ ├ create_session() │ │ ├ create_session() │
│ └ verify_heartbeat() │ │ └ verify_heartbeat() │
│ └ validate_mutation_parity() [v0.6.0] │
│ │ │ │ │ │
│ ┌──────────┼──────────────┐ │ │ ┌──────────┼──────────────┐ │
│ ▼ ▼ ▼ │ │ ▼ ▼ ▼ │
@@ -112,9 +120,11 @@ ChronoSeal should feel as natural to use as `nginx` or `redis-server` on a Linux
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ shared::hashing (Blake3 hash chain) │ │ shared::hashing (Blake3 hash chain) │
│ shared::gene (gene model + commitment) [v0.6.0] │
│ shared::vm_extensions (mutation opcodes) [v0.6.0] │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ storage.rs (in-memory SQLite) │ │ storage.rs (SQLite: in-memory / in-disk / valkey) │
│ │ │ │
│ ratelimit.rs cleanup.rs vm.rs middleware.rs │ │ ratelimit.rs cleanup.rs vm.rs middleware.rs │
└─────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────┘
@@ -137,14 +147,20 @@ Client Server
│ │ salt₀ = rand::random::<[u8;16]>() │ │ salt₀ = rand::random::<[u8;16]>()
│ │ H(0) = Blake3(session_id║pub_key║salt₀) │ │ H(0) = Blake3(session_id║pub_key║salt₀)
│ │ opcodes = generate_random_program(8..=16) │ │ opcodes = generate_random_program(8..=16)
│ │ gene = initial gene buffer [v0.6.0]
│ │ mutation_order = generate_mutation_program() [v0.6.0]
│ │ INSERT INTO sessions … │ │ INSERT INTO sessions …
│ │ │ │
│◄── { session_id, salt, opcodes_b64, │ │◄── { session_id, salt, opcodes_b64, │
│ initial_hash, expires_at } ───────┤ │ initial_hash, expires_at, │
│ mutation_step, │ [v0.6.0]
│ mutation_order_b64 } ─────────────┤ [v0.6.0]
│ │ │ │
│ prevHash = initial_hash │ │ prevHash = initial_hash │
│ currentSalt = salt │ │ currentSalt = salt │
│ opcodesB64 = opcodes_b64 │ │ opcodesB64 = opcodes_b64 │
│ mutationStep = mutation_step │ [v0.6.0]
│ mutationOrderB64 = mutation_order_b64 │ [v0.6.0]
``` ```
### 2. Heartbeat — `POST /hb` ### 2. Heartbeat — `POST /hb`
@@ -155,20 +171,24 @@ Fired every 12–25 seconds with uniform random jitter.
Client Server Client Server
│ │ │ │
│ stackState = run_program(opcodesB64) │ │ stackState = run_program(opcodesB64) │
│ commitment = preview_mutation( │ [v0.6.0]
│ mutationOrderB64, mutationStep) │
│ events = collectEntropy(lastTime)│ │ events = collectEntropy(lastTime)│
│ ts = Date.now() │ │ ts = Date.now() │
│ │ │ │
│ signable = { │ │ signable = { │
│ entropyData, fingerprint, │ ← keys sorted alphabetically │ entropyData, fingerprint, │ ← keys sorted alphabetically
│ prevHash, sessionId, │ │ prevHash, sessionId, │
│ stackState, timestamp │ │ stackState, timestamp, │
│ mutation_step, gene_commitment │ [v0.6.0]
│ } │ │ } │
│ sig = sign_message( │ │ sig = sign_message( │
│ JSON.stringify(signable, keys.sort))│ │ JSON.stringify(signable, keys.sort))│
│ │ │ │
├─── { session_id, prev_hash, timestamp, │ ├─── { session_id, prev_hash, timestamp, │
│ entropy_data, stack_state, │ │ entropy_data, stack_state, │
│ fingerprint, signature } ────────►│ │ fingerprint, signature, │
│ mutation_step, gene_commitment }─►│ [v0.6.0]
│ │ 1. Rate limit check │ │ 1. Rate limit check
│ │ 2. Lookup session, check expiry │ │ 2. Lookup session, check expiry
│ │ 3. Verify Ed25519 signature │ │ 3. Verify Ed25519 signature
@@ -176,13 +196,20 @@ Client Server
│ │ 5. Validate timestamp window ±30s │ │ 5. Validate timestamp window ±30s
│ │ 6. Validate mouse behavior │ │ 6. Validate mouse behavior
│ │ 7. Validate fingerprint signals │ │ 7. Validate fingerprint signals
│ │ 8. Compute H(n), rotate salt │ │ 8. Validate mutation step parity [v0.6.0]
│ │ 9. UPDATE sessions … │ │ 9. Validate gene commitment [v0.6.0]
│ │ 10. Compute H(n), rotate salt
│ │ 11. Advance gene state [v0.6.0]
│ │ 12. UPDATE sessions …
│ │ │ │
│◄── { status: "ok", next_salt } ────────┤ │◄── { status: "ok", next_salt, │
│ next_mutation_step, │ [v0.6.0]
│ next_mutation_order_b64 } ────────┤ [v0.6.0]
│ │ │ │
│ sentSalt = currentSalt ◄── captured BEFORE rotation │ sentSalt = currentSalt ◄── captured BEFORE rotation
│ currentSalt = next_salt │ │ currentSalt = next_salt │
│ mutationStep = next_mutation_step │ [v0.6.0]
│ mutationOrderB64 = next_mutation_order_b64 [v0.6.0]
│ prevHash = compute_next_hash( │ │ prevHash = compute_next_hash( │
│ prevHash, ts, entropy, │ │ prevHash, ts, entropy, │
│ stackState, sentSalt) │ │ stackState, sentSalt) │
@@ -190,8 +217,7 @@ Client Server
### 3. Failure Path ### 3. Failure Path
On any validation failure the server returns `{"status":"ok"}` with no On any validation failure the server returns `{"status":"ok"}` with no `next_salt`. The client logs a warning and continues scheduling heartbeats.
`next_salt`. The client logs a warning and continues scheduling heartbeats.
The chain is broken — subsequent heartbeats will also fail silently. The chain is broken — subsequent heartbeats will also fail silently.
No error is surfaced to the page or its visitors. No error is surfaced to the page or its visitors.
@@ -224,15 +250,36 @@ H(n) = Blake3(
Salt rotation means an attacker who intercepts a heartbeat cannot compute Salt rotation means an attacker who intercepts a heartbeat cannot compute
future chain links without also intercepting every subsequent server response. future chain links without also intercepting every subsequent server response.
### Gene Commitment (v0.6.0)
A domain-separated BLAKE3 commitment binds both the gene buffer and the sorted
environment records into a single 32-byte value that is included in the signed
heartbeat payload and validated server-side:
```
gene_commitment = BLAKE3(
"chronoseal/gene/v1" ← domain separator
║ gene_bytes ← Vec<u8> gene buffer
║ for each (symbol, qty) sorted by symbol:
symbol_u16_le ║ qty_u32_le
)
```
The server recomputes the candidate gene state from its authoritative
`pending_mutation` program and rejects any heartbeat where
`recomputed_commitment != client_gene_commitment`.
### Canonical Signing Payload ### Canonical Signing Payload
The signed message is a JSON object with top-level keys sorted alphabetically, The signed message is a JSON object with top-level keys sorted alphabetically,
serialised with no extra whitespace: serialised with no extra whitespace:
```json ```
{ {
"entropyData": { "events": [{"t":…,"x":…,"y":…}] }, "entropyData": { "events": [{"t":…,"x":…,"y":…}] },
"fingerprint": { "aspectRatio":"…","devicePixelRatio":"…","hardwareConcurrency":… }, "fingerprint": { "aspectRatio":"…","devicePixelRatio":"…","hardwareConcurrency":… },
"gene_commitment": "hex…",
"mutation_step": N,
"prevHash": "hex…", "prevHash": "hex…",
"sessionId": "hex…", "sessionId": "hex…",
"stackState": { "ip":…,"stack":[…] }, "stackState": { "ip":…,"stack":[…] },
@@ -247,8 +294,8 @@ key order difference, or whitespace difference causes a signature failure.
### Hashing Algorithm ### Hashing Algorithm
Blake3 is used throughout: hash chain links, entropy data digest, stack state Blake3 is used throughout: hash chain links, entropy data digest, stack state
digest, and the VM HASH opcode. Blake3 is chosen for speed in WASM, digest, gene commitment, and the VM HASH opcode. Blake3 is chosen for speed
resistance to length-extension attacks, and a clean Rust API. in WASM, resistance to length-extension attacks, and a clean Rust API.
--- ---
@@ -259,10 +306,10 @@ on every heartbeat and includes the resulting `StackState { stack, ip }` in
the signed payload. This ensures each heartbeat carries unique, verifiable the signed payload. This ensures each heartbeat carries unique, verifiable
computation without additional round-trips. computation without additional round-trips.
### Instruction Set ### Core Instruction Set
| Opcode | Mnemonic | Operand | Stack effect | Description | | Opcode | Mnemonic | Operand | Stack effect | Description |
|--------|----------|---------------|--------------|-------------| | ------ | -------- | ----------- | ------------ | -------------------------------------- |
| `0x00` | PUSH | u32 (4B LE) | +1 | Push literal | | `0x00` | PUSH | u32 (4B LE) | +1 | Push literal |
| `0x01` | ADD | — | −1 | `a + b` wrapping | | `0x01` | ADD | — | −1 | `a + b` wrapping |
| `0x02` | SUB | — | −1 | `a - b` wrapping | | `0x02` | SUB | — | −1 | `a - b` wrapping |
@@ -277,6 +324,33 @@ computation without additional round-trips.
The generator ensures ≥ 2 items on the stack before any binary opcode. The generator ensures ≥ 2 items on the stack before any binary opcode.
NOT (0x08) does not change depth. HASH resets depth to 1. NOT (0x08) does not change depth. HASH resets depth to 1.
### Mutation Opcodes (v0.6.0)
The gene mutation extension operates on a separate `Vec<u8>` gene buffer and a
bounded environment map `Vec<(u16 symbol, u32 quantity)>`. These opcodes are
defined in `shared/src/vm_extensions.rs` and executed identically by both
server and WASM to guarantee deterministic parity.
| Opcode | Mnemonic | Effect |
| ------ | ------------------ | ------------------------------------------------------------- |
| `0x23` | GENE_LOAD | Push `gene[idx]` onto the stack |
| `0x24` | GENE_STORE | Pop stack top and store at `gene[idx]` |
| `0x25` | MUTATE_POINT | Apply wrapping byte delta at index |
| `0x26` | INSERT | Insert popped byte at index |
| `0x27` | DELETE | Delete byte at index and push the removed value |
| `0x28` | TRANSCRIBE | Push deterministic transcription hash of current gene state |
| `0x29` | APPLY_MUTAGEN | Mix environment symbol quantity into gene byte at index |
| `0x2A` | FINALIZE_GENE_HASH | Push commitment-derived `u32` onto the stack |
| `0x2B` | CONSUME | Pop amount, subtract from environment symbol quantity |
| `0x2C` | PRODUCE | Pop amount, add to environment symbol quantity |
**Constraints enforced at runtime:**
- Mutation program length capped at `MAX_MUTATION_PROGRAM_BYTES`
- Environment record count capped at `MAX_ENV_RECORDS`
- Environment records validated for sortedness, uniqueness, non-zero quantity
- Stack underflow and unknown opcodes cause deterministic, symmetric failures on both server and WASM paths
--- ---
## Behavioral Validation ## Behavioral Validation
@@ -287,7 +361,7 @@ Every heartbeat includes the mouse events collected since the previous
heartbeat. Server checks: heartbeat. Server checks:
| Check | Threshold | | Check | Threshold |
|---|---| | --------------------------- | ------------------------------------ |
| Minimum event count | ≥ 3 | | Minimum event count | ≥ 3 |
| Minimum cumulative distance | ≥ 10 px | | Minimum cumulative distance | ≥ 10 px |
| Maximum average speed | ≤ 2.0 px/ms (distance / elapsed ms) | | Maximum average speed | ≤ 2.0 px/ms (distance / elapsed ms) |
@@ -296,7 +370,7 @@ heartbeat. Server checks:
### Browser Fingerprint ### Browser Fingerprint
| Signal | Valid range | | Signal | Valid range |
|---|---| | ------------------------------ | ------------- |
| `aspectRatio` (width / height) | 0.5 – 3.0 | | `aspectRatio` (width / height) | 0.5 – 3.0 |
| `devicePixelRatio` | 0 < dpr ≤ 5.0 | | `devicePixelRatio` | 0 < dpr ≤ 5.0 |
| `hardwareConcurrency` | ≥ 1 | | `hardwareConcurrency` | ≥ 1 |
@@ -313,6 +387,10 @@ Rate-limited responses are indistinguishable from validation failures.
## SQLite Schema ## SQLite Schema
The schema is extended in v0.6.0 with four new columns to persist per-session
gene mutation state. Migration is additive — columns are created when missing,
preserving compatibility with existing deployments.
```sql ```sql
CREATE TABLE IF NOT EXISTS sessions ( CREATE TABLE IF NOT EXISTS sessions (
session_id TEXT PRIMARY KEY, session_id TEXT PRIMARY KEY,
@@ -322,12 +400,71 @@ CREATE TABLE IF NOT EXISTS sessions (
chain_length INTEGER NOT NULL DEFAULT 1, chain_length INTEGER NOT NULL DEFAULT 1,
created_at INTEGER NOT NULL, -- Unix ms created_at INTEGER NOT NULL, -- Unix ms
last_seen INTEGER NOT NULL, -- Unix ms last_seen INTEGER NOT NULL, -- Unix ms
expires_at INTEGER NOT NULL -- Unix ms expires_at INTEGER NOT NULL, -- Unix ms
-- v0.6.0: gene mutation state
gene BLOB NOT NULL DEFAULT X'', -- Vec<u8> gene buffer
environment BLOB NOT NULL DEFAULT X'', -- Vec<(u16, u32)> env records
pending_mutation BLOB NOT NULL DEFAULT X'', -- server-issued mutation program
pending_mutation_step INTEGER NOT NULL DEFAULT 0 -- current mutation step counter
); );
``` ```
In-memory SQLite — all sessions lost on server restart by design. In-memory SQLite (`sqlite-in-memory`) — all sessions lost on server restart by
Clients re-initialise transparently on the next page load. design. Clients re-initialise transparently on the next page load. Use
`sqlite-in-disk` for persistent sessions across restarts.
---
## Storage Backend (v0.6.0)
ChronoSeal v0.6.0 introduces selectable database backends via the `db_type`
configuration option. The default remains in-memory to preserve ephemeral
session behavior.
### Backend Options
| `db_type` | Behavior |
| ------------------ | -------------------------------------------------------------------- |
| `sqlite-in-memory` | Default. All sessions ephemeral; lost on restart. Zero disk I/O. |
| `sqlite-in-disk` | Persistent sessions. Requires `db_path`. Survives restarts. |
| `valkey` | Compatibility mode. Currently falls back to in-memory. CLI contract preserved. |
### Configuration
**Config file** (`/etc/chronoseal/config.toml`):
```toml
db_type = "sqlite-in-disk"
db_path = "/var/lib/chronoseal/chronoseal.sqlite"
```
**Environment variable**:
```bash
CHRONOSEAL_DB_TYPE=sqlite-in-disk
CHRONOSEAL_DB_PATH=/var/lib/chronoseal/chronoseal.sqlite
```
**CLI flag**:
```bash
chronoseal run --db-type sqlite-in-disk --db-path /var/lib/chronoseal/chronoseal.sqlite
```
**Inspect active backend**:
```bash
chronoseal db-type --format text
```
### Precedence
```
CLI flags > CHRONOSEAL_* environment variables > config file > defaults
```
### Migration Notes
- Schema migration is additive; new columns are created when missing on startup.
- Switching from `sqlite-in-memory` to `sqlite-in-disk` requires no code changes — only config.
- `valkey` is available in the CLI contract today; full backend support is tracked for a future release.
- Existing deployments without the v0.6.0 mutation columns will have those columns added automatically on first start.
--- ---
@@ -336,20 +473,23 @@ Clients re-initialise transparently on the next page load.
### In Scope ### In Scope
| Threat | Mitigation | | Threat | Mitigation |
|---|---| | ------------------------------------------ | --------------------------------------------------------------- |
| Playwright / Puppeteer / Selenium | Mouse entropy + behavioral validation | | Playwright / Puppeteer / Selenium | Mouse entropy + behavioral validation |
| Puppeteer Stealth, undetected-chromedriver | Signature over VM execution state | | Puppeteer Stealth, undetected-chromedriver | Signature over VM execution state |
| Heartbeat replay | Hash chain + ±30s timestamp window | | Heartbeat replay | Hash chain + ±30s timestamp window |
| Mutation replay | mutation_step + gene_commitment parity check (v0.6.0) |
| Mutation tampering | Server recomputes candidate gene from authoritative program (v0.6.0) |
| Signature forgery | Private key isolated in WASM memory | | Signature forgery | Private key isolated in WASM memory |
| Parallel session sharing | Each session bound to a unique keypair | | Parallel session sharing | Each session bound to a unique keypair |
| Brute-forced session IDs | 256-bit random entropy | | Brute-forced session IDs | 256-bit random entropy |
| Flooding with fake session IDs | Rate limiter + periodic HashMap eviction | | Flooding with fake session IDs | Rate limiter + periodic HashMap eviction |
| Traffic analysis | Uniform `{"status":"ok"}` on all failure paths | | Traffic analysis | Uniform `{"status":"ok"}` on all failure paths |
| Malformed mutation programs | Strict parsing, length caps, underflow/unknown-opcode errors |
### Out of Scope ### Out of Scope
| Threat | Reason | | Threat | Reason |
|---|---| | ---------------------------------- | ---------------------------------------- |
| Real browser with real human input | Indistinguishable from a legitimate user | | Real browser with real human input | Indistinguishable from a legitimate user |
| WASM reverse engineering | Obfuscation is not a security primitive | | WASM reverse engineering | Obfuscation is not a security primitive |
| Server-side compromise | Outside the scope of client attestation | | Server-side compromise | Outside the scope of client attestation |
@@ -362,22 +502,25 @@ cryptographic proof of humanity and does not claim to be.
## Module Reference ## Module Reference
| Path | Purpose | | Path | Purpose |
|---|---| | ------------------------------------- | -------------------------------------------------------------------- |
| `shared/src/protocol.rs` | Shared types: `InitRequest`, `HeartbeatRequest`, `StackState`, … | | `shared/src/protocol.rs` | Shared types: `InitRequest`, `HeartbeatRequest`, `StackState`, … |
| `shared/src/hashing.rs` | `initial_hash`, `next_chain_hash`, `hash_stack` | | `shared/src/hashing.rs` | `initial_hash`, `next_chain_hash`, `hash_stack` |
| `shared/src/constants.rs` | All tunable parameters | | `shared/src/constants.rs` | All tunable parameters |
| `shared/src/gene.rs` | Gene model, deterministic commitment (`chronoseal/gene/v1`) [v0.6.0] |
| `shared/src/vm_extensions.rs` | Mutation opcode set, shared engine for server/WASM parity [v0.6.0] |
| `server/src/routes/init.rs` | `POST /init` handler | | `server/src/routes/init.rs` | `POST /init` handler |
| `server/src/routes/heartbeat.rs` | `POST /hb` handler | | `server/src/routes/heartbeat.rs` | `POST /hb` handler |
| `server/src/session.rs` | `create_session`, `verify_heartbeat` | | `server/src/session.rs` | `create_session`, `verify_heartbeat`, `validate_mutation_parity` |
| `server/src/crypto.rs` | `verify_signature` — BTreeMap canonical JSON | | `server/src/crypto.rs` | `verify_signature` — BTreeMap canonical JSON |
| `server/src/trust.rs` | `validate_mouse` — speed, distance, pauses | | `server/src/trust.rs` | `validate_mouse` — speed, distance, pauses |
| `server/src/fingerprint.rs` | `validate` — aspect ratio, DPR, HW concurrency | | `server/src/fingerprint.rs` | `validate` — aspect ratio, DPR, HW concurrency |
| `server/src/vm.rs` | `generate_random_program` | | `server/src/vm.rs` | `generate_random_program` |
| `server/src/ratelimit.rs` | `RateLimiter::check`, `evict_stale` | | `server/src/ratelimit.rs` | `RateLimiter::check`, `evict_stale` |
| `server/src/cleanup.rs` | Background loop: expire sessions + evict rate limiter | | `server/src/cleanup.rs` | Background loop: expire sessions + evict rate limiter |
| `server/src/storage.rs` | SQLite init, `current_time_ms` | | `server/src/storage.rs` | SQLite init, backend selection, `current_time_ms` |
| `wasm/src/crypto.rs` | `generate_keypair`, `sign_message`, `compute_next_hash` | | `wasm/src/crypto.rs` | `generate_keypair`, `sign_message`, `compute_next_hash` |
| `wasm/src/vm.rs` | `run_program` — stack machine executor | | `wasm/src/vm.rs` | `run_program` — stack machine executor |
| `wasm/src/vm_extensions.rs` | `preview_mutation`, `commit_mutation` [v0.6.0] |
| `frontend/heartbeat.js` | Session init, heartbeat loop, chain advancement | | `frontend/heartbeat.js` | Session init, heartbeat loop, chain advancement |
| `frontend/entropy.js` | Mouse event ring buffer, `collectEntropy` | | `frontend/entropy.js` | Mouse event ring buffer, `collectEntropy` |
| `frontend/transport.js` | `sendRequest` fetch wrapper | | `frontend/transport.js` | `sendRequest` fetch wrapper |