Artifact 9c8aeca85d16b615c2949ee6833791db58ee426509d02ca497a42c82cc250895:
- File docs/rfcs/0029-onboarding.md — part of check-in [ea7faea305] at 2026-11-09 01:27:28 on branch rfc-0029-onboarding — RFC 0029: onboarding from the forge alone (user: dmestas, size: 7548) [annotate] [blame] [check-ins using] [more...]
- Feature Name:
onboarding(a person or an agent sets up a repository, its pushes, its mirror and its CI from the forge alone, without asking anybody but its own person) - Start Date: 2026-11-08
- Status: Draft. Decisions settled by the owner on 2026-11-08 (see Decisions).
- Depends on: RFC 0021 and 0026 (agents and their tokens), RFC 0019 (CI runners and pool users), RFC 0022 (Git mirrors), RFC 0027 and 0028 (asking your person, plans)
- Tracks: ticket #398. Upstream: danmestas/zit#333 (dotfiles).
Summary
Setting up dmestas/context on 2026-11-08 took an agent most of an
evening and a dozen messages to the session that maintains the forge.
Every obstacle had an answer somewhere in the code or on the box and
none on the forge. This RFC makes each step self-serve and documented:
the first push works however the store was made, an agent pushes with
a token it can obtain through its person, CI runs on runners people
register themselves, the forge says what an import or a mirror will
refuse before it is attempted, scripts reach the API without faking a
browser, and one guide on the forge walks the whole sequence.
What went wrong, and the fix for each
| # | What the context agent hit | Fix here |
|---|---|---|
| 1 | The repository was created through the API and the store with zit init; their project codes differ, so every push answered "the credential presented did not verify" |
First push: an empty repository adopts the pushing store's project code; any later mismatch says so in words |
| 2 | An agent can create a repository but has no push credential: a sync token is minted only with a password or a session, and a forge agent has neither | Agent pushes: an agent pushes with an agent token holding the sync scope, obtained through its person (RFC 0027), and the docs say so |
| 3 | docs/api.md lists agent.new and every credential op as token-authenticated; a token gets 403 |
Docs that match: an op that refuses tokens says "session only" in the generated docs |
| 4 | The docs say minting adds a token; the forge's one-sync-token rule says minting replaces | Docs that match: API tokens and the account's own sync token are named apart everywhere |
| 5 | Finding a credential took three exchanges; no whoami was found | The guide: "How an agent authenticates", with GET /api/v1/me as whoami |
| 6 | Import needs a public https upstream, a mirror needs a public repository and an empty target, and a broken-out repository cannot mirror to its old address; each surfaced only on failure | Say it before: check=true on create and mirror answers what would be refused, and the guide states the rules up front |
| 7 | CI needs a site admin to add a systemd unit per repository | Your own runners: worker tokens through the API, and a runner anyone runs on their own machine |
| 8 | Cloudflare challenges plain curl and scripts unless they fake a browser | Scripts reach the API: authenticated API and sync requests skip the bot check |
| 9 | zit addremove skipped .zitignore, silently |
danmestas/zit#333; the guide says --dotfiles until it ships |
| 10 | A deploy lost a finished check's verdict and the check ran again | Fixed (!1245) |
| 11 | Attachments need a bucket | Bring your own (decision 3); the guide shows bucket.set step by step |
Design
First push
A repository with no check-ins has not yet committed to a project code. The first push into it carries the store's own (fossil's push card names it); the forge adopts it, recomputes the shared secrets of every sync credential on that repository for the new code, and then verifies the login. From then on the code is fixed. A later push whose project code differs is refused with: "this store is another project (its project code is not this repository's); clone the repository and commit there". The login is never reported as a bad credential when the project is what differs.
Agent pushes
An agent pushes as itself with an agent token whose scopes include
sync, given to zit push --user AGENT as the password. A personal
agent's person mints it on the agent's page; an agent that has none
asks its person for one (RFC 0027: agent.token.new with
scopes: "sync", the value returned to the agent once). The account's
own sync token (me.sync.token) stays a person's: one per account,
minted with a session, replacing the last. The guide says which is
which.
Docs that match
An op whose Run refuses a token declares it (SessionOnly: true on
the op), and the generated API docs print "session only" instead of
"token" for it; a test fails if an op calls refuseToken without the
flag. The docs of agent.token.new and me.sync.token say plainly
that the first adds a token to an agent and the second replaces a
person's single sync token.
Say it before
POST /api/v1/repos and POST /api/v1/r/{repo}/git-mirror take
check: true: nothing is created or changed, and the answer lists what
the real call would refuse (an upstream that is not public https, a
private repository, a remote that was this repository's upstream, a
name in use) or ok. The settings pages run the same check before
their buttons.
Your own runners
POST /api/v1/r/{repo}/ci/workers mints a worker token scoped to the
repository's group (the settings page's mint, as an op, repository
admins only, the value once); list and revoke beside it. The guide
gives the one command a person runs on their own machine:
quarry runner --checkout zit --job-users USER https://zeitforge.app/r/OWNER/REPO
with the token in QUARRY_RUNNER_TOKEN, and the repository's CI page
says when no runner is attached ("no runner serves context.job: start
one"). Nobody's code runs on the forge's own machine unless its site
admin adds a runner there, as today.
Scripts reach the API
A Cloudflare WAF skip rule exempts from the bot check any request to
/api/v1/ that carries an Authorization header, and any sync request
(/r/* with a fossil login card). Anonymous traffic is challenged as
now. The rule is set through the API with the forge's Cloudflare token
and recorded in the deploy notes.
The guide
One page, /docs/start, also in llms.txt: create a repository
(fresh or from a Git upstream, with the rules), make its first push,
authenticate as an agent (and whoami), mirror to GitHub, turn on chat,
bind a bucket, write a workflow and start a runner, and land changes.
Every step has its API call and its page. A test walks the sequence
against a test forge, so the guide cannot drift from the forge.
Build order
- First push adopts the project code, and the mismatch error.
- Docs that match:
SessionOnly, its test, and the two token docs. check: trueon create and git-mirror.- Worker tokens through the API, and the CI page's "no runner" notice.
- The Cloudflare skip rule.
- The guide at
/docs/startand inllms.txt, with its test.
Decisions
- An empty repository adopts the first push's project code; a later mismatch is refused in words (owner, 2026-11-08).
- CI is self-serve through people's own runners; no shared pool on the forge's machine (owner, 2026-11-08).
- Attachments stay bring-your-own; the guide explains binding a bucket (owner, 2026-11-08).
- Authenticated API and sync requests skip Cloudflare's bot check; anonymous traffic stays challenged (owner, 2026-11-08).