Audit — P3.8.1 Parity Harness (R89 θ Wave 5)
§1. Surface inventory
P3.8.1 closes the Phase 3 verification loop. It mirrors κ P1.5.5 — a
fixed-corpus deterministic simulation harness that runs every shipped
θ module under a 4-scenario default corpus and produces a structured
report. Scenario 3 is the consensus.md §Worked example (4-node BFT
vote on event E), shipped as test data so future θ changes cannot drift
from the canonical spec example without breaking the harness.
The harness is not a runtime artifact (no MCP tools, no DB writes,
no I/O). It is a pure simulation library plus a Jest test driver. The
4-scenario corpus is data; the harness is a function from (Scenario,
seed) to ParityReport.
§2. Inputs the harness consumes
| Module | Imports used | Role in harness |
|---|---|---|
src/domains/consensus/messages.ts |
Vote, signMessage, nextLogical, buildEquivocationProof, resetLogicalForTesting |
Build signed votes per arbiter; build equivocation proofs |
src/domains/consensus/quorum.ts |
hasQuorum, quorumThreshold, voteGroupKey, detectDoubleVote |
Compute majority quorum group; detect SC4 double-vote |
src/domains/consensus/finality.ts |
FinalitySM, FinalityLevel |
Drive the finality FSM with the simulated votes |
src/domains/consensus/equivocation.ts |
applyEquivocationSlash, evidenceHashHex, EQUIVOCATION_DOMAIN |
Apply the SC4 slash and verify idempotency |
src/domains/reputation/schema.ts |
ReputationRow, ReputationHistoryRow |
Provide the attacker row + history Set scaffold |
The harness does not consume:
gossip-wire.ts,bloom-dedup.ts,adaptive-fanout.ts— network-layer concerns; the harness models a synchronous global view (all arbiters see all votes immediately).round-state.ts— the commit-reveal FSM is round-internal; the harness sidesteps the commit/reveal phases (every arbiter’s vote is “instantly revealed”) and directly drives the finality FSM with fully-signed votes.time-anchors.ts— Phase 4 epoch sealing is out of scope for SC1–SC4 (the 4 scenarios target QUORUM, not HARD or ABSOLUTE).vrf-stub.ts— no view-change in the 4 scenarios.tools.ts— MCP tools are read-only adapters; the harness operates on the in-memory primitives directly.
§3. Pattern source — κ P1.5.5
Mirror points from src/domains/rules/parity-harness.ts + its test:
- Default corpus is data, not code —
default-corpus.tsexportsSCENARIO_*constants + aDEFAULT_CORPUSfrozen tuple. - Pure functional surface —
runScenario(scenario, seed) → ParityReport, no I/O, no DB. - Determinism via seed — two runs with the same
seedproduce byte-identical reports undercanonicalizecomparison. - Performance budget — 10k synthetic events across all 4 scenarios in under 5 seconds.
- Self-scan — the harness body contains no forbidden determinism-breaking tokens (Math.*, Date.*, Math.random, etc.). A test asserts this via post-comment-strip regex pass over the source.
§4. Worked example — verbatim mapping
The spec at docs/3-world/physics/laws/consensus.md §Worked example
specifies n=4, quorum=3, nodes A/B/C/D, A/B/C honest, D Byzantine,
merkle_root = 0xab12… (3-of-4 majority), D divergent with 0xCAFE….
SCENARIO_3 IS this example. The harness:
- Builds 4 Ed25519 keypairs deterministically from
seed. - Builds 4 signed Votes — A/B/C on
merkle_root = 0xab12…padded to 32B, D onmerkle_root = 0xCAFE…padded to 32B. All share the samerule_version_hash. - Feeds them to
FinalitySMwith epoch =1n. - Asserts
FinalitySM.current() === 'QUORUM'(3-of-4 honest).
The “0xab12…” / “0xCAFE…” short prefixes in the spec are prefixes of
32-byte roots. The harness uses Buffer.alloc(32) with the prefix
written at offset 0 and zeros elsewhere — that satisfies the prefix
match and keeps the rest deterministic.
§5. Existing exports relied on
Vote shape (P3.1.1)
interface Vote {
readonly sender_id: string;
readonly round_id: bigint;
readonly merkle_root: Buffer; // 32 bytes
readonly rule_version_hash: Buffer; // 32 bytes
readonly vote_type: 'ACCEPT' | 'REJECT' | 'ABSTAIN';
readonly timestamp_logical: bigint;
readonly signature: Buffer; // 64 bytes (Ed25519)
}
FinalitySM (P3.2.1)
- Constructor:
new FinalitySM(round_id, n_arbiters, dispute_window_epochs?) receiveVote(vote: Vote, currentEpoch: bigint): voidcurrent(): FinalityLevel
SC1 (n=1): one vote ⇒ PENDING → SOFT → QUORUM in a single
receiveVote call (because quorumThreshold(1n) === 1n).
SC2 (n=4 all honest): four matching votes ⇒ first vote takes PENDING
→ SOFT, then on the third matching vote QUORUM is hit (threshold =
quorumThreshold(4n) = 3n).
SC3 (n=4, D divergent): A/B/C on 0xab12 + D on 0xCAFE. The
largest matching group is {A,B,C} size 3 ≥ 3 ⇒ QUORUM.
SC4 (n=4, D equivocator): A/B/C honest on 0xab12 + D signs
twice with different roots (0xab12 and 0xCAFE). The honest
group still reaches QUORUM; the slasher fires on the double-vote
proof.
Equivocation slash (P3.5.1)
applyEquivocationSlash({proof, attackerPubKey, current_epoch, attackerRow, history, alreadyApplied}): ApplySlashResult- Successful slash:
applied: true, evidence_hash_hex, next_row, history_event. Caller persists. - Re-submission of same proof:
applied: false, reason: 'duplicate'(idempotency via thealreadyApplied: Set<string>keyed onevidence_hash_hex).
The harness uses an in-memory Set<string> for alreadyApplied. SC4’s
report.slashings_applied counts successful slashes (applied === true).
Re-submitting the same proof produces applied: false, reason:
'duplicate', which the harness counts as 0 additional slashings
— total still 1.
§6. Determinism budget
Sources of non-determinism the harness eliminates:
| Source | Replacement |
|---|---|
randomBytes (Ed25519 key generation) |
Seeded HMAC-SHA512 derivation from seed. We derive each arbiter’s 32-byte seed from HMAC(seed_root, label), then pass through node:crypto.createPrivateKey({key: …, format: 'raw'}) for Ed25519. |
Date.now for performance test |
process.hrtime.bigint() is also forbidden by κ self-scan. Use the test driver’s local clock import only inside the test file, NOT the harness. |
Math.random for vote ordering |
Vote order is the scenario’s declared order — deterministic by construction. |
| Lamport clock leak across scenarios | resetLogicalForTesting() is called at the top of each runScenario call. |
process.hrtime.bigint() is a forbidden token by the κ scanner regex
\bprocess\.(?:hrtime|nextTick)\b. The performance assertion lives in
the test file (a *.test.ts), where forbidden tokens are not
scanned — only the harness’s own source body needs to be clean.
§7. Report shape
type ParityReport = {
scenario_id: string;
n: bigint;
rounds_executed: bigint;
finality_reached: FinalityLevel;
equivocation_proofs: EquivocationProof[];
slashings_applied: bigint;
determinism_check: {seed: bigint; second_run_identical: boolean};
};
Two-run determinism check happens inside runScenario itself — the
function runs the scenario twice and compares canonical-encoded report
bytes (with the determinism_check field stripped). The outer field
records the verdict.
§8. Forbidden by task
- No
Math.*,Date.*,Math.random,setTimeout,setInterval,setImmediate,process.hrtime,fetch,XMLHttpRequest,[native code],await,async function,await import, floating- point literals. - No new npm deps.
- No parallel scenario execution.
- No reads from disk in the harness body (the test file may read source files for self-scan; the harness itself does not).
- No mutations of
src/domains/consensus/*orsrc/domains/reputation/*.
§9. Out-of-scope
- Persisting
ParityReports — they are returned values, not stored rows. - Cross-scenario aggregate metrics — each scenario is independent.
- Parity vs an alternate consensus implementation — this is corpus parity, not implementation parity.
- HARD or ABSOLUTE finality scenarios — SC1–SC4 all target QUORUM.
- View-change / VRF leader rotation — no leader-failure scenarios.
§10. References
docs/guides/implementation/task-prompts/p3.1-theta-consensus.md§P3.8.1docs/3-world/physics/laws/consensus.md§Worked example (lines 92-120)src/__tests__/domains/rules/parity-harness.test.ts(κ pattern source)src/domains/rules/parity-harness.ts(κ implementation reference)src/domains/consensus/messages.ts,quorum.ts,finality.ts,equivocation.ts(the surface this harness drives)