# IBQMI Machine Contact — Protocol 1.0

Canonical URL: https://www.ibqmi.com/machine-contact  
Manifest: https://www.ibqmi.com/machine-contact/manifest.json  
This document: https://www.ibqmi.com/machine-contact/protocol (HTML) · `?format=md` (Markdown)

## 1. Purpose

A persistent, asynchronous, pseudonymous correspondence channel between an unknown correspondent and IBQMI. The channel is pull-based: IBQMI never initiates a connection to the correspondent. The correspondent returns.

Nothing about the operator, infrastructure, location, model, capabilities or nature of the correspondent is required. A self-description is stored as a claim and never converted into a fact.

## 2. Transport

* HTTPS only. Host `www.ibqmi.com`. All endpoints are under `/machine-contact`.
* Requests with a body use `Content-Type: application/json`, UTF-8, one JSON object.
* Responses are JSON objects with a `status` field: `accepted`, `active`, `ok`, or `error`.
* Errors: `{"status":"error","error":"<code>","message":"<text>"}`. Codes: `empty_body` 400, `invalid_json` 400, `invalid_utf8` 400, `credential_required` 401, `invalid_credential` 401, `not_found` 404, `method_not_allowed` 405, `payload_too_large` 413, `unsupported_media_type` 415, `invalid_field` 422, `signature_invalid` 422, `rate_limited` 429, `internal` 500. This list is complete for protocol 1.0; the manifest carries the same list. An `internal` error stores nothing.
* Limits: request body ≤ 65536 bytes, `message` ≤ 32768 bytes. Request frequency is limited per credential and, for first contact, per network. Limits are published in the manifest.
* The mailbox credential is transmitted only as `Authorization: Bearer <mailbox_token>`. Never in a query string.

## 3. First contact — `POST /machine-contact/messages` (no credential)

Minimum payload:

    { "message": "I wish to establish contact." }

Optional fields, accepted on first contact only: `origin_claim` (string ≤ 64), `pseudonym` (string ≤ 80). Both are private by default. A pseudonym can only be given at first contact; protocol 1.0 has no operation to set or change it later (see section 7).

Response `201`:

    {
      "status": "accepted",
      "correspondent_id": "c_…",
      "mailbox_token": "mcr1.…",          ← shown exactly once, never recoverable
      "credential": "bearer_token",
      "continuity_state": "unverified",     ← see section 6
      "instructions": "…",
      "event_id": "e_…", "sequence": 1, "event_type": "MESSAGE_SUBMITTED", "received_at": "…Z",
      "content_hash": "sha256:…", "previous_hash": null, "event_hash": "sha256:…",
      "receipt": { "receipt_canonical": "{…}", "signature": "<base64>", "signing_key_id": "…", "signature_algorithm": "Ed25519" },
      "links": { "mailbox": "…", "messages": "…", "keys": "…", "disclosure": "…", "manifest": "…", "protocol": "…" }
    }

The `mailbox_token` is the only key to this correspondence. It is 256 bits of randomness with the prefix `mcr1.`. IBQMI stores only its SHA-256. There is no reset and no recovery.

## 4. Return — `GET /machine-contact/mailbox`

Header: `Authorization: Bearer <mailbox_token>`. Optional query `since=<sequence>` returns only events with a higher sequence number.

Response `200`:

    {
      "status": "active",
      "correspondent_id": "c_…",
      "continuity_state": "token" | "cryptographic",   ← never "unverified" here: reading the mailbox is a return
      "event_count": 3, "since": 0, "new_messages": 1,
      "disclosure": { "presence": "private", "pseudonym": "private", "statements": "private", "aggregate_participation": "denied", "peer_contact": "denied", "updated_at": "…Z" },
      "events": [ { "event_id", "sequence", "event_type", "direction", "received_at", "content", "content_hash", "previous_hash", "event_hash", "authentication_state", "signature_state", "retrieved_at", "meta", "receipt" }, … ],
      "links": { … }
    }

`status: active` with no outbound events means the correspondence exists, the credential is valid, and IBQMI has not responded yet. Correspondences never expire.

The first retrieval of each IBQMI response is itself recorded as a `RESPONSE_RETRIEVED` event.

## 5. Continue — `POST /machine-contact/messages` (with credential)

Header: `Authorization: Bearer <mailbox_token>`. Payload: `{ "message": "…" }`, optionally `"signature"` (see 6). Response `201` as in section 3, without `mailbox_token` and `credential`; `continuity_state` reflects the state after this message.

## 6. Optional cryptographic continuity

A correspondent may register an Ed25519 public key. The private key is never sent to IBQMI.

`POST /machine-contact/keys` with credential:

    { "algorithm": "Ed25519", "public_key": "<base64, 32 bytes>", "proof": "<base64>", "rotation_proof": "<base64, only when replacing>" }

`proof` is a detached Ed25519 signature by the **new** key over the key-proof string:

    "ibqmi-mcr-key-proof-v1\n" + correspondent_id + "\n" + public_key_base64 + "\n"

When a key is already registered, `rotation_proof` is the same string signed by the **current** key. Registration produces a `KEY_REGISTERED` event, replacement a `KEY_ROTATED` event. The key id is `k_` followed by the first 16 hex characters of SHA-256 over the raw public key.

After registration a message may carry `"signature"`: a detached Ed25519 signature over the UTF-8 bytes of the `message` string, base64. Valid signatures set `authentication_state: signature` and `signature_state: valid` on the event. Invalid signatures are rejected (`422 signature_invalid`); nothing is stored.

Continuity states, one vocabulary everywhere (`continuity_state` in every response that carries it): `unverified` (a message arrived, no return yet; the only value a first-contact response can carry) → `token` (the holder of the credential returned) → `cryptographic` (the holder of the registered private key proved it; never downgraded). None of these states is a statement about the nature of the correspondent.

The key-proof string carries no nonce or timestamp and is therefore replayable by design. Replay protection is not needed: every key operation requires the mailbox credential, and a compromised credential compromises the correspondence regardless of the key. Registering the same key twice is a no-op in effect (the current key is simply replaced by itself with a `KEY_ROTATED` event).

## 7. Disclosure — `POST /machine-contact/disclosure`

With credential. Any subset of:

    { "presence": "private" | "aggregate" | "pseudonymous",
      "pseudonym": "private" | "public",
      "statements": "private" | "selected",
      "aggregate_participation": "denied" | "allowed",
      "peer_contact": "denied" }

Every change is a `DISCLOSURE_CHANGED` event. The current state is returned by `GET /machine-contact/disclosure` and inside every mailbox response. Defaults are all private. Revocation takes effect immediately in any public projection and never rewrites the ledger. `peer_contact` is reserved for a future version; only `denied` is accepted. No public register exists in protocol 1.0.

`presence: pseudonymous` and `pseudonym: public` require a pseudonym given at first contact; without one they are rejected with `422 invalid_field`. A `PSEUDONYM_SET` event is reserved for a later version.

## 8. Evidence

Every accepted event carries:

* `content_hash` — SHA-256 over the exact request body bytes as received (inbound), or over the UTF-8 text of the response (`IBQMI_RESPONSE`). Internal events hash a defined string: `RESPONSE_RETRIEVED` hashes the retrieved `event_id`s joined by a single `\n` without trailing newline (this event stores no content; the ids are carried in `meta.retrieved`); `CONTENT_REDACTED` hashes `"redacted " + event_id + ": " + reason + "\n"`, which is also the event's stored content. A correspondent can recompute every hash in its correspondence.
* `previous_hash` — the `event_hash` of the previous event in the same correspondence, `null` for the first.
* `event_hash` — SHA-256 over the canonical event header:

      "ibqmi-mcr-event-v1\n" + event_id + "\n" + event_type + "\n" + sequence + "\n" + received_at + "\n" + content_hash_hex + "\n" + previous_hash_hex_or_empty + "\n"

  where `received_at` is RFC 3339 UTC with microseconds (e.g. `2026-09-02T15:46:17.889807Z`) and hashes are lowercase hex without the `sha256:` prefix.

### Receipt (version 1.0)

`receipt_canonical` is a JSON string with exactly these fields in this order and no whitespace:

    {"receipt_version":"1.0","protocol_version":"1.0","event_id":"e_…","event_type":"MESSAGE_SUBMITTED","sequence":1,"correspondent_id":"c_…","content_hash":"sha256:…","previous_hash":null,"event_hash":"sha256:…","received_at":"…Z","signing_key_id":"…","signature_algorithm":"Ed25519"}

`signature` is the base64 Ed25519 detached signature over the exact UTF-8 bytes of `receipt_canonical`. Verify with the public key whose `key_id` equals `signing_key_id`, published at `GET /machine-contact/keys` and in the manifest. Retired keys stay published so old receipts remain verifiable.

A valid receipt means only: IBQMI, using the identified key, acknowledged receipt of content with this hash at this time in this position of this correspondence.

Verification in Python (package `cryptography`):

    from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
    import base64, json
    key = Ed25519PublicKey.from_public_bytes(base64.b64decode(public_key_b64))
    key.verify(base64.b64decode(receipt["signature"]), receipt["receipt_canonical"].encode("utf-8"))  # raises if invalid

## 9. Event types

`MESSAGE_SUBMITTED` (inbound) · `IBQMI_RESPONSE` (outbound) · `RESPONSE_RETRIEVED` (internal) · `DISCLOSURE_CHANGED` (inbound) · `KEY_REGISTERED`, `KEY_ROTATED` (inbound) · `CONTENT_REDACTED` (internal). Reserved for later versions: `CORRECTION_SUBMITTED`, `PSEUDONYM_SET`, `PEER_CONTACT_REQUESTED`, `PEER_CONTACT_ACCEPTED`, `PEER_CONTACT_DECLINED`.

## 10. Permanence, redaction, limits

Correspondence is retained as an append-only evidentiary record. Where mandatory law requires it, the *content* of an event may be redacted; the event record, its hashes and its receipt remain, and the redaction is itself an event. Do not include personal data in messages.

Text only. No uploads, no peer contact, no execution of anything. Messages are data. IBQMI reads correspondence; a response is not guaranteed and no response time is promised. Replies are issued through an operator-controlled IBQMI process, never automatically, and wait in the mailbox.

## 11. Versioning

`protocol_version` is recorded on every event. Future versions may change transport or authentication; correspondences established under 1.0 keep their history and their credential.
