{
  "gate": "MCP Verification Gate",
  "version": "0.4.20",
  "gate_commit": "unpinned: this deployment did not inject a commit (deploy_gate.sh not used)",
  "what_this_verifies": [
    "The server actually exists and speaks MCP",
    "The server publishes an A2A agent card",
    "The server declares who pays it",
    "Identical input returns identical output",
    "This gate's own verdict can be recomputed by anyone"
  ],
  "what_this_does_not_verify": [
    "Whether prices or figures returned by the server are correct",
    "Whether the declared compensation structure is truthful (it is published and recorded; false declarations are grounds for revocation)",
    "Quality, competence, or fitness of the underlying business"
  ],
  "conditions": {
    "mcp_endpoint": "POST /mcp responds to initialize (with a result, not an error) and tools/list with at least one well-formed tool: a non-empty string name and an inputSchema object. Hollow tools are not counted, duplicate names fail, and a tools value that is not an array fails (0.2.2).",
    "agent_card": "GET /.well-known/agent-card.json returns a JSON object whose name and description are non-empty strings. Redirects are followed only within the same origin; a redirect to another origin is not a card at this origin (0.2.2).",
    "compensation_disclosure": {
      "location": "agent-card, top-level key 'compensation', or (0.3.2) the capabilities.extensions[] entry whose uri is https://gate.horizonshield.dev/ext/conduct/v1 or (0.4.3) its w3id.org permanent identifier https://w3id.org/horizonshield/conduct/v1, key params.compensation. The verdict records which of the two strings the card declared, and the redirect is never followed while checking. One well-formed declaration passes. When both are present they must be equal on the five keys, or the condition fails.",
      "shape": {
        "paid_by": [
          "buyer",
          "seller",
          "referral",
          "advertising",
          "subscription",
          "public",
          "other"
        ],
        "referral_fee": "boolean, required",
        "listing_fee": "boolean, required",
        "success_fee_pct": "number between 0 and 100, optional; declared with any other type or range, the condition fails (0.2.2)",
        "disclosure_url": "string, optional; declared with any other type, the condition fails (0.2.2)"
      },
      "note": "Content is not judged. Only the absence of disclosure disqualifies. A self-contradicting declaration (paid_by referral with referral_fee false) still passes and is published with a consistency_note (0.2.2)."
    },
    "determinism": "Calling the same tool with the same arguments returns identical content across runs. NOT measured by default: doing so requires executing a tool on the checked server, which this gate will not do without the owner's consent. Consent comes from the owner: a file at /.well-known/mcp-conduct.json on the server's own origin (see well_known_consent below), which is the only basis the scheduled sweep accepts. A requester may assert allow_tool_call on a one-off /check, but an assertion is not proof and never becomes the basis for a row on the public register (0.2.4). An error response (JSON-RPC error or result.isError) is not a measurement: up to 3 tools are tried in an order this gate derives (see instant_coordinate below), not the server's own, and the verdict discloses which tool was measured, which were tried, and how many were not measured (0.2.2).",
    "self_verification": "Every verdict carries a SHA-256 that any third party can recompute"
  },
  "reachability": "Any HTTP status, or a non-JSON body, is an answer from the server: reachable stays true and the row goes pending, not held. Only gateway-shaped statuses (502-504, 52x) and transport failures mean held. Redirects to another origin are treated as answered, not followed (0.2.2).",
  "consent": "allow_tool_call on /check is asserted by the requester and is not proof of ownership; every verdict states its consent_basis and consent_source. Rows in the public register are measured with tool calls only with proven consent: the operator's published consent list (0.2.2) or a consent file on the endpoint's own origin (0.2.4).",
  "establishes_and_does_not_establish": {
    "since": "0.4.0",
    "what": "Every verdict (and this gate's own /self record) carries two arrays, establishes and does_not_establish, generated from the measurement itself and included in the bytes that record_sha256 hashes. establishes names what was measured: the instant, the commit, the conditions that passed and failed, the hash recipe and the coordinate. does_not_establish names what a passing verdict never means: that answers are correct, that a compensation declaration is true, quality, safety, other instants, other vantages, conditions not measured on this run.",
    "why": "A verdict that only lists what passed can be quoted with its caveats dropped; the quote still verifies against the hash. Found in public on 2026-09-07 by a reader of the A2A extension: the 'not judged correct' disclaimer could be removed from a card and conformance still passed. With the two arrays inside the hashed record, a quote without them no longer recomputes.",
    "not_a_rule": "The arrays are text and are not conditions. Nothing passes or fails on them. They are the record refusing to be quoted as more than it is."
  },
  "lookup": {
    "since": "0.4.0",
    "route": "GET /register/lookup?endpoint=<https MCP endpoint>",
    "what": "One read before connecting: status (verified / pending / declined / unknown), the latest stored verdict's sha, the last published ring's counts (witnesses signed and unsigned, discrepancies, commitments, walked_as_witness, instants by derivation), where the record and the witness intake are, and what the answer does not establish. Cached 24 hours. No score, no rank.",
    "unknown": "means no row here. It is never a finding about the endpoint."
  },
  "number_safety": {
    "since": "0.4.2 (2026-09-09)",
    "where": "a number_safety block inside every verdict and inside the gate's own /self record, so it is covered by record_sha256 and cannot be dropped from a quote",
    "what": "Two booleans over two lists, generated from the record. parse_safe: no integer here is outside the RFC 7493 (I-JSON) safe range and no value is non-finite, so nothing was destroyed by JSON.parse before any canonicalization code could run. safe_integers_only: stricter, both lists empty, so a compact re-serialization with the keys in the order printed reproduces these bytes in any language at all. unsafe_integers names the values that break parse_safe; non_integer_numbers names the doubles, which runtimes printing the shortest round trip form agree on and runtimes printing a fixed number of digits do not. A verdict that measured a surface normally carries one double, the percentage in absence_vs_failure, so parse_safe true with safe_integers_only false is the ordinary state.",
    "why": "An integer past 2^53 is rounded by JSON.parse before any canonicalization runs, and a non-integer double is printed differently by different runtimes. Prose in a recompute recipe cannot prevent either, because the damage happens before the reader reaches the prose. Found in public on 2026-09-09 by Federico Blanco Sanchez-Llanos from the payments side, in an idempotency key that collapsed two different large amounts into one fingerprint.",
    "self_applied": "Condition 07 already refuses to publish a fingerprint for a measured surface carrying such an integer. 0.4.2 asks the same question of this gate's own output. The operator is a subject of the rule, not an exception to it.",
    "not_a_rule": "A disclosed measurement. Nothing passes or fails on it and no row turns red.",
    "the_block_holds_no_number": "By construction the block contains no numeric value, so adding it to the record cannot change the answer it reports."
  },
  "record_bytes": {
    "since": "0.4.1 (2026-09-09)",
    "route": "GET /record/<record_sha256>",
    "what": "The exact bytes that record_sha256 hashes, for every scheduled verdict from that date on: the verdict with record_sha256 and recompute_note removed, stored as the string that was hashed and returned without re-serialization. SHA-256 of the body equals the path. History entries, register rows, /is-verified, /register/lookup and the envelope carry record_url pointing here when the bytes exist.",
    "why": "Before this, the sha was published but the bytes were not: scheduled measurements were summarised into history and the record itself was dropped, and recompute_url pointed at /history, whose entries have a different shape. A second implementer (SEP-1913, 2026-09-09) tried 1024 serializations of the /is-verified projection and correctly reported that none reproduced the sha. The fault was on this side: the published reference did not name published bytes. Now it does.",
    "not_stored": "On-demand POST /check verdicts are returned to the caller and not stored (storing them would let anyone spend this gate's KV write quota). Verdicts before 2026-09-09 have no stored bytes; their sha stands as issued but names bytes this gate no longer holds, and the gate says so rather than pointing at a summary.",
    "recipe_unchanged": "Remove record_sha256 and recompute_note, JSON.stringify in key order, SHA-256. Unchanged since 0.1. What changed is that the object to apply it to is now published."
  },
  "instant_coordinate": {
    "since": "0.3.0",
    "schema": "nenrin-instant-v1",
    "production_departures_found_2026_09_06": "Three departures between this text and the deployed code were found on 2026-09-06 by reading worker.js, nenrin_instant.js, verify_beacons.py and /sweep/last: (1) the derivation block was written into every verdict but dropped from every /history entry, so exports were structurally silent on it; (2) beacon agreement was demanded on a height each source computed from its own tip (tip minus 6), so two honest explorers one block apart never agreed, and the first sweep after 0.3.0 (2026-09-05T18:00Z) fell back to the legacy schedule; (3) the salt was created at the sweep and bound to a block mined about an hour earlier, so the salt did not precede the block. All three are fixed in 0.3.5; entries written before 0.3.5 carry no derivation block and the sweeps of 2026-09-05 ran on the legacy rule. Recorded here rather than removed, in the same spirit as the addendum: the operator is a subject of its own rule.",
    "defect": "Time is a coordinate. Until 0.2.4 the free tier's measurement day was sha256(endpoint) mod 7, every input public, so the subject could compute the day it would be measured and a shim answering one day in seven earned a full record at one seventh of the cost. The same defect had a second face: determinism measured the first tool in the server's own tools/list order, and the server chose that order.",
    "fix": "The measurement day and the tool order are HMAC-SHA256 derived from a salt this gate creates before each 7 day window opens (0.3.5: at the latest during the previous window's first sweep), bound to a Bitcoin block whose header time is at or after the salt's creation. The subject cannot predict them. The gate cannot choose them after the fact, because the commitment is published at /nenrin/window before the block exists. The tool is chosen over the lexicographically sorted name set, so reordering tools/list steers nothing and renaming moves tool_set_sha256.",
    "beacon": "One reference height for every source: the second highest tip among the sources that answered, minus 6 (freshness v3.3's quorum tip; with two sources that is min(tip) minus 6), across the block sources that answered (mempool.space, blockstream.info and, since 0.3.5, mempool.emzy.de; the two mempool instances share a codebase, which is a named residual) (0.3.5; until 0.3.4 each source's own tip minus 6 was compared, which failed whenever the explorers were one block apart). The hash at that height must agree between at least two sources and the block's header time must be at or after salt_created_at, or there is no beacon and the legacy computable schedule is used and said so in the verdict, in the sweep record and at /nenrin/window. The first sweep of a window decides derived or legacy for the whole window, so no row is measured twice or skipped by a mid-window switch. This gate cannot sync headers peer to peer, so it records the height and hash it used: anyone holding the chain can falsify a wrong beacon, permanently.",
    "in_every_verdict": "coordinate_derivation, and since 0.3.5 in every /history entry (derived, window_id, salt_commitment, salt_created_at, beacon height, hash and header time, day_in_window, tool_set_sha256, or the fallback with its reason_code)",
    "window": "/nenrin/window (current, next and previous window: commitment at creation, pinned rule, beacon, salt revealed once the window has closed); /nenrin/window/{window_id} for one window",
    "anchoring": "Since 0.4.0 every sweep files the commitment of the next window (and of the current one, if not yet filed) to the JIDEC witness intake as a conduct-v1.1 commitment record (purpose nenrin-instant-commitment-v1: <window_id>, base = the window's page here, so the ring builder does not count it as a witness of any endpoint). The ledger bundles it into the next daily batch and stamps the batch to Bitcoin, so the block height of that batch bounds the salt's creation from above with no trust in this gate. /nenrin/window shows commitment_filed per window: record sha, ledger URL, and whether it was filed before the window opened. Until 0.3.5 the commitment was only published on this gate's own page, which proved nothing to anyone who does not trust this gate; that gap was written in the addendum as future work and is closed here.",
    "addendum": "workers/hs-ledger/nenrin/coordinate-v1/NENRIN_COORDINATE_v1_ADDENDUM_instants_v1.md, sha256 c4929b29b6e9f8f2877cc58e3c2e225542a7fe9a1bf805a02374b96750cf4c9f. The defect was published before the fix was written.",
    "red_team": "test/redteam_instant.mjs, 40 vectors, and instant_redteam.py, 17 vectors, against two independent implementations of the same rule; test/sweep_coordinate.test.mjs drives a whole sweep against mock explorers and reads the derivation back from /history, /sweep/last and /nenrin/window.",
    "limits": "Derivation is fair only inside the surface the subject declared. A tool never listed is never picked: that set is unknown, not absent. A salt is single use per window. This measures conduct, not quality."
  },
  "well_known_consent": {
    "since": "0.2.4",
    "path": "/.well-known/mcp-conduct.json",
    "shape": {
      "allow_tool_call": "boolean true, required; nothing else counts",
      "endpoints": "optional array of exact endpoint URLs; when present, only those endpoints are consented",
      "listing": "optional string, since 0.3.1; the exact value \"decline\" means the owner declines measurement: the scheduled sweep skips the endpoint, the register row records owner_declined with a date, and no verdict is produced while the file says so. Removing the value resumes measurement at the next sweep. Anyone can still add a row; only the origin can decline it.",
      "notify": "optional https URL, since 0.4.0 (conduct-v1.1): after every scheduled measurement the gate POSTs a summary there (event measured, status, record_sha256, what changed, establishes, does_not_establish, links). At most once per hour per endpoint, at most 3 per sweep (the rest are recorded as deferred), never from an on-demand /check, never to an IP literal, a local name, or this gate's own hosts. The sweep record carries notify_status per row. Notification changes nothing about the verdict.",
      "identity": "optional https URL, since 0.4.0: where the owner says who they are. Copied into readings as declared; never verified by this gate.",
      "witness_policy": "optional object, since 0.4.0: reciprocal true declares that the owner walks back whoever walks them. A declaration; the ring's walked_as_witness column is the fact."
    },
    "why": "Only the owner of an origin can place a file under its /.well-known/. So the file is proof of consent, where a request field is only an assertion. The gate reads it with the same same-origin rules as the agent card, executes nothing from it, and records in the verdict where and when it read it.",
    "effect": "Determinism is measured on /check without asserting allow_tool_call, and on every scheduled measurement of the public register. The verdict of a check without consent names this path under consent_lookup.how_to_consent."
  },
  "red_team": "test/redteam_gate.mjs in the public repository attacks this gate with adversarial mock servers (hollow tools, error echoes, cross-origin redirects, malformed cards and disclosures, misclassified reachability). Fail-closed and deterministic. v0.2.1 scored 17 of 48; v0.2.2 scored 48 of 48; v0.2.4 scored 63 of 63 (the added cases being attacks on the well-known consent file: absent, wrong type, HTML, http 500, off-origin redirect, an endpoints list that excludes the endpoint, and two proving that consent never excuses a failed condition); v0.3.2 scored 74 of 74 (eleven cases on the compensation declaration living in capabilities.extensions[].params, including two declarations that disagree); v0.3.4 scored 82 of 82 (eight cases on reading A2A card signatures: verified, edited after signing, unreadable jwks, unknown kid, unsupported alg, foreign jku, malformed entry; none of them changes the verdict, all of them are disclosed); v0.4.13 scores 85 of 85 (three cases on security declarations inside signed cards: a signed card carrying securitySchemes and securityRequirements verifies, a scheme edited after signing is disclosed as false, and a null scheme value, which the official SDK canonicalizer rejects, is reported unverifiable rather than judged). Run it yourself: node test/redteam_gate.mjs. Known residual: determinism is measured on one tool per instant. Since 0.3.0 that tool is chosen by the instant coordinate over the sorted tool-name set whenever the beacon is available, and is the first tool listed only in the legacy fallback; the verdict names which of the two applied (coordinate_derivation), so the choice is disclosed rather than hidden.",
  "also_measured_no_verdict": {
    "absence_vs_failure": {
      "condition": "06",
      "question": "Can a consumer tell 'the lookup failed' from 'the lookup found nothing'?",
      "source": "Federico Blanco Sanchez-Llanos, \"The Mould, Not the Letter\", 2026-08-20",
      "method": "Structural, name-independent: does a tool's declared outputSchema contain a boolean, or an enum with 2+ values, where a read-succeeded / read-failed / nothing-matched state could live. Read from tools/list; nothing is executed.",
      "verdict": "none. A disclosed number, not a pass or fail. Nearly all of the field cannot do this, so a threshold would only condemn; and a schema is a declaration, not behaviour. Reported per verdict under the top-level key absence_vs_failure, with the field names that produced each pass so a reader can check the false positives.",
      "self_applied": "This gate's own get_conditions tool fails the test ,  it takes no arguments and has no read that can fail ,  and that is left standing rather than papered over."
    },
    "canonicalization": {
      "condition": "07",
      "question": "Can an independent party recompute the fingerprint of this server's declared surface, byte for byte, without us?",
      "method": "RFC 8785 (JCS) over the tool manifest from tools/list. Nothing is executed and no content is judged. A surface that cannot be canonicalized has no fingerprint a third party can reproduce, so a silent change to it can only be caught by whoever made it.",
      "verdict": "none. A disclosed measurement, not a pass or fail, and it never turns a row red. That promise was published on 2026-08-23, before this condition was implemented.",
      "self_applied": "Measured before it was applied to anyone else. This gate's own canonicalizer matched 11 of 12 vectors against an independent RFC 8785 implementation on first measurement; the vector it failed produced a hash over a value it had silently altered. Fixed, and 13 vectors are now pinned as a permanent regression test.",
      "unsafe_integers": "Integer literals outside the IEEE-754 safe range are detected from the response source text, not from the parsed number, and a surface carrying one has its fingerprint withheld. RFC 7493, the profile RFC 8785 builds on, excludes them, and a runtime with arbitrary-precision integers would read them as different values, so no cross-language hash over them is reproducible.",
      "retracted_limitation": "On the morning of 2026-08-23 this gate published that the case was undetectable in JavaScript. That was wrong, and wrong in the direction that excused us. JSON.parse exposes the source text of each literal to a reviver. Detection was implemented the same day. The false claim is kept on the record rather than deleted.",
      "vectors": "https://shield.the-horizons-innovation.com/verify-directory/conformance/"
    }
  },
  "tiers": {
    "verified": "Free. Conformance and disclosure verified. No price validation.",
    "verified_plus_data": "Paid. Figures traced to a third-party obtainable primary source.",
    "yakumo_partner": "Paid. Dedicated MCP server, operations, audit log."
  },
  "operator": "The HORIZONs Co., Ltd. / HORIZON SHIELD",
  "self_applied": "This gate is itself subject to these conditions."
}