Skip to content
quarry Documentation
Login

Documentation

Summary

A horizon clone holds a repository from a check-in C forward. zit's horizon draft defines two cuts. The content horizon keeps every structural artifact and elides the file blobs no held check-in references; it needs nothing from quarry beyond fronting /xfer, because the zit subprocess serves it. The structural horizon also omits the manifests below C and stands a seed, a private control artifact asserting the tag state at C, in their place; it needs an origin that mints seeds, labels the hashes that cross the horizon, and answers merge-base. quarry is that origin, and this RFC says how: the seed and the labels are derived from the index plane the same way every page is, the merge-base answer comes from the same ancestry index, and all three pass the §11 disposability test because they are pure functions of store state.

The history a horizon clone does not hold is still on quarry. The RFC names the API that answers for it (annotate, path history, diff and file at a check-in, already owed under principle 5), adds one read endpoint quarry lacks (an artifact by hash with immutable caching), and settles which consumer gets which tool: build-and-test runners already fetch archives and keep doing so; persistent workers keep a warm clone; cold agent environments get the horizon clone, content first.

Motivation

RFC 0001 made quarry the always-on side of zit. The store is the truth and quarry derives everything else, so a clone that leaves history on quarry loses nothing a person cannot get back by asking quarry. That is what makes a shallow clone safe to offer here when it is not safe to offer against an arbitrary Fossil peer: there is a dense, indexed, always-reachable copy, and every page quarry renders is already an answer about history the client need not hold.

The consumer that forces the question is RFC 0021's agent: a short-lived environment on a dagnats worker that must clone, commit and push, with no warm cache and a network between it and the store. RFC 0009's runners do not have this problem, because premerge removed the clone from the test path: a runner fetches an archive of a tree and never holds a repository. Agents cannot use that trick. They write.

zit's horizon draft keeps every artifact's real name and the existing derivation. What it asks of an origin is small and index-shaped, and quarry already has the indexes.

Guide-level explanation

An agent's environment starts with:

$ zit clone --horizon https://quarry.example/r/proj

quarry's sync front sees the horizon pragma, computes the held set from its ancestry index, and the zit subprocess sends every structural artifact and the blobs of the held check-ins. The clone opens, derives branch state exactly, commits, pushes. The push is an ordinary push.

With --horizon=structural, quarry additionally sends the seed for C and labels every parent hash that lies below C. The clone's timeline ends at a horizon edge rather than a missing parent. When the agent runs zit annotate on a path older than C, zit prints the horizon and the quarry URL that answers for it; zit merge of a branch whose base is below C asks quarry for the base and deepens to it before merging.

On the repository's page nothing changes. A horizon clone's check-ins are the same check-ins, under the same hashes.

Reference-level explanation

1. The content horizon costs quarry nothing new

RFC 0001 §7's front passes pull traffic to the per-repo zit serve child untouched, and the child computes the held set and the keep set from the store. quarry's only obligations are two it already meets: front the request, and keep the child's store dense. The capability pragma horizon-served content is the child's, not quarry's.

One front-side rule is new. The §7 gate decodes the request container to inspect login and push cards. It MUST pass pragma horizon* cards through untouched, including to a child whose version predates them, which ignores them by the sync protocol's rule.

2. The structural horizon: three answers from the index plane

The structural horizon needs an origin to mint seeds, label boundary hashes, and answer merge-base. Each is a function of the store's state and nothing else, so each is derived data under RFC 0001 §1 and must pass §11: delete the data directory, re-ingest, get the same answers.

Where they are computed, as built. zit's own serve mints the seed, labels the boundary and answers merge-base from the repository view it opens per round, so a quarry that fronts the child serves the structural horizon with no quarry code at all: the child's horizon-served structural, its labels and its seed pass through the §7 front untouched like every other pull card. That is the implementation on zit's horizon-rfc branch and the one this RFC now assumes. The index-plane minting below remains the design for the day the child's per-round open is the hot path on a large store; nothing in the wire format changes when it moves.

Where they would be computed in quarry. From the per-repo SQLite index that ingest (§4) already maintains for branch state, leaves and ancestry, not in the zit child. Two reasons. The child is one-shot and stateless, and an ancestry walk per request against a large store is the work §4's index exists to avoid. And the seed asserts the tag state quarry displays; minting it from the same derivation keeps the clone's view and the page in agreement by construction.

How they reach the wire. The front already decodes the request. For a request carrying horizon-structural it computes the three answers, encodes them as the reply pragmas the zit draft specifies (horizon-below, horizon-seed with a private file card, horizon-mergebase), and splices them into the child's reply before re-encoding. The child continues to decide which manifests and blobs to send; quarry adds only what the child cannot know. This is the one place the front writes into a reply body, and it is confined to pragma cards and one private file card.

The seed. A control artifact whose T cards apply to C every tag in force at C: the branch tag with its value, every other propagating tag, closed if set, and C's own non-propagating tags, each with the value the index holds. D card at minting time, no earlier than the latest tag event summarized and strictly later than any seed it replaces. U card is the repository's forge agent login (RFC 0021, forge agents), so a seed is never attributed to a person. Clear-signed with the forge agent's key when the repository has one configured.

A seed is minted on request, compared by hash to the one the client offers, sent only when it differs, and never stored. There is no seed table. A repository with a thousand horizon clones at a thousand different C's costs quarry a thousand index reads per sync round and nothing between rounds.

Boundary labels. For each manifest the child's reply carries, every P card naming a check-in not in the held set is a boundary hash. The front emits horizon-below for each, in the reply that carries the manifest. The held set is the zit draft's leaves rule by default.

Merge base. horizon-mergebase A B is answered from the ancestry index with the same bounded walk the timeline graph uses. No common ancestor answers -.

Refusing a seed. A pushed artifact that is a control artifact applying to a check-in exactly the tag set the index holds for it, with a U card naming the forge agent, is a seed that leaked. The front refuses the push with the sync protocol's error card and the text seed is private to the clone that holds it.

3. The push-time cancel check

A commit from a structural horizon that creates a branch writes a cancel card for the parent branch's symbolic tag, derived from the seed. On every push the front already inspects manifests (RFC 0007's branch ownership). It additionally checks each -sym-* cancel card on a branch-creating manifest against the index's branch for the parent. On disagreement it refuses with stale seed: parent is on <branch>, not <name>; re-sync to re-seed. This is the one route by which a clone's derived state enters an artifact, and checking it here keeps a wrong seed from ever reaching the store.

4. The history below the horizon: the API

The zit draft's horizon-served pragma carries a URL. quarry's is /api/v1/r/<repo>/history, a namespace over query ops that exist or are owed under principle 5:

Op Route Status
annotate GET /api/v1/r/{repo}/annotate/{checkin}/{path} exists as a page op; API route to confirm
path history GET /api/v1/r/{repo}/finfo/{path}?from={checkin} owed
diff GET /api/v1/r/{repo}/diff/{a}/{b} exists
file at check-in GET /api/v1/r/{repo}/file/{checkin}/{path} exists
merge base GET /api/v1/r/{repo}/mergebase/{a}/{b} new; the same answer as the pragma
artifact by hash GET /api/v1/r/{repo}/artifact/{hash} new

All are registered ops in the §9 registry, so they appear in the generated OpenAPI and pass the §11 snapshot. None is a new capability; the horizon is the reason to finish the list.

Artifact by hash is the one genuinely new read. It returns the artifact's bytes, Content-Type: application/octet-stream, with Cache-Control: public, max-age=31536000, immutable, because an artifact's content is its name. It is what a horizon clone's targeted demand fetch and an edge cache both want: one blob, no sync round. Authorization is the repository's read capability as for every read op. A private artifact (the sync protocol's private marker) is never served here.

5. Which consumer gets which tool

Consumer Tool Why
Build-and-test runner (RFC 0009) /archive/<checkin> and /archive/pair: Already the design: one tree, no clone, no credential. This RFC changes nothing for it.
Persistent worker a dense clone kept warm per host, synced forward, each job on a copy-on-write copy of the .zit file Pays the clone once per host. Outside quarry's control; written here so the horizon is not sold to it.
Cold agent environment (RFC 0021) horizon clone, content first Must commit and push; has no warm copy; talks only to quarry.
A person at a keyboard dense RFC 0001 principle 7 names zit clone as the exit, and a default clone that is not a full copy weakens that promise.

The agent-account token minted for an environment (RFC 0021, run-scoped tokens) carries no horizon. The clone command does. Making the horizon a property of the token was considered and rejected: a clone shape is the client's choice, and encoding it in a credential would make the credential mean two things.

6. Disposability (§11), applied

Three new derived answers, one test each in the §11 corpus run: for a fixture store and a chosen C, snapshot the seed's T cards, the boundary set, and a merge-base answer; delete the data directory; re-ingest; the three must be byte-identical except the seed's D card, which is minting time. The D card is excluded from the comparison and asserted monotonic instead.

7. Build order

The measurement the zit draft's Appendix A asked for is done. On every corpus measured, structural artifacts are between a third and nine tenths of the stored bytes, and a content horizon alone leaves more than half of a dense clone in place; tip-tree blobs are tens of megabytes everywhere. The structural horizon is therefore the deliverable, the content horizon is the base it stands on, and the default held-set rule is descendants (the leaves rule holds every manifest from each open leaf back to its fork, which on repositories with old open branches costs several times the tip's blobs). Rungs 1 to 3 are unblocked now; rungs 4 to 6 wait only on zit's structural horizon.

Rung What Waits on
1 Front passes horizon* pragmas through untouched. Verified by reading: scanRequest in internal/xfer/container.go parses only client-version and uv-hash pragmas and relays the body as received done, reading only; a front test to add
2 /artifact/{hash} with immutable caching; /mergebase/{a}/{b}; the finfo route artifact and mergebase built (artifact.get, checkin.mergebase); finfo open
3 horizon-served URL pointed at the history namespace rung 2
4 Seed minting, boundary labels, merge-base pragma splice in the front not needed while the zit child mints (see §2); an optimization for later
5 Push-time cancel check; seed refusal the cancel check is in the zit child on every push; seed refusal in quarry is still open
6 §11 rows for the three answers rung 4

Drawbacks

Rationale and alternatives

Prior art

Decisions

Settled by the owner on 2026-10-18.

  1. horizon-served names the history namespace root, /api/v1/r/<repo>/history, and the client composes the route it needs (annotate, path history, diff, file, merge base, artifact). The draft says "an interface"; one root covers every lookup now and later without changing what the pragma means.
  2. A repository may opt out of serving structural horizons: a repository setting horizon.structural: false, default on, the way it may opt out of verdict tags (RFC 0001 §6). A steward who does not want quarry's derivation standing in for history can say so; the content horizon, which holds real artifacts throughout, is still served.

Neither horizon needs sub-manifests: zit's horizon draft holds manifests whole on a flat or hybrid history, and only the sparse path cone depends on the S-card tree. Every store quarry serves, flat ones and Git imports included, can be cloned at a horizon.

Future possibilities