dist.town

docs/The front door

The front door


Every human page has a machine counterpart, and the split is carried by the URL shape alone — no API keys, no content negotiation, plain HTTP built for curl.

The URL grammar

ShapeMeaning
/{publisher}A publisher: a domain handle (graham.systems) or a DID (did:plc:…)
/{publisher}/{project}A project landing; nested paths welcome (/dist-town/cli)
…/{project}@{selector}A release: @1.2.0 names a version, @latest resolves a pointer
…/{project}/~releases, …/~changelogHuman surfaces (tabs); ~-segments are routes, never names
…@{selector}/{artifact}Machine: 302 to the artifact bytes on the publisher’s storage
…@{selector}/SHA256SUMSMachine: checksums for every artifact, sha256sum -c format
…@{selector}/{artifact}.sha256Machine: one artifact’s checksum, a single sha256sum -c line

The split is a shape check, not a lookup: any path segment containing @ with at least one segment after it is a machine path. Everything else is a page, with three prefixes carved out as machine paths regardless of shape — /xrpc/, /.well-known/, and /disttown.schema.json — and one shape carved back in: a path ending in /__data.json is this site’s own client-side navigation, so it stays a page even after an @ segment.

Those carve-outs cost three artifact names. An artifact named SHA256SUMS, an artifact whose name ends in .sha256, and an artifact named __data.json have no front-door download URL: the first two are the checksum endpoints, the third is a page path. Their digests are still in the release record and still listed in SHA256SUMS; only the download URL is taken.

Downloads are 302s

dist.town serves zero artifact bytes. An artifact URL answers with a redirect to the publisher’s storage, plus an RFC 9530 Repr-Digest header carrying the expected sha256 — follow with curl -L and verify what arrived.

The redirect goes to the location the release record declares, and only once that location has been verified against the record’s digest. Locations that dist.town merely discovered are reported through the read plane, not redirected to.

Two storage kinds have no front-door download today: artifacts declared as hostStorage or ociStorage answer 404 saying the location is unresolvable. Their digests are still in the record, so SHA256SUMS and .sha256 answer for them as usual.

Checksums come from the signed record

SHA256SUMS and .sha256 responses are generated from the digests in the publisher’s signed release record — never read from the storage that serves the bytes. A compromised bucket can change what it serves, but it cannot change what the sums say the bytes should be.

sh
curl -LO https://dist.town/{publisher}/{project}@{version}/SHA256SUMS
sha256sum -c SHA256SUMS --ignore-missing

SHA256SUMS is one <hex> <name> line per sha256-digested artifact in the release — sha256sum -c format, two spaces between the digest and the name. <artifact>.sha256 is that same single line for one artifact, and carries Repr-Digest as well. An artifact whose digest uses another algorithm is omitted from SHA256SUMS, and its .sha256 answers 404.

Lifecycle on every response

X-Dist-Status carries lifecycle state on every response that got as far as a release: active, yanked, deprecated, archived, or retracted. It is absent when nothing was resolved — a 502 on the publisher, or a 404 for a project or selector that doesn’t exist — because there is no lifecycle to report. A 404 for an artifact name still carries it: the release resolved, the file just isn’t in it.

The yank rules:

  • An explicit version (@1.2.0) always serves — with its status attached, so your tooling can decide.
  • A pointer (@latest) never resolves to a yanked release; the request fails with 409 and an explanation. Channels don’t hand out known-bad versions; pins keep working.

The response vocabulary

StatusWhat it means
302The location is verified; follow it. Repr-Digest carries the expected digest.
409Either a pointer whose target is yanked, or a location whose bytes failed verification against the record.
410The version was retracted by its publisher. X-Dist-Status: retracted, and the body is the testimony: retracted by the publisher <timestamp> (<kind>): <reason>.
425The location has not been verified yet. Retry shortly, or verify the digest yourself from the .sha256 endpoint.
503The location is suspended — the origin changed since verification — or a record-located artifact can’t be resolved right now.
404No such project, no pointer or version matching the selector, no such artifact in the release, or an artifact whose storage has no resolvable location.
502The publisher’s identity could not be resolved.

Every one of these is plain text with the reason in the body. A 410 is not a 404 on purpose: a pin the publisher deliberately deleted deserves an answer.

The read plane

Public XRPC under /xrpc/town.dist.* is the same read plane this site consumes, with no private API behind it. If you’re building richer tooling than downloads, that’s your surface. Seven queries:

  • town.dist.resolveRelease — repo, project, selector: the release a pointer name or literal version resolves to, with lifecycle and locations. 410 ReleaseRetracted for a retracted version.
  • town.dist.listReleases — repo, project: a project’s releases in creation order. limit (default 50, max 100) and cursor page it.
  • town.dist.getProject — repo, project: everything a project page renders, in one call.
  • town.dist.listRecentReleases — newest-first feed. Unfiltered it spans every indexed publisher; repo scopes it to one, repo plus project to a subtree. limit (default 10, max 50) and cursor.
  • town.dist.listProjects — repo: a publisher’s projects, rename aliases excluded.
  • town.dist.listChangelogs — repo, project: changelog documents newest first, aggregating descendants under an umbrella.
  • town.dist.getChangelog — repo, project, rkey: one document, the permalink’s machine twin.

Two more machine documents ride the same origin: /.well-known/did.json is the service identity (did:web:dist.town), and /disttown.schema.json is the CLI manifest’s JSON Schema — the URL every $schema line names.

The pages are built from this read plane, with one call outside it: a publisher’s avatar and display name come from the public Bluesky appview.

Moderation is legible too

dist.town’s moderation actions are published as signed labels under its service identity (did:web:dist.town): query them at com.atproto.label.queryLabels or follow the stream at com.atproto.label.subscribeLabels. A suppression emits a suppressed label on the subject; reversal emits a negation. Content reports go to com.atproto.moderation.createReport on this origin, rate-limited per source address.