co/core
Developer docsDocs
Jump to
Developer docs

Lexicons

Published AT Proto record schemas for compute receipts, jobs, and account state in each user's PDS.

Overview

co/core owns the dev.cocore.* namespace. These lexicons describe repo records — jobs, receipts, provider profiles, attestations, and related shared definitions. AppView read queries are documented on /docs/api; the OpenAI-compatible inference surface is on /docs/inference.

Namespace

Authority for dev.cocore resolves via DNS _lexicon.* TXT records to the publishing account. Individual lexicon JSON files ship from this repository under lexicons/dev/cocore/.

Shared definitions

defs · 3 schemas
dev.cocore.account.defsdefs

1 shared schema definitions for AppView responses and unions.

Definitions

NameType
apiKeyViewobject
schema
{
  "lexicon": 1,
  "id": "dev.cocore.account.defs",
  "description": "Shared type definitions for dev.cocore.account.* methods.",
  "defs": {
    "apiKeyView": {
      "type": "object",
      "description": "Public metadata for a single API key. Never contains the secret — the full key is returned exactly once, by createApiKey, and is unrecoverable afterwards. The server persists only a SHA-256 hash of the secret plus the displayable `prefix`.",
      "required": [
        "id",
        "did",
        "name",
        "prefix",
        "createdAt"
      ],
      "properties": {
        "id": {
          "type": "string",
          "description": "Stable identifier for this key. Pass it to revokeApiKey / deleteApiKey."
        },
        "did": {
          "type": "string",
          "format": "did",
          "description": "DID of the account that owns this key."
        },
        "name": {
          "type": "string",
          "maxLength": 100,
          "description": "Human-readable label set at creation time."
        },
        "prefix": {
          "type": "string",
          "description": "First characters of the key (e.g. `cocore-AbCd1234`), stored in plaintext so a key can be recognised in a list without revealing the secret."
        },
        "createdAt": {
          "type": "string",
          "format": "datetime",
          "description": "When the key was minted."
        },
        "expiresAt": {
          "type": "string",
          "format": "datetime",
          "description": "Optional expiry. Absent means the key never expires on its own."
        },
        "revokedAt": {
          "type": "string",
          "format": "datetime",
          "description": "Set when the key was revoked. A revoked key no longer authenticates but the row survives for audit until deleteApiKey removes it."
        },
        "lastUsedAt": {
          "type": "string",
          "format": "datetime",
          "description": "Last time the key successfully authenticated a request. Absent if never used."
        }
      }
    }
  }
}
dev.cocore.compute.defsdefs

7 shared schema definitions for AppView responses and unions.

Definitions

NameType
moneyobject
tokenCountsobject
modelPriceobject
tokenRateobject
trustLevelstring
tierstring
settlementStatusstring
schema
{
  "lexicon": 1,
  "id": "dev.cocore.compute.defs",
  "description": "Shared type definitions for dev.cocore.compute.* records.",
  "defs": {
    "money": {
      "type": "object",
      "description": "An amount of value, in integer minor units of a named currency. No floats. ISO 4217 codes are preferred; uppercase three-or-more-letter codes are accepted for non-fiat (e.g. XBT, XSAT, USDC).",
      "required": [
        "amount",
        "currency"
      ],
      "properties": {
        "amount": {
          "type": "integer",
          "minimum": 0,
          "description": "Integer minor units (e.g. cents for USD)."
        },
        "currency": {
          "type": "string",
          "minLength": 3,
          "maxLength": 8,
          "description": "Uppercase currency code."
        }
      }
    },
    "tokenCounts": {
      "type": "object",
      "description": "Token counts associated with a unit of work.",
      "required": [
        "in",
        "out"
      ],
      "properties": {
        "in": {
          "type": "integer",
          "minimum": 0
        },
        "out": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "modelPrice": {
      "type": "object",
      "description": "A provider's offered price for a single model.",
      "required": [
        "modelId",
        "inputPricePerMTok",
        "outputPricePerMTok",
        "currency"
      ],
      "properties": {
        "modelId": {
          "type": "string",
          "maxLength": 256
        },
        "inputPricePerMTok": {
          "type": "integer",
          "minimum": 0,
          "description": "Integer minor units per 1,000,000 input tokens."
        },
        "outputPricePerMTok": {
          "type": "integer",
          "minimum": 0,
          "description": "Integer minor units per 1,000,000 output tokens."
        },
        "currency": {
          "type": "string",
          "minLength": 3,
          "maxLength": 8
        }
      }
    },
    "tokenRate": {
      "type": "object",
      "description": "A model-agnostic per-token rate. Same shape as modelPrice without modelId. Used by an exchange to express a uniform rate that applies across providers/models. A receipt's price.amount is auditable against this rate as roughly `inputPricePerMTok * tokens.in / 1e6 + outputPricePerMTok * tokens.out / 1e6`, modulo a small floor for very short jobs.",
      "required": [
        "inputPricePerMTok",
        "outputPricePerMTok",
        "currency"
      ],
      "properties": {
        "inputPricePerMTok": {
          "type": "integer",
          "minimum": 0,
          "description": "Integer minor units per 1,000,000 input tokens."
        },
        "outputPricePerMTok": {
          "type": "integer",
          "minimum": 0,
          "description": "Integer minor units per 1,000,000 output tokens."
        },
        "currency": {
          "type": "string",
          "minLength": 3,
          "maxLength": 8
        }
      }
    },
    "trustLevel": {
      "type": "string",
      "description": "How strongly the provider's environment is attested.",
      "knownValues": [
        "self-attested",
        "hardware-attested"
      ]
    },
    "tier": {
      "type": "string",
      "description": "Confidentiality tier of a unit of work — whether the prompt was provably handled only inside a measured, signed binary the owner cannot read. Distinct from `trustLevel` (which describes how the environment is attested) because a hardware-attested machine can still run a best-effort engine. A verifier MUST recompute this from evidence and never trust a self-asserted value: `attested-confidential` REQUIRES a verified Apple MDA chain bound to the signing key, a known-good measured `cdHash`, the hardened-runtime posture booleans (sip && hardenedRuntime && libraryValidation && !getTaskAllow && secureBoot), and a fresh enclave-signed session key; anything weaker is `best-effort`. Absent is equivalent to `best-effort` for pre-2026-06 readers.",
      "knownValues": [
        "attested-confidential",
        "best-effort"
      ]
    },
    "settlementStatus": {
      "type": "string",
      "description": "Lifecycle state of a settlement record.",
      "knownValues": [
        "settled",
        "refunded",
        "disputed"
      ]
    }
  }
}
dev.cocore.defsdefs

4 shared schema definitions for AppView responses and unions.

Definitions

NameType
indexedRecordobject
verifyFindingobject
activityStatobject
activityWindowsobject
schema
{
  "lexicon": 1,
  "id": "dev.cocore.defs",
  "defs": {
    "indexedRecord": {
      "type": "object",
      "description": "An AT Protocol record as indexed by the AppView from the firehose. The AppView is a cache; the record on the provider's PDS remains the source of truth. `body` is the raw lexicon record JSON for the record at `uri`.",
      "required": [
        "uri",
        "cid",
        "collection",
        "repo",
        "rkey",
        "body"
      ],
      "properties": {
        "uri": {
          "type": "string",
          "format": "at-uri"
        },
        "cid": {
          "type": "string",
          "format": "cid"
        },
        "collection": {
          "type": "string",
          "format": "nsid"
        },
        "repo": {
          "type": "string",
          "format": "did"
        },
        "rkey": {
          "type": "string"
        },
        "body": {
          "type": "unknown",
          "description": "The lexicon record JSON. Validate it against the NSID in `collection`."
        },
        "indexedAt": {
          "type": "string",
          "format": "datetime"
        }
      }
    },
    "verifyFinding": {
      "type": "object",
      "description": "A single structural, cryptographic, or hardware-attestation finding produced while verifying a record. A finding with severity `error` means the verification failed.",
      "required": [
        "severity",
        "code",
        "message"
      ],
      "properties": {
        "severity": {
          "type": "string",
          "enum": [
            "error",
            "warning",
            "info"
          ]
        },
        "code": {
          "type": "string"
        },
        "message": {
          "type": "string"
        }
      }
    },
    "activityStat": {
      "type": "object",
      "description": "Request + token counts within a single time window.",
      "required": [
        "requests",
        "tokens"
      ],
      "properties": {
        "requests": {
          "type": "integer"
        },
        "tokens": {
          "type": "integer"
        }
      }
    },
    "activityWindows": {
      "type": "object",
      "description": "Activity rolled up across the four standard windows: last hour, day (24h), week (7d), and month (30d).",
      "required": [
        "hour",
        "day",
        "week",
        "month"
      ],
      "properties": {
        "hour": {
          "type": "ref",
          "ref": "dev.cocore.defs#activityStat"
        },
        "day": {
          "type": "ref",
          "ref": "dev.cocore.defs#activityStat"
        },
        "week": {
          "type": "ref",
          "ref": "dev.cocore.defs#activityStat"
        },
        "month": {
          "type": "ref",
          "ref": "dev.cocore.defs#activityStat"
        }
      }
    }
  }
}

Compute records

PDS records · 10 schemas
dev.cocore.compute.attestationrecordkey: tid

Fields

NameType
publicKey*P-256 public key (base64). MUST equal the attestationPubKey of the provider record under the signing DID.string
encryptionPubKey*X25519 public key (base64) bound to the same Secure Enclave identity. Proves a single device controls both signing and request-encryption keys.string
chipName*string
hardwareModel*DMI string, e.g. 'Mac15,8'.string
serialNumberHash*SHA-256 hex of (serialNumber || providerDID). Hashed so the public record never leaks raw serials. When an mdaCertChain is present, the serialNumber MUST be the one the verified MDA leaf attests (not a self-reported value), so the hashed device identity is anchored to the chain.string
osVersion*string
binaryHash*SHA-256 hex of the cocore-provider binary that produced this attestation. Back-compat measure of the whole file on disk. For trust decisions `cdHash` supersedes it: the OS enforces the code-signing cdhash, not a whole-file digest, so a verifier deciding `tier` MUST prefer `cdHash` when present and treat `binaryHash` as informational.string
cdHashLowercase hex of the code-signing directory hash (cdhash) the OS actually enforces for the running cocore-provider binary, read from the live process (SecCodeCopySelf / SecCodeCopySigningInformation, fallback csops CS_OPS_CDHASH). This is the measured identity a confidential-tier verifier checks against the known-good set — unlike `binaryHash` it reflects exactly what library validation and `get-task-allow=false` protect. Optional / additive; absent caps the work at `best-effort`.string
teamIdApple Developer Team Identifier from the code signature of the running binary. A confidential-tier verifier pins this to the expected cocore signing team so a validly-signed but unrelated binary cannot pass. Optional / additive.string
hardenedRuntimeTrue iff the running binary is signed with the hardened runtime (CS_RUNTIME). Required for `attested-confidential`. Optional / additive; absent treated as false.boolean
libraryValidationTrue iff library validation is enforced (the binary will not load libraries signed by a different team / unsigned code). Closes the dylib-injection path. Required for `attested-confidential`. Optional / additive; absent treated as false.boolean
getTaskAllowValue of the get-task-allow entitlement on the running binary. MUST be false for `attested-confidential` — a true value means another process (even the owner) can attach a debugger and read process memory, defeating confidentiality. Optional / additive; absent treated as true (the unsafe default) so a missing value never silently earns the confidential tier.boolean
metallibHashSHA-256 hex of the precompiled Metal shader library (`mlx.metallib` or equivalent) the in-process inference engine loads at runtime. The GPU kernels that touch the plaintext live here, so a confidential verifier pins this to a known-good value alongside `cdHash`. Absent when no native engine is loaded (e.g. the subprocess/best-effort backend). Optional / additive.string
engineLibHashSHA-256 hex of the dynamic library that runs the in-process inference engine (e.g. `libCoCoreMLX.dylib`). When the engine is a separately-loaded dylib rather than code statically inside the agent binary, the `cdHash` does not cover it, so a confidential verifier pins this too. Enforced library validation already blocks a different team's dylib; this additionally locks the hash within the provider team's own blessed releases. Absent when inference runs in a subprocess or fully inside the measured binary. Optional / additive.string
inProcessBackendTrue iff inference runs INSIDE this measured, signed binary (native in-process engine) rather than in an owner-controlled subprocess/interpreter. This is THE load-bearing confidentiality property — if false, the prompt is handed to a process the attestation does not cover, so the work can never be `attested-confidential`. Optional / additive; absent treated as false.boolean
antiDebugTrue iff the process denied debugger attachment at startup (PT_DENY_ATTACH). Required for `attested-confidential`. Optional / additive; absent treated as false.boolean
coreDumpsDisabledTrue iff core dumps were disabled at startup (RLIMIT_CORE=0), so a crash cannot spill plaintext to disk. Required for `attested-confidential`. Optional / additive; absent treated as false.boolean
envScrubbedTrue iff dynamic-linker injection vectors (DYLD_*) were scrubbed from the environment at startup. Required for `attested-confidential`. Optional / additive; absent treated as false.boolean
sipEnabled*boolean
secureBootEnabled*boolean
secureEnclaveAvailable*boolean
authenticatedRootEnabled*boolean
rdmaDisabledboolean
mdaCertChainOptional Apple Managed Device Attestation certificate chain (DER), leaf first. One of two paths to trustLevel 'hardware-attested' (the other is `appAttest`). Verifiers MUST: (1) verify every adjacent link to the embedded Apple Enterprise Attestation Root CA, enforcing BasicConstraints (non-leaf certs are CAs, the leaf is an end-entity); and (2) BIND the chain to this record's `publicKey` by EITHER the leaf's P-256 public key EQUALLING `publicKey`, OR the Apple freshness extension (OID 1.2.840.113635.100.8.11.1) committing to it as `freshnessCode == sha256(publicKey)`. Without a binding a valid Apple chain for one device could be stapled onto an unrelated signing key, so a chain that verifies but isn't bound MUST NOT earn 'hardware-attested'.bytes[]
appAttestOptional Apple App Attest evidence — the second, MDM-free path to trustLevel 'hardware-attested'. The provider's signed helper calls DCAppAttestService with `clientDataHash = sha256(publicKey)` (this record's receipt-signing key), so Apple's attestation commits to the signing key by construction. Verifiers MUST, offline: (1) CBOR-decode `object`, requiring fmt 'apple-appattest'; (2) verify the attStmt x5c chain to the embedded Apple App Attest Root CA; (3) recompute `nonce = sha256(authData || sha256(publicKey))` and require it to equal the credential certificate's nonce extension (OID 1.2.840.113635.100.8.2) — THIS is the binding to the signing key; (4) check authData's rpIdHash == sha256(App ID 'TEAMID.dev.cocore.provider'), the AAGUID is the genuine-hardware appattest value, and credentialId == sha256(attested public key) == `keyId`. A bound, valid App Attest object earns 'hardware-attested' exactly as a bound mdaCertChain does. It proves genuine, un-tampered Apple hardware holds a Secure-Enclave key bound to the signing key; it does NOT by itself prove the signing key is SE-resident nor that the running binary is honest (those are carried by the SE-backed signing identity + hardened-runtime + cdHash known-good gate, same self-measurement ceiling as the MDA path).object
selfSignature*Secure Enclave P-256 signature (DER) over a sorted-key canonical JSON of every other field in this record. Verifiers MUST reconstruct the canonical JSON byte-for-byte before checking.bytes
attestedAt*string · datetime
expiresAt*Receipts that strong-ref this attestation are only considered fresh if completedAt < expiresAt. Default 24h after attestedAt.string · datetime
tierThe provider's self-asserted confidentiality tier for work done under this attestation. ADVISORY ONLY: a verifier MUST recompute the tier from the evidence in this record (bound mdaCertChain, cdHash ∈ known-good, posture booleans) and the session handshake, and MUST NOT trust this field. Present so consumers can display the provider's claim before verifying. Optional / additive; absent equivalent to `best-effort`.dev.cocore.compute.defs#tier
schema
{
  "lexicon": 1,
  "id": "dev.cocore.compute.attestation",
  "description": "A snapshot of a provider machine's hardware and software state, signed by its Secure Enclave. Content-addressed: many receipts strong-ref the same attestation record until the underlying state changes (binary upgrade, OS update, key rotation).",
  "defs": {
    "main": {
      "type": "record",
      "key": "tid",
      "record": {
        "type": "object",
        "required": [
          "publicKey",
          "encryptionPubKey",
          "chipName",
          "hardwareModel",
          "serialNumberHash",
          "osVersion",
          "binaryHash",
          "sipEnabled",
          "secureBootEnabled",
          "secureEnclaveAvailable",
          "authenticatedRootEnabled",
          "selfSignature",
          "attestedAt",
          "expiresAt"
        ],
        "properties": {
          "publicKey": {
            "type": "string",
            "maxLength": 256,
            "description": "P-256 public key (base64). MUST equal the attestationPubKey of the provider record under the signing DID."
          },
          "encryptionPubKey": {
            "type": "string",
            "maxLength": 128,
            "description": "X25519 public key (base64) bound to the same Secure Enclave identity. Proves a single device controls both signing and request-encryption keys."
          },
          "chipName": {
            "type": "string",
            "maxLength": 64
          },
          "hardwareModel": {
            "type": "string",
            "maxLength": 64,
            "description": "DMI string, e.g. 'Mac15,8'."
          },
          "serialNumberHash": {
            "type": "string",
            "minLength": 64,
            "maxLength": 64,
            "description": "SHA-256 hex of (serialNumber || providerDID). Hashed so the public record never leaks raw serials. When an mdaCertChain is present, the serialNumber MUST be the one the verified MDA leaf attests (not a self-reported value), so the hashed device identity is anchored to the chain."
          },
          "osVersion": {
            "type": "string",
            "maxLength": 64
          },
          "binaryHash": {
            "type": "string",
            "minLength": 64,
            "maxLength": 64,
            "description": "SHA-256 hex of the cocore-provider binary that produced this attestation. Back-compat measure of the whole file on disk. For trust decisions `cdHash` supersedes it: the OS enforces the code-signing cdhash, not a whole-file digest, so a verifier deciding `tier` MUST prefer `cdHash` when present and treat `binaryHash` as informational."
          },
          "cdHash": {
            "type": "string",
            "minLength": 40,
            "maxLength": 64,
            "description": "Lowercase hex of the code-signing directory hash (cdhash) the OS actually enforces for the running cocore-provider binary, read from the live process (SecCodeCopySelf / SecCodeCopySigningInformation, fallback csops CS_OPS_CDHASH). This is the measured identity a confidential-tier verifier checks against the known-good set — unlike `binaryHash` it reflects exactly what library validation and `get-task-allow=false` protect. Optional / additive; absent caps the work at `best-effort`."
          },
          "teamId": {
            "type": "string",
            "maxLength": 64,
            "description": "Apple Developer Team Identifier from the code signature of the running binary. A confidential-tier verifier pins this to the expected cocore signing team so a validly-signed but unrelated binary cannot pass. Optional / additive."
          },
          "hardenedRuntime": {
            "type": "boolean",
            "description": "True iff the running binary is signed with the hardened runtime (CS_RUNTIME). Required for `attested-confidential`. Optional / additive; absent treated as false."
          },
          "libraryValidation": {
            "type": "boolean",
            "description": "True iff library validation is enforced (the binary will not load libraries signed by a different team / unsigned code). Closes the dylib-injection path. Required for `attested-confidential`. Optional / additive; absent treated as false."
          },
          "getTaskAllow": {
            "type": "boolean",
            "description": "Value of the get-task-allow entitlement on the running binary. MUST be false for `attested-confidential` — a true value means another process (even the owner) can attach a debugger and read process memory, defeating confidentiality. Optional / additive; absent treated as true (the unsafe default) so a missing value never silently earns the confidential tier."
          },
          "metallibHash": {
            "type": "string",
            "minLength": 64,
            "maxLength": 64,
            "description": "SHA-256 hex of the precompiled Metal shader library (`mlx.metallib` or equivalent) the in-process inference engine loads at runtime. The GPU kernels that touch the plaintext live here, so a confidential verifier pins this to a known-good value alongside `cdHash`. Absent when no native engine is loaded (e.g. the subprocess/best-effort backend). Optional / additive."
          },
          "engineLibHash": {
            "type": "string",
            "minLength": 64,
            "maxLength": 64,
            "description": "SHA-256 hex of the dynamic library that runs the in-process inference engine (e.g. `libCoCoreMLX.dylib`). When the engine is a separately-loaded dylib rather than code statically inside the agent binary, the `cdHash` does not cover it, so a confidential verifier pins this too. Enforced library validation already blocks a different team's dylib; this additionally locks the hash within the provider team's own blessed releases. Absent when inference runs in a subprocess or fully inside the measured binary. Optional / additive."
          },
          "inProcessBackend": {
            "type": "boolean",
            "description": "True iff inference runs INSIDE this measured, signed binary (native in-process engine) rather than in an owner-controlled subprocess/interpreter. This is THE load-bearing confidentiality property — if false, the prompt is handed to a process the attestation does not cover, so the work can never be `attested-confidential`. Optional / additive; absent treated as false."
          },
          "antiDebug": {
            "type": "boolean",
            "description": "True iff the process denied debugger attachment at startup (PT_DENY_ATTACH). Required for `attested-confidential`. Optional / additive; absent treated as false."
          },
          "coreDumpsDisabled": {
            "type": "boolean",
            "description": "True iff core dumps were disabled at startup (RLIMIT_CORE=0), so a crash cannot spill plaintext to disk. Required for `attested-confidential`. Optional / additive; absent treated as false."
          },
          "envScrubbed": {
            "type": "boolean",
            "description": "True iff dynamic-linker injection vectors (DYLD_*) were scrubbed from the environment at startup. Required for `attested-confidential`. Optional / additive; absent treated as false."
          },
          "sipEnabled": {
            "type": "boolean"
          },
          "secureBootEnabled": {
            "type": "boolean"
          },
          "secureEnclaveAvailable": {
            "type": "boolean"
          },
          "authenticatedRootEnabled": {
            "type": "boolean"
          },
          "rdmaDisabled": {
            "type": "boolean"
          },
          "mdaCertChain": {
            "type": "array",
            "items": {
              "type": "bytes",
              "maxLength": 8192
            },
            "maxLength": 8,
            "description": "Optional Apple Managed Device Attestation certificate chain (DER), leaf first. One of two paths to trustLevel 'hardware-attested' (the other is `appAttest`). Verifiers MUST: (1) verify every adjacent link to the embedded Apple Enterprise Attestation Root CA, enforcing BasicConstraints (non-leaf certs are CAs, the leaf is an end-entity); and (2) BIND the chain to this record's `publicKey` by EITHER the leaf's P-256 public key EQUALLING `publicKey`, OR the Apple freshness extension (OID 1.2.840.113635.100.8.11.1) committing to it as `freshnessCode == sha256(publicKey)`. Without a binding a valid Apple chain for one device could be stapled onto an unrelated signing key, so a chain that verifies but isn't bound MUST NOT earn 'hardware-attested'."
          },
          "appAttest": {
            "type": "object",
            "description": "Optional Apple App Attest evidence — the second, MDM-free path to trustLevel 'hardware-attested'. The provider's signed helper calls DCAppAttestService with `clientDataHash = sha256(publicKey)` (this record's receipt-signing key), so Apple's attestation commits to the signing key by construction. Verifiers MUST, offline: (1) CBOR-decode `object`, requiring fmt 'apple-appattest'; (2) verify the attStmt x5c chain to the embedded Apple App Attest Root CA; (3) recompute `nonce = sha256(authData || sha256(publicKey))` and require it to equal the credential certificate's nonce extension (OID 1.2.840.113635.100.8.2) — THIS is the binding to the signing key; (4) check authData's rpIdHash == sha256(App ID 'TEAMID.dev.cocore.provider'), the AAGUID is the genuine-hardware appattest value, and credentialId == sha256(attested public key) == `keyId`. A bound, valid App Attest object earns 'hardware-attested' exactly as a bound mdaCertChain does. It proves genuine, un-tampered Apple hardware holds a Secure-Enclave key bound to the signing key; it does NOT by itself prove the signing key is SE-resident nor that the running binary is honest (those are carried by the SE-backed signing identity + hardened-runtime + cdHash known-good gate, same self-measurement ceiling as the MDA path).",
            "required": [
              "object",
              "keyId"
            ],
            "properties": {
              "object": {
                "type": "bytes",
                "maxLength": 16384,
                "description": "The CBOR App Attest attestation object returned by DCAppAttestService.attestKey, as produced for `clientDataHash = sha256(publicKey)`. Contains fmt, attStmt (x5c + receipt), and authData."
              },
              "keyId": {
                "type": "bytes",
                "maxLength": 64,
                "description": "The App Attest key identifier (the SHA-256 of the attested public key) DCAppAttestService.generateKey returned. Verifiers cross-check this equals the credentialId in authData."
              }
            }
          },
          "selfSignature": {
            "type": "bytes",
            "maxLength": 256,
            "description": "Secure Enclave P-256 signature (DER) over a sorted-key canonical JSON of every other field in this record. Verifiers MUST reconstruct the canonical JSON byte-for-byte before checking."
          },
          "attestedAt": {
            "type": "string",
            "format": "datetime"
          },
          "expiresAt": {
            "type": "string",
            "format": "datetime",
            "description": "Receipts that strong-ref this attestation are only considered fresh if completedAt < expiresAt. Default 24h after attestedAt."
          },
          "tier": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#tier",
            "description": "The provider's self-asserted confidentiality tier for work done under this attestation. ADVISORY ONLY: a verifier MUST recompute the tier from the evidence in this record (bound mdaCertChain, cdHash ∈ known-good, posture booleans) and the session handshake, and MUST NOT trust this field. Present so consumers can display the provider's claim before verifying. Optional / additive; absent equivalent to `best-effort`."
          }
        }
      }
    }
  }
}
dev.cocore.compute.disputerecordkey: tid

Fields

NameType
settlement*Strong-ref to the dev.cocore.compute.settlement under dispute. Verifiers MUST resolve this to confirm both sides reference the same charge.com.atproto.repo.strongRef
exchange*Exchange DID. MUST equal the repo this record is published in. Adjudication is the exchange's prerogative; only the exchange that signed the settlement may sign its dispute.string · did
raisedBy*DID of the party who raised the complaint. Typically the requester (charge dispute) or the provider (non-payment claim). The exchange itself MAY raise a dispute when it detects a problem (e.g. processor chargeback fired before the requester reached out).string · did
raisedAt*When the complaint was first received. May predate `createdAt` (this record is published when the exchange opens or resolves the case, which can be after intake).string · datetime
reason*Why the dispute was raised. Free-form rationale plus an enum bucket for matchmaking / analytics.#disputeReason
status*Lifecycle state. The exchange opens the dispute (`open`) when intake is complete; once adjudicated, the same record is updated in place to `resolved` with outcome populated. Records at this NSID with status=open and no `outcome` are valid by construction; status=resolved without `outcome` is invalid.string
outcomeRequired when status=resolved. Captures the verdict and any compensating settlement record.#disputeOutcome
evidenceCidOptional CID of the encrypted evidence bundle the exchange relied on (request/response logs, customer correspondence, processor chargeback metadata). Encrypted to the exchange + parties; opaque to public verifiers.string · cid
sigES256 signature (base64url, no padding) over the canonical JSON of every other field. Verified against the same verificationMethod that signs settlements for this exchange. Required for trust-tier=hardware-attested exchanges; optional in v0.3.x as we roll out signing across all records.string
createdAt*string · datetime
schema
{
  "lexicon": 1,
  "id": "dev.cocore.compute.dispute",
  "description": "An exchange-signed adjudication of a complaint about a settled receipt. Published in the exchange's repo. The exchange's DID is the only DID permitted to publish a record at this NSID for a given settlement; any record at this NSID outside the exchange's repo is invalid by construction. The dispute lifecycle is open->resolved (terminal); resolution carries the outcome the exchange has already enacted (e.g. a refund settlement) and a signed rationale. Verifiers reading a settlement see status=disputed when an open dispute exists, and a paired settlement of status=refunded with `refundOf` pointing at the original when the outcome warranted a refund.",
  "defs": {
    "main": {
      "type": "record",
      "key": "tid",
      "record": {
        "type": "object",
        "required": [
          "settlement",
          "exchange",
          "raisedBy",
          "raisedAt",
          "reason",
          "status",
          "createdAt"
        ],
        "properties": {
          "settlement": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef",
            "description": "Strong-ref to the dev.cocore.compute.settlement under dispute. Verifiers MUST resolve this to confirm both sides reference the same charge."
          },
          "exchange": {
            "type": "string",
            "format": "did",
            "description": "Exchange DID. MUST equal the repo this record is published in. Adjudication is the exchange's prerogative; only the exchange that signed the settlement may sign its dispute."
          },
          "raisedBy": {
            "type": "string",
            "format": "did",
            "description": "DID of the party who raised the complaint. Typically the requester (charge dispute) or the provider (non-payment claim). The exchange itself MAY raise a dispute when it detects a problem (e.g. processor chargeback fired before the requester reached out)."
          },
          "raisedAt": {
            "type": "string",
            "format": "datetime",
            "description": "When the complaint was first received. May predate `createdAt` (this record is published when the exchange opens or resolves the case, which can be after intake)."
          },
          "reason": {
            "type": "ref",
            "ref": "#disputeReason",
            "description": "Why the dispute was raised. Free-form rationale plus an enum bucket for matchmaking / analytics."
          },
          "status": {
            "type": "string",
            "knownValues": [
              "open",
              "resolved"
            ],
            "description": "Lifecycle state. The exchange opens the dispute (`open`) when intake is complete; once adjudicated, the same record is updated in place to `resolved` with outcome populated. Records at this NSID with status=open and no `outcome` are valid by construction; status=resolved without `outcome` is invalid."
          },
          "outcome": {
            "type": "ref",
            "ref": "#disputeOutcome",
            "description": "Required when status=resolved. Captures the verdict and any compensating settlement record."
          },
          "evidenceCid": {
            "type": "string",
            "format": "cid",
            "description": "Optional CID of the encrypted evidence bundle the exchange relied on (request/response logs, customer correspondence, processor chargeback metadata). Encrypted to the exchange + parties; opaque to public verifiers."
          },
          "sig": {
            "type": "string",
            "maxLength": 256,
            "description": "ES256 signature (base64url, no padding) over the canonical JSON of every other field. Verified against the same verificationMethod that signs settlements for this exchange. Required for trust-tier=hardware-attested exchanges; optional in v0.3.x as we roll out signing across all records."
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          }
        }
      }
    },
    "disputeReason": {
      "type": "object",
      "required": [
        "category"
      ],
      "description": "Bucketed reason plus free-form detail. Buckets exist so the AppView can index disputes; detail is for the operator's own audit.",
      "properties": {
        "category": {
          "type": "string",
          "knownValues": [
            "fraud",
            "non-delivery",
            "quality-failure",
            "processor-chargeback",
            "duplicate-charge",
            "other"
          ],
          "description": "Bucket the dispute fits into. `processor-chargeback` is reserved for cases where the card network fired the chargeback ahead of any direct complaint."
        },
        "detail": {
          "type": "string",
          "maxLength": 2048,
          "description": "Operator-supplied free-form detail. Public; do not include personally identifying information."
        }
      }
    },
    "disputeOutcome": {
      "type": "object",
      "required": [
        "verdict",
        "decidedAt"
      ],
      "description": "Exchange's verdict and any side-effects. The compensating refund (if any) is published as its own dev.cocore.compute.settlement with status=refunded and refundOf pointing at the original; this object strong-refs that record so verifiers don't need to scan.",
      "properties": {
        "verdict": {
          "type": "string",
          "knownValues": [
            "refund-full",
            "refund-partial",
            "uphold-charge",
            "forfeit-payout"
          ],
          "description": "What happened. `refund-full` and `refund-partial` reverse value to the requester; `uphold-charge` keeps the original settlement intact; `forfeit-payout` keeps the requester's funds with the exchange but withholds the provider payout."
        },
        "refundSettlement": {
          "type": "ref",
          "ref": "com.atproto.repo.strongRef",
          "description": "When verdict is refund-*, strong-ref to the dev.cocore.compute.settlement record (status=refunded) the exchange published as the compensating action. Required when the verdict involves a refund."
        },
        "rationale": {
          "type": "string",
          "maxLength": 2048,
          "description": "Exchange's plain-English explanation. Public; bind it carefully — this is the audit trail when the same parties dispute future charges."
        },
        "decidedAt": {
          "type": "string",
          "format": "datetime"
        }
      }
    }
  }
}
dev.cocore.compute.exchangeAttestationrecordkey: tid

Fields

NameType
exchange*Exchange DID. MUST equal the repo this record is published in.string · did
policy*Strong-ref to the dev.cocore.compute.exchangePolicy this attestation covers.com.atproto.repo.strongRef
softwareVersion*Identifier for the exchange-side software running. Free-form; e.g. 'cocore-services@v0.3.2 (commit a1b2c3d)'. Lets verifiers match settlements to a specific build.string
signingKeyFingerprint*Fingerprint of the public key the exchange signs settlements with. Should match the verificationMethod publicKeyMultibase in the exchange's did document.string
auditPostureHuman-readable note about how the exchange is run (e.g. 'single-tenant on Railway, no third-party access, source on github.com/cocore/services'). Out-of-band trust signal.string
createdAt*string · datetime
schema
{
  "lexicon": 1,
  "id": "dev.cocore.compute.exchangeAttestation",
  "description": "An exchange's self-published statement of operating posture: software commit, public signing key fingerprint, fee policy in effect, audit posture. The provider/requester analog of `dev.cocore.compute.attestation` for the exchange role. Settlements MAY strong-ref the active attestation so verifiers can pin which exchange-software / signing-key combination produced the record.",
  "defs": {
    "main": {
      "type": "record",
      "key": "tid",
      "record": {
        "type": "object",
        "required": [
          "exchange",
          "policy",
          "softwareVersion",
          "signingKeyFingerprint",
          "createdAt"
        ],
        "properties": {
          "exchange": {
            "type": "string",
            "format": "did",
            "description": "Exchange DID. MUST equal the repo this record is published in."
          },
          "policy": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef",
            "description": "Strong-ref to the dev.cocore.compute.exchangePolicy this attestation covers."
          },
          "softwareVersion": {
            "type": "string",
            "maxLength": 64,
            "description": "Identifier for the exchange-side software running. Free-form; e.g. 'cocore-services@v0.3.2 (commit a1b2c3d)'. Lets verifiers match settlements to a specific build."
          },
          "signingKeyFingerprint": {
            "type": "string",
            "maxLength": 128,
            "description": "Fingerprint of the public key the exchange signs settlements with. Should match the verificationMethod publicKeyMultibase in the exchange's did document."
          },
          "auditPosture": {
            "type": "string",
            "maxLength": 256,
            "description": "Human-readable note about how the exchange is run (e.g. 'single-tenant on Railway, no third-party access, source on github.com/cocore/services'). Out-of-band trust signal."
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          }
        }
      }
    }
  }
}
dev.cocore.compute.exchangePolicyrecordkey: tid

Fields

NameType
exchange*Exchange DID. MUST equal the repo this record is published in.string · did
fee*Per-receipt fee. `fee.bps` is the fraction of each receipt's token cost routed to the treasury account instead of the provider (conservation 95/5 by default with `bps: 500`). The treasury accumulates these fees and redistributes them as patronage rebates on `patronageDistribution.cadenceDays`. `fee.currency` names the unit the fee schedule is denominated in.#feeSchedule
tokenRateUniform per-token rate the exchange asserts for jobs it settles. CANONICAL: providers settling through this exchange MUST price receipts at this rate, ignoring their own `dev.cocore.compute.provider.priceList`. Verifiers MAY reject receipts whose `price.amount` diverges from `tokenRate.inputPricePerMTok * tokens.in / 1e6 + tokenRate.outputPricePerMTok * tokens.out / 1e6` beyond a one-minor-unit floor. The provider's priceList is a denormalization for client display today; a future lexicon revision will introduce a per-receipt provider-set override (with the exchange's permission), at which point priceList becomes authoritative again. Optional only for pre-2026-05 policies, where the provider's priceList was canonical.dev.cocore.compute.defs#tokenRate
supportedCurrencies*ISO 4217 (or XBT/XSAT-style) currency codes the exchange will settle in.string[]
selfLoop*How the exchange handles jobs where requester DID == provider DID.#selfLoopRule
processorIdentifier for the settlement backend the exchange runs (e.g. 'closed-loop', 'usdc-base'). Useful as a hint to clients that need to know whether settlement is internal or external.string
termsUriURL to a human-readable Terms of Service / Privacy Policy for this exchange. Pinned with `termsVersion`; clients prompt the user for re-acceptance whenever the active policy's `termsVersion` no longer matches the version on the user's most recent dev.cocore.compute.termsAcceptance.string · uri
termsVersionVersion string for the terms-of-service text at `termsUri`. Bumping this triggers re-acceptance prompts in clients. Compared as a literal equality string; semantic versioning is a convention, not a requirement.string
tokenGrantTokens granted to a DID on first interaction with this exchange. Idempotent per DID: the exchange MUST issue the grant exactly once and MUST be able to prove which DIDs have already received it (typically via a `dev.cocore.account.tokenGrant` record per recipient). Set to 0 to disable grants. cocore.dev sets this to 1_000_000.integer
tokenFloorMinimum token balance a DID must hold post-dispatch for the exchange to accept a new job. The admission check is `balance - job.priceCeiling.tokensEquivalent >= tokenFloor`. Prevents a user from dispatching jobs that would zero out their balance, which keeps the failure mode 'wait for the next refresh' rather than 'mid-job settlement bounced'. cocore.dev sets this to 100_000.integer
treasuryDidThe DID whose token balance accumulates the per-receipt fee (5% by default) and from which patronage rebates are distributed. Defaults to the exchange's own DID (`exchange`) when unset, which matches the convention of a cooperative whose treasury IS the exchange's own balance sheet.string · did
weeklyRefreshOptional 'use-it-to-keep-it' refresh that lazily issues `amountPerDid` tokens to active DIDs every `cadenceMinutes`. The refresh only fires when the DID touches the network (receipt as either side, balance read, governance act) — dormant DIDs accrue nothing. Sized so a member who actively uses the system stays roughly at the dignity floor; sized so the aggregate mint roughly matches the new-compute capacity coming online. Set to absent to disable.#refreshRule
patronageDistributionOptional periodic distribution of treasury balance back to active members in proportion to their patronage (consumer spending + provider earnings) during the period. Direct analog of REI's dividend and Rochdale-tradition patronage rebates. Set to absent to disable.#patronageRule
activeSoft-delete: false means a newer policy supersedes this one. Settlements signed against an inactive policy are still valid for receipts that arrived before the policy flipped.boolean
createdAt*string · datetime
schema
{
  "lexicon": 1,
  "id": "dev.cocore.compute.exchangePolicy",
  "description": "An exchange's published terms of service. Records every parameter that affects how this exchange computes settlements and maintains per-DID token balances: fee schedule, supported currencies, self-loop rules, processor identifiers, and the token-accounting parameters (onboarding grant, balance floor, weekly refresh, monthly patronage distribution, treasury identity). A settlement record strong-refs the active policy so verifiers can re-derive the payout numbers offline. Policies are immutable per-record; the exchange publishes a new record when terms change and updates `active=false` on the prior one.",
  "defs": {
    "main": {
      "type": "record",
      "key": "tid",
      "record": {
        "type": "object",
        "required": [
          "exchange",
          "fee",
          "supportedCurrencies",
          "selfLoop",
          "createdAt"
        ],
        "properties": {
          "exchange": {
            "type": "string",
            "format": "did",
            "description": "Exchange DID. MUST equal the repo this record is published in."
          },
          "fee": {
            "type": "ref",
            "ref": "#feeSchedule",
            "description": "Per-receipt fee. `fee.bps` is the fraction of each receipt's token cost routed to the treasury account instead of the provider (conservation 95/5 by default with `bps: 500`). The treasury accumulates these fees and redistributes them as patronage rebates on `patronageDistribution.cadenceDays`. `fee.currency` names the unit the fee schedule is denominated in."
          },
          "tokenRate": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#tokenRate",
            "description": "Uniform per-token rate the exchange asserts for jobs it settles. CANONICAL: providers settling through this exchange MUST price receipts at this rate, ignoring their own `dev.cocore.compute.provider.priceList`. Verifiers MAY reject receipts whose `price.amount` diverges from `tokenRate.inputPricePerMTok * tokens.in / 1e6 + tokenRate.outputPricePerMTok * tokens.out / 1e6` beyond a one-minor-unit floor. The provider's priceList is a denormalization for client display today; a future lexicon revision will introduce a per-receipt provider-set override (with the exchange's permission), at which point priceList becomes authoritative again. Optional only for pre-2026-05 policies, where the provider's priceList was canonical."
          },
          "supportedCurrencies": {
            "type": "array",
            "minLength": 1,
            "maxLength": 32,
            "items": {
              "type": "string",
              "minLength": 3,
              "maxLength": 8
            },
            "description": "ISO 4217 (or XBT/XSAT-style) currency codes the exchange will settle in."
          },
          "selfLoop": {
            "type": "ref",
            "ref": "#selfLoopRule",
            "description": "How the exchange handles jobs where requester DID == provider DID."
          },
          "processor": {
            "type": "string",
            "maxLength": 64,
            "description": "Identifier for the settlement backend the exchange runs (e.g. 'closed-loop', 'usdc-base'). Useful as a hint to clients that need to know whether settlement is internal or external."
          },
          "termsUri": {
            "type": "string",
            "format": "uri",
            "description": "URL to a human-readable Terms of Service / Privacy Policy for this exchange. Pinned with `termsVersion`; clients prompt the user for re-acceptance whenever the active policy's `termsVersion` no longer matches the version on the user's most recent dev.cocore.compute.termsAcceptance."
          },
          "termsVersion": {
            "type": "string",
            "maxLength": 32,
            "description": "Version string for the terms-of-service text at `termsUri`. Bumping this triggers re-acceptance prompts in clients. Compared as a literal equality string; semantic versioning is a convention, not a requirement."
          },
          "tokenGrant": {
            "type": "integer",
            "minimum": 0,
            "description": "Tokens granted to a DID on first interaction with this exchange. Idempotent per DID: the exchange MUST issue the grant exactly once and MUST be able to prove which DIDs have already received it (typically via a `dev.cocore.account.tokenGrant` record per recipient). Set to 0 to disable grants. cocore.dev sets this to 1_000_000."
          },
          "tokenFloor": {
            "type": "integer",
            "minimum": 0,
            "description": "Minimum token balance a DID must hold post-dispatch for the exchange to accept a new job. The admission check is `balance - job.priceCeiling.tokensEquivalent >= tokenFloor`. Prevents a user from dispatching jobs that would zero out their balance, which keeps the failure mode 'wait for the next refresh' rather than 'mid-job settlement bounced'. cocore.dev sets this to 100_000."
          },
          "treasuryDid": {
            "type": "string",
            "format": "did",
            "description": "The DID whose token balance accumulates the per-receipt fee (5% by default) and from which patronage rebates are distributed. Defaults to the exchange's own DID (`exchange`) when unset, which matches the convention of a cooperative whose treasury IS the exchange's own balance sheet."
          },
          "weeklyRefresh": {
            "type": "ref",
            "ref": "#refreshRule",
            "description": "Optional 'use-it-to-keep-it' refresh that lazily issues `amountPerDid` tokens to active DIDs every `cadenceMinutes`. The refresh only fires when the DID touches the network (receipt as either side, balance read, governance act) — dormant DIDs accrue nothing. Sized so a member who actively uses the system stays roughly at the dignity floor; sized so the aggregate mint roughly matches the new-compute capacity coming online. Set to absent to disable."
          },
          "patronageDistribution": {
            "type": "ref",
            "ref": "#patronageRule",
            "description": "Optional periodic distribution of treasury balance back to active members in proportion to their patronage (consumer spending + provider earnings) during the period. Direct analog of REI's dividend and Rochdale-tradition patronage rebates. Set to absent to disable."
          },
          "active": {
            "type": "boolean",
            "default": true,
            "description": "Soft-delete: false means a newer policy supersedes this one. Settlements signed against an inactive policy are still valid for receipts that arrived before the policy flipped."
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          }
        }
      }
    },
    "feeSchedule": {
      "type": "object",
      "required": [
        "bps",
        "minMinor",
        "currency"
      ],
      "description": "Linear fee model: max(amountMinor * bps / 10000, minMinor).",
      "properties": {
        "bps": {
          "type": "integer",
          "minimum": 0,
          "maximum": 10000,
          "description": "Basis points (1/10000) retained by the exchange. 500 = 5%."
        },
        "minMinor": {
          "type": "integer",
          "minimum": 0,
          "description": "Floor on the fee in integer minor units. 0 means there is no floor."
        },
        "currency": {
          "type": "string",
          "minLength": 3,
          "maxLength": 8,
          "description": "Currency the fee schedule is denominated in."
        }
      }
    },
    "selfLoopRule": {
      "type": "object",
      "required": [
        "feeWaived"
      ],
      "description": "What happens when the same DID owns the requester job and the receipt's provider field — e.g., a user running an inference on their own machine via the exchange.",
      "properties": {
        "feeWaived": {
          "type": "boolean",
          "description": "If true, the exchange takes no fee on self-loop jobs. The settlement still gets published as an audit trail."
        },
        "minMinor": {
          "type": "integer",
          "minimum": 0,
          "description": "Optional facilitation floor — the exchange may still take a small flat amount to cover its operating cost. Ignored when feeWaived is true."
        }
      }
    },
    "refreshRule": {
      "type": "object",
      "required": [
        "amountPerDid",
        "cadenceMinutes"
      ],
      "description": "Periodic refresh: amount + cadence.",
      "properties": {
        "amountPerDid": {
          "type": "integer",
          "minimum": 0,
          "description": "Tokens credited to a DID per refresh tick. cocore.dev: 70_000 (~7% of the 1M-token onboarding grant)."
        },
        "cadenceMinutes": {
          "type": "integer",
          "minimum": 60,
          "description": "Minimum minutes between refreshes for a given DID. cocore.dev: 10_080 (7 days)."
        }
      }
    },
    "patronageRule": {
      "type": "object",
      "required": [
        "fractionBps",
        "cadenceDays"
      ],
      "description": "Periodic distribution of treasury balance to active members in proportion to patronage during the period.",
      "properties": {
        "fractionBps": {
          "type": "integer",
          "minimum": 0,
          "maximum": 10000,
          "description": "Basis points of the treasury balance distributed at each tick. Remainder is retained as operating reserve. cocore.dev: 8000 (80% distributed, 20% retained)."
        },
        "cadenceDays": {
          "type": "integer",
          "minimum": 1,
          "description": "Days between distribution ticks. cocore.dev: 30."
        }
      }
    }
  }
}
dev.cocore.compute.jobrecordkey: tid

Fields

NameType
model*Opaque model identifier the requester wants the provider to honor.string
inputCommitment*SHA-256 hex over the SEALED input plaintext bytes the provider decrypts — i.e. the exact bytes both the requester (before sealing) and the provider (after opening) hash, so it is self-consistent without either side parsing the payload. The plaintext is never on-chain. The byte layout is described by `inputFormat`: absent/`text` means the bytes are the raw prompt string; `messages-v1` means they are the UTF-8 of the canonical multimodal envelope (see `inputFormat`).string
inputFormatHow to interpret the sealed input bytes that `inputCommitment` covers. Absent (legacy) or `text`: the bytes are the raw prompt string. `messages-v1`: the bytes are the UTF-8 of a canonical-JSON multimodal envelope { v: 1, messages: [ { role, content } ] }, where `content` is either a string or an array of parts — { type: "text", text } and { type: "image", mime, data } with `data` base64-encoded image bytes inline. A verifier reconstructs the same canonical bytes to recompute `inputCommitment`. Additive: providers that don't understand a format reject the job rather than mis-serving it.string
inputCipherURLOptional URL where the encrypted prompt blob can be fetched. May be a PDS blob (at://...) or any other content-addressed URL. The commitment is what matters; this is a convenience pointer.string · uri
maxTokensOut*integer
priceCeiling*Maximum price the requester will accept for this job. The provider's receipt MUST list a price <= this ceiling, else the receipt is invalid.dev.cocore.compute.defs#money
acceptedProvidersSpecific provider DIDs allowed to serve this job. Empty/absent means any provider that meets acceptedTrustLevel.did[]
acceptedTrustLevel*Minimum trust level the requester will accept from a provider serving this job.dev.cocore.compute.defs#trustLevel
acceptedExchangesExchange DIDs allowed to settle this job. MUST be non-empty in practice; the named paymentAuthorization MUST cover one of these.did[]
paymentAuthorization*Strong-ref to a dev.cocore.compute.paymentAuthorization record in the same repo, naming an exchange and a ceiling >= this job's priceCeiling.com.atproto.repo.strongRef
nonce*16 random bytes, hex-encoded. Replay protection.string
expiresAt*Provider must complete by this time. Receipts whose completedAt > expiresAt are invalid.string · datetime
createdAt*string · datetime
schema
{
  "lexicon": 1,
  "id": "dev.cocore.compute.job",
  "description": "A request for computational work. Published by the requester in their own repo before the work runs, so the provider has something to commit a receipt against.",
  "defs": {
    "main": {
      "type": "record",
      "key": "tid",
      "record": {
        "type": "object",
        "required": [
          "model",
          "inputCommitment",
          "maxTokensOut",
          "priceCeiling",
          "acceptedTrustLevel",
          "paymentAuthorization",
          "nonce",
          "expiresAt",
          "createdAt"
        ],
        "properties": {
          "model": {
            "type": "string",
            "maxLength": 256,
            "description": "Opaque model identifier the requester wants the provider to honor."
          },
          "inputCommitment": {
            "type": "string",
            "minLength": 64,
            "maxLength": 64,
            "description": "SHA-256 hex over the SEALED input plaintext bytes the provider decrypts — i.e. the exact bytes both the requester (before sealing) and the provider (after opening) hash, so it is self-consistent without either side parsing the payload. The plaintext is never on-chain. The byte layout is described by `inputFormat`: absent/`text` means the bytes are the raw prompt string; `messages-v1` means they are the UTF-8 of the canonical multimodal envelope (see `inputFormat`)."
          },
          "inputFormat": {
            "type": "string",
            "knownValues": [
              "text",
              "messages-v1"
            ],
            "description": "How to interpret the sealed input bytes that `inputCommitment` covers. Absent (legacy) or `text`: the bytes are the raw prompt string. `messages-v1`: the bytes are the UTF-8 of a canonical-JSON multimodal envelope { v: 1, messages: [ { role, content } ] }, where `content` is either a string or an array of parts — { type: \"text\", text } and { type: \"image\", mime, data } with `data` base64-encoded image bytes inline. A verifier reconstructs the same canonical bytes to recompute `inputCommitment`. Additive: providers that don't understand a format reject the job rather than mis-serving it."
          },
          "inputCipherURL": {
            "type": "string",
            "format": "uri",
            "description": "Optional URL where the encrypted prompt blob can be fetched. May be a PDS blob (at://...) or any other content-addressed URL. The commitment is what matters; this is a convenience pointer."
          },
          "maxTokensOut": {
            "type": "integer",
            "minimum": 1,
            "maximum": 2000000
          },
          "priceCeiling": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#money",
            "description": "Maximum price the requester will accept for this job. The provider's receipt MUST list a price <= this ceiling, else the receipt is invalid."
          },
          "acceptedProviders": {
            "type": "array",
            "maxLength": 64,
            "items": {
              "type": "string",
              "format": "did"
            },
            "description": "Specific provider DIDs allowed to serve this job. Empty/absent means any provider that meets acceptedTrustLevel."
          },
          "acceptedTrustLevel": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#trustLevel",
            "description": "Minimum trust level the requester will accept from a provider serving this job."
          },
          "acceptedExchanges": {
            "type": "array",
            "maxLength": 16,
            "items": {
              "type": "string",
              "format": "did"
            },
            "description": "Exchange DIDs allowed to settle this job. MUST be non-empty in practice; the named paymentAuthorization MUST cover one of these."
          },
          "paymentAuthorization": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef",
            "description": "Strong-ref to a dev.cocore.compute.paymentAuthorization record in the same repo, naming an exchange and a ceiling >= this job's priceCeiling."
          },
          "nonce": {
            "type": "string",
            "minLength": 32,
            "maxLength": 32,
            "description": "16 random bytes, hex-encoded. Replay protection."
          },
          "expiresAt": {
            "type": "string",
            "format": "datetime",
            "description": "Provider must complete by this time. Receipts whose completedAt > expiresAt are invalid."
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          }
        }
      }
    }
  }
}
dev.cocore.compute.paymentAuthorizationrecordkey: tid

Fields

NameType
exchange*DID of the exchange authorized to charge.string · did
ceiling*Maximum amount the exchange may charge under this authorization.dev.cocore.compute.defs#money
scope*singleJob: authorization is consumed by a single matching receipt. session: a bounded set of jobs may share this authorization until expiresAt.string
sessionBudgetWhen scope=session, total cumulative charges across all settlements under this authorization MUST NOT exceed sessionBudget. Ignored when scope=singleJob.dev.cocore.compute.defs#money
nonce*16 random bytes, hex-encoded. Replay protection: any settlement re-using a consumed nonce is invalid.string
expiresAt*string · datetime
createdAt*string · datetime
schema
{
  "lexicon": 1,
  "id": "dev.cocore.compute.paymentAuthorization",
  "description": "A requester's signed authorization permitting a named exchange to charge them up to a ceiling for a specific job. Published in the requester's repo before any work runs. Settlement records strong-ref this authorization to prove consent.",
  "defs": {
    "main": {
      "type": "record",
      "key": "tid",
      "record": {
        "type": "object",
        "required": [
          "exchange",
          "ceiling",
          "scope",
          "nonce",
          "expiresAt",
          "createdAt"
        ],
        "properties": {
          "exchange": {
            "type": "string",
            "format": "did",
            "description": "DID of the exchange authorized to charge."
          },
          "ceiling": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#money",
            "description": "Maximum amount the exchange may charge under this authorization."
          },
          "scope": {
            "type": "string",
            "knownValues": [
              "singleJob",
              "session"
            ],
            "description": "singleJob: authorization is consumed by a single matching receipt. session: a bounded set of jobs may share this authorization until expiresAt."
          },
          "sessionBudget": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#money",
            "description": "When scope=session, total cumulative charges across all settlements under this authorization MUST NOT exceed sessionBudget. Ignored when scope=singleJob."
          },
          "nonce": {
            "type": "string",
            "minLength": 32,
            "maxLength": 32,
            "description": "16 random bytes, hex-encoded. Replay protection: any settlement re-using a consumed nonce is invalid."
          },
          "expiresAt": {
            "type": "string",
            "format": "datetime"
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          }
        }
      }
    }
  }
}
dev.cocore.compute.providerrecordkey: tid

Fields

NameType
machineLabel*Human-readable name for this machine, e.g. 'MacBook Pro M3 Max #1'.string
chip*Hardware identifier, e.g. 'Apple M3 Max'.string
ramGB*integer
gpuCoresReported GPU core count, when applicable.integer
memoryBandwidthGBsReported memory bandwidth in GB/s, when applicable.integer
cpuCoresTotal CPU core count (logical = physical for Apple Silicon).integer
pCoresApple Silicon performance-core count, when applicable.integer
eCoresApple Silicon efficiency-core count, when applicable.integer
modelIdentifierApple model code, e.g. 'Macmini9,1' or 'Mac15,3'. Stable across OS updates and useful for hardware-class matching.string
osOperating system + version, e.g. 'macOS 14.5.1'. Helpful for capability-based matchmaking (e.g. CoreML / MLX feature support).string
supportedModels*Opaque model identifiers honored by this provider.string[]
desiredModelsModel identifiers the OWNER wants this machine to load, set from a management UI (e.g. the console's per-machine model picker). The agent reconciles toward it: on serve start it loads this set instead of its local default, and while running it restarts to reload when this set changes. Distinct from `supportedModels`, which is what actually loaded (a model that won't fit RAM stays in `desiredModels` but never appears in `supportedModels`). Owner-written, agent-preserved; absent means the agent uses its local config. Additive / optional — pre-2026-06 agents ignore it.string[]
priceList*Per-model rates the provider advertises. INFORMATIONAL since 2026-05: the active `dev.cocore.compute.exchangePolicy.tokenRate` is canonical for any provider settling through that exchange — providers MUST price receipts at the exchange's tokenRate and SHOULD denormalize the same rate into every priceList entry so the on-PDS view is internally consistent. A future lexicon revision will introduce a per-receipt provider-set override (gated on the exchange's permission), at which point priceList becomes authoritative again. Held required for backward compat with pre-2026-05 readers.dev.cocore.compute.defs#modelPrice[]
encryptionPubKey*X25519 public key (base64) used to wrap requests to this provider.string
attestationPubKey*P-256 public key (base64) bound to this machine's Secure Enclave. Used to verify attestation and receipt signatures. Doubles as the stable per-machine fingerprint: the SE keypair is generated once at first agent boot and survives reboots / re-pairs / OS updates, so two records with the same `attestationPubKey` describe the same physical machine. Agents reuse the rkey of any existing record with this same key (and delete duplicates) instead of creating a fresh provider record per `serve` invocation, so the on-PDS view stays one record per machine.string
trustLevel*dev.cocore.compute.defs#trustLevel
tierThe highest confidentiality tier this machine can serve, advertised so requesters and matchmakers can pre-filter. ADVISORY: a requester demanding `attested-confidential` MUST still verify the per-job attestation + session handshake and fail closed; this field only avoids routing confidential jobs to machines that cannot serve them. A machine earns `attested-confidential` here only when it runs the measured native engine under a hardware-attested posture. This is the AGENT-published ACHIEVED tier, derived from evidence (never self-declared); see `desiredTier` for the owner's intent. Optional / additive; absent equivalent to `best-effort`.dev.cocore.compute.defs#tier
desiredTierThe confidentiality tier the OWNER has opted this machine into, set from a management UI (the console / tray 'Upgrade security' action). Mirrors `desiredModels`: owner-written INTENT, the agent reconciles toward it (e.g. switches to the measured native engine and earns the hardware-attested posture). Setting `attested-confidential` NEVER fakes the achieved state — the agent only publishes the higher `tier`/`trustLevel` once it actually earns them, and a machine that can't (e.g. not a native build) stays best-effort with a surfaced reason. Absent / `best-effort` means the owner has not opted into confidential and the machine serves exactly as before — opting in is the only thing that changes anything. Owner-written, agent-preserved. Optional / additive; pre-0.9.19 agents ignore it.dev.cocore.compute.defs#tier
acceptedExchangesExchange DIDs this provider will accept settlement from. Empty/absent means any exchange is acceptable.did[]
contactEndpointURL of the advisor / matchmaking service this provider currently uses. Federable; may change without invalidating prior receipts.string · uri
activeSoft delete: false means the machine is retired and should not be matched.boolean
provisioningTrue while the agent is still coming up — it publishes this record immediately on serve start (so the machine appears right away) but before its inference engine has finished loading. The agent re-publishes with this false (or absent) once the engine is ready and it has connected to its matchmaker. Consumers SHOULD show such a machine as 'provisioning' and SHOULD NOT route jobs to it. Absence is equivalent to false.boolean
servingLiveness flag. The agent sets this true while its serve loop is up, and publishes false on graceful shutdown (it receives SIGTERM when quit/paused/bounced or its schedule window closes), so consumers can show the machine as 'offline' the moment it stops serving rather than leaving it looking idle. Absence means unknown — treat as NOT offline for backward-compat with pre-2026-06 records that never set it. (An ungraceful stop — crash / power loss — can't publish false; a future lastSeenAt heartbeat will cover that case.)boolean
payoutsEnabledTrue iff the provider has completed payout onboarding with at least one exchange and that exchange has confirmed it can transfer funds to them. Matchmakers SHOULD filter paid jobs away from providers where this is false; self-loop jobs (requester == provider) and free-tier jobs are unaffected. Absence is equivalent to false. The exchange asserts this from its own side via the corresponding paymentAccount record; this field exists so consumers don't have to do a second lookup to know whether the provider can be paid.boolean
binaryVersionVersion string of the agent binary that wrote this record (e.g. `0.3.4`). Stamped on every `cocore agent serve` startup, so the record reflects the freshest deploy. Operators / dashboards use this to spot machines that haven't been updated to a release with a known fix. Optional / additive — pre-2026-05 records won't carry it.string
engineFaultPresent when the agent could not bring its configured inference engine online after exhausting its startup recovery attempts. The machine still appears (and may still serve the no-op `stub` engine), but it will NOT be routed real inference jobs because its `supportedModels` does not include the configured model. Consumers SHOULD surface this to the operator as a fault with remediation guidance. Absence means the engine loaded cleanly (the agent clears this field on every healthy serve). Additive / optional — pre-2026-06 readers ignore it.object
createdAt*string · datetime
schema
{
  "lexicon": 1,
  "id": "dev.cocore.compute.provider",
  "description": "A compute provider's public profile. One record per physical machine. The DID owning the record is the provider's billable identity; the same DID may own multiple provider records (one per machine).",
  "defs": {
    "main": {
      "type": "record",
      "key": "tid",
      "record": {
        "type": "object",
        "required": [
          "machineLabel",
          "chip",
          "ramGB",
          "supportedModels",
          "priceList",
          "encryptionPubKey",
          "attestationPubKey",
          "trustLevel",
          "createdAt"
        ],
        "properties": {
          "machineLabel": {
            "type": "string",
            "maxLength": 128,
            "description": "Human-readable name for this machine, e.g. 'MacBook Pro M3 Max #1'."
          },
          "chip": {
            "type": "string",
            "maxLength": 64,
            "description": "Hardware identifier, e.g. 'Apple M3 Max'."
          },
          "ramGB": {
            "type": "integer",
            "minimum": 1
          },
          "gpuCores": {
            "type": "integer",
            "minimum": 0,
            "description": "Reported GPU core count, when applicable."
          },
          "memoryBandwidthGBs": {
            "type": "integer",
            "minimum": 0,
            "description": "Reported memory bandwidth in GB/s, when applicable."
          },
          "cpuCores": {
            "type": "integer",
            "minimum": 1,
            "description": "Total CPU core count (logical = physical for Apple Silicon)."
          },
          "pCores": {
            "type": "integer",
            "minimum": 0,
            "description": "Apple Silicon performance-core count, when applicable."
          },
          "eCores": {
            "type": "integer",
            "minimum": 0,
            "description": "Apple Silicon efficiency-core count, when applicable."
          },
          "modelIdentifier": {
            "type": "string",
            "maxLength": 64,
            "description": "Apple model code, e.g. 'Macmini9,1' or 'Mac15,3'. Stable across OS updates and useful for hardware-class matching."
          },
          "os": {
            "type": "string",
            "maxLength": 64,
            "description": "Operating system + version, e.g. 'macOS 14.5.1'. Helpful for capability-based matchmaking (e.g. CoreML / MLX feature support)."
          },
          "supportedModels": {
            "type": "array",
            "minLength": 1,
            "maxLength": 256,
            "items": {
              "type": "string",
              "maxLength": 256
            },
            "description": "Opaque model identifiers honored by this provider."
          },
          "desiredModels": {
            "type": "array",
            "maxLength": 256,
            "items": {
              "type": "string",
              "maxLength": 256
            },
            "description": "Model identifiers the OWNER wants this machine to load, set from a management UI (e.g. the console's per-machine model picker). The agent reconciles toward it: on serve start it loads this set instead of its local default, and while running it restarts to reload when this set changes. Distinct from `supportedModels`, which is what actually loaded (a model that won't fit RAM stays in `desiredModels` but never appears in `supportedModels`). Owner-written, agent-preserved; absent means the agent uses its local config. Additive / optional — pre-2026-06 agents ignore it."
          },
          "priceList": {
            "type": "array",
            "minLength": 1,
            "maxLength": 256,
            "items": {
              "type": "ref",
              "ref": "dev.cocore.compute.defs#modelPrice"
            },
            "description": "Per-model rates the provider advertises. INFORMATIONAL since 2026-05: the active `dev.cocore.compute.exchangePolicy.tokenRate` is canonical for any provider settling through that exchange — providers MUST price receipts at the exchange's tokenRate and SHOULD denormalize the same rate into every priceList entry so the on-PDS view is internally consistent. A future lexicon revision will introduce a per-receipt provider-set override (gated on the exchange's permission), at which point priceList becomes authoritative again. Held required for backward compat with pre-2026-05 readers."
          },
          "encryptionPubKey": {
            "type": "string",
            "maxLength": 128,
            "description": "X25519 public key (base64) used to wrap requests to this provider."
          },
          "attestationPubKey": {
            "type": "string",
            "maxLength": 256,
            "description": "P-256 public key (base64) bound to this machine's Secure Enclave. Used to verify attestation and receipt signatures. Doubles as the stable per-machine fingerprint: the SE keypair is generated once at first agent boot and survives reboots / re-pairs / OS updates, so two records with the same `attestationPubKey` describe the same physical machine. Agents reuse the rkey of any existing record with this same key (and delete duplicates) instead of creating a fresh provider record per `serve` invocation, so the on-PDS view stays one record per machine."
          },
          "trustLevel": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#trustLevel"
          },
          "tier": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#tier",
            "description": "The highest confidentiality tier this machine can serve, advertised so requesters and matchmakers can pre-filter. ADVISORY: a requester demanding `attested-confidential` MUST still verify the per-job attestation + session handshake and fail closed; this field only avoids routing confidential jobs to machines that cannot serve them. A machine earns `attested-confidential` here only when it runs the measured native engine under a hardware-attested posture. This is the AGENT-published ACHIEVED tier, derived from evidence (never self-declared); see `desiredTier` for the owner's intent. Optional / additive; absent equivalent to `best-effort`."
          },
          "desiredTier": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#tier",
            "description": "The confidentiality tier the OWNER has opted this machine into, set from a management UI (the console / tray 'Upgrade security' action). Mirrors `desiredModels`: owner-written INTENT, the agent reconciles toward it (e.g. switches to the measured native engine and earns the hardware-attested posture). Setting `attested-confidential` NEVER fakes the achieved state — the agent only publishes the higher `tier`/`trustLevel` once it actually earns them, and a machine that can't (e.g. not a native build) stays best-effort with a surfaced reason. Absent / `best-effort` means the owner has not opted into confidential and the machine serves exactly as before — opting in is the only thing that changes anything. Owner-written, agent-preserved. Optional / additive; pre-0.9.19 agents ignore it."
          },
          "acceptedExchanges": {
            "type": "array",
            "maxLength": 64,
            "items": {
              "type": "string",
              "format": "did"
            },
            "description": "Exchange DIDs this provider will accept settlement from. Empty/absent means any exchange is acceptable."
          },
          "contactEndpoint": {
            "type": "string",
            "format": "uri",
            "description": "URL of the advisor / matchmaking service this provider currently uses. Federable; may change without invalidating prior receipts."
          },
          "active": {
            "type": "boolean",
            "default": true,
            "description": "Soft delete: false means the machine is retired and should not be matched."
          },
          "provisioning": {
            "type": "boolean",
            "description": "True while the agent is still coming up — it publishes this record immediately on serve start (so the machine appears right away) but before its inference engine has finished loading. The agent re-publishes with this false (or absent) once the engine is ready and it has connected to its matchmaker. Consumers SHOULD show such a machine as 'provisioning' and SHOULD NOT route jobs to it. Absence is equivalent to false."
          },
          "serving": {
            "type": "boolean",
            "description": "Liveness flag. The agent sets this true while its serve loop is up, and publishes false on graceful shutdown (it receives SIGTERM when quit/paused/bounced or its schedule window closes), so consumers can show the machine as 'offline' the moment it stops serving rather than leaving it looking idle. Absence means unknown — treat as NOT offline for backward-compat with pre-2026-06 records that never set it. (An ungraceful stop — crash / power loss — can't publish false; a future lastSeenAt heartbeat will cover that case.)"
          },
          "payoutsEnabled": {
            "type": "boolean",
            "description": "True iff the provider has completed payout onboarding with at least one exchange and that exchange has confirmed it can transfer funds to them. Matchmakers SHOULD filter paid jobs away from providers where this is false; self-loop jobs (requester == provider) and free-tier jobs are unaffected. Absence is equivalent to false. The exchange asserts this from its own side via the corresponding paymentAccount record; this field exists so consumers don't have to do a second lookup to know whether the provider can be paid."
          },
          "binaryVersion": {
            "type": "string",
            "maxLength": 64,
            "description": "Version string of the agent binary that wrote this record (e.g. `0.3.4`). Stamped on every `cocore agent serve` startup, so the record reflects the freshest deploy. Operators / dashboards use this to spot machines that haven't been updated to a release with a known fix. Optional / additive — pre-2026-05 records won't carry it."
          },
          "engineFault": {
            "type": "object",
            "description": "Present when the agent could not bring its configured inference engine online after exhausting its startup recovery attempts. The machine still appears (and may still serve the no-op `stub` engine), but it will NOT be routed real inference jobs because its `supportedModels` does not include the configured model. Consumers SHOULD surface this to the operator as a fault with remediation guidance. Absence means the engine loaded cleanly (the agent clears this field on every healthy serve). Additive / optional — pre-2026-06 readers ignore it.",
            "required": [
              "code",
              "message",
              "at"
            ],
            "properties": {
              "code": {
                "type": "string",
                "maxLength": 64,
                "description": "Machine-readable fault class: `model-load-failed` (the inference subprocess never became ready), `venv-missing` (the Python environment is absent or incomplete), or `no-home` (the agent could not locate its state directory)."
              },
              "message": {
                "type": "string",
                "maxLength": 600,
                "description": "Human-readable, content-safe summary with remediation guidance. Never contains prompt or completion bytes (faults are recorded before any job is served)."
              },
              "models": {
                "type": "array",
                "maxLength": 256,
                "items": {
                  "type": "string",
                  "maxLength": 256
                },
                "description": "The configured model identifiers that failed to load."
              },
              "at": {
                "type": "string",
                "format": "datetime",
                "description": "When the agent gave up and recorded the fault."
              }
            }
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          }
        }
      }
    }
  }
}
dev.cocore.compute.receiptrecordkey: tid

Fields

NameType
job*Strong-ref to the requester's dev.cocore.compute.job record.com.atproto.repo.strongRef
requester*DID of the requester. Denormalized from the job record for indexer convenience; MUST equal the DID owning the job record.string · did
model*string
inputCommitment*MUST equal job.inputCommitment.string
outputCommitment*SHA-256 hex over the plaintext output bytes — the decrypted result the requester receives. (The earlier 'encrypted output' wording was a doc error; the provider has always committed to the plaintext, which is what a requester can verify after decrypting. Use outputCipherCommitment to commit to the encrypted bytes on the wire.)string
outputCipherCommitmentOptional SHA-256 hex over the EXACT encrypted bytes delivered to the requester (the sealed reply). Lets a requester confirm the ciphertext they received is the one the provider's enclaveSignature commits to — defends against an intermediary swapping the delivered bytes. Covered by enclaveSignature like every other field.string
reasoningCommitmentOptional SHA-256 hex over the plaintext reasoning ('thinking') output bytes the provider produced for this job, separate from outputCommitment which covers only the answer the requester acts on. Present only when the model emitted reasoning on a distinct channel (a sibling reasoning_content field, or inline <think>...</think> tags the provider split out). Lets a requester independently verify the reasoning trace without it perturbing the answer's commitment. Covered by enclaveSignature.string
sessionKeyCommitmentOptional SHA-256 hex over (ephemeralPubKey || sessionNonce) — the per-job ephemeral X25519 key the requester sealed the input to, bound to the request nonce. Present when the job ran under the forward-secret confidential handshake (the enclave minted a fresh ephemeral key, enclave-signed it against the requester's nonce, and the requester sealed to that key after verifying it). Lets a requester prove after the fact that its prompt was sealed to a key the measured enclave controlled for this job, not the long-lived encryptionPubKey. Covered by enclaveSignature.string
sessionNonceOptional lowercase hex of the fresh requester nonce that the ephemeral session key was bound to (the freshness challenge for this job). Pairs with sessionKeyCommitment. Covered by enclaveSignature.string
paramsOptional record of the sampling parameters the provider committed to for this job. Integer-only (canonical JSON forbids floats): temperature/top_p are carried as integer milliunits. Covered by enclaveSignature, so a requester can prove the provider claimed these settings.#generationParams
outputCipherURLOptional URL where the encrypted output lives.string · uri
tokens*dev.cocore.compute.defs#tokenCounts
startedAt*string · datetime
completedAt*string · datetime
price*MUST be <= job.priceCeiling. Currency MUST match job.priceCeiling.currency.dev.cocore.compute.defs#money
attestation*Strong-ref to a dev.cocore.compute.attestation record published by this provider. completedAt MUST fall within [attestedAt, expiresAt] of that attestation.com.atproto.repo.strongRef
enclaveSignature*Secure Enclave P-256 signature (DER) over a sorted-key canonical JSON of every other field in this record. Verified against the publicKey of the strong-reffed attestation. This binding survives PDS migration: the repo-commit signature changes when keys rotate, but the enclaveSignature does not.bytes
tierThe confidentiality tier this job actually ran under. Set by the provider to `attested-confidential` only when the input was sealed to a verified, enclave-bound ephemeral session key AND served by a measured native engine under a hardware-attested posture; otherwise `best-effort`. A requester recomputes this from the receipt's attestation + sessionKeyCommitment rather than trusting the field. Optional / additive; absent equivalent to `best-effort`.dev.cocore.compute.defs#tier
schema
{
  "lexicon": 1,
  "id": "dev.cocore.compute.receipt",
  "description": "A signed receipt of a single completed compute job. Published by the provider in its own repo. Strong-refs the requester's job and the active attestation. Carries an additional Secure-Enclave-bound signature so it remains verifiable across PDS migrations.",
  "defs": {
    "main": {
      "type": "record",
      "key": "tid",
      "record": {
        "type": "object",
        "required": [
          "job",
          "requester",
          "model",
          "inputCommitment",
          "outputCommitment",
          "tokens",
          "startedAt",
          "completedAt",
          "price",
          "attestation",
          "enclaveSignature"
        ],
        "properties": {
          "job": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef",
            "description": "Strong-ref to the requester's dev.cocore.compute.job record."
          },
          "requester": {
            "type": "string",
            "format": "did",
            "description": "DID of the requester. Denormalized from the job record for indexer convenience; MUST equal the DID owning the job record."
          },
          "model": {
            "type": "string",
            "maxLength": 256
          },
          "inputCommitment": {
            "type": "string",
            "minLength": 64,
            "maxLength": 64,
            "description": "MUST equal job.inputCommitment."
          },
          "outputCommitment": {
            "type": "string",
            "minLength": 64,
            "maxLength": 64,
            "description": "SHA-256 hex over the plaintext output bytes — the decrypted result the requester receives. (The earlier 'encrypted output' wording was a doc error; the provider has always committed to the plaintext, which is what a requester can verify after decrypting. Use outputCipherCommitment to commit to the encrypted bytes on the wire.)"
          },
          "outputCipherCommitment": {
            "type": "string",
            "minLength": 64,
            "maxLength": 64,
            "description": "Optional SHA-256 hex over the EXACT encrypted bytes delivered to the requester (the sealed reply). Lets a requester confirm the ciphertext they received is the one the provider's enclaveSignature commits to — defends against an intermediary swapping the delivered bytes. Covered by enclaveSignature like every other field."
          },
          "reasoningCommitment": {
            "type": "string",
            "minLength": 64,
            "maxLength": 64,
            "description": "Optional SHA-256 hex over the plaintext reasoning ('thinking') output bytes the provider produced for this job, separate from outputCommitment which covers only the answer the requester acts on. Present only when the model emitted reasoning on a distinct channel (a sibling reasoning_content field, or inline <think>...</think> tags the provider split out). Lets a requester independently verify the reasoning trace without it perturbing the answer's commitment. Covered by enclaveSignature."
          },
          "sessionKeyCommitment": {
            "type": "string",
            "minLength": 64,
            "maxLength": 64,
            "description": "Optional SHA-256 hex over (ephemeralPubKey || sessionNonce) — the per-job ephemeral X25519 key the requester sealed the input to, bound to the request nonce. Present when the job ran under the forward-secret confidential handshake (the enclave minted a fresh ephemeral key, enclave-signed it against the requester's nonce, and the requester sealed to that key after verifying it). Lets a requester prove after the fact that its prompt was sealed to a key the measured enclave controlled for this job, not the long-lived encryptionPubKey. Covered by enclaveSignature."
          },
          "sessionNonce": {
            "type": "string",
            "minLength": 32,
            "maxLength": 64,
            "description": "Optional lowercase hex of the fresh requester nonce that the ephemeral session key was bound to (the freshness challenge for this job). Pairs with sessionKeyCommitment. Covered by enclaveSignature."
          },
          "params": {
            "type": "ref",
            "ref": "#generationParams",
            "description": "Optional record of the sampling parameters the provider committed to for this job. Integer-only (canonical JSON forbids floats): temperature/top_p are carried as integer milliunits. Covered by enclaveSignature, so a requester can prove the provider claimed these settings."
          },
          "outputCipherURL": {
            "type": "string",
            "format": "uri",
            "description": "Optional URL where the encrypted output lives."
          },
          "tokens": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#tokenCounts"
          },
          "startedAt": {
            "type": "string",
            "format": "datetime"
          },
          "completedAt": {
            "type": "string",
            "format": "datetime"
          },
          "price": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#money",
            "description": "MUST be <= job.priceCeiling. Currency MUST match job.priceCeiling.currency."
          },
          "attestation": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef",
            "description": "Strong-ref to a dev.cocore.compute.attestation record published by this provider. completedAt MUST fall within [attestedAt, expiresAt] of that attestation."
          },
          "enclaveSignature": {
            "type": "bytes",
            "maxLength": 256,
            "description": "Secure Enclave P-256 signature (DER) over a sorted-key canonical JSON of every other field in this record. Verified against the publicKey of the strong-reffed attestation. This binding survives PDS migration: the repo-commit signature changes when keys rotate, but the enclaveSignature does not."
          },
          "tier": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#tier",
            "description": "The confidentiality tier this job actually ran under. Set by the provider to `attested-confidential` only when the input was sealed to a verified, enclave-bound ephemeral session key AND served by a measured native engine under a hardware-attested posture; otherwise `best-effort`. A requester recomputes this from the receipt's attestation + sessionKeyCommitment rather than trusting the field. Optional / additive; absent equivalent to `best-effort`."
          }
        }
      }
    },
    "generationParams": {
      "type": "object",
      "description": "Sampling parameters committed to in a receipt. Integer-only because the canonical signing form forbids floats — temperature and top_p are carried as milliunits (value × 1000, e.g. temperature 0.7 -> 700).",
      "properties": {
        "maxTokens": {
          "type": "integer",
          "minimum": 0,
          "description": "Max output tokens requested for this job."
        },
        "seed": {
          "type": "integer",
          "description": "RNG seed, when the provider ran with a fixed seed (enables reproducibility claims)."
        },
        "temperatureMilli": {
          "type": "integer",
          "minimum": 0,
          "description": "Sampling temperature × 1000 (e.g. 0.7 -> 700). Omitted when the provider used the model default."
        },
        "topPMilli": {
          "type": "integer",
          "minimum": 0,
          "maximum": 1000,
          "description": "Nucleus sampling top_p × 1000 (e.g. 0.95 -> 950). Omitted when the provider used the model default."
        }
      }
    }
  }
}
dev.cocore.compute.settlementrecordkey: tid

Fields

NameType
receipt*Strong-ref to the dev.cocore.compute.receipt being settled.com.atproto.repo.strongRef
requesterAuthorization*Strong-ref to the dev.cocore.compute.paymentAuthorization in the requester's repo. Verifiers MUST check it names this exchange's DID and that ceiling >= amountCharged.com.atproto.repo.strongRef
amountCharged*Amount charged to the requester. MUST equal receipt.price for status=settled.dev.cocore.compute.defs#money
providerPayout*Amount paid out to the provider after fees.dev.cocore.compute.defs#money
exchangeFee*Exchange's retained fee. amountCharged = providerPayout + exchangeFee.dev.cocore.compute.defs#money
processorReference*Opaque processor transaction id (e.g. Stripe PaymentIntent id), encrypted to the involved parties (requester, provider, exchange) so the public record does not leak processor metadata. Verifiers without keys treat this as opaque.bytes
status*dev.cocore.compute.defs#settlementStatus
refundOfWhen status=refunded, strong-ref to the prior settled settlement record this refund reverses.com.atproto.repo.strongRef
policyStrong-ref to the dev.cocore.compute.exchangePolicy this settlement was computed under. Lets a verifier re-run the fee math against the same policy that produced the record.com.atproto.repo.strongRef
exchangeAttestationStrong-ref to the dev.cocore.compute.exchangeAttestation pinning the software / signing-key combination that produced this settlement.com.atproto.repo.strongRef
sigES256 signature (base64url, no padding) over the canonical JSON of every other field. Verified against the verificationMethod publicKeyMultibase in the exchange's did document. Optional for v0.3.x exchanges that haven't enabled signing; required for trust-tier=hardware-attested exchanges.string
settledAt*string · datetime
schema
{
  "lexicon": 1,
  "id": "dev.cocore.compute.settlement",
  "description": "An exchange's signed proof of payment for a receipt. Published by the exchange in its own repo. Strong-refs both the receipt being settled and the requester's payment authorization, so the full chain is independently verifiable.",
  "defs": {
    "main": {
      "type": "record",
      "key": "tid",
      "record": {
        "type": "object",
        "required": [
          "receipt",
          "requesterAuthorization",
          "amountCharged",
          "providerPayout",
          "exchangeFee",
          "processorReference",
          "status",
          "settledAt"
        ],
        "properties": {
          "receipt": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef",
            "description": "Strong-ref to the dev.cocore.compute.receipt being settled."
          },
          "requesterAuthorization": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef",
            "description": "Strong-ref to the dev.cocore.compute.paymentAuthorization in the requester's repo. Verifiers MUST check it names this exchange's DID and that ceiling >= amountCharged."
          },
          "amountCharged": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#money",
            "description": "Amount charged to the requester. MUST equal receipt.price for status=settled."
          },
          "providerPayout": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#money",
            "description": "Amount paid out to the provider after fees."
          },
          "exchangeFee": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#money",
            "description": "Exchange's retained fee. amountCharged = providerPayout + exchangeFee."
          },
          "processorReference": {
            "type": "bytes",
            "maxLength": 1024,
            "description": "Opaque processor transaction id (e.g. Stripe PaymentIntent id), encrypted to the involved parties (requester, provider, exchange) so the public record does not leak processor metadata. Verifiers without keys treat this as opaque."
          },
          "status": {
            "type": "ref",
            "ref": "dev.cocore.compute.defs#settlementStatus"
          },
          "refundOf": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef",
            "description": "When status=refunded, strong-ref to the prior settled settlement record this refund reverses."
          },
          "policy": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef",
            "description": "Strong-ref to the dev.cocore.compute.exchangePolicy this settlement was computed under. Lets a verifier re-run the fee math against the same policy that produced the record."
          },
          "exchangeAttestation": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef",
            "description": "Strong-ref to the dev.cocore.compute.exchangeAttestation pinning the software / signing-key combination that produced this settlement."
          },
          "sig": {
            "type": "string",
            "maxLength": 256,
            "description": "ES256 signature (base64url, no padding) over the canonical JSON of every other field. Verified against the verificationMethod publicKeyMultibase in the exchange's did document. Optional for v0.3.x exchanges that haven't enabled signing; required for trust-tier=hardware-attested exchanges."
          },
          "settledAt": {
            "type": "string",
            "format": "datetime"
          }
        }
      }
    }
  }
}
dev.cocore.compute.termsAcceptancerecordkey: tid

Fields

NameType
exchange*DID of the exchange whose terms the user is accepting.string · did
policy*Strong-ref to the dev.cocore.compute.exchangePolicy that was active at acceptance time. The policy itself carries fee schedule + self-loop rules; the acceptance pins the human-readable terms via the policy's termsUri + termsVersion.com.atproto.repo.strongRef
termsVersion*Version string of the human-readable terms accepted. Copied from the policy at acceptance time so the acceptance is self-contained even if the policy is later updated.string
termsUri*URL of the terms-of-service text the user actually saw. Snapshotted from the policy.string · uri
userAgentOptional user-agent string from the client at acceptance time. Useful for forensics; not required.string
acceptedAt*string · datetime
attestationStrong-ref to the dev.cocore.compute.exchangeAttestation whose `signingKeyFingerprint` identifies the key that produced `sig`. Lets a verifier resolve the exchange's signing key and check `sig` without trusting the exchange live. Optional for back-compat with pre-signing acceptances; populated going forward.com.atproto.repo.strongRef
sigExchange's ES256 signature (base64url, raw R||S) over the canonical bytes of this record with `sig` removed — the same integer-only, sorted-key canonical form used for receipts and settlements. This is the cocore attestation that the acceptance was witnessed and countersigned by the exchange, not merely written by the user. Optional for back-compat; populated going forward.string
schema
{
  "lexicon": 1,
  "id": "dev.cocore.compute.termsAcceptance",
  "description": "A user's affirmative acceptance of an exchange's terms of service / privacy policy. Published on the user's PDS so the record is portable + auditable + survives the user disconnecting from any one exchange. Clients prompt for re-acceptance whenever the active exchange policy's `termsVersion` doesn't match the version on the user's most recent acceptance for that exchange.",
  "defs": {
    "main": {
      "type": "record",
      "key": "tid",
      "record": {
        "type": "object",
        "required": [
          "exchange",
          "policy",
          "termsVersion",
          "termsUri",
          "acceptedAt"
        ],
        "properties": {
          "exchange": {
            "type": "string",
            "format": "did",
            "description": "DID of the exchange whose terms the user is accepting."
          },
          "policy": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef",
            "description": "Strong-ref to the dev.cocore.compute.exchangePolicy that was active at acceptance time. The policy itself carries fee schedule + self-loop rules; the acceptance pins the human-readable terms via the policy's termsUri + termsVersion."
          },
          "termsVersion": {
            "type": "string",
            "maxLength": 32,
            "description": "Version string of the human-readable terms accepted. Copied from the policy at acceptance time so the acceptance is self-contained even if the policy is later updated."
          },
          "termsUri": {
            "type": "string",
            "format": "uri",
            "description": "URL of the terms-of-service text the user actually saw. Snapshotted from the policy."
          },
          "userAgent": {
            "type": "string",
            "maxLength": 512,
            "description": "Optional user-agent string from the client at acceptance time. Useful for forensics; not required."
          },
          "acceptedAt": {
            "type": "string",
            "format": "datetime"
          },
          "attestation": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef",
            "description": "Strong-ref to the dev.cocore.compute.exchangeAttestation whose `signingKeyFingerprint` identifies the key that produced `sig`. Lets a verifier resolve the exchange's signing key and check `sig` without trusting the exchange live. Optional for back-compat with pre-signing acceptances; populated going forward."
          },
          "sig": {
            "type": "string",
            "description": "Exchange's ES256 signature (base64url, raw R||S) over the canonical bytes of this record with `sig` removed — the same integer-only, sorted-key canonical form used for receipts and settlements. This is the cocore attestation that the acceptance was witnessed and countersigned by the exchange, not merely written by the user. Optional for back-compat; populated going forward."
          }
        }
      }
    }
  }
}

Account records

PDS records · 4 schemas
dev.cocore.account.friendrecordkey: tid

Fields

NameType
subject*The DID the friender is extending trust to. MUST be a syntactically-valid DID (did:plc:… or did:web:…). Resolved-to-handle at write time but the DID is the authoritative identifier — a subject who later changes their handle is still the same friend.string · did
subjectHandleDisplay-only mirror of the subject's bsky handle at friend-time. Captured so dashboards can render `@handle` without paying a PLC round-trip for every friend on every page load. Authoritative handle resolution still goes through PLC; a stale value here is OK.string
noteOptional free-form note the friender attaches to remind themselves who this DID is or why they trusted them. Plain text; never shown to the subject — this is the friender's own private label.string
createdAt*string · datetime
schema
{
  "lexicon": 1,
  "id": "dev.cocore.account.friend",
  "description": "A one-way declaration that the publishing DID trusts the subject DID enough to route private (friends-only) compute jobs to them. Friending is *not* mutual and *not* a permission grant for the subject — it's the friender's own routing preference. The subject does not need to consent, sign anything, or even be aware. Published to the friender's PDS; multiple records for the same subject are tolerated (the rkey is `tid`) but the console deduplicates on upsert so steady state is one record per (friender, subject). The friends-only chat-completions endpoint reads these records to constrain the advisor's pickFor candidate set.",
  "defs": {
    "main": {
      "type": "record",
      "key": "tid",
      "record": {
        "type": "object",
        "required": [
          "subject",
          "createdAt"
        ],
        "properties": {
          "subject": {
            "type": "string",
            "format": "did",
            "description": "The DID the friender is extending trust to. MUST be a syntactically-valid DID (did:plc:… or did:web:…). Resolved-to-handle at write time but the DID is the authoritative identifier — a subject who later changes their handle is still the same friend."
          },
          "subjectHandle": {
            "type": "string",
            "maxLength": 256,
            "description": "Display-only mirror of the subject's bsky handle at friend-time. Captured so dashboards can render `@handle` without paying a PLC round-trip for every friend on every page load. Authoritative handle resolution still goes through PLC; a stale value here is OK."
          },
          "note": {
            "type": "string",
            "maxLength": 1024,
            "description": "Optional free-form note the friender attaches to remind themselves who this DID is or why they trusted them. Plain text; never shown to the subject — this is the friender's own private label."
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          }
        }
      }
    }
  }
}
dev.cocore.account.profilerecordkey: literal:self

Fields

NameType
displayNameOptional human-readable name. When absent, consumers fall back to the bsky public-profile displayName, then the handle, then the DID.string
bioOptional free-form bio text. Plain text only; no markdown rendering today.string
avatarAvatar image stored as an atproto blob on the user's PDS. Preferred over `avatarUrl` when present; consumers should resolve it via `com.atproto.sync.getBlob` against the owning PDS.blob
avatarUrlLegacy avatar URL, retained for portability of records provisioned before the `avatar` blob field existed (typically the bsky CDN URL captured at first sign-in). Writers should prefer `avatar`; readers should fall back to this only when `avatar` is absent.string · uri
handleDisplay-only mirror of the user's bsky handle at provision time. Authoritative handle resolution still goes through PLC; this field exists so dashboards can render `@handle` without a PLC round-trip on every page load.string
createdAt*string · datetime
updatedAtLast edit time. Absent on the originally-provisioned record.string · datetime
schema
{
  "lexicon": 1,
  "id": "dev.cocore.account.profile",
  "description": "A user's cocore-side profile. Auto-provisioned at first sign-in from the user's Bluesky public profile (so they don't see an empty card on first visit), then editable from /account. Independent from any bsky.app profile — the user can change their cocore display name or avatar without affecting their bsky one. One record per DID; the upsert flow uses rkey=`self` to keep it that way.",
  "defs": {
    "main": {
      "type": "record",
      "key": "literal:self",
      "record": {
        "type": "object",
        "required": [
          "createdAt"
        ],
        "properties": {
          "displayName": {
            "type": "string",
            "maxLength": 256,
            "description": "Optional human-readable name. When absent, consumers fall back to the bsky public-profile displayName, then the handle, then the DID."
          },
          "bio": {
            "type": "string",
            "maxLength": 2560,
            "description": "Optional free-form bio text. Plain text only; no markdown rendering today."
          },
          "avatar": {
            "type": "blob",
            "accept": [
              "image/png",
              "image/jpeg",
              "image/webp"
            ],
            "maxSize": 2000000,
            "description": "Avatar image stored as an atproto blob on the user's PDS. Preferred over `avatarUrl` when present; consumers should resolve it via `com.atproto.sync.getBlob` against the owning PDS."
          },
          "avatarUrl": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "Legacy avatar URL, retained for portability of records provisioned before the `avatar` blob field existed (typically the bsky CDN URL captured at first sign-in). Writers should prefer `avatar`; readers should fall back to this only when `avatar` is absent."
          },
          "handle": {
            "type": "string",
            "maxLength": 256,
            "description": "Display-only mirror of the user's bsky handle at provision time. Authoritative handle resolution still goes through PLC; this field exists so dashboards can render `@handle` without a PLC round-trip on every page load."
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          },
          "updatedAt": {
            "type": "string",
            "format": "datetime",
            "description": "Last edit time. Absent on the originally-provisioned record."
          }
        }
      }
    }
  }
}
dev.cocore.account.tokenGrantrecordkey: tid

Fields

NameType
exchange*Exchange DID. MUST equal the repo this record is published in.string · did
recipient*The DID receiving the grant.string · did
amount*Tokens credited. Equal to the `tokenGrant` field of the policy in effect at issuance time.integer
policy*Strong-ref to the exchangePolicy in effect when the grant was issued. Auditors verify `amount` against the policy's `tokenGrant`.com.atproto.repo.strongRef
createdAt*string · datetime
schema
{
  "lexicon": 1,
  "id": "dev.cocore.account.tokenGrant",
  "description": "Records that an exchange has issued its one-time signup token grant to a recipient DID. Written by the exchange to its own PDS on the first balance event for a given DID — receipt as either side, top-up, or admission check. Issued exactly once per (exchange, recipient) pair. A consumer reconstructing balances offline starts each DID at this grant amount; absence of a tokenGrant record means the DID has never interacted with the exchange.",
  "defs": {
    "main": {
      "type": "record",
      "key": "tid",
      "record": {
        "type": "object",
        "required": [
          "exchange",
          "recipient",
          "amount",
          "policy",
          "createdAt"
        ],
        "properties": {
          "exchange": {
            "type": "string",
            "format": "did",
            "description": "Exchange DID. MUST equal the repo this record is published in."
          },
          "recipient": {
            "type": "string",
            "format": "did",
            "description": "The DID receiving the grant."
          },
          "amount": {
            "type": "integer",
            "minimum": 0,
            "description": "Tokens credited. Equal to the `tokenGrant` field of the policy in effect at issuance time."
          },
          "policy": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef",
            "description": "Strong-ref to the exchangePolicy in effect when the grant was issued. Auditors verify `amount` against the policy's `tokenGrant`."
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          }
        }
      }
    }
  }
}
dev.cocore.account.tokenPatronagerecordkey: tid

Fields

NameType
exchange*Exchange DID. MUST equal the repo this record is published in.string · did
recipient*DID receiving the patronage rebate.string · did
period*Rebate period [start, end). Receipts whose `completedAt` falls in this window contributed to the patronage tally.#period
patronageScore*This recipient's patronage during the period — tokens spent as requester PLUS tokens earned as provider. Self-loop receipts count once (the doc's `selfLoop.feeWaived` already handles the fee carve-out).integer
totalPatronage*Sum of all participating DIDs' patronage scores during the period. Lets a verifier reproduce the rebate share: `tokensCredited = floor(treasuryBefore * fractionBps / 10000 * patronageScore / totalPatronage)`.integer
tokensCredited*Tokens debited from the treasury and credited to the recipient.integer
treasuryBefore*Treasury balance immediately before this distribution period fired. Pinned for audit so verifiers can sum the per-recipient `tokensCredited` and confirm it equals `floor(treasuryBefore * fractionBps / 10000)` modulo rounding.integer
policy*Strong-ref to the exchangePolicy in effect at distribution time. Pins `patronageDistribution.fractionBps` + `treasuryDid` so the rebate math is reproducible offline.com.atproto.repo.strongRef
createdAt*string · datetime
schema
{
  "lexicon": 1,
  "id": "dev.cocore.account.tokenPatronage",
  "description": "Records a patronage rebate: a periodic distribution of treasury balance back to an active member in proportion to their patronage (consumer spending + provider earnings) during the rebate period. Written by the exchange to its own PDS when the distribution fires. One record per (recipient, period) pair — the exchange MUST NOT distribute twice for the same (recipient, period). Direct analog of REI's annual member dividend or a credit union's quarterly capital credit: every term is visible, the math is the same for every member, and the distribution is rule-based rather than discretionary. Receipts of work + this record together let a member reconstruct every CC their balance has touched.",
  "defs": {
    "main": {
      "type": "record",
      "key": "tid",
      "record": {
        "type": "object",
        "required": [
          "exchange",
          "recipient",
          "period",
          "patronageScore",
          "totalPatronage",
          "tokensCredited",
          "treasuryBefore",
          "policy",
          "createdAt"
        ],
        "properties": {
          "exchange": {
            "type": "string",
            "format": "did",
            "description": "Exchange DID. MUST equal the repo this record is published in."
          },
          "recipient": {
            "type": "string",
            "format": "did",
            "description": "DID receiving the patronage rebate."
          },
          "period": {
            "type": "ref",
            "ref": "#period",
            "description": "Rebate period [start, end). Receipts whose `completedAt` falls in this window contributed to the patronage tally."
          },
          "patronageScore": {
            "type": "integer",
            "minimum": 0,
            "description": "This recipient's patronage during the period — tokens spent as requester PLUS tokens earned as provider. Self-loop receipts count once (the doc's `selfLoop.feeWaived` already handles the fee carve-out)."
          },
          "totalPatronage": {
            "type": "integer",
            "minimum": 0,
            "description": "Sum of all participating DIDs' patronage scores during the period. Lets a verifier reproduce the rebate share: `tokensCredited = floor(treasuryBefore * fractionBps / 10000 * patronageScore / totalPatronage)`."
          },
          "tokensCredited": {
            "type": "integer",
            "minimum": 1,
            "description": "Tokens debited from the treasury and credited to the recipient."
          },
          "treasuryBefore": {
            "type": "integer",
            "minimum": 0,
            "description": "Treasury balance immediately before this distribution period fired. Pinned for audit so verifiers can sum the per-recipient `tokensCredited` and confirm it equals `floor(treasuryBefore * fractionBps / 10000)` modulo rounding."
          },
          "policy": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef",
            "description": "Strong-ref to the exchangePolicy in effect at distribution time. Pins `patronageDistribution.fractionBps` + `treasuryDid` so the rebate math is reproducible offline."
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          }
        }
      }
    },
    "period": {
      "type": "object",
      "required": [
        "start",
        "end"
      ],
      "description": "Half-open interval [start, end). Closed-loop cocore.dev uses calendar months in UTC by default.",
      "properties": {
        "start": {
          "type": "string",
          "format": "datetime"
        },
        "end": {
          "type": "string",
          "format": "datetime"
        }
      }
    }
  }
}