Skip to content
quarry Documentation
Login

Documentation

Summary

RFC 0001 chose a flat repository namespace, and it is still flat underneath. RFC 0018 gave every repository a namespace, the person or team it appears under, and /r/dmestas/quarry resolves; but the name after the namespace is still the forge-wide key of the repository. There can be one tools on the whole forge. Every person and team competes for every name, and a forge that grows runs out of good ones.

This RFC splits a repository's key from its address. The key is a permanent ID that every table, store path, route and record refers to, and that never changes. The address is namespace/name, unique within its namespace: dmestas/tools and some-team/tools are two repositories. Moving a repository between namespaces (a person to a team, a mirror to its steward) or renaming it changes one row; nothing keyed by it is rewritten.

Every existing repository's ID is its current name. Those names are unique today, so every column that holds one, every store path, and every clone URL already in use stays valid with no data rewritten. Only repositories made after this lands get generated IDs.

Flat names remain a deployment option: a single-user build (quarry ui, and the WebAssembly build that will run as zit ui) has one person and no namespaces, and addresses repositories by name alone.

Decisions

Settled by the owner on 2026-10-21:

  1. The key is a permanent ID, not the address string. A move or a rename touches one row, and no grant, link, CI record or store path has to follow it.
  2. A bare /r/<name> redirects when it is unique. Exactly one repository with that name: redirect to its namespaced address, so every link saved so far keeps working. Several: a short page listing them. New links always carry the namespace.
  3. Forge-held repositories live in one forge namespace, zeitforge. The mirrors nobody owns (tcl, tk, sqlite, fossil, netbsd-src, pkgsrc and the rest) appear as zeitforge/tcl. The name is reserved: no account or team can take it. A mirror may later be stewarded by a person or team; it then moves into their namespace like any other move, and its zeitforge/ address keeps redirecting.
  4. Teams are namespaces exactly as people are. A team's slug scopes names the way a person's handle does. The deed stays with a person (RFC 0018: dissolving a team never strands code); this RFC does not change who holds the deed.
  5. Namespacing is a deployment option. Flat names for single-user builds; namespaced for a forge.

Design

Key and address

The repo table's name column stays the key column, so none of the 66 columns across 49 files that refer to a repository change type or meaning: they hold the ID. Two columns carry the address:

UNIQUE (namespace, handle) replaces the forge-wide uniqueness of the name. For every row that exists when this lands, handle = name.

A new repository's ID is minted when it is created, in a shape the account and team name rule refuses, so an ID can never be read as a namespace in a path. It is never shown as the repository's name; a reader sees the address.

A new store is created at <repos-root>/<id>. Existing stores keep the path their row already records.

Addresses on the wire

Routes are not rewritten. The forge already reads /r/<namespace>/<repo> before its routes match (RFC 0018); that reading now resolves the pair to the ID, and the 173 web routes and 164 API routes continue to receive {repo} as they do today. The same resolution applies to:

A first segment is read as a namespace when the second segment is a repository in it, and otherwise as a repository: the pools stay disjoint as RFC 0018 requires, now between namespaces and IDs rather than namespaces and names.

The bare form

/r/<x> with no namespace resolves in this order:

  1. x is an ID: that repository. Every existing repository's ID is its name, so every remote cloned from /r/quarry keeps syncing forever, whatever is created later.
  2. x is the handle of exactly one repository: redirect a page to its address; answer sync and the API as that repository.
  3. x is the handle of several: a page listing them. Sync and the API are refused with a message that names each namespaced address.
  4. Otherwise: not found.

Moves already redirect through repo_place_past (RFC 0018); a rename adds the old handle to the same memory, so an address that was ever valid keeps leading to the repository that held it.

Creating a repository

The new-repository form gains Owner: the reader, or one of their teams where they may create. The name is checked within that namespace as it is typed (#979 checks it forge-wide; this narrows the check), and an agent creating a repository places it in its person's namespace, where the deed already goes (#969).

Flat mode

quarry serve --repo-names=flat|namespaced, namespaced by default. In flat mode the address is the handle alone, UNIQUE (handle) holds forge-wide, links are /r/<handle>, and the form has no owner field. quarry ui and the WebAssembly zit ui build run flat. The key and the stores are the same in both modes, so a repository moves between a flat build and a forge without changing anything but how it is addressed.

Migration

Each rung lands on its own and leaves the forge working.

  1. Schema, no behaviour change. Add handle, backfilled from name; give forge-held repositories the zeitforge namespace and reserve the name; add UNIQUE (namespace, handle) beside the existing uniqueness; add --repo-names, defaulting to namespaced.
  2. Addresses resolve to IDs. The /r/<namespace>/<handle> reading, the API and sync forms, the bare-form rules and the listing page, and rename memory in repo_place_past. Pages show the handle.
  3. Creation is per namespace. Minted IDs and <repos-root>/<id> stores; the owner field; the name check within the namespace; the forge-wide uniqueness of name stops applying to handles.
  4. Mirrors move to zeitforge/. Stewardship is a move into the steward's namespace.
  5. Flat mode for quarry ui and the WebAssembly zit ui build.

Not in this RFC