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/.
dev.cocore.compute.defsdefs
6 shared schema definitions for AppView responses and unions.
Definitions
| Name | Type |
|---|---|
| money | object |
| tokenCounts | object |
| modelPrice | object |
| tokenRate | object |
| trustLevel | string |
| settlementStatus | string |
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" ] }, "settlementStatus": { "type": "string", "description": "Lifecycle state of a settlement record.", "knownValues": [ "settled", "refunded", "disputed" ] } } }
Compute records
PDS records · 10 schemasdev.cocore.compute.attestationrecordkey: tid
Fields
| Name | Type |
|---|---|
| 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. | string |
| sipEnabled* | boolean |
| secureBootEnabled* | boolean |
| secureEnclaveAvailable* | boolean |
| authenticatedRootEnabled* | boolean |
| rdmaDisabled | boolean |
| mdaCertChainOptional Apple Managed Device Attestation certificate chain (DER), leaf first. Present when trustLevel is 'hardware-attested'. 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) require the leaf's P-256 public key to EQUAL this record's `publicKey` — i.e. the chain is BOUND to the receipt-signing key. Without (2) 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'. Producers (the MDA provisioning tool) MUST therefore attest the signing key itself. | bytes[] |
| 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 |
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." }, "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. Present when trustLevel is 'hardware-attested'. 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) require the leaf's P-256 public key to EQUAL this record's `publicKey` — i.e. the chain is BOUND to the receipt-signing key. Without (2) 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'. Producers (the MDA provisioning tool) MUST therefore attest the signing key itself." }, "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." } } } } } }
dev.cocore.compute.disputerecordkey: tid
Fields
| Name | Type |
|---|---|
| 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
| Name | Type |
|---|---|
| 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
| Name | Type |
|---|---|
| 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
| Name | Type |
|---|---|
| model*Opaque model identifier the requester wants the provider to honor. | string |
| inputCommitment*SHA-256 hex of the encrypted prompt bytes the provider will decrypt. The plaintext prompt is never on-chain. | 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 of the encrypted prompt bytes the provider will decrypt. The plaintext prompt is never on-chain." }, "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
| Name | Type |
|---|---|
| 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
| Name | Type |
|---|---|
| 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 |
| 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" }, "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
| Name | Type |
|---|---|
| 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 |
| 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 |
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." }, "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." } } } }, "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
| Name | Type |
|---|---|
| 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
| Name | Type |
|---|---|
| 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 schemasdev.cocore.account.friendrecordkey: tid
Fields
| Name | Type |
|---|---|
| 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
| Name | Type |
|---|---|
| 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
| Name | Type |
|---|---|
| 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
| Name | Type |
|---|---|
| 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" } } } } }