Skip to content

Repository files navigation

troposphere

troposphere is the AT Protocol arm of Coop, ROOST's open-source trust & safety platform. It's what lets Coop stand in for Ozone on atproto: it ingests AT Protocol content into Coop, signs and serves moderation labels (the standard subscribeLabels firehose and queryLabels endpoint), routes user reports into Coop's review queue, and enforces takedowns on your PDS.

Coop is the moderation tool where you set up your own policies, rules, and actions and where your reviewers work; troposphere is the atproto plumbing Coop uses. It works for any AT Protocol project regardless of its lexicons, PDS, or AppView.

Built on the maintained @atproto/* primitives (@atproto/crypto, @atproto/xrpc-server, @atproto/common). The label and enforcement endpoints are plain authenticated HTTP, so non-Coop tooling can use them too, but Coop is what troposphere is designed for.

About this project. troposphere and its documentation were generated by Claude Code, Anthropic's agentic coding tool. Spot something wrong or unclear? Reach out to @julietshen.online on Bluesky, or open a pull request.

What it does today

  • Ingests AT Protocol content (Jetstream) into Coop's item intake (npm run ingest).
  • Signs labels with a secp256k1 key (canonical dag-cbor, verifiable against the labeler DID).
  • Serves com.atproto.label.queryLabels (public HTTP).
  • Serves com.atproto.label.subscribeLabels (WebSocket firehose) with cursor backfill and a live tail, so a consumer can catch up from any point and cut over to live with no gap.
  • Exposes POST /admin/labels, a bearer-authenticated API to create and negate labels.
  • Accepts com.atproto.moderation.createReport from any AT Protocol AppView (verifying the reporter's inter-service auth against their DID), resolves the reported subject to its content, and forwards the enriched report to Coop's review queue (Coop's /api/v1/report envelope with REPORT_FORWARD_FORMAT=coop, or raw JSON for other tooling).
  • Enforces takedowns and restores on your own PDS via POST /admin/enforce, alongside labeling.
  • Accepts Coop's CUSTOM_ACTION webhook directly at POST /coop/action (label and/or take down), so a Coop action uses it with no adapter.
  • Persists labels and reports in Postgres, with a monotonic sequence for firehose cursors. Emitted labels are permanent on the network, so the store is durable by design.

Coop is the moderation tool (your policies and actions, review queues, reviewer roles, event history), playing the role Ozone's UI would; troposphere is the atproto plumbing it uses.

Quickstart

Requires Node 24+ and Postgres.

npm install

# 1. Generate a signing key. Prints LABELER_SIGNING_KEY and the publicKeyMultibase
#    to publish in your DID document.
npm run keygen

# 2. Configure. Copy .env.example to .env and fill in LABELER_DID, LABELER_SIGNING_KEY,
#    ADMIN_TOKEN, DATABASE_URL.
cp .env.example .env

# 3. Create the schema, then run.
npm run db:init
npm run dev        # or: npm run build && npm start

# 4. Smoke-test a running instance (emits, queries, verifies signatures, tails the firehose).
LABELER_URL=http://localhost:4100 ADMIN_TOKEN=... SIGNING_DID_KEY=did:key:z... npm run smoke

Emitting a label

Coop (or other tooling) posts a subject and the label values to create and/or negate:

curl -X POST http://localhost:4100/admin/labels \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H "content-type: application/json" \
  -d '{
        "subject": { "uri": "at://did:plc:.../app.bsky.feed.post/abc", "cid": "bafy..." },
        "create": ["spam"]
      }'

To retract a label, negate it (emits a neg label with a later timestamp, per the spec):

  -d '{ "subject": { "uri": "at://..." }, "negate": ["spam"] }'

Account-level labels use a bare did: as the subject uri and omit cid.

Publishing the labeler identity

The service signs labels, but the network only trusts them if the labeler's DID document advertises the matching key and endpoint. Both DID methods are supported:

  • did:web (recommended for self-hosting under your own domain, no dependency on the PLC directory). Host a did.json that includes a verification method #atproto_label with the publicKeyMultibase from npm run keygen, and a service #atproto_labeler of type AtprotoLabeler whose endpoint is this server's public URL.
  • did:plc (portable, with PLC key recovery). Update the labeler account's PLC identity to add the same key and service entry.

Either way, publish an app.bsky.labeler.service declaration record so clients can discover the labeler and the label values it uses.

Not yet implemented (roadmap)

  • Multi-PDS enforcement: resolve the subject's PDS from its DID and select the matching admin credential, rather than a single configured PDS.
  • did:web / did:plc identity provisioning helpers and declaration publishing.
  • Multi-tenant operation (one deployment serving several labeler identities).
  • A helper to generate the labeler declaration from Coop's configured labels. (The Coop integration itself is done: /coop/action for labels/takedowns out, REPORT_FORWARD_FORMAT=coop for reports in. Creating the actions and queues inside Coop is operator setup, not troposphere code.)

License

Licensed under the Apache License, Version 2.0. See LICENSE.

About

A self-hostable alternative to Ozone designed to work with Coop

Topics

Resources

Stars

14 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages