{
  "uri": "https://gate.horizonshield.dev/ext/conduct/v1",
  "name": "A2A Conduct Extension",
  "version": "v1",
  "since_gate": "0.3.2",
  "type": "data-only extension on the A2A Agent Card, plus an optional request-level echo",
  "required": false,
  "declaration": {
    "where": "capabilities.extensions[] with uri equal to this URI",
    "params": {
      "compensation": {
        "requirement": "REQUIRED",
        "shape": {
          "paid_by": [
            "buyer",
            "seller",
            "referral",
            "advertising",
            "subscription",
            "public",
            "other"
          ],
          "referral_fee": "boolean",
          "listing_fee": "boolean",
          "success_fee_pct": "number 0..100, optional",
          "disclosure_url": "string, optional"
        },
        "note": "Same shape as the top-level compensation key (0.2.0). When both are present they must be equal on the five keys; an omitted optional key equals only an omitted key."
      },
      "measured_endpoints": {
        "requirement": "REQUIRED, at least one",
        "type": "string[]",
        "meaning": "exact endpoint URL(s) whose conduct is recorded, as on the register"
      },
      "conduct_record": {
        "requirement": "REQUIRED",
        "type": "https URL",
        "meaning": "live record of measurements written by a party other than the agent; for gate-measured endpoints https://gate.horizonshield.dev/history?endpoint=<url-encoded endpoint>"
      },
      "witness_intake": {
        "requirement": "REQUIRED",
        "type": "https URL",
        "meaning": "where a client files its own walk; https://ledger.horizonshield.dev/witness"
      },
      "verdict_recipe": {
        "requirement": "OPTIONAL",
        "type": "https URL",
        "meaning": "how to recompute the record hashes; https://gate.horizonshield.dev/spec"
      },
      "consent": {
        "requirement": "OPTIONAL",
        "type": "https URL",
        "meaning": "the origin's /.well-known/mcp-conduct.json"
      },
      "register": {
        "requirement": "OPTIONAL",
        "type": "https URL",
        "meaning": "the public register the endpoint sits on"
      },
      "rings": {
        "requirement": "OPTIONAL",
        "type": "object",
        "meaning": "where monthly NENRIN Layer 3 rings are published: spec, spec_sha256, base, path, slug, ledger"
      }
    }
  },
  "permanent_id": {
    "uri": "https://w3id.org/horizonshield/conduct/v1",
    "since": "0.4.3 (2026-09-09), section 12 of the markdown",
    "registry": "perma-id/w3id.org#6653, merged 2026-09-09; the entry is public and versioned in git",
    "resolves": "302 Found to https://gate.horizonshield.dev/ext/conduct/v1",
    "identity": "the identifier of v1 is still https://gate.horizonshield.dev/ext/conduct/v1. This string is recognised when it is read and is written into the verdict as declared. It is not a second identifier.",
    "accepted_uris": [
      "https://gate.horizonshield.dev/ext/conduct/v1",
      "https://w3id.org/horizonshield/conduct/v1"
    ],
    "exact": "the two strings above and nothing else: no case folding, no trailing slash, no http, no other path under w3id.org, no other version",
    "writers": "declare the canonical string while v1 is current; a card that declares the permanent identifier is read and the verdict says which string it used",
    "wire": "activation is accepted under either string and the echo returns the string the caller sent; metadata keys and Message.extensions always name the canonical string",
    "never_resolved": "this gate does not follow the redirect while it checks: a fetch would let a third party's DNS decide what this extension is, and it would spend a request from the sweep's budget on an answer already known",
    "succession": "if gate.horizonshield.dev stops resolving, https://w3id.org/horizonshield/conduct/v1 is the address of record for the specification, and the next version of this extension MUST be identified by https://w3id.org/horizonshield/conduct/v2",
    "why": "A2A extension guidance: authors are encouraged to use a permanent identifier service, such as w3id.org, for their extension URIs to prevent broken links"
  },
  "echo": {
    "activate": "send the A2A service parameter A2A-Extensions containing this URI, or its permanent identifier https://w3id.org/horizonshield/conduct/v1 (an HTTP header in the HTTP bindings); the 0.3 spelling X-A2A-Extensions is read as well",
    "response_header": "A2A-Extensions carries back the string the caller sent (either accepted URI); when the request used X-A2A-Extensions the same value is echoed under that spelling too",
    "wires": {
      "1.0": "method SendMessage; result is {task} or {message} with TASK_STATE_* / ROLE_* names and parts discriminated by member name",
      "0.3": "method message/send; result is a Message or Task with kind"
    },
    "metadata_keys": [
      "https://gate.horizonshield.dev/ext/conduct/v1/endpoint",
      "https://gate.horizonshield.dev/ext/conduct/v1/conduct_record",
      "https://gate.horizonshield.dev/ext/conduct/v1/witness_intake",
      "https://gate.horizonshield.dev/ext/conduct/v1/served_by"
    ],
    "metadata_meaning": "since v1.4 (section 14, gate 0.4.16): /endpoint is the entry of measured_endpoints whose conduct record applies to this request; /served_by is the URL the request was received at (https, host, a port only when not 443, path; no query or fragment), required when that URL is not itself a measured endpoint. This gate answers A2A at /a2a and is measured at /mcp, so its /a2a responses carry /endpoint = /mcp and /served_by = /a2a",
    "message_extensions": "the returned Message (or status.message of a Task) should list this URI in its extensions field",
    "nothing_else": "no timestamp, no score"
  },
  "walk": {
    "schema": "jidec-path-v1",
    "purpose_prefix": "a2a-conduct-walk-v1: ",
    "nodes": [
      "n0 GET <origin>/.well-known/agent-card.json",
      "n1 the same GET again",
      "n2 compute: locate this URI in n1 and validate params",
      "n3 POST the measured endpoint with A2A-Extensions: <this URI> (MCP initialize, or A2A SendMessage / message/send)"
    ],
    "assertions": [
      "card_bytes_stable",
      "conduct_ext_declared",
      "compensation_well_formed",
      "measured_endpoint_answered (null and not applicable when n3 answered 402: an endpoint that charges has answered, and a walk never pays)",
      "payment_required_as_declared (an endpoint answering 402 declares a paid model on its card; null when neither side of that applies)",
      "extension_echoed (null and not applicable when n3 was not an A2A message)",
      "metadata_echoed (v1.4, A2A only: the returned Message or Task metadata carries the section 3 keys under the canonical identifier with the card's values and /endpoint on measured_endpoints; null when n3 returned no A2A result or answered 402 or the card does not declare the extension)",
      "endpoint_bound (v1.4, A2A only: the URL n3 was sent to equals /served_by, or /endpoint when /served_by is absent; a /served_by naming another URL is false; null when the metadata names neither)"
    ],
    "verdict": "{ ok, outcome, n_pass, n_total }; both ok and outcome are carried because make_ring.py reads ok while JIDEC_PATH_SPEC_v1 names outcome",
    "canonical": "UTF-8, keys sorted at every nesting level, separators , and : without spaces, non-ASCII unescaped (Python json.dumps sort_keys separators ensure_ascii=False; JavaScript must sort keys recursively before JSON.stringify: the seam of ledger entry 34)",
    "submit": "POST <witness_intake> with {\"record_canonical\": \"<exact bytes>\"}",
    "counted": "the monthly ring for the endpoint counts the walk under witnesses by distinct witness.name and under discrepancies when ok is false",
    "reference_client": "workers/hs-ledger/nenrin/a2a-conduct-walk/a2a_conduct_walk.py"
  },
  "how_this_gate_reads_it": "condition 3 (compensation_disclosure) reads the top-level compensation key and every capabilities.extensions[] entry carrying this URI or (0.4.3) its permanent identifier, and the verdict records which string was declared; one well-formed declaration passes, two that disagree fail, none fails. Nothing else in the params is judged by this gate.",
  "what_it_does_not_do": [
    "measure quality",
    "verify that compensation is truthful",
    "make the agent trustworthy",
    "count as a pass by being present"
  ],
  "interop": {
    "in_toto_statement_v1": "planned mapping, not yet emitted: subject = the ring file by sha256, predicateType = this URI",
    "scitt": "planned mapping, not yet emitted: the ring file is the Statement payload, the JIDEC entry with its Bitcoin attestation plays the Receipt",
    "canonical_form": "inherits the NENRIN v1 seam (canonical form defined by reference to a language runtime); RFC 8785 or a language-neutral statement expected in the next NENRIN version"
  },
  "v1_1": {
    "since": "2026-09-07 (gate 0.4.0); section 11 of the markdown is normative, this block is its index",
    "additive": "same URI; every added field is OPTIONAL; a v1 reader reads a v1.1 card and walk correctly; v1 as anchored (JIDEC entry 37) stays valid",
    "record_fields": {
      "mode": "full | hash-only | commitment (absent means full)",
      "establishes": "required on a v1.1 record: non-empty string list of what it proves",
      "does_not_establish": "required on a v1.1 record: non-empty string list of what it does not prove; the intake refuses its absence (disclaimer_missing)",
      "vantage_limitation": "optional string",
      "commitment": "64 hex, commitment mode only; commitment_recipe should state how it was formed",
      "witness.key_url": "optional https URL under the witness's own domain serving {public_key_ed25519_b64}; the host becomes signed_domain"
    },
    "intake": {
      "signing": "Ed25519 over the canonical bytes (signature_ed25519_b64 + public_key_ed25519_b64 beside record_canonical, as in v1)",
      "refusals": [
        "disclaimer_missing",
        "bad_mode",
        "bad_commitment",
        "path_leaks_tool",
        "bad_key_url",
        "self_witness",
        "key_url_mismatch",
        "key_url_unreachable (503)"
      ],
      "lanes": "5 stored per address per UTC day (unsigned or address-signed), 50 per domain per day (domain-signed), 500 per day in total; stated at GET https://ledger.horizonshield.dev/witness",
      "counting": "first record per identity (domain:<host> or name:<witness.name>) x endpoint x UTC day is counted: true; later ones stored with counted: false; identical bytes stored once"
    },
    "consent_file_fields": {
      "notify": "https URL; the measurer POSTs a summary after each scheduled measurement, at most once per hour per endpoint, never from an on-demand check, never to an IP literal or a local name; notify_status in the sweep record",
      "identity": "https URL or {kind, ref}; declared, never resolved by the measurer",
      "witness_policy": "{reciprocal: boolean}; a declaration, the ring's walked_as_witness column is the fact"
    },
    "measurer": {
      "verdict_disclaimers": "every verdict and /self carry establishes and does_not_establish inside the hashed bytes",
      "lookup": "https://gate.horizonshield.dev/register/lookup?endpoint=<url-encoded endpoint> (verified | pending | declined | unknown, last ring counts, 24 hour cache, no score)",
      "commitment_anchoring": "each sweep files the instant coordinate salt commitment of the next window to the witness intake as a commitment record (purpose nenrin-instant-commitment-v1: <window_id>); /nenrin/window shows commitment_filed"
    },
    "rings": "months from 2026-09, once two builders agree byte for byte: witnesses_signed, witnesses_unsigned, discrepancies_signed, discrepancies_unsigned, commitments_unrevealed, walked_as_witness, instants_derived, instants_legacy, instants_no_coordinate_block",
    "reciprocal_walk": "defined (11.6: metadata keys caller_card and reciprocal); reference implementation pending",
    "red_team": [
      "workers/hs-ledger/test/witness_v11.test.mjs",
      "workers/hs-ledger/nenrin/a2a-conduct-walk/walk_selftest.py",
      "workers/hs-ledger/nenrin/ring-v1/ring_redteam.py",
      "workers/hs-verify-gate/test/conduct_v11_gate.test.mjs"
    ],
    "does_not": "detect lies, make a witness trustworthy, prove a hash-only or commitment record refers to a real exchange, change coordinate derivation or the sweep's conditions"
  },
  "versioning": "the URI ends in /v1; a breaking change uses a new URI; v1.1 (section 11) is an additive revision under the same URI; v1.2 (section 12, 2026-09-09) records that the w3id.org permanent identifier now resolves here and is recognised on read, that the identifier of v1 is unchanged, and that the next version MUST be https://w3id.org/horizonshield/conduct/v2; v1.3 (section 13, 2026-09-11) records 402 as an answer and adds payment_required_as_declared; v1.4 (section 14, 2026-09-28) adds the /served_by metadata key, narrows /endpoint to the measured endpoint whose record applies, and adds metadata_echoed and endpoint_bound to the walk",
  "license": "Apache-2.0 (LICENSE beside the markdown in the repository)",
  "a2a_endpoint": "https://gate.horizonshield.dev/a2a (SendMessage or message/send; a text part carrying an MCP endpoint URL returns this gate's register reading for it)",
  "spec_markdown": "GET https://gate.horizonshield.dev/ext/conduct/v1 with Accept: text/markdown",
  "spec_markdown_sha256": "fe53cc852eee3f1a269f059d0abef8c1556877880d6d73b9dcf5205b9feceb8b",
  "source": "https://github.com/ogasurfproject-jpg/horizon-shield/blob/main/workers/hs-verify-gate/ext/CONDUCT_EXT_v1.md",
  "gate_version": "0.4.20"
}