~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
220 lines · 18.5 KB Raw History

outside: lexicons we use but do not own

  • records and XRPC methods defined by atproto, Bluesky, tangled, atirc and fedproxy that socialweb-computer reads, writes or calls
  • the OAuth scopes each role asks for, ours and theirs
  • index: README.md

At A Glance

NSIDownertypeweGostep
sh.tangled.graph.vouchtangledrecordread (policy), write (market network operators)internal/policyengine/trust/trust.go, internal/app/compose/records.goS1
chat.atirc.mintatircprocedurecall (requester)internal/market/requester/atirc.go, sessionatirc.goS8
chat.atirc.revokeatircprocedurecall (requester)sameS9
com.atproto.server.getServiceAuthatprotoquerycall (every role), serve (in-repo PDS)internal/atproto/pdsclient/serviceauth.go, internal/atproto/xrpc/serviceauth.goS3 to S9
com.atproto.repo.getRecordatprotoquerycall (everywhere), serveinternal/atproto/pdsclient/client.goall
com.atproto.repo.putRecord, createRecordatprotoprocedurecall, servesameall
com.atproto.repo.deleteRecordatprotoprocedurecall (own contract-scoped records only), servesameS9
com.atproto.repo.listRecordsatprotoquerycall (legacy fallback, discovery), servesame-
com.atproto.repo.applyWritesatprotoprocedureserve only (one custody call)internal/atproto/pds, internal/gateway/custody/signin.go-
com.atproto.repo.strongRefatprotoobjectvendored copy in lexicons/com/atproto/repo/strongRef.jsoninternal/atproto/marketall
com.atproto.sync.subscribeReposatprotosubscriptionsubscribe (relay, PDS), serve (relay, in-repo PDS)internal/events/relay, internal/atproto/relay, internal/atproto/pdsclient/subscribe.goS3 to S9
com.atproto.sync.getRepoStatus, getRepo, getLatestCommit, requestCrawlatprotoquery / procedurecall, serve (relay, PDS)internal/eventstream/market.go, internal/atproto/relay-
com.atproto.server.createSession, refreshSession, createAccount, describeServer, getSession, deleteSessionatprotoprocedure / querycall (password identities, dev accounts), serveinternal/atproto/pdsclient/client.go, internal/pds/remoteS0
network.bsky.jetstream.subscribeEventsBlueskysubscription (Jetstream v2)subscribeinternal/events/jetstream, internal/controllers/jetstreameventsourceS3 to S9
app.bsky.feed.post, app.bsky.richtext.facet#mentionBlueskyrecord, facetread (association kind bskyPost)internal/policyengine/trust/association.goS1
town.delve.feed.postdelverecordread (association kind delvePost)sameS1
com.fedproxy.rbacfedproxyrecordwrite (requester), read (guest control plane)internal/market/requester/secrets.go, internal/guestidentityS5, S8
com.fedproxy.sshPublicKeyfedproxyrecordscope onlyinternal/atproto/oauth/scope.go-

sh.tangled.graph.vouch

fieldvalue
NSIDsh.tangled.graph.vouch (tangled's lexicon; no copy under lexicons/)
typerecord
rkeythe subject DID
key fieldssubject, createdAt; kind (a denounce is not a vouch)
written bymarket network operator vouches, both directions (internal/app/marketnetwork/vouches.go -> compose ApplyOperatorVouch, an AtprotoRecord in the operator repo); people, from any tangled client
read bytrust.Resolver.Vouches: one getRecord at at://<voucher>/sh.tangled.graph.vouch/<subject>; requester discovery vouchedBy (listRecords)
stepS1; read at every policy decision (S4, S5)
  • the default policy on both sides: policy.md
  • OAuth: repo:sh.tangled.graph.vouch?action=create&action=update is in the requester set but optional (OptionalScopes)

chat.atirc

  • the full protocol (token kinds, mint rules, leases, every chat.atirc.* NSID, frame shapes) lives in the atirc repo: docs/protocol/atirc.md (atirc da089a0, git@tangled.org:did:plc:vtad4w7ujsvz7t6o6iuvmhjq)
  • here: only what socialweb-computer uses
itemvalue
basehttps://irc.fedfork.com (DefaultAtircURL)
auddid:web:irc.fedfork.com#atirc (AtircServiceAudience)
callPOST <base>/xrpc/<nsid>, Authorization: Bearer <service auth or parent token>, 30 s timeout, 64 KiB response cap
mint input{subhandle, exp, lease?}
mint output{token} (no whitespace)
lease{receipt {uri, cid}, termination (the bidder's vm.terminated at-uri), contract (the accept at-uri), association? {uri, cid}}
revoke input / output{subhandle} / {revokedAt}
token envATIRC_TOKEN, ATIRC_SUBHANDLE, ATIRC_EXP

Mint

sequenceDiagram
  autonumber
  participant R as requester
  participant P as requester PDS
  participant A as atirc (irc.fedfork.com)
  participant G as guest

  Note over R: S6 receipt verified (no mint before it)
  R->>P: getServiceAuth aud irc.fedfork.com atirc service, lxm chat.atirc.mint
  P-->>R: service auth JWT
  R->>A: chat.atirc.mint {subhandle LABEL-RFPRKEY.HANDLE, exp, lease}
  A-->>R: {token} with leases claim
  R->>R: leases == parent chain + own lease, else revoke and refuse
  R->>G: S8 token in the secrets bundle (envFile), guest.secretsUpdated
  • issuer: the requester's own identity via service auth, or a parent atirc token (spec.atircParentRef, vault path identity/atirc-parent) for a guest requester; its child label is vm
  • exp: min(now + 24 h, contract deadline, issuer exp). an ssh session token is capped at the session cap
  • the guest requester (R2) token carries the association strongRef in its lease; atirc clamps its exp to the association expiresAt
  • ssh gateway sessions: child ssh-<rfp rkey>.<handle>, sent as LC_ATIRC_TOKEN (internal/market/requester/sessionatirc.go); scope rpc:chat.atirc.mint + rpc:chat.atirc.revoke (internal/app/compose/atprotoscope.go SshGatewayAtprotoScope)
  • every mint is recorded (path, subhandle, token, exp) under atircMinted in the contract's bundle secret

Revoke

  • before vm.delete, while the lease is alive (revokeAtircToken, revokeBeforeDestroy)
  • bearer order (revokeMinted): service auth with lxm chat.atirc.revoke when the OAuth session was granted rpc:chat.atirc.revoke and the subhandle is below the requester handle; else the parent token; else the token itself
  • a 401 "lease is not alive" counts as revoked (byLease); other 401s for the own token count as already revoked
  • outcome in status.atircRevokes (Done, Pending, Abandoned after 24 h)
  • orphan sweep on every requester start (internal/market/requester/orphanrevoke.go); skipped when the requester has no vaultRef

What atirc checks (as this repo relies on it)

  • mint needs an explicit exp, at most 24 h
  • a leased token dies once its lease's vm.terminated record exists
  • revoking a subhandle revokes everything below it; revoke answers 200 for any name in the caller's scope, so the requester sends a revoke only for the token's own subhandle claim
  • a contract-scoped association ref needs expiresAt; the minted exp is clamped to it

com.atproto.repo.strongRef

fieldvalue
NSIDcom.atproto.repo.strongRef (atproto's; a vendored copy in lexicons/com/atproto/repo/strongRef.json so the embedded catalog resolves refs)
typeobject {uri (at-uri), cid}
used inmarket.rfp (payload, policies[], ingress[]), market.bid, market.accept, market.receipt, market.event, market.association (contract), badgeBlueKeys (proof), network.attested.proof (subject)
Gointernal/atproto/market StrongRef, StrongRefNSID (generated with the market types)
  • a reader fetches the target with one getRecord and compares the cid where the flow pins it (attached policy, association proof)

Service Auth

  • every cross-service call carries an atproto service-auth JWT from com.atproto.server.getServiceAuth (aud, lxm, exp)
lxmaudcallerverified by
com.publicdomainrelay.temp.market.submitRfp, submitAccept, submitEventthe bidder's DID or did#serviceIdrequester (internal/market/requester/market.go)internal/atproto/market/auth.go via participant/comms.go
market.submitBid, submitEventthe requester's DID or did#serviceIdbidder (internal/market/bidder/submit.go, adapters.go, participant/serviceauth.go)same
com.publicdomainrelay.temp.cluster.*gateway audienceinternal/gatewayhttp/cluster.gointernal/xrpcserver, internal/atprotoauth
computer.socialweb.k8s.*did:web:k8s.socialweb.computercluster ownerssame
chat.atirc.mint, chat.atirc.revokedid:web:irc.fedfork.com#atircrequesteratirc
  • verifiers use indigo auth.ServiceAuthValidator, except the ingress relay (golang-jwt plus an lxm equality check, internal/atproto/ingress/auth.go)
  • a did:key issuer (did:key ingress) signs locally, no PDS round trip (internal/atproto/ingress/auth.go): identity.md
  • the in-repo PDS serves getServiceAuth (internal/atproto/pds/service_auth.go)
  • atproto-proxy: only forwarded as a header on ingress subscriptions; no client sets it
  • the in-repo PDS's own session JWTs carry scope com.atproto.access / com.atproto.refresh (internal/atproto/pds/auth.go)

Repo Reads And Writes

  • one getRecord at a deterministic rkey beats a listRecords scan; listRecords remains only as a legacy fallback
  • client: internal/atproto/pdsclient (getRecord, putRecord, createRecord, deleteRecord, listRecords, getRepo, getLatestCommit)
  • payload records are read through the strongRef with getRecord, never taken from a stream
deterministic rkeywhere
bid market.BidRKey, termination TerminationKey, events EventRKeys, session SessionRKey, rbac RBACRKeyinternal/atproto/market/records.go
receipt receipt-<32 hex>internal/atproto/market/attest.go ReceiptRKey
badgeBlueKeys signer bindinginternal/atproto/market/badgeblue.go
contract-scoped association / requestinternal/atproto/market/guest.go ContractScopedRKey
offering, heartbeat, relaysself
infra, ephemeralInfraobject name or successor, index at self: infra.md
listRecords still usedwhy
internal/guestidentity/rbac_reader.goaccepts from before stable rbac rkeys (RBACAtAcceptSince)
internal/market/requester/discovery.gotrust lists (vouches)
internal/ssh/ownersession/ownersession.go, internal/gateway/custody/signin.go, internal/atproto/market/http.golegacy and custody paths
  • deleteRecord: only on records the app wrote itself for a contract: the requester's market.association and com.fedproxy.rbac grants (internal/market/requester/association.go, secrets.go), a bidder outbox event it retracts (internal/market/bidder/adapters.go), an AtprotoRecord object whose policy is not Retain (internal/controllers/atprotorecord/apply.go). never on Infra records, never on someone else's records
serverserves
internal/atproto/pdsthe HTTP PDS: server, repo, sync, subscribeRepos (the app's own Pds component, dev networks)
internal/pds/localin-process adapters over that PDS (localRepo identities)
internal/pds/remoteHTTP to a remote PDS through pdsclient (pdsAccount, OAuth)
internal/services/requester/routes.gorepo.getRecord, repo.listRecords, server.describeServer of the requester's repo

Streams

sourceNSIDfilterGo
our relay https://relay.mini-cloud-0002.chadig.com (or the network relay)com.atproto.sync.subscribeReposrepeated wantedCollections (max 100), wantedDids (max 10000)internal/events/relay, internal/controllers/relayeventsource
Jetstream v2 wss://jetstream.us-east.bsky.network, wss://jetstream.us-west.bsky.networknetwork.bsky.jetstream.subscribeEventscollections, dids, kinds=commitinternal/events/jetstream, internal/controllers/jetstreameventsource
a Jetstream URL ending in /subscribeJetstream v1wantedCollections, wantedDidssame
  • requester stream: market.bid, market.receipt, market.event; bidder stream: market.rfp, market.accept, market.event (internal/app/compose/streams.go)
  • after a commit, com.atproto.sync.getRepoStatus on the relay finds the author's PDS, then getRecord there (internal/eventstream/market.go)
  • the relay we run (internal/atproto/relay) serves describeServer, requestCrawl, subscribeRepos, getRepo, getRepoStatus, listRepos, listReposByCollection, getLatestCommit, listHosts, getHostStatus; its crawler dials each PDS's subscribeRepos
  • detail of the market streams: market.md

Bluesky Posts As Associations

  • association kinds bskyPost (app.bsky.feed.post) and delvePost (town.delve.feed.post), opt-in per verifier
  • match: text equals the template with {did} filled in (default associate {did}, trimmed) AND exactly one app.bsky.richtext.facet#mention whose did is the subject (internal/policyengine/trust/association.go postMatches)
  • posts are made from any client and pasted as an at-uri; we never ask for a feed.post scope
  • detail: identity.md

com.fedproxy

NSIDrkeywritten byread bypurpose
com.fedproxy.rbacnode.<accept rkey> (RBACRKey)requester (internal/market/requester/secrets.go)guest control plane (internal/guestcp/openbao/plane.go -> internal/guestidentity/grants.go, rbac_reader.go)roles {role_name, definition {iss, aud, sub, policies}} for the guest secrets and node-join JWT roles
com.fedproxy.sshPublicKey-nobody in Gonobodycreate scope in ComputeSPAOAuthScope only

Scope-Only Collections

  • NSIDs that appear only as OAuth scopes: com.publicdomainrelay.temp.auth.allowlist.rbacDid (create, bidder and desktop sets), com.fedproxy.sshPublicKey, com.publicdomainrelay.temp.market.bidderAssociation (identity.md), rpc:com.publicdomainrelay.temp.requester.associateConfirm (identity.md)

OAuth Scopes Per Role

  • sets: internal/atproto/oauth/scope.go (identities) and internal/app/compose/atprotoscope.go (the OpenBao atproto plugin of the gateway and ssh gateway)
  • every set starts with atproto; a repo:<nsid>?action=... per collection; rpc:<nsid>?aud=... per method
setused byrepo (write)rpc
RequesterOAuthScopeCLI and long-lived requesters (internal/market/requester/requester.go), pkg/embedded (iOS), requester client metadata (internal/services/requester/routes.go), dev requester identities (internal/devcore)create+update: compute.vm, market.rfp, market.accept, market.event, compute.events.vm.delete, vm.onNetwork, guest.publicKey, guest.signalWebRTC, guest.secrets, guest.agentStart, guest.secretsUpdated, guest.r2Start, guest.r2Release, policy.ghalite, policy.typescript; create+update+delete: com.fedproxy.rbac, badgeBlueKeys, market.association, market.associationRequest, computer.socialweb.infra, ephemeralInfra; optional: sh.tangled.graph.vouchmarket.submitRfp, submitAccept, submitBid, submitEvent (aud=*); chat.atirc.mint, chat.atirc.revoke (aud did:web:irc.fedfork.com%23atirc, revoke optional)
BidderOAuthScopebidder identities (internal/devcore, bidder client metadata internal/services/bidder/routes.go)create: auth.allowlist.rbacDid, market.bid, bids.free, market.receipt, market.event, compute.config.wif.simple, compute.vm, market.rfp, market.accept, vm.delete, vm.onNetwork, vm.terminated, vm.hostKeys, guest.publicKey, guest.signalWebRTC, guest.secretsApplied, guest.secretsRequested, guest.requesterDid, guest.r2Released, com.fedproxy.rbac, market.bidderAssociation, policy.ghalite, policy.typescript; create+update: market.offering, market.heartbeat; create+update+delete: badgeBlueKeys, market.associationthe four market.submit*
DesktopOAuthScopedesktop clientcreate+update: rbacDid, offering, bid, bids, receipts, event, wif.simple, com.fedproxy.rbac, bidderAssociation; create: badgeBlueKeyssubmitBid, submitEvent
ComputeSPAOAuthScopecompute SPAcreate: compute.vm, badgeBlueKeys, com.fedproxy.rbac, com.fedproxy.sshPublicKeysubmitRfp, submitAccept
DIDKeyAssociatorOAuthScopeoauthQr sign-in at qr.fedfork.com (internal/stack/componentcore/controllers.go QRScopes)bidder + requester sets+ requester.associateConfirm
K8sOAuthScopek8s requesterrequester set+ cluster.createCluster, listClusters, getKubeconfig, controlPlaneUp, controlPlaneDown, deleteCluster
OAuthScopethe unionall of the aboveall of the above
GatewayAtprotoScope()gateway OpenBao atproto plugin and gateway session owners (internal/app/gateway)compose requesterScope (a shorter requester list: no guest.agentStart/r2*, no vouch) + heartbeatmarket.submit* + cluster.*
SshGatewayAtprotoScope()ssh gateway OpenBao plugin and session owners (internal/app/sshgateway)compose requesterScopemarket.submit* + chat.atirc.mint, chat.atirc.revoke

Optional Scopes

  • OptionalScopes = rpc:chat.atirc.revoke?aud=did:web:irc.fedfork.com%23atirc, repo:sh.tangled.graph.vouch?action=create&action=update
  • RequiredScopes drops them from the identity's check (internal/marketidentity/atproto/oauth.go): a session granted without them stays Ready
  • a missing optional scope -> status.pendingAction {kind: signInAgain, reason: ScopeUpgrade}; the feature that needs it falls back (revoke with the token itself)
  • a new REQUIRED scope = SessionExpired for OAuth identities on rollout. plan the re-sign-in first
  • granted scopes are read from the token response, never the access token's scope claim (the claim is an opaque ref:<cid> on bsky)
  • a scope change is mirrored token for token in the other clients (typescript-helpers lib/oauth-scope/mod.ts, did-key-associator generated/oauth-scope.js, kcp-wasm scripts/serve-public.ts) per plan 0036

See Also