R89.C — θ Phase 3 Staging — Implementation Packet
Step 3 of the 5-step chain for β task
4f718f92-12d7-4176-a3c6-3682ba27aef9.Audit (§Step 1) inventoried the spec surface and locked the 13-entry shape. Contract (§Step 2) locked the per-entry behavioural shape and the eight invariants the file must encode. This packet (§Step 3) plans the writing: entry-by-entry, gives the title, the worktree slug, the Depends-on / Effort, and the 1-line description for each entry. Step 4 (the prompt file itself) then fills in the per-entry body.
1. Round + base + writeback expectations
- Round: R89 Phase A (T0 mandate: autonomous Phase 2+3 execution)
- Sub-round letter: C (R89.A = λ Phase 2 kickoff; R89.B = TBD; R89.C = θ Phase 3 staging)
- Base SHA:
fab4bf57(main; R88.B close) - Branch:
feature/r89-c-theta-phase-3-staging - Worktree:
.worktrees/claude/r89-c-theta-phase-3-staging - β task ID:
4f718f92-12d7-4176-a3c6-3682ba27aef9 - Test gates:
npm run build && npm run lint && npm test— all three must pass on the unchanged Phase 0 + κ tree (nosrc/deltas)
2. Entry-by-entry packet
13 entries in the order they will appear in the prompt file. Slugs follow
kappa convention (p3-x-y-<short-name>). Each row notes the canonical
mapping back to task-breakdown.md.
| # | Title | Worktree slug | Effort | Depends on | Wave | Canonical map |
|---|---|---|---|---|---|---|
| 1 | P3.1.1 — Vote Message Types + Canonical Wire | p3-1-1-vote-messages |
M | Phase 2 λ seal; κ P1.5.4 canonical (shipped) | 1 | P3.1.1 |
| 2 | P3.1.2 — Quorum Computation | p3-1-2-quorum |
M | P3.1.1 | 2 | P3.1.2 |
| 3 | P3.1.3 — Round / View State Machine | p3-1-3-round-state-machine |
L | P3.1.2, P3.6.1 (VRF stub for leader selection) | 3 | P3.1.3 (state-machine half) |
| 4 | P3.2.1 — Finality State Machine (5 levels) | p3-2-1-finality-sm |
L | P3.1.2 | 3 | P3.2.1 |
| 5 | P3.3.1 — Gossip Protocol — IHAVE/IWANT Wire | p3-3-1-gossip-ihave-iwant |
M | P3.1.1 | 2 | P3.3.1 (wire half) |
| 6 | P3.3.2 — Gossip — Bloom Filter Dedup | p3-3-2-bloom-dedup |
M | P3.3.1 | 4 | P3.3.1 (dedup half) |
| 7 | P3.3.3 — Gossip — Adaptive Fanout | p3-3-3-adaptive-fanout |
S | P3.3.1 | 4 | P3.3.1 (fanout half) |
| 8 | P3.4.1 — Signed Time Anchors (STA) | p3-4-1-time-anchors |
M | P3.1.1, λ P2.1.1 | 2 | P3.4.1 |
| 9 | P3.5.1 — Equivocation Detection + Idempotent Slashing | p3-5-1-equivocation |
M | P3.1.2, λ P2.2.2 | 3 | P3.5.1 |
| 10 | P3.6.1 — VRF Stub (Leader Election) | p3-6-1-vrf-stub |
M | P3.1.1; ADR-002 (PROPOSED — ships stub) | 3 | (new) |
| 11 | P3.7.1 — θ MCP Tool Surface | p3-7-1-mcp-tools |
M | P3.1.2, P3.2.1, P3.4.1 | 4 | (new) |
| 12 | P3.8.1 — Test Corpus + Parity Harness | p3-8-1-parity-harness |
L | P3.1.2, P3.2.1, P3.5.1 | 5 | (new) |
| 13 | P3.9.1 — Fork Trigger Hook (ι handoff stub) | p3-9-1-fork-hook |
S | P3.1.2 | 5 | (new) |
Granularity total: 13. Inside contract §7’s [12, 15] band. ✓
3. Wave gating rationale
Wave 1 (post-Phase-2 λ seal)
- P3.1.1 — Solo. Every other slice consumes its message types and canonical wire shape. No useful concurrent work exists until this lands.
Wave 2 (post-P3.1.1, 3-parallel)
After P3.1.1 lands, three independent surfaces can fan out:
- P3.1.2 — Quorum computation. Pure-function module; depends only on P3.1.1’s message types.
- P3.3.1 — Gossip wire layer (IHAVE/IWANT). Independent of voting.
- P3.4.1 — Time Anchors. Independent of voting; needs only P3.1.1 sigs.
This is the maximum-parallelism wave. All three are scoped, testable in isolation, and have no inter-slice cross-references.
Wave 3 (post-P3.1.2, 4-parallel)
This is one above the 3-parallel safety ceiling. The justification: all four sub-tasks are state-machine work with hard dependencies on P3.1.2 but no inter-dependency. They were each Phase-1-κ-grade complexity slices (L or M), so each takes ~1 day. The R87 κ Wave 7 ran 3-parallel; this stretches to 4 because the slices are even more isolated (different files, no shared type-base modifications beyond what P3.1.2 stabilizes).
- P3.1.3 — Round / View state machine. Uses VRF stub (P3.6.1) for leader selection. The VRF stub itself is in the same wave; the executor can mock the VRF result during development and import the real stub at PR-finalization.
- P3.2.1 — Finality SM. Uses quorum from P3.1.2.
- P3.5.1 — Equivocation. Uses P3.1.2 vote types + λ P2.2.2 penalty hook.
- P3.6.1 — VRF stub. Independent module; HMAC-SHA256 stub per ADR-002 Option A.
If Wave 3 proves too wide, PM should drop P3.6.1 (the VRF stub) into a post-Wave-3 Wave 3.5 — its only consumer in this wave is P3.1.3, which can proceed with a mocked VRF until Wave 3.5 lands.
Wave 4 (post-P3.3.1, 3-parallel)
After the gossip wire lands, the gossip-specific extensions go parallel:
- P3.3.2 — Bloom filter dedup. Self-contained module.
- P3.3.3 — Adaptive fanout. Self-contained module.
- P3.7.1 — MCP tool surface. Depends on P3.1.2, P3.2.1, P3.4.1 which all land in Waves 2–3. Independent of gossip-extension work.
Wave 5 (closer)
- P3.8.1 — Test corpus + parity harness. Tests every preceding slice. Mirrors P1.5.5 in κ.
- P3.9.1 — Fork trigger hook. ι Phase 5 handoff stub. Tiny.
4. ADR-status handling
4.1 ADR-002 (VRF) — PROPOSED
The prompt file ships P3.6.1 as a stub per ADR-002 Option A (HMAC-SHA256 internal implementation). Acceptance criteria for P3.6.1 are deliberately stub-shaped:
vrf_eval(seed: bigint, input: bigint): {output: bigint, proof: Buffer}- Output deterministic; verifiable via
vrf_verify(seed, input, output, proof) - HMAC-SHA256 internals (NOT RFC 9381 ECVRF)
- Interface designed for swap-in: a future Wave 6 or Phase 1.5 task can
replace the HMAC core with
@noble/curvesEd25519 without changing the public API - Documentation comment cites ADR-002 and warns “NOT externally verifiable”
This is the same shape δ took at P0.5.1/P0.5.2 per ADR-005 §Decision.
4.2 ADR-003 (BFT library) — PROPOSED
The state-machine slices (P3.1, P3.2, P3.5) are network-transport-agnostic; they ship UNCONDITIONALLY (Option C “spike” content). The gossip-transport slices (P3.3.x) have the following disclosure embedded in their entries:
> **GATE:** This slice ships gossip-transport code. ADR-003 is PROPOSED at
> R89.C staging. Executor should confirm with PM that ADR-003 has been
> Accepted (or that Option C-spike is the active strategy) BEFORE picking
> up this entry. If ADR-003 selects Option B (libp2p), the file paths and
> dependency footprint in this entry MAY change; re-issue the prompt with
> a revised packet.
This delivers the ADR-003 disclosure where it bites — in the gossip slices themselves — not just in the file intro.
5. λ dependency handling
The prompt file’s intro publishes:
> **Phase 2 λ Reputation is in flight at R89.C staging time.** ...
Two specific λ outputs are consumed:
- P2.1.1 reputation schema — consumed by P3.4.1 (STA needs “top N arbiters by arbitration reputation”). Without P2.1.1 the entry’s “Eligible publishers” acceptance criterion cannot land.
- P2.2.2 penalties — consumed by P3.5.1 (equivocation slashes the arbitration domain by 8000bps via λ’s penalty surface). Without P2.2.2 the slashing acceptance criterion cannot land.
The prompt file enforces this by:
- Naming both λ deps explicitly in the affected entries’ “Depends on” line
- Repeating the λ-in-flight disclosure inside each affected entry’s body
- Recommending the executor delay dispatch of those entries until Phase 2 seals
6. Single-arbiter compatibility
Per contract §5, two entries (P3.1.2 quorum, P3.2.1 finality) embed the single-arbiter compatibility clause in their acceptance criteria. The implementation pattern:
function quorumThreshold(n: bigint): bigint {
return (n * 2n) / 3n + 1n; // n=1 → 1, n=4 → 3, n=7 → 5, n=10 → 7
}
// Phase 0 deployment: n = 1 always; quorum_threshold(1n) = 1n; trivial.
// Phase 3+ deployment: n > 1; real BFT semantics activate.
The acceptance criterion is a unit test:
test("quorum is 1 for single-arbiter", () => {
expect(quorumThreshold(1n)).toBe(1n);
});
This protects the consensus.md §Phase 0 posture promise: “trivially finalized because n = 1”.
7. Per-entry body sketches (Step 4 inputs)
Below are the 1–2 line content sketches for each of the 13 entries. Step 4 will expand each into ≥80 lines per the contract.
Entry 1: §P3.1.1 — Vote Message Types + Canonical Wire
Defines Vote, Commit, Reveal, ViewChange, EquivocationProof message
types. Each carries the 3-tuple (round_id, merkle_root, rule_version_hash)
plus Ed25519 signature. Canonical JSON serialization (deterministic key
order, no whitespace) feeds the hash inputs. Tests: serialize 5 messages,
verify hash determinism across 1000 iterations, verify signatures roundtrip.
Entry 2: §P3.1.2 — Quorum Computation
Pure functions: quorumThreshold(n), maxFaulty(n), hasQuorum(votes, n),
intersect(quorumA, quorumB) (honest-majority overlap property). Worked
table from consensus.md §Quorum math is an acceptance fixture. Tests:
property test over n ∈ [1, 100]; single-arbiter clause.
Entry 3: §P3.1.3 — Round / View State Machine
Implements the commit-reveal protocol. Phase A commit, Phase B reveal, Phase
C verify+aggregate. Timer constants from consensus.md (T_round=30s,
T_commit_phase=10s, T_reveal_phase=10s, T_timeout=60s). View-change
triggered on timeout | equivocation_observed | malformed_proposal. VRF
called for next-leader selection. Emits VIEW_CHANGE_ACCEPTED into ζ.
Entry 4: §P3.2.1 — Finality State Machine (5 levels)
PENDING → SOFT → QUORUM → HARD → ABSOLUTE. Monotonic. Dispute window 100 epochs (per task-breakdown.md). HARD requires two consecutive QUORUM rounds without conflicting reveal. ABSOLUTE on epoch seal. No external side effects before HARD. Single-arbiter clause: n=1 reaches QUORUM trivially.
Entry 5: §P3.3.1 — Gossip Protocol — IHAVE/IWANT Wire
Wire shapes IHAVE, IWANT. Triple-anchor validation: rule_version_hash,
state_root continuity, fork_id all must match before IWANT is issued. ADR-003
gate disclosure inside the entry.
Entry 6: §P3.3.2 — Gossip — Bloom Filter Dedup
Bloom filter sized via m = -n * ln(p) / (ln(2))². k = (m/n) * ln(2) ≈ 7
hashes per insert / query. False positive rate < 1%. Fresh filter per round.
Entry 7: §P3.3.3 — Gossip — Adaptive Fanout
fanout = max(3, min(10, 15 − connectivity_score)). Recomputed every 5
epochs from IHAVE/IWANT success metrics. Worked table from s08 is a fixture.
Entry 8: §P3.4.1 — Signed Time Anchors (STA)
Eligible publishers: top N arbiters by λ.reputation.arbitration (λ dep).
Anchor format {publisher, timestamp_ms, epoch, signature}. Median over
last K epochs. Drift detection: |local - median| > 30000ms. Monotonicity
- replay protection (epoch < current - 10 rejected). λ-in-flight disclosure.
Entry 9: §P3.5.1 — Equivocation Detection + Idempotent Slashing
Detects double-signing on same (round_id, finality_level) tuple by same
arbiter. Generates proof per consensus.md §Equivocation proof structure.
Verifies BOTH signatures. Calls λ.applyPenalty(arbiter, DAMAGE_FRAUD=8000bps,
domain=”arbitration”, event_id=proof_hash). Idempotent: proof_hash dedup.
Entry 10: §P3.6.1 — VRF Stub (Leader Election)
HMAC-SHA256(seed || input) → output. verify(seed, input, output) recomputes
HMAC. NOT externally verifiable; comment cites ADR-002 Option A. Interface
designed for swap to @noble/curves ECVRF without API change. Tests:
determinism (10k inputs same seed → 10k identical outputs); collision
property (different seeds → different outputs).
Entry 11: §P3.7.1 — θ MCP Tool Surface
Registers 5 MCP tools: consensus_propose, consensus_vote,
consensus_finality, consensus_gossip, vrf_eval. Each is a thin handler
over the corresponding domain module. Single-arbiter compat: tools return
trivially-finalized results when n=1. Test plan: in-process MCP harness.
Entry 12: §P3.8.1 — Test Corpus + Parity Harness
Multi-arbiter simulation. 4 scenarios in default corpus:
- n=1 (single-arbiter Phase 0 compat)
- n=4, all honest (quorum reached trivially)
- n=4, 1 Byzantine (vote divergence → quorum still reached)
- n=4, equivocator (slashing fires; idempotent under re-submission) Mirrors P1.5.5 in κ. 10k synthetic events in <5s. Determinism: two runs produce byte-identical reports.
Entry 13: §P3.9.1 — Fork Trigger Hook (ι handoff stub)
When θ cannot reach quorum within 2*T_timeout, fire ForkTriggerEvent
into a registered handler. Default handler: no-op (logs only). ι Phase 5
will register a real handler. Acceptance: hook fires with right payload
shape; no-op handler does not crash; ι.implementation is OUT OF SCOPE.
8. Risk register
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| ADR-002 moves to Rejected (require Option B noble) | Low | M | Stub interface designed for swap; only P3.6.1 entry rewrites |
| ADR-003 moves to Option B (libp2p) | Medium | L | Gossip slices flag the gate; rewrite needed only for P3.3.x |
| Phase 2 λ slips past R110 | Medium | M | Prompt file STAYS staged; no dispatch authority. Replan at PM’s call |
| Granularity creep beyond 15 | Low | L | Contract §7 hard cap; verification gate at Step 5 |
| Per-entry length under 80 lines | Low | M | kappa-pattern body covers it; verification checklist Step 5 |
| Heritage citation slips in | Low | L | Contract §6 prohibition; grep gate at Step 5 |
9. Acceptance check for Step 4 output
Step 4 produces docs/guides/implementation/task-prompts/p3.1-theta-consensus.md.
Step 4 is acceptable iff:
- File exists at that path ✓
- Frontmatter matches contract §1.2 ✓
- All 6 structural sections present per contract §1.3 ✓
- 13 entries present in the order specified in this packet §2 ✓
- Each entry has the 15-element shape per contract §1.4 ✓
- Each entry’s body ≥80 lines (target ≥100 for kappa parity) ✓
- ADR-002 + ADR-003 status disclosures present (intro + per-slice) ✓
- λ-dependency disclosure present (intro + 2 affected slices) ✓
- Single-arbiter clauses in P3.1.2 + P3.2.1 ✓
- No heritage citations except flagged HERITAGE ✓
- No
src/deltas ✓ - All file refs link-checked ✓
Step 5 verification (Step 5) audits these and lands the coverage matrix.
10. Test plan for the chain
The 5-step chain itself has no src/ deltas, but the build/lint/test gates
must still pass on the unchanged tree:
cd .worktrees/claude/r89-c-theta-phase-3-staging
npm run build # passes on unchanged Phase 0 + κ
npm run lint # passes on unchanged tree
npm test # passes; expected ~2406/2406 per memory
Expected result: zero regressions, since the chain ships only docs/
artefacts.
Packet complete. Step 4 (write the prompt file itself) follows.