# Requirements of draft-fossil-horizon-00, written from the draft text.
# Implemented so far: the horizon member of the cone (§3.3); the §3.1 held
# set (internal/horizon); the content horizon over a store's own manifests
# (§3.2, §4.1-§4.3: internal/content's classification); and its client
# synchronization against a horizon-ignorant peer (§4.4, §4.5, §6.3:
# local classification of what a dense peer sends, no cluster held but
# what a received cluster names fetched, demand fetch and deepen); the
# horizon pragmas with the origin that serves them at the content level
# (§6.1, §6.2, §6.2.1, §6.2.5, §6.4: go-libfossil's handler and client, and
# zit both ways) and the cursor (§6.2.6); and the §5.2 seed's minting,
# holding outside the artifact store and application to the derivation
# (internal/horizon/seed); and the structural horizon end to end (§5, §6.2.2,
# §6.2.3: the store's classification, boundary labels, the seed on the wire,
# re-seeding, fallback, verification and crossing diagnostics, go-libfossil
# and zit both ways); and Appendix A step 6 (§5.5, §5.7, §5.8, §5.9,
# §6.2.4: the merge-base query, deepen, merge and bisect crossings, the
# push-time cancellation check and seed verification, zit both ways where
# zit implements them); and the origin's classification memo (§6.2: kept by
# cone and store prefix, extended by appends, over a reference graph read once
# per store); and a path cone's node elision under either horizon (§3.2,
# §4.1, §5.1, §6.2: the store's classification, the origin's slot walk of
# the trees it keeps, kept with the memo, and the cursor it voids, §6.2.6);
# and named horizons (§3.4, §6.1, §6.2.7: horizon-name, horizon-resolved,
# its errors and the client's local fallback); and the origin's refusal of a
# pushed seed by its own label or a horizon-seed T card, judged when it first
# becomes expandable and recorded store-locally (§5.2.3, §5.7); and the
# seed's binding card, the clone's binding check and D floor, and the
# origin's seed-signing command and SSH namespace (§5.2.1, §5.2.4); and the
# seed's D from the repository's latest tag event (§5.2.2) and the receive
# rule: dropping a seed on every receive path, the record of dropped hashes,
# and withholding a delta that cannot yet be expanded (§5.2.3); and the
# origin's minted floor, kept across restarts in the store's config (§5.2.2).
# Rows those cover partly say what they leave out; every
# other row carries a scope note naming the Appendix A step that will cover
# it.
# Skipped, with reasons: 1.2 and 1.3 (design statements that the normative
# sections restate), 3.4 except its two SHOULDs, 5.9 except its SHOULD, 7
# and Appendix B (informative), 9 (IANA).
# Format: <id>\t<section>\t<requirement>[\t<scope note>]
# draft	draft-fossil-horizon-00.txt	b1ce1b3f3efb807d2a13b44e880f87a261f87bcca96addde750d2e43ebc79b57
HZ-1.1-1	1.1	An implementation may provide the content horizon alone; the structural horizon presupposes the content horizon.
HZ-3.1-1	3.1	A horizon names one check-in C by its full artifact hash and one of two held-set rules: descendants (the default), C and every descendant of C; or leaves, those plus, for every childless check-in L not descended from C, every descendant of each nearest common ancestor of L and C, that ancestor included.
HZ-3.1-2	3.1	A client that expects to merge across live branches offline should choose the leaves rule knowingly.
HZ-3.1-3	3.1	The held set is a function of C, the rule and the check-in graph, recomputed by clone and origin from what each holds, and stored by no party.
HZ-3.1-4	3.1	Both rules are stated over P cards alone and yield a set closed under descendants: every child of a check-in in K is in K. Childless means no check-in names it in a P card, open or closed, not the same-branch leaf of repo-state.
HZ-3.2-1	3.2	A horizon is store-adjacent local state: not an artifact, not synchronized, affecting no artifact's bytes or name; its loss reclassifies every elided artifact as a transit phantom and the store heals toward dense.
HZ-3.2-2	3.2	With both a horizon and a path cone, the blobs kept are the intersection of the two keep sets; the check-in manifests held are governed by the horizon alone; sub-manifest nodes by both, the horizon deciding whose trees are held and the path cone eliding within them the nodes no S-card slot that may hold an in-cone path reaches, as the sparse store does; every other structural artifact is held whatever either says.
HZ-3.3-1	3.3	A horizon serializes into the canonical cone form as one member of a distinct kind carrying C's hash and the rule name, canonically, so the cone's digest stays a valid memoization key.
HZ-3.3-2	3.3	A cone with a horizon member is marked with a version distinct from a cone without one, so that an implementation predating this document refuses rather than misreads it.
HZ-3.4-1	3.4	A tool that suggests a C should show what each rule would hold (check-ins and bytes) before the choice is made; this is guidance for the tool, not a wire obligation, and an origin may expose such figures through an interface outside this protocol.
HZ-3.4-2	3.4	A client that wants N recent check-ins or the tip of a branch may name it with the horizon-name pragma, resolved once by the origin, and records only the check-in it resolved to; clients should not send a count or a name in any later request, and should not express a horizon as a date at the protocol layer.
# HZ-4.1-1: horizon origins are zit and go-libfossil; stock fossil is out of scope.
HZ-4.1-1	4.1	A content-horizon clone holds every structural artifact of every age but clusters (under a path cone, of the sub-manifest nodes only those on a path into the cone), every blob an F card of a check-in in the held set names, and elided phantoms for every other blob; it holds, mints and requests no cluster.
HZ-4.1-2	4.1	Composed with a path cone, a content horizon holds, of every check-in of every age, the sub-manifest nodes reached from the manifest through S-card slots that may hold an in-cone path; the others are elided phantoms, which an origin withholds, every in-cone tree is whole, and an operation needing an elided node (a commit, a listing outside the cone) obtains it as the sparse-cone draft's §5.3 provides.
HZ-4.2-1	4.2	A blob is above the horizon when a check-in in the held set names it, directly or through a sub-manifest; elision is the sparse-cone draft's third phantom class with all of its rules: never spontaneously requested, counted distinctly from damage, reclassified by recomputation.
HZ-4.2-2	4.2	A phantom created because a held artifact arrived as a delta against it is a transit phantom whatever its horizon classification.
HZ-4.3-1	4.3	Derived caches and prefix-anchored checkpoints are horizon-independent: their keys do not incorporate the horizon.
HZ-4.3-2	4.3	The derivation of repository state runs over check-in manifests and control artifacts and reads no sub-manifest node; a content horizon holds every one of them, with or without a path cone, so the derivation is the dense repository's.
HZ-4.4-1	4.4	An operation needing a blob below the horizon resolves the elided phantom by demand fetch, targeted, or by moving C (a deepen).
HZ-4.5-1	4.5	A horizon clone does not hold the clusters it is sent. Against an origin whose replies carry horizon-served it does not request a phantom whose only reference is a cluster artifact; against a horizon-ignorant origin it requests the phantoms a received cluster names, as transit, and classifies what arrives. It requests phantoms named by igot cards, by the P, F and S cards of held artifacts, and by delta sources of held artifacts.
HZ-5.1-1	5.1	A structural-horizon clone holds the manifests and nodes of check-ins in the held set and everything they reach through B and S cards, every control, wiki, ticket, event, forum and attachment artifact, the blobs of the content horizon, elided phantoms for the rest, and the seed, which it keeps from every peer; under a path cone, of those trees' nodes only those on a path into the cone.
HZ-5.1-2	5.1	A clone does not enter the structural horizon against a horizon-ignorant peer.
HZ-5.2-1	5.2.1	The seed is a control artifact minted by the origin whose T cards apply to every entry of K (C, and each check-in of K whose primary parent is not in K) every tag in force there in the dense repository, each with the value the dense derivation holds; its D and U cards are those of Section 5.2.2.
HZ-5.2-8	5.2.1	Every seed carries one binding card, T +horizon-seed <C> <rule>, a singleton tag on C whose value is the rule, part of the signed bytes; it is not a tag in force: the clone strips it before derivation, and the origin never counts it toward the one T card a control artifact needs, so with no other tag in force it still mints no seed.
HZ-5.2-13	5.2.1	An origin must not derive a horizon-seed tag into a seed.
HZ-5.2-2	5.2.2	The origin sets each seed's D card to the latest mtime of any tag event in the repository, whether it applied, propagated or cancelled a tag, rounded up to a whole second and written with no fractional part (C's own event time, rounded the same way, in a repository with no tag event); no new tag event moves it earlier, and an origin should also date a seed no earlier than the latest D it has minted and keep that D across restarts, so a shun of the latest tag events does not move it earlier; it changes whenever any tag in the repository changes and the next exchange carries the new seed; its U card is the origin's own seed label, never a user's login, and the label must appear on no artifact the origin writes for sharing; an origin should choose a label no user holds, and a user whose login equals it has their pushed control artifacts refused; a seed is a function of C, the tag state and the latest D its origin has minted, so two origins mint the same seed for the same C and state up to the U card, unless a shun has left one a later D than the other. No seed need be dated later than the seed it replaces.
HZ-5.2-3	5.2.3	A clone never sends its seed to any peer, private-content synchronization included; it holds the seed private or outside the artifact store and applies it at every open.
HZ-5.2-4	5.2.3	An origin must refuse a seed offered to it, identifying one by its own seed label on the U card of a pushed control artifact, judged on the expanded content after any delta is applied, a delta whose base arrives later in the same request included; the refusal is atomic: it stores nothing the request carried and replies with one error card alone, seed refused: a seed is private to the clone that holds it; it is never pushed. It may also refuse a pushed artifact whose name matches a seed the same request offered as held.
HZ-5.2-9	5.2.3	A seed is judged when it first becomes expandable, by whatever path supplies its missing base (a push, a pull or clone from another peer, a local check-in); a delta that then expands to a seed is never crosslinked, applied or served, and is recorded as refused in local state that is never synchronized, never in the shun list; the rest of the transfer that supplied the base is stored and crosslinked normally; the atomic refusal applies to the request that carried the seed whenever the seed can be judged within it.
HZ-5.2-10	5.2.3	An origin refuses only its own label, a control artifact bearing another implementation's seed label being by that label ordinary; it must also refuse, in the same way and with the same error card, any pushed control artifact carrying a horizon-seed T card whatever its U card; only a foreign artifact carrying neither the origin's label nor a binding card is stored as ordinary.
HZ-5.2-14	5.2.3	A store never keeps a seed it did not receive through a horizon-seed announcement: on every receive path (pull, clone and sync) it drops an artifact that is a seed by its own seed label or by a binding card, and stores one that carries neither as an ordinary control artifact; the test is made when the artifact first becomes storable, whether it arrives in full or as a delta completed in a later round; a dropped artifact is not counted as received and must not be requested again in the same session; a seed found on the origin path is dropped the same way; a store still skips a stored seed when deriving.
HZ-5.2-15	5.2.3	Across sessions a store should keep a record of the hashes it dropped, keyed by hash and never synchronized, and skip them when a peer announces them; a store without one asks again and drops again.
HZ-5.2-16	5.2.3	A store that keeps a pushed or received delta whose base it lacks must not announce or serve that delta while it cannot be expanded; dropping such deltas also conforms.
HZ-5.2-5	5.2.4	An origin holding a signing key should sign the seed with the clear-signing envelope.
HZ-5.2-6	5.2.4	A clone that knows its origin's public key verifies every seed's signature and discards an unsigned or wrongly signed seed, falling back to the content horizon; a clone with no key accepts an unsigned seed.
HZ-5.2-11	5.2.4	Before holding a seed, signed or not, a clone must check that it carries exactly one horizon-seed card naming its own store cone's C and rule, and must refuse a seed whose D card is earlier than the held seed's (an equal D is accepted; a deepen discards the held seed first, so the floor restarts at the new C); a refused seed is handled as a wrongly signed one, the clone falling back to the content horizon and reporting it.
HZ-5.2-12	5.2.4	An origin's seed-signing command is configured apart from its check-in signing command and, for SSH, signs under the namespace fossil-horizon-seed, never the check-in namespace; an origin with no seed-signing command configured sends seeds unsigned even when it signs check-ins; PGP relies on the binding card alone.
HZ-5.2-7	5.2.1	When no tag is in force at any check-in the seed would cover, the origin mints no seed and serves the request at the content level, since a control artifact carries at least one T card.
HZ-5.3-1	5.3	A boundary hash is a P-card parent, not in K, of any check-in manifest the clone holds, a held baseline below C included; a clone classifies a labelled boundary hash as an elided phantom and an unlabelled unheld reference as a transit phantom, which it requests.
HZ-5.3-2	5.3	When a new check-in forks from below the horizon, or a new delta manifest reaches a baseline below it, the origin labels the new parents in the round that delivers the manifest naming them; a clone requests an unheld unlabelled parent, and an origin does not send one it does not keep.
HZ-5.4-1	5.4	A boundary hash should be rendered as the horizon and is never described as damage; verification reports manifests below the horizon as elided, distinctly from damage.
HZ-5.5-1	5.5	An operation needing history below the horizon (a merge base, a symbolic name, a checkout, diff or annotate below C, a bisection) detects the crossing, names the horizon in its diagnostic and offers the stated remedy.
HZ-5.5-2	5.5	An implementation never merges against a base it does not hold.
HZ-5.5-3	5.5	Whenever the ancestry of either side of a merge crosses the horizon the clone asks the origin for the base, even when it finds a common ancestor among what it holds, and deepens to the base when it lies below the horizon.
HZ-5.6-1	5.6	A clone requests the seed at each synchronization, naming the seed it holds; the origin replies with a new seed only when it differs, keeping no per-client state, and the clone replaces the seed it holds with the new one whole.
HZ-5.7-1	5.7	A commit from a structural horizon is an ordinary commit: on an existing branch it writes no branch tag; creating a branch it writes the repository-state T cards, the parent branch's cancellation included.
HZ-5.7-2	5.7	An origin should check a pushed cancellation against its own derivation and refuse on disagreement, naming the stale seed and the parent's branch, judging the check-in's expanded content, a delta whose base arrives later in the same request included; it admits the check-in when the push or its store carries a control artifact cancelling the parent's branch tag on it.
HZ-5.8-1	5.8	A deepen names an ancestor C' of C, recomputes the held set and synchronizes at C'; nothing held is replaced, and the deepening clone discards the old seed before applying the new one.
HZ-5.8-2	5.8	A deepen that must reach a check-in outside K names as C' a common ancestor of C and that check-in, computed locally or asked of the origin; when none is known it is refused naming the remedy, a fresh clone with an earlier horizon, and C never moves to a check-in that is not its ancestor.
HZ-5.9-1	5.9	An implementation should offer seed verification by deepening as a verification mode.
HZ-6.1-1	6.1	A horizon-scoped request carries at most one pragma horizon <hash> <rule>, may carry path-cone pragmas (the keep set is then the intersection), and adds pragma horizon-structural for the structural horizon; any request may carry pragma horizon-cursor.
HZ-6.1-2	6.1	A request may carry pragma horizon-name <name> <rule> <depth> in place of pragma horizon, never both: name is tip, a symbolic name or a full check-in hash, as a card token with Fossil's \s escaping; rule as for pragma horizon; depth required, plain base-10 digits that fit an unsigned 32-bit integer, 0 the named check-in itself; the structural, seed, cursor and path-cone pragmas combine with it as with pragma horizon. A client sends it only in the first request of a clone; every later request of the clone, and every later pull, sync or deepen, sends pragma horizon <hash> <rule> with the resolved hash, and the store records the hash, never the name.
HZ-6.2-1	6.2	An origin receiving a horizon pragma computes the held set over the dense graph and restricts the blobs it sends; for a structural request it also restricts manifests and nodes, sends every other structural artifact, the boundary labels and the seed, and bounds the per-round walk.
HZ-6.2-2	6.2	An origin should not send cluster artifacts in reply to a horizon-scoped request.
HZ-6.2-3	6.2.1	An origin does not send, in reply to a horizon-scoped request, a delta whose base is outside the keep set; it sends that artifact in full, may send in-keep-set deltas verbatim, should bound reconstruction per round, and may send a delta verbatim when it cannot reconstruct it.
HZ-6.2-4	6.2.2	For a structural request the origin emits pragma horizon-below for every boundary hash the manifests in the reply introduce, the parents of every check-in manifest a baseline below C included, no later than the reply carrying the first manifest that names it.
HZ-6.2-5	6.2.3	A structural request carries pragma horizon-seed with the held seed or '-'; the origin announces a differing seed with the pragma before its file card; the client verifies the bytes hash to the announced name and parse as a control artifact, drops a card failing either, routes it to its seed holding, and never announces or sends it.
HZ-6.2-6	6.2.3	An origin does not advertise the structural level unless it sends a seed in the same reply or the client's offered seed is the one it would send; because the D card is fixed by the repository's tag events and the latest D the origin has minted, two mints over unchanged state produce the same name.
HZ-6.2-7	6.2.4	A client may ask pragma horizon-mergebase <primary> <secondary> [<secondary>]; an origin that can answer repeats every hash of the query in the order asked and appends Fossil's pivot of the primary over the secondaries, or '-' when no common ancestor exists, and bounds the ancestry walk; an answer whose hashes do not repeat the query is no answer.
HZ-6.2-16	6.2.4	In a merge the primary is the check-in merged in and the secondaries are the checkout and each plain merge pending in it (not a cherry-pick, backout or integrate); a query carries at most two secondaries and an origin answers none with more, nor one naming a check-in it does not hold.
HZ-6.2-8	6.2.5	An origin emits pragma horizon-served <level> [<url>] in every reply to a horizon request; a client does not enter the structural horizon unless its first reply carried structural, and should report the mode a transfer used.
HZ-6.2-9	6.2	An origin should keep its classification keyed by the cone digest and the store prefix it classified, extend it by the artifacts appended since, and recompute in full only when an appended artifact carries a name a held P, B or S card cited while it was absent.
# HZ-6.2-10: a horizon's trees enter the graph when a walk first reaches them,
# not when the store is read: a tree no horizon walks is never read. Each is
# read once per memo, kept as blob ids (a full manifest as its difference from
# the one before; fossil-scm.org's whole history in about 11MiB) with no byte
# bound, and outlives a full read of the store, since its name fixes its bytes.
# A kept tree serves only where a fresh read would give it: the store holds its
# name at its rid and can expand it.
HZ-6.2-10	6.2	An origin should read each structural artifact's kind and P, B, S, F and A references once per store into a cone-independent graph keyed to the same prefix, extend it by appended artifacts, and classify every cone over that graph.
HZ-6.2-11	6.2.6	An origin that classified a request emits pragma horizon-cursor <n> in every reply, n its artifact-sequence position through which the reply was served; receiving a cursor it announces no artifact at or below it and every kept artifact above it the request did not name, and after a reclassification, or after an appended tree reaches through an in-cone slot an older node the classification withheld (which it then keeps), answers a request carrying a cursor below the position it recorded once as if it carried none; it announces with igot only in reply to a request that pulls, and a reply to a push-only request announces nothing, cursor or not.
HZ-6.2-14	6.2	When a horizon request also carries path members, the origin withholds at either depth the sub-manifest nodes of the trees it keeps that no S-card slot that may hold an in-cone path reaches, neither sending them in a clone nor answering a request for them; a request without path members receives every node of those trees.
HZ-6.2-15	6.2	Under a content horizon with path members the origin's walk should be kept like the classification: descending only slots that may reach the cone, visiting each node once per bound it is reached under (a last slot's bound its parent's, a wider bound deciding for a narrower), and recording what it walked, so that a later request walks only the trees appended since.
HZ-6.2-12	6.2.6	A client that converged records the last reply's cursor and its own artifact count per origin and sends the cursor on every request to that origin; it may then announce nothing on a pull-only request, must still announce on a request that sends but may announce only artifacts appended after the recorded count, and advances the count only past artifacts the origin holds.
HZ-6.2-13	6.2.6	The cursor changes what is announced, never what is answered: an origin answers a gimme for any kept artifact whatever cursor the request carried, a client requests every phantom it wants as without one, and either side may drop the cursor and fall back to a full announcement.
HZ-6.2-17	6.2.7	An origin resolves a horizon-name over its dense repository: a well-formed full hash (40 or 64 hex digits) is looked up as a hash first and names that check-in if one has it, otherwise it is tried as a symbolic name; tip is the most recent check-in; any other name is the most recent check-in carrying sym-<name> as an active tag (singleton or propagated, not cancelled), covering branches and tags alike; most recent means the highest event mtime with a tie going to the lexicographically lowest full hash, never a local row number, and a hash prefix is never accepted; it then steps back depth times along primary parents, stopping at a check-in with none. Before any file card and before horizon-served it emits pragma horizon-resolved <hash> <steps>, C's full hash and the steps taken, and serves the request as if it carried pragma horizon <hash> <rule>, minting any seed for that C.
HZ-6.2-18	6.2.7	A horizon-name that resolves to nothing, a malformed depth, or both pragma forms in one request is answered with one error card and nothing else, horizon\sname\snot\sfound:\s<name> or malformed\shorizon-name\spragma; login and authorization errors come first and a name error after them, as the single error card; the client then fails the clone and leaves nothing on disk.
HZ-6.2-19	6.2.7	A client whose first reply carries horizon-resolved takes its hash as C and reports when the steps taken are fewer than the depth requested, history having run out.
HZ-6.2-20	6.2.7	The client fails the clone and leaves nothing on disk when horizon-resolved names a malformed hash, takes more steps than the depth requested, arrives after any file card or in reply to a request that sent no horizon-name, or when the clone ends without the resolved check-in having been received.
HZ-6.2-21	6.2.7	A client whose reply carries no horizon-resolved completes the dense transfer, resolves the name and depth over what it holds by the same rule, classifies locally at the content horizon (Section 6.3), records the hash and reports that the name was resolved locally; a structural request against such a peer falls back to the content horizon.
HZ-6.3-1	6.3	Against a horizon-ignorant peer a content-horizon client classifies locally, stores the blobs above the horizon, records elided phantoms and reports the counts; a structural clone request falls back to the content horizon for that transfer and reports that it did.
HZ-6.3-2	6.3	A store at the structural horizon whose pull or sync replies carry no horizon-served structural falls back to the content horizon at the same C and rule, records that depth, discards its seed, fetches the manifests and nodes below C, and reports that it did.
# HZ-6.4-1: a refused horizon sync still accepts the peer's push. The pull half
# is refused and the reply sends the peer no content, which is what §6.4's "no
# content" governs: the refusal protects the requester from a partial answer.
# What the peer pushes is its own history, which the store can hold whatever its
# horizon, so the gimmes answering its announcements stay and its files land.
HZ-6.4-1	6.4	A server over a horizon clone refuses a dense clone request, and a horizon request whose held set is not a subset of its own, with one error card and no content; the requesting client treats that reply as fatal.
HZ-8-1	8	The origin's held-set walk and merge-base query carry the depth and visited-node bounds of any server-side graph walk, and delta inflation on the serve path is bounded by the round's batch limit.
