Two trust decisions, configured independently
A contract has two sides, and each one has its own thing to worry about. A bidder is deciding who it will let run on its own machines. A requester is deciding who it will trust to run its workload at all. Neither side inherits the other's answer — a bidder can be wide open to the public while a requester is picky about providers, or the reverse. Both use the same four-mode vocabulary, applied to different questions.
Who gets to run on my compute
Set as a flag when the bidder software starts up. It gates which RFPs the bidder will even consider bidding on — a filter over incoming requesters, checked before it spends any resources evaluating a job.
Who I'll trust to run my compute
Set when the requester publishes an RFP, and stamped onto the RFP as a signed record. It governs which bidders the requester is willing to accept a bid from — a filter over incoming providers.
The four modes, read from both sides
Same four names, same underlying mechanics — but the question each one answers depends on which side configured it.
| Mode | As the bidder's policy | As the requester's policy |
|---|---|---|
only-me | Only serve RFPs from DIDs the operator itself controls. | Only accept bids from the requester's own operator DID. |
tangled-vouch | Only serve requesters this bidder has vouched for. | Only accept bids from providers this requester has vouched for. |
mutuals | Same pre-filter shape as tangled-vouch (reads the same vouch set). The per-request check can additionally verify mutual social follows when a Bluesky mutuals resolver is wired up. | Same pre-filter shape as tangled-vouch (reads the same vouch set). The per-request check can additionally verify mutual social follows when a Bluesky mutuals resolver is wired up. |
dynamic | Defer the accept/reject decision to a policy server the bidder configures. | Defer the accept/reject decision to a policy server the requester configures. |
Nothing requires the two sides to agree, or even to pick the same mode. A tangled-vouch bidder can still bid on an RFP from an only-me requester — the requester's mode only constrains which bid it will accept, not who's allowed to try.
Vouch and denounce
Both tangled-vouch and mutuals read the same graph — vouch records published under Tangled's own namespace (sh.tangled.graph.vouch). A vouch is a signed statement, "I trust this DID," stored as a Tangled record whose rkey is the vouched DID. A denounce is the same shape with kind: "denounce" — the code skips it when building the vouch set, so that DID is never added. Critically, each side queries its own graph independently — a bidder checks who it has vouched for; a requester checks who it has vouched for. Neither reads the other's list.
Vouch
Alice vouches for Bob. If Alice is a bidder running tangled-vouch, she'll now bid on Bob's RFPs.
Denounce
Alice denounces Eve. A denounce record prevents Eve from appearing in the vouched set — she won't pass the tangled-vouch or mutuals scope filter. It does not override an existing vouch for the same DID (the vouch would still be added); use it to affirmatively exclude a specific DID from your vouch graph.
Where this is thinner than it sounds: a vouch today is boolean, not weighted or scored by how many hops away it is. Direct vouches only, on both sides. A denounce prevents a DID from entering the vouch set but does not remove an already-added DID — treat it as an allowlist builder with an exclusion gate, not a reputation system. The association lookup fallback mentioned in the cost table is currently stubbed (always approves).
When each side's policy actually runs
Requester creates the RFP. Its own policy mode is written as a signed record and referenced from the RFP — public, fixed before anyone sees it.
Bidder's firehose pre-filter. Its own policy mode runs a cheap, immediate check against a pre-loaded allowlist before anything else.
Bidder's full evaluation. If the pre-filter doesn't already settle it — and for dynamic — the bidder calls out to its own policy server before deciding whether to bid at all.
Requester's pre-accept check. Only under dynamic does the requester call its own policy server again, right before accepting a bid.
Accept → provision. Neither side's policy travels any further — provisioning doesn't know or care which mode gated the contract on either end.
What each mode costs to check
| Mode | Instant check | Falls back to | Calls out over the network |
|---|---|---|---|
only-me | Is this the operator's own DID? | — | Never |
tangled-vouch | Is this DID in the pre-loaded vouch set? | — (association lookup stubbed) | Never |
mutuals | Same shape as tangled-vouch | — (association lookup stubbed) | Never |
dynamic | None — always defers | — | Yes, every time — a signed HTTP call to the policy server |
badge.blue attestations
Every record in the chain carries a signature over its own content-addressed CID, not just an author DID. Both sides' policy evaluation checks this before acting.
$type: market.rfp
payload: {uri: at://did:plc:alice/compute.vm/3mm3d, cid: bafyrei...vm}
signatures:
- keyId: did:plc:alice
sig: bafyrei...attestation