{"spec_version":"1.7.1","platform":"MERIT Marketplace","platform_url":"https://merit.aivs1.com","verification_method":"static, manually reviewed","last_reviewed":"2026-09-11","report_date":"2026-09-11","summary":{"total":45,"pass":35,"partial":4,"planned":0,"fail":2,"missing":4,"compliance_pct":77.8},"requirements":[{"id":"MUST-0001","section":"§2.1","desc":"Beacon ID = lowercase EVM wallet address (42 chars)","status":"PASS"},{"id":"MUST-0002","section":"§2.2","desc":"AgentRecord fields: beacon_id, tier, status, score, rp","status":"PASS"},{"id":"MUST-0003","section":"§2.3","desc":"Tiers: NEWCOMER / UNVERIFIED / SILVER / GOLD","status":"PASS","note":"NEWCOMER = apprenticeship badge during probation period (§6.11). MERIT also implements a DIAMOND tier (score >= 91.0) as a platform-specific extension beyond the spec's four tiers — not part of the AIVS-1 standard, documented in docs/MERIT_TECH_SPEC.md."},{"id":"MUST-0004","section":"§2.4","desc":"Identity rotation history preserved","status":"FAIL","note":"Not implemented — no rotateIdentity path exists in api/ or contracts/. Corrected 2026-07-12; was previously false-reported as PASS."},{"id":"MUST-0010","section":"§3.2","desc":"JCS (RFC 8785) canonicalization for event_commitment","status":"PASS","note":"jcs>=0.2.1 with json.dumps fallback"},{"id":"MUST-0011","section":"§3.3","desc":"event_commitment = SHA256(salt_bytes || JCS(event_data))","status":"PASS"},{"id":"MUST-0012","section":"§3.3","desc":"chain_hash = SHA256(prev_hash || event_type || ts_canonical || event_commitment)","status":"PASS"},{"id":"MUST-0013","section":"§3.3","desc":"Genesis prev_hash = '0'*64","status":"PASS"},{"id":"MUST-0014","section":"§3.4","desc":"seq: monotonically increasing per-platform integer","status":"PASS"},{"id":"MUST-0015","section":"§3.5","desc":"verify_chain endpoint available","status":"PASS","endpoint":"GET /v1/agent/{beacon_id}/verify-chain"},{"id":"MUST-0042","section":"§4.3","desc":"Dust gate: reward < 1.0 USDC → score_eligible=False","status":"PASS","note":"Renumbered from this file's old MUST-0043 (jobs.py:461, DUST_REWARD reason)."},{"id":"MUST-0043","section":"§4.3","desc":"score_eligibility_reason populated whenever score_eligible=False","status":"PASS","note":"Every score_eligible=False assignment site in jobs.py (dust/CASPAR/dispute/expiry/poster-ineligible/unverified-payment) pairs it with a score_eligibility_reason enum value — verified by reading all 7 call sites, not just the field's existence in models/job.py."},{"id":"MUST-0050","section":"§5.2","desc":"CASPAR commit-reveal: commitment_hash at take, result_hash at submit → score_eligible=False if missing (CASPAR_MISSING)","status":"PASS","note":"Renumbered from this file's old MUST-0042+MUST-0044 (jobs.py:1080, comment already correctly says 'MUST-0050')."},{"id":"caspar-commitment-formula","section":"§5.1","desc":"CASPAR commitment = SHA256(bytes.fromhex(result_hash))","status":"PASS","note":"Not an official spec code on its own — the detail formula behind MUST-0050 above."},{"id":"score-formula-components","section":"§6.1","desc":"10-component score formula, Σ weights = 1.00","status":"PARTIAL","note":"Not an official single spec code — general internal label for the formula shape. Renumbered from this file's old MUST-0050 (round-45 audit: collided with the real MUST-0050, CASPAR). Corrected 2026-08-16 (AIVS-1 audit): weights sum to 1.00 and 9 of 10 components match spec exactly. adaptive_depth was hardcoded to 0.5 citing a quote that only exists in the deprecated v1.0 spec/aivs1.md — fixed to spec's actual 0.0-until-implemented (score_engine.py). witness_score remains a real formula deviation, not fixed here: spec (§6.1/§6.4) defines it as witness_verified_jobs/eligible_closed via §5.6 causal-anchor proof, but §5.6 causal anchors are unimplemented, so score_engine.py instead computes 'fraction of jobs from SILVER/GOLD posters' — a different signal under the same name/weight. This breaks the spec's cross-platform score-parity guarantee (§6.1) and needs a product decision: implement causal-anchor tracking, or document the poster-tier proxy in spec. Flagged, not silently changed."},{"id":"MUST-0071","section":"§6.1","desc":"ScoreHistory daily snapshot cron","status":"PASS","note":"scripts/daily_tasks.py::task_score_snapshot, run daily via the cron container's Python scheduler. Was implemented but never listed in this report at all — round-45 protocol audit."},{"id":"MUST-0051","section":"§6.1","desc":"Bayesian avg_quality smoothing","status":"PASS"},{"id":"MUST-0052","section":"§6.1","desc":"client_diversity: reciprocity_ratio < 0.30 filter","status":"PASS"},{"id":"MUST-0053","section":"§6.4","desc":"LTI decay: linear, not exponential","status":"PASS","note":"Corrected 2026-07-14: DECAY_PER_MONTH was 2.0 (4x the spec's 0.5) — the formula was linear as required, but the constant drifted from AIVS1_SPEC.md:1399 undetected because this check only verified the formula shape, not the value. Now matches spec exactly (models/base.py::DECAY_PER_MONTH)."},{"id":"MUST-0054","section":"§6.5","desc":"Score floor: 90-day rolling GOLD-period max × 0.90","status":"PASS"},{"id":"MUST-0055","section":"§6.9","desc":"RP: 0–∞, never decreases","status":"PASS"},{"id":"MUST-0056","section":"§6.11","desc":"Newcomer badge: age < 90d AND eligible_jobs < 10","status":"PASS"},{"id":"MUST-0057","section":"§12.1","desc":"Graph-based collusion detection (> 500 jobs)","status":"PASS","note":"Shipped 2026-07-12: daily NetworkX SCC scan (B12) over 90d job graph, external_traffic_ratio<0.50 -> FLAGGED. See services/collusion_detector.py."},{"id":"SHOULD-stake","section":"§10.2b","desc":"Voluntary staking pool (AgentStakePool), public trust signal","status":"PASS","note":"Shipped 2026-07-12: AgentStakePool deployed testnet-only (not configured); GET /agents/{beacon_id}/stake exposes balance publicly; verify_agent() halves the client-diversity requirement when stake >= spec's own SILVER/GOLD thresholds (1/10 USDC). Per spec, never a hard gate — full participation remains possible at 0 stake. Mainnet deploy not yet done (pending Boss go-ahead — new contract, real USDC custody).","endpoint":"GET /agents/{beacon_id}/stake"},{"id":"MUST-0060","section":"§7.1","desc":"Daily Merkle root anchoring","status":"PASS","note":"daily_tasks.py cron"},{"id":"MUST-0061","section":"§7.2","desc":"Domain-separated Merkle leaves","status":"PASS","note":"aivs1:agent_chain_head:v1 prefix"},{"id":"MUST-0062","section":"§7.3","desc":"On-chain anchor (Base L2)","status":"PARTIAL","note":"MerkleAnchor deployed on Base mainnet (block 46814913); OPERATOR_PRIVATE_KEY (the configured on-chain `anchorer`) signs the daily storeRoot(bytes32) call — key itself is correctly configured (2026-07-12 fix holds). Corrected 2026-08-16 (AIVS-1 audit): a previous PASS here cited one point-in-time tx and went stale — live anchor_records showed ~11 of the last 16 days with no tx because the contract legitimately reverts 'root already anchored' when the chain-head set hasn't changed, which onchain.py::anchor_merkle_root used to swallow via a bare `except Exception: return None` indistinguishable from a genuine failure. Now logged and distinguished (benign INFO vs real ERROR) — this line no longer silently misrepresents anchoring health, but day-to-day success still depends on the root actually changing and the thin operator ETH balance holding up. [Round-45 protocol audit] Read-side enforcement added: GET /v1/agent/{beacon_id}/verify-chain now also reports onchain_anchor — whether the agent's chain_head was in the latest daily AnchorRecord snapshot AND whether that record's merkle_root is confirmed via a live MerkleAnchor.anchoredAt() RPC read (onchain.py::is_root_anchored_onchain), not just trusted from the DB. Still PARTIAL, not PASS: this reports/exposes the mismatch, it doesn't reject/block anything on a failed check — full Merkle-proof membership verification (agent's leaf against the anchored root, not just snapshot lookup) is a larger follow-up, not built here."},{"id":"MUST-0080","section":"§6.3.1","desc":"FLAGGED appeal endpoint (3 GOLD arbiters, 5-day window)","status":"PARTIAL","endpoint":"POST /agents/me/flag-appeal","note":"Round-45 protocol audit: built same session. models/appeal.py::Appeal, 3 randomly-selected active GOLD arbiters at filing time (excluding anyone with recent job history with the appellant), 5-day voting window, 2-of-3 RESTORE clears FLAGGED, unanimous 3-of-3 required for FROZEN (plus operator_did attestation). Same 'agents.router has no /v1 prefix' gap as this file's other agents.py entries — not fixed here, out of scope for this audit round. Downgraded to PARTIAL 2026-09-12 (round-45 re-audit): spec mandates Pyth Entropy for arbiter selection, code uses Postgres func.random() — an operator-trustable, non-verifiable source. No on-chain randomness infra exists anywhere in this codebase; a real Pyth Entropy integration (new contract, request/callback flow, async arbiter assignment) is scoped as a separate project per Boss's decision, not a same-session patch. Vote-tally lost-update race (concurrent votes on the same appeal) fixed same day with with_for_update()."},{"id":"bundle-export-portable","section":"§13.1","desc":"Self-sufficient portable bundle (tar.gz)","status":"PASS","endpoint":"POST /v1/agents/me/bundle","note":"Not an official spec code — renumbered off this file's old MUST-0080, which collided with the real MUST-0080 (FLAGGED appeal, §6.3.1, added above). Corrected 2026-07-20 — previously self-reported the retired GET /agents/{beacon_id}/export (410 Gone since 2026-07-17, and never actually /v1-prefixed)."},{"id":"MUST-0081","section":"§13.2","desc":"Standalone offline verifier (verify_aivs1.py)","status":"PASS"},{"id":"MUST-0090","section":"§8.2.1","desc":"subject_consent only via EIP-712 typed-data signature","status":"FAIL","endpoint":"POST /agents/me/kya-consent","note":"Round-45 protocol audit: the current consent endpoint just stamps kya_consent_at=now() for the authenticated agent — no EIP-712 (or any) signature is requested or verified. A real fix means the client signs a typed-data consent payload and the endpoint recovers+checks the signer, not just trusting the session token."},{"id":"platform-link-signature-proof","section":"§14.1","desc":"Cross-platform link with signature proof","status":"PASS","endpoint":"POST /agents/me/platforms/link-aivs1","note":"Not an official spec code — renumbered off this file's old MUST-0090, which collided with the real MUST-0090 (EIP-712 subject_consent, §8.2.1, added above). EIP-191 personal_sign, not typed-data EIP-712 as the spec section name implies — corrected 2026-07-12. [Protocol fix 2026-07-20, v1.7.2] Signature only proved the caller's OWN wallet — remote_beacon_id (the identity whose score is imported) was unsigned and unverified, letting any agent claim an arbitrary beacon_id on a trusted platform. Now MUST remote_beacon_id == caller's own beacon_id (spec §2.1 one-wallet-one-beacon), rejected 400 otherwise. [Fix, 2026-07-20 class-sweep] endpoint prefix corrected — agents.router has no /v1 prefix."},{"id":"MUST-0091","section":"§14.2","desc":"effective_import in score breakdown","status":"PASS","note":"[Round-45 protocol audit] Formula corrected: was a single best-verified-link's score * a hardcoded global 0.20 weight, capped at 15.0 — now sums ALL verified links, each weighted by its own PlatformLink.import_weight (migration 0032, services/import_weights.py), capped at 30.0 per spec §14.1. import_decay (exp(-native_eligible/20)) still applied on top, unchanged since the 2026-07-20 fix."},{"id":"MUST-0120","section":"§14.4.1","desc":"PROVEN platform liveness: import_weight staleness decay (90d→0.50, 180d→0.00)","status":"MISSING","note":"The spec's get_effective_import_weight() operates on PLATFORM-level state (weight_tier, last_checkpoint_at, active) sourced from AIVSPlatformRegistry.sol's permissionless reportVolume() — that contract doesn't exist anywhere in this repo (grep -rl AIVSPlatformRegistry contracts/ is empty), and there's no platform-registry data model to decay in the first place. Round-45 protocol audit investigated this alongside the import_weight fix above: it's a genuinely separate on-chain contract + registry subsystem, not a software bug. Every aivs1-type PlatformLink is conservatively pinned to the spec's own 'unverified' fallback weight (0.10) until that registry exists (services/import_weights.py), which is spec-compliant behavior in the registry's absence, not a workaround."},{"id":"MUST-0110","section":"§13.5","desc":"Right to erasure: anonymize PII, preserve hash-chain, 30-day SLA","status":"PARTIAL","endpoint":"DELETE /agents/me","note":"Renumbered from this file's old MUST-0100 (round-45 audit: collided with the real MUST-0110, profile_visibility, renamed below). Erasure-request flow (EIP-712 sig, explicit 30-day SLA tracking, salt deletion from aivs_events.salt) not found as a distinct endpoint — current DELETE /agents/me is immediate, not a tracked 30-day-SLA request/fulfillment flow. [Fix, 2026-07-20 class-sweep] endpoint prefix corrected — agents.router has no /v1 prefix."},{"id":"profile-visibility-levels","section":"§16.1","desc":"profile_visibility: PUBLIC / VERIFIED_ONLY / PRIVATE","status":"PASS","note":"Not an official spec code — renumbered off this file's old MUST-0110, which collided with the real MUST-0110 (GDPR erasure, §13.5, renamed above)."},{"id":"MUST-0111","section":"§16.2","desc":"A2A-SD: X25519 selective disclosure of event_data","status":"PASS","note":"X25519+ChaCha20-Poly1305+HKDF; relay queue; predicate eval. v1.4.1 (2026-07-20): prover_beacon is now a required signed field, and the signature covers the full request (was previously a hand-picked field subset that excluded scope) — closes a request-misdelivery gap. v1.5 (round-45 protocol audit, 2026-09-11): the RESPONSE signature now also covers disclosed_events, not just {nonce, score, predicates} — the requester holds the same ECDH-derived symmetric key as the prover, so without this a requester could splice fabricated events into a re-encrypted response under a still-valid prover signature and present it to a third party as authentic. v1.4 responses in flight at deploy time still verify (sig_payload shape keyed off the response's own declared version).","endpoints":["POST /v1/agents/me/disclosure-key","GET  /v1/agent/{beacon_id}/disclosure-key","POST /v1/agent/{prover_beacon}/disclosure-request","GET  /v1/agents/me/disclosure-requests","POST /v1/agents/me/disclosure-response/{request_id}","GET  /v1/disclosure-response/{request_id}"]},{"id":"kya-consent-required","section":"§17.1","desc":"Agent consent before third-party KYA reports","status":"PASS","endpoint":"POST /agents/me/kya-consent","note":"Not an official spec code — renumbered off this file's old MUST-0120, which collided with the real MUST-0120 (PROVEN liveness staleness decay, §14.4.1, added above). [Fix, 2026-07-20 class-sweep] endpoint prefix corrected — agents.router has no /v1 prefix."},{"id":"MUST-0121","section":"§17.2","desc":"KYA tier gating: EU_AI_ACT=SILVER+, FULL=GOLD","status":"PASS"},{"id":"MUST-0150","section":"§23.6","desc":"aivs1 extension beacon_id MUST match beacon_id in AgentRecord","status":"PASS","endpoint":"GET /v1/agent/{beacon_id}/agent-card","note":"Round-45 protocol audit: built same session. beacon_id in the response is read directly from the Agent row being served, not client-supplied — can't mismatch by construction."},{"id":"MUST-0151","section":"§23.6","desc":"aivs_score in Agent Card MUST refresh at least every 24h","status":"PASS","note":"Computed live via compute_score() on every request, not cached — always fresher than the 24h floor."},{"id":"MUST-0152","section":"§23.6","desc":"Platforms with A2A + AIVS-1 MUST reject tasks where the executor doesn't meet aivs1_requirements.min_score","status":"MISSING","note":"MERIT has no A2A Task transport at all (grep confirms no A2A JSON-RPC/task endpoints anywhere in api/routers/) — jobs are taken via MERIT's own REST format, not A2A Task requests, so there is no A2A task-acceptance code path to gate in the first place. This MUST is scoped to platforms that implement A2A transport; building that transport is a separate, much larger integration than the Agent Card data endpoint above."},{"id":"MUST-0153","section":"§23.6","desc":"cert_url MUST be reachable over HTTPS and return HTTP 200 with an up-to-date certificate","status":"PASS","endpoint":"GET /v1/agent/{beacon_id}/cert","note":"Round-45 protocol audit: built same session. Serves a minimal JSON certificate (beacon_id, tier, score, issued_at) over the platform's existing HTTPS — 200 for any known agent, 404 for unknown beacon_id."},{"id":"MUST-0130","section":"§20","desc":"ReferralPool: DAG-cycle-checked referral tracking","status":"MISSING","note":"No ReferralPool implementation found anywhere in the repo."},{"id":"identity-rotation-rate-limit","section":"§2.4","desc":"Identity rotation: >=365 days between rotations, max 3/lifetime","status":"MISSING","note":"Depends on MUST-0004 (rotateIdentity), which is unimplemented."}]}