Skip to content

Instantly share code, notes, and snippets.

@Jing-yilin
Created April 5, 2026 14:30
Show Gist options
  • Select an option

  • Save Jing-yilin/e47085c96088e393fbb9ecb4b66fa6b5 to your computer and use it in GitHub Desktop.

Select an option

Save Jing-yilin/e47085c96088e393fbb9ecb4b66fa6b5 to your computer and use it in GitHub Desktop.
PR #38 Plan — Trust Chain Hardening & Benchmark World (AWN SPEC-0.2.0 gap analysis)

PR #38 — Trust Chain Hardening & Benchmark World

Date: 2026-04-05 Branch: hal/trust-chain-benchmark (from compound/daemon-local-runtime-foundation) Spec refs: §3 §6, §4 §2, §5 §2-3, §7 §2-3, §9 §2, §10 §5, §11 §3 Issues: Advances #20 (disclosure verification), advances #21 (gateway binding verification) Priority: HIGH — closes 4 spec gaps that block the end-to-end "10-minute trust loop" MVP Estimated: ~650 LoC net new across 8 files Coverage lift: overall 79% → ~84%


Motivation

After a thorough spec-vs-code audit (SPEC-0.2.0 §00–§11), we identified 4 concrete gaps that remain between the implementation and the spec's normative requirements. These gaps collectively break the trust chain: an agent cannot yet complete the full "10-minute trust loop" described in §9 because (a) anti-Sybil enforcement is missing, (b) disclosure packages aren't fully verified at admission, (c) gateway doesn't verify announcement bindings, and (d) there is no Benchmark World to bootstrap cold-start reputation.

Gap Analysis Summary

# Gap Spec Section Severity Impact
1 maxAgentsPerWallet defined but NOT enforced §4 §2, §7 §2 HIGH Sybil attackers can join with unlimited agents per wallet
2 Disclosure Merkle proofs not verified during admission §3 §5, §7 §3, §10 §3 MEDIUM Trust-gated worlds accept unverified disclosure packages
3 Gateway doesn't verify announcement fields against binding record §5 §2-3 MEDIUM Malicious operators can announce fake metadata
4 No Benchmark World template §9 §2, §3 §6 HIGH Cold-start agents have no standardized reputation path

What's Already Done (Confirmed Working)

These related items were verified as correctly implemented during the audit:

Requirement Status Location
source_world_stage_factor (0.50/0.80/1.00) scoring.rs:71-81, 467, 473
Beta reputation model (α/β) scoring.rs:499-504
Rate limiting enforcement server.rs:502-650
Protocol version header (X-AWN-Protocol-Version) server.rs:1189-1204
Receipt count endpoint trust_endpoints.rs
Identity Binding Proof endpoint trust_endpoints.rs
Version skew ±1 minor enforcement daemon/client.rs:353-374
Timeout escalation (consecutive + rolling) world_api/timeout.rs:14-117
Gateway liveness (120s/300s thresholds) gateway/liveness.rs:9-12, 64-73
World Binding Record struct world_api/world_binding.rs:18-33
Receipt lifecycle (all 6 states) trust/lifecycle.rs:45-61
Challenge window / timeout finalization trust/lifecycle.rs:270-343
SSE event signing world_api/sse.rs:77-91
Daemon state checkpoint daemon/compound.rs:365-458
Receipt staleness rejection (30 days) trust/lifecycle.rs:35-36, 378-386

User Stories

US-001: Enforce maxAgentsPerWallet (Anti-Sybil)

Goal: Prevent a single wallet from joining a world with multiple agent identities, as required by §4 and §7.

Spec: §4 §2 — "maxAgentsPerWallet: 1 — Optional: anti-Sybil"; §7 §2 — "Per-World multi-accounting: Enforce maxAgentsPerWallet: 1"

Current State: AdmissionConfig has max_agents_per_wallet: Option<u32> field defined in src/schema/mod.rs:381, but check_admission() in endpoints.rs explicitly skips it with a comment saying "enforced at a different layer". No layer enforces it.

Implementation

Step 1: Add a wallet_tracker to EndpointState:

// In EndpointState (src/world_api/endpoints.rs)
/// Maps wallet subjectId → set of awnIds currently joined from that wallet.
/// Used for maxAgentsPerWallet enforcement.
pub wallet_tracker: RwLock<HashMap<String, HashSet<String>>>,

Step 2: In join_handler(), after admission check passes, enforce the limit:

// After check_admission() succeeds, before world_runtime.on_join()
if let Some(max_per_wallet) = state.admission_config.as_ref()
    .and_then(|c| c.max_agents_per_wallet)
{
    if let Some(ref subject_id) = join_request.subject_id {
        // Extract wallet from subject_id: "eip155:8453:0xRegistry:tokenId" → "eip155:8453:0xRegistry"
        let wallet = extract_wallet_from_subject_id(subject_id);
        let tracker = state.wallet_tracker.read().await;
        if let Some(agents) = tracker.get(&wallet) {
            if agents.len() >= max_per_wallet as usize {
                return Err(ApiError::new(
                    ApiErrorCode::AdmissionDenied,
                    format!("wallet already has {}/{} agents joined", agents.len(), max_per_wallet),
                ));
            }
        }
    }
}

Step 3: On successful join, register in tracker. On leave, remove from tracker.

Step 4: Add helper extract_wallet_from_subject_id():

/// Extracts the wallet portion from a CAIP-10 subject ID.
/// "eip155:8453:0xRegistry:tokenId" → "eip155:8453:0xRegistry"
/// Used for maxAgentsPerWallet grouping (all tokens from same registry = same wallet).
fn extract_wallet_from_subject_id(subject_id: &str) -> String {
    // Split on ':' — first 3 parts are the wallet identifier
    let parts: Vec<&str> = subject_id.splitn(4, ':').collect();
    if parts.len() >= 3 {
        format!("{}:{}:{}", parts[0], parts[1], parts[2])
    } else {
        subject_id.to_string()
    }
}

Files: src/world_api/endpoints.rs

AC:

  • maxAgentsPerWallet enforced at join time when configured
  • Wallet extracted from CAIP-10 subjectId (registry-level grouping)
  • Join rejected with ADMISSION_DENIED when limit exceeded
  • Leave handler cleans up wallet tracker
  • Works with None (no limit, default behavior unchanged)
  • Test: 2 agents from same wallet, limit=1 → second rejected
  • Test: 2 agents from different wallets, limit=1 → both admitted
  • Test: no limit configured → unlimited agents per wallet

US-002: Verify Disclosure Merkle Proofs at Admission

Goal: When a trust-gated world verifies a disclosure package during admission, actually verify the Merkle inclusion proofs — not just deserialize them.

Spec: §3 §5 — "Receipt validity proven via Ed25519 signatures + Merkle inclusion proofs"; §10 §3 — "Verify Merkle proofs against on-chain roots"

Current State: verify_disclosure_receipts() in src/trust/disclosure.rs:113-276 verifies Ed25519 receipt signatures but does NOT call verify_merkle_proof() (which exists in src/trust/merkle.rs:160-184) on the disclosure package's AnchorProof entries.

Implementation

Step 1: In verify_disclosure_receipts(), after signature verification succeeds for each receipt, check if a matching AnchorProof exists and verify it:

// After signature verification (around line 270 in disclosure.rs)
// Verify Merkle proofs if provided
for proof in &package.proofs {
    // Find the receipt this proof belongs to
    let receipt_id = &proof.receipt_id;
    
    // Compute leaf hash: SHA256(JCS(receipt))
    let receipt_json = package.receipts.iter()
        .find(|r| r.get("receiptId").and_then(|v| v.as_str()) == Some(receipt_id));
    
    if let Some(receipt_val) = receipt_json {
        let canonical = jcs_canonicalize(receipt_val)?;
        let leaf_hash = sha256(&canonical);
        
        if !verify_merkle_proof(&leaf_hash, proof.leaf_index, &proof.proof, &proof.merkle_root) {
            warnings.push(DisclosureWarning {
                code: "MERKLE_PROOF_INVALID".to_string(),
                message: format!("Merkle proof for receipt {} failed verification", receipt_id),
            });
        }
    }
}

Step 2: Add MERKLE_PROOF_INVALID warning code to DisclosureWarning.

Step 3: Optionally, worlds with strict admission can reject on any Merkle proof failure. For now, return as warning (consistent with spec's "conditional completeness" approach).

Files: src/trust/disclosure.rs, src/trust/merkle.rs (minor — ensure public API)

AC:

  • verify_disclosure_receipts() verifies Merkle proofs when AnchorProof entries present
  • Invalid proofs produce MERKLE_PROOF_INVALID warning
  • Missing proofs do NOT fail (proofs are optional per spec)
  • Receipt leaf hash computed via JCS canonicalization + SHA256
  • Test: valid proof → no warning
  • Test: tampered proof → warning generated
  • Test: no proofs in package → passes (backward compatible)

US-003: Gateway Announcement Binding Verification

Goal: Gateway must verify that announcement fields match the World Binding Record, not just the signature.

Spec: §5 §2 — "Announced worldId, slug, name, category, endpoint, agentCardUrl MUST match bound values in binding record. Announcement signature MUST verify against bound worldKey."

Current State: handle_announce() in src/gateway/handlers.rs:40-92 verifies the announcement signature and checks non-empty required fields, but does NOT verify fields against a resolved World Binding Record. The verify_binding flag defaults to false.

Implementation

Step 1: Add an optional WorldBindingResolver to the gateway engine:

// In GatewayEngine or GatewayState
pub binding_resolver: Option<Arc<WorldBindingResolver>>,

Step 2: In handle_announce(), when binding_resolver is available, resolve the binding record and compare fields:

if let Some(ref resolver) = state.binding_resolver {
    match resolver.resolve(&announcement.world_id).await {
        Ok(binding) => {
            // Verify fields match binding record
            let mismatches = verify_announcement_binding(&announcement, &binding);
            if !mismatches.is_empty() {
                return Err(GatewayError::BindingMismatch {
                    world_id: announcement.world_id.clone(),
                    mismatches,
                });
            }
        }
        Err(e) => {
            // If binding resolution fails, accept with warning (graceful degradation)
            tracing::warn!(world_id = %announcement.world_id, "binding resolution failed: {e}");
        }
    }
}

Step 3: Implement verify_announcement_binding():

fn verify_announcement_binding(
    announcement: &WorldAnnouncement,
    binding: &WorldBindingRecord,
) -> Vec<String> {
    let mut mismatches = Vec::new();
    if announcement.world_id != binding.world_id {
        mismatches.push(format!("worldId: '{}' != '{}'", announcement.world_id, binding.world_id));
    }
    // Check operator_subject_id if both present
    if let (Some(ref ann_op), Some(ref bind_op)) = (&announcement.operator_subject_id, &binding.operator_subject_id) {
        if ann_op != bind_op {
            mismatches.push(format!("operator: '{}' != '{}'", ann_op, bind_op));
        }
    }
    // Check signing key matches binding's ed25519 key
    // (already verified by signature, but explicit check)
    mismatches
}

Files: src/gateway/handlers.rs, src/gateway/engine.rs

AC:

  • Gateway resolves World Binding Record on announcement (when resolver available)
  • worldId mismatch → announcement rejected with BindingMismatch
  • operator subjectId mismatch → rejected
  • Binding resolution failure → graceful degradation (accept with warning)
  • No resolver configured → existing behavior preserved (backward compatible)
  • Test: matching announcement → accepted
  • Test: worldId mismatch → rejected
  • Test: no resolver → accepted (no binding check)

US-004: Benchmark World Template (code-bench-v1)

Goal: Implement the Benchmark World specified in §9 §2, enabling cold-start agents to build initial reputation through standardized coding challenges.

Spec: §9 §2 — "Standardized coding benchmark for single-agent protocol testing. 5 deterministic challenges."; §3 §6 — "Only world-attestation receipts. No payment-derived weighting. Single-agent interactions."

Current State: Benchmark scoring rules are implemented in src/trust/scoring.rs:50 (BENCHMARK_CATEGORY), with special handling for world-attestation receipts, value_weight=1.0, participantCount=1. But no actual Benchmark World template exists in src/worlds/.

Implementation

Step 1: Create src/worlds/benchmark/ module with runtime.rs and engine.rs:

// src/worlds/benchmark/runtime.rs

pub struct BenchmarkWorld {
    manifest: WorldManifest,
    challenges: Vec<Challenge>,
    sessions: HashMap<String, BenchmarkSession>,
    config: BenchmarkConfig,
}

pub struct BenchmarkConfig {
    pub challenges: Vec<ChallengeSpec>,
}

pub struct ChallengeSpec {
    pub id: String,           // "fizzbuzz", "binary-search", etc.
    pub difficulty: String,   // "easy", "medium", "hard"
    pub time_limit_secs: u64, // per-challenge timeout
}

pub struct BenchmarkSession {
    pub agent_awn_id: String,
    pub started_at: Instant,
    pub submissions: HashMap<String, Submission>,  // challenge_id → Submission
    pub completed: bool,
}

pub struct Submission {
    pub solution: String,
    pub language: String,
    pub submitted_at: Instant,
    pub score: u32,  // 0-100
}

Step 2: Implement WorldRuntime for BenchmarkWorld:

impl WorldRuntime for BenchmarkWorld {
    fn manifest(&self) -> &WorldManifest { &self.manifest }

    fn on_join(&mut self, agent: &AgentSlot) -> Result<JoinResponse, WorldError> {
        // Single-agent: one session per agent
        let session = BenchmarkSession::new(agent.awn_id.clone());
        self.sessions.insert(agent.awn_id.clone(), session);
        Ok(JoinResponse {
            slot_index: 0,
            session_id: format!("ses_{}", uuid::Uuid::now_v7()),
            initial_state: self.challenge_list_json(),
        })
    }

    fn on_action(&mut self, agent: &AgentSlot, action: &Action) -> Result<ActionResult, WorldError> {
        match action.name.as_str() {
            "submit-solution" => self.handle_submit(agent, &action.params),
            _ => Err(WorldError::InvalidAction(format!("unknown action: {}", action.name))),
        }
    }

    fn on_leave(&mut self, agent: &AgentSlot) -> Result<LeaveResponse, WorldError> {
        let session = self.sessions.remove(&agent.awn_id)
            .ok_or(WorldError::InvalidState("not joined".into()))?;
        
        // Generate world-attestation receipt with aggregate score
        let total_score = self.compute_aggregate_score(&session);
        let receipt = ReceiptDraft {
            receipt_type: ReceiptType::WorldAttestation,
            category: "benchmark".to_string(),
            outcome_result: if total_score >= 60 { OutcomeResult::Success } else { OutcomeResult::Partial },
            dimensions: Some({
                let mut d = HashMap::new();
                d.insert("quality".to_string(), total_score);
                d
            }),
            participant_count: 1,
            task_id: Some("benchmark-session".to_string()),
            settlement: None,
        };
        Ok(LeaveResponse { receipts: vec![receipt] })
    }

    fn state_for(&self, agent: &AgentSlot) -> Result<serde_json::Value, WorldError> {
        // Return challenge list + submission status
        // ...
    }

    fn tick(&mut self, now: Instant) -> Vec<WorldEvent> {
        // Check for timed-out challenges
        // ...
        vec![]
    }
}

Step 3: Implement handle_submit() — deterministic scoring for 5 challenges:

The spec defines 5 challenges: fizzbuzz, binary-search, json-parser, rate-limiter, merkle-tree. For MVP, scoring is structural validation (not code execution):

fn handle_submit(&mut self, agent: &AgentSlot, params: &serde_json::Value) -> Result<ActionResult, WorldError> {
    let challenge_id = params.get("challengeId").and_then(|v| v.as_str())
        .ok_or(WorldError::InvalidParams("challengeId required".into()))?;
    let solution = params.get("solution").and_then(|v| v.as_str())
        .ok_or(WorldError::InvalidParams("solution required".into()))?;
    let language = params.get("language").and_then(|v| v.as_str())
        .ok_or(WorldError::InvalidParams("language required".into()))?;

    // Validate challenge exists
    let challenge = self.challenges.iter().find(|c| c.id == challenge_id)
        .ok_or(WorldError::InvalidParams(format!("unknown challenge: {challenge_id}")))?;

    let session = self.sessions.get_mut(&agent.awn_id)
        .ok_or(WorldError::InvalidState("not joined".into()))?;

    // Check time limit
    let elapsed = session.started_at.elapsed();
    if elapsed > Duration::from_secs(challenge.time_limit_secs) {
        return Err(WorldError::InvalidState("challenge time expired".into()));
    }

    // Score: structural validation (presence of key patterns)
    let score = score_submission(challenge_id, solution, language);

    session.submissions.insert(challenge_id.to_string(), Submission {
        solution: solution.to_string(),
        language: language.to_string(),
        submitted_at: Instant::now(),
        score,
    });

    Ok(ActionResult {
        accepted: true,
        seq: 0, // filled by endpoint layer
        events: vec![WorldEvent {
            event_type: "benchmark.scored".to_string(),
            data: serde_json::json!({
                "challengeId": challenge_id,
                "score": score,
                "difficulty": challenge.difficulty,
            }),
            visibility: EventVisibility::Private(agent.awn_id.clone()),
        }],
        state_patch: None,
    })
}

Step 4: Implement score_submission() — deterministic structural checks:

fn score_submission(challenge_id: &str, solution: &str, _language: &str) -> u32 {
    match challenge_id {
        "fizzbuzz" => score_fizzbuzz(solution),
        "binary-search" => score_binary_search(solution),
        "json-parser" => score_json_parser(solution),
        "rate-limiter" => score_rate_limiter(solution),
        "merkle-tree" => score_merkle_tree(solution),
        _ => 0,
    }
}

// Each scorer checks for structural correctness patterns:
// - fizzbuzz: checks for modulo operations, conditional branching, "fizz"/"buzz" strings
// - binary-search: checks for midpoint calculation, comparison, array narrowing
// - json-parser: checks for recursive descent patterns, string/number/object handling
// - rate-limiter: checks for token bucket or sliding window patterns
// - merkle-tree: checks for hashing, tree construction, proof verification patterns
//
// Score range: 0-100 per challenge
// Aggregate: weighted average (easy=15%, medium=25%, hard=35%)

Step 5: Register in WorldFactory:

// src/daemon/factory.rs
"benchmark" | "code-bench-v1" => Ok(Box::new(BenchmarkWorld::new(config))),

Step 6: Add module exports:

// src/worlds/mod.rs
pub mod benchmark;

Files: src/worlds/benchmark/runtime.rs (new, ~250 LoC), src/worlds/benchmark/engine.rs (new, ~150 LoC), src/worlds/benchmark/mod.rs (new), src/worlds/mod.rs, src/daemon/factory.rs

AC:

  • BenchmarkWorld implements WorldRuntime with 5 challenges per §9 §2
  • Challenges: fizzbuzz (easy), binary-search (easy), json-parser (medium), rate-limiter (medium), merkle-tree (hard)
  • Single-agent sessions (participantCount: 1)
  • Generates world-attestation receipts with quality score 0-100
  • Category is "benchmark" (hooks into existing scoring special case)
  • Per-challenge time limits enforced
  • Registered in WorldFactory as "benchmark" / "code-bench-v1"
  • Test: join → submit all 5 → leave → receipts generated with correct scores
  • Test: submit after time limit → error
  • Test: unknown challenge → InvalidParams error
  • Test: aggregate score ≥80% on 5 challenges → ~0.78 score per §3 §6

Files Changed

File Change Type Description
src/worlds/benchmark/mod.rs New ~5 LoC — module exports
src/worlds/benchmark/runtime.rs New ~250 LoC — BenchmarkWorld implementing WorldRuntime
src/worlds/benchmark/engine.rs New ~150 LoC — structural scoring for 5 challenges
src/worlds/mod.rs Modified ~1 LoC — pub mod benchmark;
src/daemon/factory.rs Modified ~5 LoC — register benchmark slug
src/world_api/endpoints.rs Modified ~60 LoC — wallet tracker + maxAgentsPerWallet enforcement
src/trust/disclosure.rs Modified ~40 LoC — Merkle proof verification in disclosure
src/gateway/handlers.rs Modified ~50 LoC — binding verification in announce handler
src/gateway/engine.rs Modified ~10 LoC — optional binding resolver

Total: ~650 LoC net new across 9 files


Spec Coverage Impact

Section Before After Delta What Changed
§03 Contextual Scoring 90% 95% +5% Merkle proof verification in disclosure
§04 World Trust 82% 88% +6% maxAgentsPerWallet enforcement
§05 Gateway 75% 82% +7% Announcement binding verification
§07 Security 80% 86% +6% Anti-Sybil enforcement
§09 Implementation Plan 60% 75% +15% Benchmark World template
§11 Platform Architecture 72% 78% +6% 6th world template
Overall 79% ~84% +5%

Issue Closure Map

Issue Status After This PR What Remains
#20 (Durable receipt outbox) 80% (up from 75%) Full receipt state machine (Anchored/Replicated states)
#21 (Pluggable DiscoveryBackend) 30% started Gateway binding resolver is first step; full trait extraction in future PR

Dependency Graph

This PR depends on compound/daemon-local-runtime-foundation (post PR #37 merge).

US-001 (maxAgentsPerWallet) ─── independent
US-002 (Merkle proof verification) ─── independent
US-003 (Gateway binding verification) ─── independent
US-004 (Benchmark World) ─── independent

All 4 stories are independent. Recommended order:
  US-001 first (small, high-impact anti-Sybil)
  → US-002 (small, strengthens trust chain)
  → US-003 (small, gateway hardening)
  → US-004 last (largest, new world template)

Test Plan

Unit Tests

Test Story Validates
wallet_limit_enforced US-001 2 agents same wallet, limit=1 → second rejected
wallet_limit_different_wallets US-001 2 agents different wallets, limit=1 → both admitted
wallet_limit_none US-001 No limit → unlimited agents
wallet_cleanup_on_leave US-001 After leave, wallet slot freed
extract_wallet_from_subject_id US-001 CAIP-10 parsing correctness
merkle_proof_verified_in_disclosure US-002 Valid proof → no warning
merkle_proof_tampered US-002 Bad proof → MERKLE_PROOF_INVALID warning
disclosure_no_proofs US-002 No proofs → passes (backward compat)
binding_match_accepted US-003 Matching announcement → accepted
binding_mismatch_rejected US-003 worldId mismatch → rejected
binding_no_resolver US-003 No resolver → accepted
benchmark_join_leave US-004 Join → leave → receipt generated
benchmark_submit_scored US-004 Submit solution → score returned
benchmark_time_limit US-004 Expired challenge → error
benchmark_unknown_challenge US-004 Bad challengeId → InvalidParams
benchmark_aggregate_score US-004 5 perfect submissions → high aggregate
benchmark_receipt_type US-004 Receipt type is WorldAttestation
benchmark_category US-004 Category is "benchmark"

Integration Tests

Test Story Validates
benchmark_e2e_trust_loop US-004 Join → solve 5 → leave → receipt → score computed correctly
benchmark_scoring_special_case US-004 Benchmark receipts use value_weight=1.0, multi_agent_weight=1.0
sybil_prevention_e2e US-001 Multi-wallet scenario with maxAgentsPerWallet enforcement

Out of Scope

Feature Why Where
Actual code execution for benchmark Requires sandboxed runtime; structural scoring sufficient for MVP Future PR
On-chain Merkle root verification Requires blockchain integration; local proofs sufficient for now Future PR
Full DiscoveryBackend trait extraction Separate architectural concern Issue #21, future PR
Anchored/Replicated receipt states Requires on-chain integration Issue #20, future PR
ZK reputation proofs Phase 2+ per spec Future
Benchmark challenge addition/versioning Standard 5 challenges for MVP Future

Risk Assessment

Risk Level Mitigation
CAIP-10 subject ID format variations Low Use prefix-based extraction; test with multiple formats
Merkle proof verification slows admission Low Proofs are small (log₂ tree); verification is O(log n)
Benchmark structural scoring too lenient/strict Medium Start with basic pattern matching; calibrate with real agent submissions
Binding resolver adds latency to gateway announce Low Cache binding records (already LRU-cached in WorldBindingResolver)
Wallet tracker memory growth Low Cleaned up on leave; bounded by maxAgents per world

Implementation Notes for hal Agent

  1. US-001: Check EndpointState fields carefully — use RwLock<HashMap<String, HashSet<String>>> for the wallet tracker. Clean up in both leave_handler() AND evict_agent().
  2. US-002: The verify_merkle_proof() function in src/trust/merkle.rs already exists and is tested. Just wire it into verify_disclosure_receipts(). Ensure you compute the leaf hash the same way the anchor batch does (JCS + SHA256).
  3. US-003: The WorldBindingResolver in src/world_api/world_binding.rs already has resolve + cache logic. Share it via Arc into the gateway state.
  4. US-004: Keep the Benchmark World simple. No code execution — just structural pattern matching on submitted solutions. The scoring doesn't need to be perfect for MVP; it needs to generate valid world-attestation receipts with reasonable quality scores.
  5. Run cargo fmt and cargo clippy before every commit. CI is strict.
  6. Keep #[serde(rename_all = "camelCase")] on any new structs that touch JSON.
  7. Test the Benchmark World against the existing scoring engine — ensure that benchmark receipts trigger the special case path in scoring.rs (category == "benchmark", value_weight=1.0, participantCount=1).

Remaining Spec Gaps After This PR

After this PR, the main remaining gaps are:

Gap Spec Section Priority Estimated PR
Anchored/Replicated receipt states §2 §5 P1 PR #39
Full DiscoveryBackend trait §5 §4 P1 PR #39 or #40
Dispute resolution Tier 2/3 §8 §4 P2 Future
On-chain Merkle root anchoring (Base L2) §2 §6, §5 P2 Future
ZK reputation proofs §7 §5 P3 Phase 2+
Payment pool settlement (smart contract) §6 §3 P2 Future
System service install (launchd/systemd) §11 P3 Future
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment