50 descendants and 50 ancestors of ed53ee9c598e6be7
|
2026-10-24
| ||
| 11:10 | Merge branch 'feat/471-seed-refusal' into feat/seed-receive-drop check-in: b6e785b914 user: [email protected] tags: feat/horizon-dfloor-receive, main, trunk | |
| 11:10 |
fix(sync): an unresolvable pending chain no longer blocks a fill (#471)
Review of a067c6e. JudgeFilled treated content.Expand's chain-bound error as a store fault and returned it, and DeltaStored failed storeDeltaContent with it. A pusher could plant a dangling chain longer than maxDeltaChainDepth under a basis R, building it by re-rooting. After that, every honest push, pull or clone that supplied R as a delta failed for good. Fix: - content now wraps its cycle and depth errors in ErrChainUnresolvable, and exports MaxDeltaChainDepth. - seedcheck counts ErrChainUnresolvable as unreadable, alongside a hash mismatch and a delta that does not apply or fails its checksum. Such a row is skipped and stays pending, so it stays withheld. - Real faults are still returned, such as content that does not decompress. - The classification uses errors.Is on the sentinel, so the expansion JudgeFilled already performs is not repeated. Tests: - TestHorizonOverDeepPendingChainDoesNotBlockFill plants a pending row one node past the bound under R, then pushes R as a delta on a held basis. The push succeeds, R is stored and readable, and the over-deep row stays withheld. - TestUnresolvableChainSentinel pins the sentinel for a cycle and for a chain past the bound. - Dropping the classification, or either wrap, fails a test. A cycle never reaches seedcheck: the receive pass's own basis-chain walk (receive_pass.go chainEnd) refuses it first. check-in: 8b946de415 user: [email protected] tags: main, trunk | |
| 07:10 | Merge branch 'feat/471-seed-refusal' into feat/horizon-names-seed-refusal check-in: 0b3bc67376 user: [email protected] tags: main, trunk | |
| 06:59 | Merge branch 'feat/471-seed-refusal' into feat/seed-receive-drop check-in: 68cf05d9ea user: [email protected] tags: feat/horizon-dfloor-receive, main, trunk | |
| 06:59 |
fix(sync): judge pending rows when a received delta fills their basis (#471)
Two review nits. Liveness. A pushed artifact pending on a basis P stayed withheld for good when a pull supplied P as a delta: - Only MarkPending re-rooted, and only for a pushed delta. - Filling a phantom with a delta runs no cascade, so nothing judged the waiting rows if the delta was readable, or moved them to P's own root if it was not. A later fill of that root never reached them. The fix is one hook at the choke point every received delta passes, storeDeltaContent: seedcheck.DeltaStored. - If the stored delta is readable, the rows pending on it are judged. - If not, they move to its chain root, so whatever finally resolves the chain judges them. - It replaces MarkPending's push-only re-root. Error surfacing. JudgeFilled still skips a row whose own bytes cannot be read (a delta that does not apply or fails its checksum, or bytes that are not the artifact named) and judges the rest of the batch. Any other failure is now returned, a broken chain to the filled root included: check-in and tag return it, and the cascade logs it, as it already did for JudgeFilled errors. The separate availability skip went with it. Tests: - A pushed plain file and control artifact pending on a basis that a pull supplies as a delta, on a held basis or on one that arrives in the next round. Both become readable and stop being withheld, are announced on a push-on, and the control artifact is linked. - A row whose content does not decompress is reported. - A checksum-failing row is skipped like an invalid one. - Each hook branch and each error class fails a test when changed. check-in: 584c55d1fd user: [email protected] tags: main, trunk | |
| 06:49 |
test(conformance): re-pin the horizon draft to the zit 0.7.1 text
Pin draft-fossil-horizon-00 at zit 236b7b4285 (artifact 130b85e386), which adds named horizons (§6.2.7), the origin's own seed label and the refusal of a pushed seed on expanded content (§5.2.2, §5.2.3, §5.7), and the seed's horizon-seed binding card with the clone's checks on it. Re-read horizon.tsv against the new text: the seed-label, push-check and named-horizon rows follow its wording; the client rows of named horizons move from §6.3 to §6.2.7 (HZ-6.2-19 to HZ-6.2-21); the store-local judging of a late seed and the binding-card shape refusal get rows of their own (HZ-5.2-9, HZ-5.2-10). The binding card, its no-derive rule, the clone's binding and D-floor checks and the separate seed-signing namespace (HZ-5.2-8, HZ-5.2-11 to HZ-5.2-13) are not built and show as gaps. check-in: cfa0577b2a user: [email protected] tags: main, trunk | |
| 06:39 |
Merge branch 'feat/471-seed-refusal' into feat/horizon-names-seed-refusal
# Conflicts: # conformance/horizon_test.go # docs/site/data/conformance.json check-in: 81d00b2e3b user: [email protected] tags: main, trunk | |
| 06:29 | Merge remote-tracking branch 'origin/main' into feat/horizon-names-seed-refusal check-in: d24a684bbe user: [email protected] tags: main, trunk | |
| 06:28 |
Merge feat/471-seed-refusal into feat/seed-receive-drop
Takes the client-side announcement fix. A seed refused at the link step without a pending row (one a pull or clone completes in a later round) leaves unclustered through refuse(). A push-on from that store never names it, and a test now pins this. check-in: 9f0b995575 user: [email protected] tags: feat/horizon-dfloor-receive, main, trunk | |
| 05:58 |
fix(sync): never announce a withheld artifact from the client side (#471)
Review of 0e28b6a: when the origin pushed on to a peer, its own client announced artifacts its replies withhold. A pending seed went out from unclustered, and after verify.Rebuild (which marks every blob unclustered and unsent again) a refused seed went out too. The peer kept asking, loadFileCard withheld it, and the push ran out of rounds. Fixed in the tables the client announces from, not in each reader: - MarkPending takes a pending row out of unclustered. - decide puts it back once judged not a seed; a seed stays out. - rebuildBookkeeping leaves withheld rids out of unclustered and unsent. - That also keeps them out of cluster minting, which reads unclustered. - sendPrivate and the private gimme answer, which read the private table, skip withheld rids. - sendUnclustered needs no filter of its own, and sendAllClusters lists only linked clusters, which a withheld row never is. JudgeFilled now skips a row that does not expand (it stays pending) and judges the rest of the batch, instead of returning. Tests: - A pending seed is not announced on a push-on, publicly or privately. - A peer asking for a private pending seed outside a private sync is not told it exists. - After a rebuild, a push-on converges and never names the refused seed. - A pending file, once judged, is announced again on a push-on. - An unreadable row does not stop the next fill in the batch being judged. - Every new mechanism fails a test when disabled. check-in: df78b8aee9 user: [email protected] tags: main, trunk | |
| 05:21 |
Merge feat/471-seed-refusal into feat/seed-receive-drop
Takes feat/471's no-delete model: a refused seed stays stored, refused by its hash, and is never linked or served. Its effect on the receive side: - A seed a pull or clone can read on arrival is still never stored. - One completed in a later round is refused by the link step and kept, inert, rather than swept. The clone's sweep is gone. - Refusing a seed at the link step takes it out of unclustered, as a push-side refusal does. - A dropped seed's phantom row stays inert, since deleting it would free its rid under a receive pass. loadDBPhantoms skips refused hashes, so it is never asked for again. - The client's addPhantom filter is gone: a refused hash always has a row by then. - The handler keeps feat/471's recordStored, which refuses a re-push whose row is already held. A test now pins the client's verified-delta refusal (a seed sent as a delta on a held basis), and seeds dropped on receipt are asserted not stored (assertDropped). check-in: 5b43d4fe38 user: [email protected] tags: feat/horizon-dfloor-receive, main, trunk | |
| 04:58 |
fix(sync): keep a refused seed's bytes; record the refusal only (#471)
Final review of c077fa4. F.1 requires recording a refusal, not deleting the bytes, and every finding came from the deletion: - A delta built on a swept seed lost its basis, was withheld forever, and could not be re-pushed, because it counted as held. - The end-of-sync sweep ran outside a transaction. - A refused hash pulled from an upstream was dropped, which is a receive-side behaviour F.1 does not have. So a refused seed stays stored: - Its hash is recorded in seedrefused. - It leaves unclustered, so no inventory or cluster names it. - It is withheld from every reply, and the link step never links it. - Deltas on it stay readable and are judged like any other pending row. - Pulling the same hash stores nothing new: the row is held, and stays refused. - A re-push of it is refused on its bytes. Every form (full text, delta on a held basis, delta whose own row is held) expands to the seed. Removed: - Sweep, sweepRow and its no-root marking. - Every Sweep call: handler request end, end of sync, check-in, tag and rebuild. - requestPhantoms' refused-hash filter (a refused seed is held, so never a phantom). - ownSeedReadable's refused-hash check and its Clear (the fill's JudgeFilled already clears a non-seed). - JudgeFilled's lazy re-root, which nothing reached: only full text fills a root JudgeFilled hears of. Kept, each pinned by a test: - Pending rows keyed by chain root, with MarkPending moving the rows waiting on a rid when that rid is itself pushed as a dangling delta. Filling a phantom with a delta triggers no cascade, so this is the re-root that matters. - JudgeFilled examining only its subtree, and its hash-mismatch skip. - The link-step judge and the refused-hash check in it. - The push-time atomic refusal. - Withholding pending and refused rows, and Purge forgetting pending rows (rids are reused). Only pushed dangling deltas are marked pending. A dangling delta stored by a pull onto a pending row's basis is not, so rows waiting on it stay withheld until the link step reaches them; a plain file among them stays withheld. Tests: - The reviewer's probe, for a control artifact and a plain file built on a pending seed, pushed after it and before it: after the base arrives both are readable and not withheld, and the control artifact is linked and served. - A plain file after a forged delta is still cleared. - Every judge, filter and mark fails a test when disabled, except two performance mechanisms no behaviour shows: the per-transaction table cache and IsSeedBytes' byte prefilter. check-in: 6083693442 user: [email protected] tags: main, trunk | |
| 04:08 |
feat(sync): drop seeds on every receive path (horizon F.2, F.3) (#471)
A store keeps a seed only through a horizon-seed announcement. This lands with the zit 0.7.2 draft re-pin, after feat/471's F.1. - storeReceived refuses, before storing, a seed it can read: full text, or a delta verified against a held basis (errSeed). The push handler turns it into the atomic refusal. The client and clone drop it: not stored, not counted, and its in-memory phantom forgotten. Its hash is refused. - A hash refused earlier is dropped when sent again, even as a dangling delta, and a client never makes a phantom of one, so it is not asked for again. - The link step refuses any seed-shaped artifact it reaches, whatever stored it. An announced seed is held outside the artifact store, so it never reaches the link step. This catches a seed completed in a later round of a pull or clone, through the dephantomize cascade or the clone's receive linker, from the deck the link step has already parsed. - Received dangling deltas are not marked pending. A clone's history chains arrive newest first, and judging each would expand it a second time. - A clone sweeps refused rows after its linker finalizes. B.2's last sentence follows F.2. A seed-shaped artifact outside an announcement is ordinary only with neither this store's label nor a binding card. hzSeedOrdinary mints under zit's label, and the docs and the HZ-5.2-4 scope note cite F.2 and F.3. Tests: - A pull dropping a seed by label and by card, counted correctly and not asked for again by a second peer. - A seed delta completed in a later round, dropped on pull and on clone. - A clone dropping label, card and a re-sent form, counting none. - A refused seed re-sent to a client as a dangling delta. - Disabling any of these mechanisms fails a test. check-in: 21746b5036 user: [email protected] tags: feat/horizon-dfloor-receive, main, trunk | |
| 03:44 |
fix(sync): refuse pushed seeds by hash; judge only what a fill grounds (#471)
Horizon F.1, push side, and the fourth review's blocking finding. Refusal by hash: - seedcheck keeps two store-local tables, carried by no config group: seedpending(rid, root) and seedrefused(uuid). - A seed judged after the request that pushed it, because a later push, pull, check-in or tag filled its basis, has its hash refused. Its rows are deleted by Sweep, and only where no receive pass holds rids: end of request, end of sync, check-in, tag and rebuild. A deleted rid is reused by the next row, and a pass's caches are keyed by rid. - Until swept, such a row is withheld and never linked. A delta on a refused seed is left unreadable and pending under no root. - requestPhantoms never asks for a refused hash. ownSeedReadable refuses a re-push of one, and a full-text re-push is refused on its bytes. - A purge still forgets pending rows (ForgetDeleted). Refusals are keyed by hash, so rid reuse cannot pass them on. Judging only what a fill grounds: - JudgeReadable rescanned every pending row on each fill. A pusher could plant dangling deltas without bound and make every later fill pay for all of them. - Pending rows now record the root of their basis chain, re-rooted when that root is itself stored as a dangling delta. - JudgeFilled judges only the rows pending on the rids just stored, and reports how many it examined. - Callers: AfterDephantomize and its batch, manifest.Checkin (its files and manifest) and tag.AddTag. linkArtifact still judges a pending row it reaches, caching which tables exist per transaction. - The sweep no longer judges. A seed-shaped artifact received by pull or clone stays ordinary, per the 0.7.1 B.2. Docs and the HZ-5.2-4 scope note cite F.1. Tests: - A seed package test: one fill examines its own row, not 200 planted elsewhere. Run as a rescan mutant, it fails. - A tag whose control artifact is a seed's basis refuses the seed. - The link step refuses a seed made readable by a raw store. - A re-push refused by hash, and an igot of the hash not gimme'd. - A delta on a refused seed withheld. - A purged pending rid reused by an innocent blob. - Disabling any judge call (cascade, check-in, tag, link) or any Sweep call (request, sync, check-in, tag) fails a test. check-in: ed5e3ce5b5 user: [email protected] tags: main, trunk | |
| 03:22 |
Merge remote-tracking branch 'origin/main' into feat/471-seed-refusal
# Conflicts: # CHANGELOG.md check-in: 351bb4f35b user: [email protected] tags: main, trunk | |
| 03:06 |
Merge pull request #475 from danmestas/fix/clone-dangling-delta
fix(sync): clone an origin that holds a dangling delta check-in: 77c6eb043e user: [email protected] tags: main, trunk | |
| 02:58 |
fix(sync): clone an origin that holds a dangling delta
An origin that held a delta whose source it never received could not be cloned at all. walkCloneChain found the source unforwardable (a phantom) and asked buildCloneArtifact to send the delta expanded; expanding it failed, the handler errored, and every client stopped at clone sequence 1. The origin now withholds a chain that ends at a phantom, as it withholds a row outside the clone's cone. Sending the stored delta naming its phantom source, as fossil's send_compressed_file does, is not enough: a fossil client then rejects the whole clone for its unresolved deltas. A later pull fetches the chain once the source arrives. check-in: 67bc700498 user: [email protected] tags: main, trunk | |
| 02:27 |
fix(sync): withhold pending pushed artifacts; judge them when filled (#471)
Third review of the seedcheck design: - Withhold everything recorded, not just refusals. Every serving path (gimme/file, inventory and private igots, both clone paths, the client's file cards) now withholds any rid with a seedcheck row, pending or refused. A seed whose base a local check-in filled was served unjudged before. - Judge promptly. seedcheck.JudgeReadable judges, in one transaction, every pending row that has become readable, of any type: a seed is refused, anything else (files and non-artifacts too) loses its row, so withheld rows do not grow without bound. It runs in the transaction of each path that fills a basis: the dephantomize cascade, the sweep and rebuild (prepareCrosslinkTables), manifest.Checkin and tag.AddTag. Rows are walked in rid order through one cache, so a pending chain is expanded once. A row whose bytes fail their hash stays pending and withheld. - Forget on purge. shun.Purge drops seedcheck rows whose blob is gone (seedcheck.ForgetDeleted), since rids are reused. It is the only place blob rows are deleted. - One existence lookup per transaction. linkArtifact caches whether the table exists in the batch schedule, which is reset on commit and rollback. - Inventories read the withheld set fresh. The per-request cache serves gimme and clone only. Rows a request adds are its own unreadable deltas, which nothing can send, so the store-time and pre-inventory cache resets were unobservable and are gone. - The unsent delete on refusal is removed: only local writes fill unsent, so a pushed artifact is never in it. Tests: - The check-in-fills-base case no longer sweeps explicitly. It shows the seed refused in the check-in's transaction and served to neither a clone nor a pulling peer. - A pending seed is not answered on gimme or announced. - A pending plain file is served once its base arrives. - A purged seed's rid, reused by an innocent blob, is not withheld. - A private inventory withholds a privately pushed seed. - A clone does not send a refused seed as a delta source. - Disabling any serving filter fails a test. A clone of any store holding a dangling delta stalls at clone sequence 1. That is pre-existing and unrelated to seeds, so the pending-seed test checks pull and gimme instead. check-in: c72c1245b8 user: [email protected] tags: main, trunk | |
| 01:44 | Merge branch 'main' into feat/horizon-names-seed-refusal check-in: eb2fc856e5 user: [email protected] tags: main, trunk | |
| 01:44 |
fix(sync): judge pending pushed seeds at the crosslink step (#471)
Re-review found two faults in the shun-based design: - Only the push path judged a dangling seed. A base filled by a pull, a local check-in or a later sweep still crosslinked the seed. - Shunning the seed was wrong. The shun table syncs through the shun config group. A horizon clone that pushed its own current seed back as a dangling delta could get the origin to shun that hash, and every clone pulling the group would then drop its seed. New design, in internal/seedcheck: - A store-local seedcheck(rid, refused) table, created on first use and carried by no configuration group. - The push handler records a delta it cannot expand as pending. Only pushed artifacts are recorded: a seed-shaped artifact that arrives by any other route is ordinary (B.2). - One choke point: manifest.linkArtifact. AfterDephantomize, the sweep, rebuild, the receive linker and required-content linking all reach it. There a pending control artifact is judged: a seed becomes refused, is not linked, and leaves unclustered and unsent. Anything else loses its row and links as usual. A pending non-control row is left, inert. - A seed judged within the request that carried it still refuses that request atomically (ownSeedReadable). It expands through the pass's basis cache, so a dangling chain is expanded once along its length. - Serving honours refused: gimme/file, inventory and private igots, both clone paths, and the client's file cards when it pushes on. judgeGrounded, the shun use and the shun filters added to the sweep, rebuild and cascade are removed. Tests: - The pull-fills-base probe and a check-in-fills-base case. - A refused seed not cloned, and not pushed on even when a peer asks for it by name. - The seedcheck table is in no config group. - B.4.4 also asserts the real minted seed is refused while the shun table stays empty. - Disabling the choke point, or any of the serving filters, fails a test. Clone receive is not covered: a fresh clone has no pending rows, so that path cannot meet one. check-in: 776eae66e9 user: [email protected] tags: main, trunk | |
| 01:34 |
fix: send a clone's horizon by name even as a full hash
A horizon given through CloneOpts.Horizon or --horizon now always goes out as pragma horizon-name, a full hash at depth 0 included. The origin tries a full hash as a check-in and then as a symbolic name (§6.2.7, C.3), so a branch or tag spelled as 40 hex digits is found; a peer that resolves no name is covered by the local fallback. A horizon held on the store cone still goes out as pragma horizon on every pull, sync and deepen. nameAuthority now says why crediting the login card to the whole request is harmless, and TestResolveNameBounded fails within 2s with a message naming the unbounded walk. check-in: 017c7f9393 user: [email protected] tags: main, trunk | |
| 01:18 |
fix(sync): judge a seed when it first becomes readable; shun late seeds (#471)
Review found that a seed pushed as a delta against a missing base, then completed by a later request carrying the base, was crosslinked with no check. Per the amended erratum (spec F), the test now runs on the artifact when it first becomes readable, whatever request carried it: - Full text and a delta on a readable basis are judged as they are stored. A delta left dangling is judged when a phantom grounding it is filled: storeFile runs storeReceived, then judgeGrounded over the filled rid's dependent deltas, before the after-dephantomize cascade. - A seed the current request carried refuses that request atomically with the single error card, as before. - A seed an earlier request left dangling is recorded in the shun table. The request that supplied its base is innocent and is stored and crosslinked normally, with no error. Refusing it would let anyone block a future push by planting a dangling seed delta against a hash the origin will later receive. - A dependent whose bytes do not hash to its name is skipped. Shunning the name would let a forged delta bar the real artifact. Why the shun table: every serving path already withholds shunned names (gimme/file, igot inventories, clone), receipt drops them, and shun.Purge can remove the content. What it does not cover was crosslinking, so candidateQuerySQL (sweep and rebuild) and the dephantomize cascade's delta-child walk now skip shunned blobs. That matches fossil, where shunned content never reaches crosslink. Two consequences: the entry propagates to peers that pull the shun config group, which only shuns an attack artifact, and an admin can lift it with shun.Remove. Also from the review: - handleFile's post-store bookkeeping moves into recordStored. - isSeed and the new helpers assert their inputs. - The end-of-request pushedSeed pass is gone; each delta is judged once when it becomes readable. Tests: the two-request case is now B.4.4, a unit test and conformance case horizon.5.2.3.seed-base-later-request. The later push succeeds and the seed is shunned, never crosslinked (rebuild included), sent or announced. A new unit test judges every dependent, ordinary before seed, and checks a forged dependent is not shunned. Mutations in the judging loop were checked and are killed. check-in: 3b76d8e5f5 user: [email protected] tags: main, trunk | |
| 01:04 |
Merge pull request #474 from danmestas/fix/deterministic-gimme
fix(sync): ask for phantoms in hash order check-in: d40046f706 user: [email protected] tags: main, trunk | |
| 00:57 |
fix(sync): ask for phantoms in hash order
buildGimmeCards ranged over the phantoms map, so each round's gimme cards came out in Go's randomised map order. When a round has more phantoms than mxPhantomReq allows, that also picked a random subset to ask for. A seeded simulation then could not replay the same wire, and the docs generator's engine.json (which records a pull's cards) changed between runs, failing the generated-docs check about one run in three. Sort the phantom names before building the cards. Phantom sets are bounded by what the peer announced, so the sort is cheap next to the round trip. check-in: 82dae665dd user: [email protected] tags: main, trunk | |
| 00:37 |
docs: seeds are never pushed; regenerate conformance data (#471)
Covers what the horizons concept page and the CHANGELOG say about refusing pushed seeds, including two consequences: another implementation's label alone is ordinary, and a user whose login is go-libfossil cannot push control artifacts. check-in: e59ec7547c user: [email protected] tags: main, trunk | |
| 00:37 |
feat(sync): origin refuses a pushed seed by label or shape (#471)
Horizon §5.2.3 as the zit 0.7.1 erratum words it. A pushed control artifact is a seed when its U card is the origin's own label (go-libfossil) or it carries a horizon-seed T card under any label. Full text is judged as it arrives. A delta is judged on its expanded content at the end of the request, so a delta whose base arrives later in the same request is caught. A delta never made readable is not identified and stays a dangling delta. The refusal is atomic: the request rolls back, and the reply is the single error card "seed refused: a seed is private to the clone that holds it; it is never pushed". The horizon-seed name match is kept as an extra check, and now refuses loudly the same way instead of silently dropping the file. Conformance cases for HZ-5.2-4 cover full text, a delta against a held base, a late delta, the shape under a foreign label (full and late), and a foreign label with no binding card, which is stored. An HZ-5.7-2 case covers the stale-seed check on a late delta. An interop case has zit 0.7.0 push such an artifact to a go-libfossil origin and get refused. check-in: 1afe809b01 user: [email protected] tags: main, trunk | |
| 00:37 |
fix(sync): resolve horizon names only for authorized requesters
An origin resolved a horizon-name before the requester's login was read, and served a requester that may neither read nor clone at the horizon the name resolved to, so whether a name exists showed in its reply. The login card is now checked first, and only a requester that may read or clone has the name resolved; anyone else is answered as if the request named no horizon, the same reply for any name. A pull naming an unknown horizon is refused with the one error card, not served densely. The client fails the clone on a second horizon-resolved in a reply, and on one answering a request that sent no horizon-name. A private origin's challenge does not settle the name: the next request resends it with the login, still the clone's first request under §6.1. API: CloneOpts.Horizon replaces CloneOpts.HorizonName and the stand-in check-in a named horizon's StoreCone had to carry. It reuses the Horizon type, whose Checkin may there be a name, beside the existing HorizonDepth, and keeps the rule and the structural level where they were, so no public concept is added. StoreCone gives the path members alone: a horizon on both, or a depth without a Horizon, is an error. A full hash at depth 0 goes on the store cone as itself. Internally a NamedHorizon carries the name, depth, rule and level, not a cone holding a placeholder hash, and only the resolved hash is ever stored. The CLI passes --horizon through the same field. check-in: 54d75fbc78 user: [email protected] tags: main, trunk | |
| 00:37 |
fix(horizon): walk back in one bounded query
ResolveName stepped back one query per check-in, up to 2^32-1 times. A recursive query now walks the primary parents at most depth steps and never more than the check-ins the repository holds, so even a corrupt cycle of primary parents ends. check-in: 6f12912a01 user: [email protected] tags: main, trunk | |
| 00:04 |
fix(horizon): break mtime ties on the lowest full hash
Two check-ins tied on mtime resolve to the lexicographically lowest full hash, never the lower local rid, so two origins, and a clone resolving locally, resolve a name alike (§6.2.7 clarification C.2). HZ-6.2-17 and the horizons page say so. check-in: c941f6177b user: [email protected] tags: main, trunk | |
| 00:02 |
feat: clone a horizon by name and depth
CloneOpts.HorizonName and HorizonDepth, and repo clone --horizon NAME --depth N, ask the origin for the horizon by name; CloneResult reports the resolved check-in, the steps taken and whether the clone resolved it locally. A full hash with no depth is still sent as the hash itself. Docs and the generated CLI reference and conformance data follow. check-in: 94bff91c0d user: [email protected] tags: main, trunk | |
| 00:02 |
feat(sync): horizon-name pragma at the origin and the client
An origin resolves pragma horizon-name NAME RULE DEPTH over its dense repository, echoes pragma horizon-resolved HASH STEPS ahead of horizon-served and every file card, and serves the request as if it named the hash, minting any structural seed for that check-in. A name that resolves to nothing, a malformed depth, or both pragma forms in one request is answered with one error card, after any login or capability refusal. A clone sends horizon-name only until the name settles, takes the echoed hash as its store cone's horizon, and fails, leaving nothing on disk, on a malformed, unasked-for, late or over-deep horizon-resolved or a check-in that never arrives. Against a peer that answers none it finishes the dense transfer and resolves the name locally at the content horizon. Conformance rows HZ-6.1-2, HZ-6.2-17, HZ-6.2-18, HZ-6.3-3 and HZ-6.3-4, and HZ-3.4-2 as amended, are written from the erratum ahead of the draft's re-pin, with the nine named-horizon cases at both ends plus the malformed-request and client-check cases and a stock-fossil fallback. check-in: 1f4a69f2df user: [email protected] tags: main, trunk | |
| 00:01 |
feat(horizon): resolve horizon names and depths
ResolveName turns tip, a symbolic name or a full check-in hash into C as draft-fossil-horizon-00 §6.2.7 (zit 0.7.1 erratum) does: tip is the most recent check-in, a full hash names its check-in, and any other name is the most recent check-in carrying an active sym-NAME tag, ties going to the lower rid. It then steps back along primary parents, stopping at a root, and reports the steps taken. check-in: ee8a559b4e user: [email protected] tags: main, trunk | |
|
2026-10-23
| ||
| 22:10 |
Merge pull request #473 from danmestas/fix/one-docs-build
build(docs): one deploy build, through scripts/docs-build.sh check-in: 5526734cfd user: [email protected] tags: main, trunk | |
| 22:03 |
build(docs): wrangler builds the site through scripts/docs-build.sh
The deploy ran two builds: the Cloudflare trigger's build command and wrangler.jsonc's own. The triggers now have none, so wrangler.jsonc is the one place the deploy build is defined, and it calls the same script developers run. check-in: 235cf4fd8d user: [email protected] tags: main, trunk | |
| 22:01 |
Merge pull request #470 from danmestas/feat/docs-site
docs: rework the site and landing page, generate its facts from the code check-in: 4caaddcd19 user: [email protected] tags: main, trunk | |
| 21:44 |
docs: quote the TinyGo size CI measures
The Build (tinygo) job builds examples/tinygo on every change, so its size (3.52 MB raw, 1.26 MB gzipped) is the one the site quotes; the README keeps the macOS measurements beside it. check-in: 313f64a89e user: [email protected] tags: main, trunk | |
| 21:43 | Merge remote-tracking branch 'origin/main' into feat/docs-site check-in: 9d3f5623ff user: [email protected] tags: main, trunk | |
| 21:42 |
Merge pull request #469 from danmestas/feat/tinygo-sync
feat(wasm): build the whole public API with TinyGo check-in: b3680c4757 user: [email protected] tags: main, trunk | |
| 21:25 |
feat(wasm): build the whole public API with TinyGo
go-libfossil compiled for js/wasm only with the standard toolchain, whose output is about 16 MB, too large to ship with a page or an edge function. Three standard-library features TinyGo lacks kept it from building there: http.ErrUseLastResponse and net.DNSError in the sync client, and os.SameFile in checkout-marker recovery. Each now sits behind a tinygo build tag. Under TinyGo a sync redirect ends the request with an error instead of being followed, DNS failures are not classified as transient, and two names of one checkout marker are reported as ambiguous rather than paired; the standard build is unchanged. examples/tinygo exercises the whole public API, sync and signing included. Built with TinyGo 0.43 and wasm-opt -Oz it is 3.83 MB raw and 1.40 MB gzipped, against 16.1 MB and 4.06 MB from the standard toolchain. A new CI job builds it with TinyGo on every change. check-in: 0650171d55 user: [email protected] tags: main, trunk | |
| 21:25 |
docs: size from the measured TinyGo build of the whole API
go-libfossil now builds with TinyGo, sync and signing included (examples/tinygo). The landing page reads the measured sizes from data/wasm.json instead of an outside project's engine-only build, and the install page says how to build it and what differs under TinyGo. check-in: 7377cb7c97 user: [email protected] tags: main, trunk | |
| 21:13 |
docs: add scripts/docs-build.sh for the deploy build
Runs sync-version.sh, gen-llms-txt.sh and Hugo, given the hugo binary as its first argument. It needs no Go toolchain: the inputs that do are committed and checked for freshness in CI. make docs-build uses it. check-in: 1ef14e7167 user: [email protected] tags: main, trunk | |
| 21:12 |
docs: landing page from the approved design, docs in its typography
The home page renders its hero figure, conformance table, version and footer from the generated data and site params. Docs pages keep Hextra with Fraunces, Inter and JetBrains Mono and rust as the accent in both themes. Add the get-started pages (install, CLI quickstart, embed in Go, first sync), the conformance reference rendered from data, and point the nav, README and old links at the new layout. check-in: 41dfd5c149 user: [email protected] tags: main, trunk | |
| 21:12 |
docs: generate the site's data, CLI reference and llms.txt from the code
tools/docsgen/engine records one check-in as an artifact, as its derived rows and as the cards of a pull, from a real engine run with fixed inputs. tools/docsgen/conformance turns TestCoverage's report into per-draft and per-requirement data. scripts/gen-cli-docs.sh writes one page per command group from the libfossil binary's --help. gen-llms-txt.sh and check-llms-txt.sh are ported from dagnats, and sync-version.sh now takes the highest release tag and writes the go.mod version too. scripts/check-docs-generated.sh regenerates everything into a scratch dir and fails on any difference; make docs-check and the test-docs CI job run it. check-in: d7fd910ffc user: [email protected] tags: main, trunk | |
| 20:53 |
docs: reorganise into get-started, concepts, guides, reference
Move the flat docs pages into sections, keep each old URL as an alias, split sparse cones from horizons, and add concept pages for artifacts, repository state, sync, sub-manifests and signing plus a settings reference. Audit the moved pages against the code and fix drift. check-in: 93ad669695 user: [email protected] tags: main, trunk | |
| 20:13 |
Merge pull request #468 from danmestas/feat/signing
feat(signing): sign check-ins and horizon seeds with gpg or ssh check-in: 4a439c6de6 user: [email protected] tags: main, trunk | |
| 20:05 |
feat(signing): sign check-ins and horizon seeds with gpg or ssh
go-libfossil signed nothing and verified nothing, so a structural-horizon clone could not tell a seed its origin minted from one substituted on disk (draft-fossil-horizon-00 §5.2.4, HZ-5.2-5 and HZ-5.2-6), and a repository with fossil's clearsign setting on stored its check-ins unsigned. Signing follows fossil's contract, the same one zit follows. With clearsign on (and the legacy omitsign off), every check-in a commit or branch creation stores is clear-signed by pgp-command: unset, gpg --clearsign -o, run as COMMAND OUTFILE INFILE; a command named ssh* runs as COMMAND INFILE and its detached signature is wrapped in fossil's SSH envelope. A signer that fails, or returns text that does not strip back to the manifest, abandons the commit. NoSign and --nosign store one check-in unsigned. An origin with clearsign on signs each seed it mints. It signs only after the request is authorized, outside the request's transaction, keeps one signature per seed and command so the seed's name is stable, and re-signs a kept signature its own horizon-seed-key no longer verifies. Keeping is best effort, so a read-only mirror still serves. A clone given its origin's key (CloneOpts.SeedKey, repo clone --seed-key, kept as horizon-seed-key) verifies every seed on clone, pull and rebuild, and falls back to the content horizon from one unsigned or signed by another key. An SSH key is verified in process: namespace fossilscm, the configured key, no SHA-1 ssh-rsa, and exactly fossil's envelope. An OpenPGP fingerprint is verified by gpg, which must report one good signature by that key over text byte-equal to what the parser reads, with nothing after the signature. A verifier that cannot run keeps the held seed and fails the sync instead of discarding it. fossil 2.28 and zit read the signed check-ins; a check-in zit signs with the same SSH key verifies here; zit clones a signing origin and holds its seed. check-in: 612f2e7acc user: [email protected] tags: main, trunk | |
| 17:23 |
Merge pull request #464 from danmestas/release/v0.15.0
chore(release): v0.15.0 check-in: a992dcb34f user: [email protected] tags: main, trunk, v0.15.0 | |
| 17:16 | chore(release): v0.15.0 changelog leaf check-in: 3df0fc796c user: [email protected] tags: release/v0.15.0, trunk | |
| 17:16 |
Merge pull request #461 from danmestas/feat/horizon-cones
feat: horizon cones (draft-fossil-horizon-00) check-in: ac0bf85d72 user: [email protected] tags: main, trunk | |
| 15:19 | docs(changelog): sparse clones of large repositories, late cherrypick sources leaf check-in: ae7b0cee31 user: [email protected] tags: feat/horizon-cones, trunk | |
| 15:19 | Merge branch 'wip/clone-perf' into feat/horizon-cones check-in: ed53ee9c59 user: [email protected] tags: feat/horizon-cones, trunk | |
| 15:11 |
fix(manifest): keep a pull's file waits; skip them only in a sparse clone
The previous change let a sparse store link a check-in before its files. A pull that deepens a structural horizon then linked the new base first and left its files unrequested phantoms, and the merge over it found no content (TestRepoMergeDeepensAcrossHorizon). A pull waits on a check-in's files again, except those the store cone's path members exclude, which never arrive. Only a clone into a sparse store waits on none: the origin's walk sends every file the store keeps in the same session, and nothing reads the store before the clone ends. check-in: 35566881ec user: [email protected] tags: feat/horizon-cones, trunk | |
| 15:11 |
fix(manifest): publish a sparse check-in's spine against an absent source
A sparse store published no event or plink rows for a check-in while a P or Q source of it was absent, unless a structural horizon held it below. A source can be absent for good: fossil-scm.org's repository lacks the parents of five check-ins, which a dense clone links against phantoms, so a cone or content clone of it failed repo verify on them. SparseCheckinReady no longer requires an absent source's file set: the check-in's spine is published against the phantom, as a dense store publishes it, and its orphan row keeps mlink derivation waiting until the source arrives. check-in: 69f3048e67 user: [email protected] tags: feat/horizon-cones, trunk | |
| 13:58 | test(sync): log each round of a corpus clone check-in: 9abc4bbac2 user: [email protected] tags: feat/horizon-cones, trunk | |
| 13:57 |
fix(serve): charge a walked-past clone row what a dense round would
A cone-aware clone charged each row it walked past at its expanded size, while a dense round charges the bytes it sends, a row's stored delta. A sparse round of fossil-scm.org's repository covered a hundredth of a dense round's rows and the clone ran out of rounds. SC-6.2-1 bounds the walk as a dense clone bounds its batches, so a walked-past row is now charged its stored size, read from the four-byte prefix of blob.content without inflating it. check-in: 835557d4b1 user: [email protected] tags: feat/horizon-cones, trunk | |
| 13:57 |
perf(serve): classify a cone walk's rows without expanding every file
A cone-aware clone classifies each row it walks, and each served delta's base, by parsing the row's bytes. The handler has no content cache in production, so every row's whole delta chain was expanded and hashed again; on fossil-scm.org's repository that was the cone clone's first round, unfinished after 20 minutes. The walk now reads, once per request, the rows the crosslink tables prove to be file content (named by mlink, claimed by no artifact table; a store that has seen a sub-manifest answers nothing) and classifies only the rest, through its own unchecked cache: classifying serves nothing, and the bytes a row is sent with are still checked apart. A file version's base is the newer version, which a walk in rid order has not reached yet, so every version went out whole: a 9 MB text per version of sqlite3.c. A content base the walk has not reached is now forwardable when the store's classification of the cone keeps it (SC-6.2-2 bars only a base outside the keep set). check-in: 9a2938b626 user: [email protected] tags: feat/horizon-cones, trunk | |
| 13:57 |
perf(manifest): stop re-testing every file of a waiting check-in
A clone's receive linker tested a deferred check-in's every F card again each time one blob it waited on arrived, and each time a manifest it was a delta of arrived, and its waiter index (8192 entries) filled after a few check-ins of a large tree. A sparse store's out-of-cone files never arrive, so a cone or content clone of fossil-scm.org's repository spent its first round, past 20 minutes, in availability queries. A sparse store now waits on no file: it holds what its cone keeps, and SparseCheckinReady judges its completeness. A waiter strikes each arrival from its wait set and is examined again only once the set is empty, and a check-in already waiting is not re-examined when a delta source arrives under it. check-in: 09b62091ec user: [email protected] tags: feat/horizon-cones, trunk | |
| 10:34 |
fix(manifest): link a cherrypick source that arrives after its child
A check-in crosslinked before its cherrypick ('+') source arrived kept no cherrypick row, since insertCherrypicks skipped a source it could not find, and so never received the auxiliary mlink rows for that source: the late-arrival pass walked plink alone, which holds no cherrypick edges. Canonical skips a missing source the same way and relies on the rebuild its clone ends with; a clone here links as artifacts arrive and has no such rebuild. A clone of fossil-scm.org's repository was left with 139 cherrypick rows and 262 mlink rows fewer than its origin. insertCherrypicks now reserves a missing source as a phantom, as plink does for a missing parent, and the late-arrival pass (linkPlinkChildren, now linkLateChildren) walks the source's cherrypick children with plink's. check-in: 28f343c11b user: [email protected] tags: feat/horizon-cones, trunk | |
| 06:03 |
perf(serve): memoize a clone request's forwardable sources
forwardableSource reads a row, its shun and private state and the tree gate each time it is asked, and a clone round asks about the same rows from every chain walk and every sentEarlier search: a twelfth of a dense clone's CPU. A request reads one snapshot, so the verdicts are kept for its duration. TestCorpusClone logs an origin error, which its client sees only as an HTTP status. check-in: b9ec3c086e user: [email protected] tags: feat/horizon-cones, trunk | |
| 06:03 |
perf(manifest): keep available verdicts across a clone's links
ReceiveLinker.LinkStored made a fresh availability cache for every blob it linked, so each deferred check-in's retry walked every F card's delta chain again; after the receive fixes that walk was a sixth of a dense clone's CPU. Only the unavailable verdicts can go stale during a receive, which only adds content, and a clone whose round fails stops receiving, so LinkStored now drops just those (AvailabilityCache.ForgetUnavailable) and keeps the rest. check-in: a30155185d user: [email protected] tags: feat/horizon-cones, trunk | |
| 05:29 |
perf(serve): send a clone's delta chains once
A clone round sends each row with its delta chain, source first, but only remembered what it sent within the round. fossil deltifies each version of a file against the next, so the rows of a file's versions come in rid order, each a delta on a later one: the oldest version's round sent the whole history, and each later version's row, reached in a later round, sent the rest of it again. Cloning the Fossil repository received 333,063 delta cards by round 64 for 22,353 new artifacts, each checked again by the client. The origin now infers what earlier rounds sent: a row below the cursor that its round sent as itself reaches, through sources this clone forwards, every node of its chain. sentEarlier walks down the delta table from a row or a chain source, at most 256 rows, and a row it finds sent is skipped like one sent earlier in the round, and a chain stops at a source it finds sent. Not finding one sends again, which is only redundant. A row below the cursor filled or published after its round passed it is taken as sent; rows below the cursor are not sent again in any case. check-in: 07b30dccde user: [email protected] tags: feat/horizon-cones, trunk | |
| 05:28 |
perf(sync): check received deltas against a cached basis
The real-repository gate found a dense clone of the Fossil repository CPU-bound past an hour, in delta.Checksum and chain expansion. checkDelta rebuilt each received delta's basis from the root of its chain with content.ExpandUnchecked, after walking the chain for availability; a file's history arrives as one chain of hundreds or thousands of deltas, so the cost was quadratic in its depth, as Repo.Verify's was before 1a5abca. A receive pass now rebuilds the basis through an unchecked content cache (content.NewUncheckedCache): from the deepest node of the chain it holds, keeping each node it rebuilds, and it remembers every artifact a card stored and verified once the card's savepoint commits. A clone keeps one such cache across its rounds and clears it when a round rolls back. A basis the cache holds, or one dangling in the pass, needs no availability walk, and the clone's check for a basis to request uses the same knowledge. The cache holds what each row rebuilds to, unchecked as checkDelta's basis always was, so every verdict is unchanged; a verified delta's target also goes to the receive linker, which no longer expands it again. TestCorpusClone (LIBFOSSIL_CORPUS) clones a copy of a repository from an in-process server for profiling. check-in: b43fd24a1f user: [email protected] tags: feat/horizon-cones, trunk | |
| 05:07 |
fix(serve): serve reading requests in a read transaction
Every request ran in a write transaction (BEGIN IMMEDIATE) for as long as it was served, a horizon request read the classification memo in another, and each request created the table-sync schema on a repository without it. A clone arriving while another request was served waited for the write lock past the 5s busy timeout and got HTTP 500 "database is locked (SQLITE_BUSY)"; its client gave up after four attempts. The real-repository gate hit it with a horizon clone beside a dense one. A request none of whose cards can write now runs in a read transaction (db.WithBoundReadTx), which takes a snapshot without the write lock; the memo read does too; loading synced tables only reads, and a schema card creates the schema it registers into. Requests that write keep their write transaction. v0.14.0 has the same write transaction per request: a clone from its server fails the same way while another connection holds the lock. check-in: f8bb98e196 user: [email protected] tags: feat/horizon-cones, trunk | |
| 03:28 |
Merge branch 'wip/hz-close-sync' into feat/horizon-cones
An origin answering a horizon request never sends a cluster, even one a gimme names; an origin may name a history URL in horizon-served (horizon-history-url), which the client reports; and a merge with no peer to ask refuses whenever either side's ancestry crosses the horizon. Conflicts in the changelog and the CLI horizon tests kept both sides. check-in: c4b191ed05 user: [email protected] tags: feat/horizon-cones, trunk | |
| 03:03 |
fix(merge): refuse a merge whose ancestry crosses the horizon without a peer
draft-fossil-horizon-00 §5.5 has a structural clone ask the origin for the base whenever either side's ancestry crosses the horizon, even when its own walk finds a common ancestor: over part of the graph that ancestor need not be the nearest. Without a peer Checkout.Merge and Repo.Merge refused only when the walk itself crossed, and merged over a held base whose sides' ancestry crossed below it. Both now refuse such a merge with an error matching ErrBelowHorizon that names the horizon and points to Checkout.MergeContext with a Peer; MergeContext refuses the same way when the origin does not answer. A merge whose ancestry stays above the horizon, or one given its base, still merges locally. The CLI's merge already falls back to asking the remote on ErrBelowHorizon, so it now asks in these cases too. check-in: 263de5a736 user: [email protected] tags: feat/horizon-cones, trunk | |
| 03:02 |
Merge branch 'wip/repin-5d61' into feat/horizon-cones
The horizon draft pinned at zit horizon-rfc 5d618aaeb1: an origin with no tag in force mints no seed and serves the content level, and choosing C is shown with each rule's cost (repo cone cost, Repo.HorizonCost). check-in: 77e74bd011 user: [email protected] tags: feat/horizon-cones, trunk | |
| 03:02 |
feat(cli): report what each horizon rule would hold
Draft §3.4 asks a tool that suggests a horizon to show, before the choice, what each rule would hold in check-ins and bytes. repo cone cost <check-in> prints that for descendants and leaves from the local repository, and Repo.HorizonCost returns the figures. The --horizon help of repo clone and repo cone store points to it, noting that a clone has no repository yet. check-in: 834b28d9fe user: [email protected] tags: feat/horizon-cones, trunk | |
| 03:02 |
docs(conformance): re-pin the horizon draft to zit 5d618aaeb1
The draft now says an origin mints no seed, and serves the content level, when no tag is in force at any check-in the seed would cover, since a control artifact needs a T card. HZ-5.2-2 loses its scope note and the new HZ-5.2-7 row is proven by a case that mints none, advertises horizon-served content and leaves the clone holding no seed. HZ-3.4-1 is restated as guidance for the tool; it is covered with the cost report in the next commit. check-in: 4e9dea8cc1 user: [email protected] tags: feat/horizon-cones, trunk | |
| 02:38 |
Merge branch 'wip/hz-close-annotate' into feat/horizon-cones
Annotate in a structural store marks the lines that reach the horizon and names the deepen that would show their origin (§5.5); the origin memo keeps the trees a horizon's walk reads in a bounded cache, so a second horizon over the same store reads only trees the first did not. check-in: 479cbaad12 user: [email protected] tags: feat/horizon-cones, trunk | |
| 02:24 |
perf(content): keep the trees a horizon's walk reads in the memo
draft-fossil-horizon-00 §6.2 asks an origin to read each structural artifact's references once per store and classify every cone over that graph. The memo did so for kinds and P, B, S and A cards, but the F cards of a horizon's trees were walked from the store by the first classification of each horizon, so a second horizon under another C or rule re-read every tree it shared with the first. The memo now keeps, per store lineage, each manifest, baseline and node a walk read: its own F-card targets and its B and S references, so a delta manifest shares its baseline's entry. A view stages what it reads and the memo takes it on Publish, so a request that rolls back leaves nothing; a kept piece serves a view only when its rid is held at that view's prefix, and its size is charged as a read would charge it, so budgets fail where a fresh walk fails. Names are interned and the cache is capped at 64MiB with LRU eviction: fossil-scm's history names about 21M F entries, over 300MiB uncapped. On a copy of a 21k-check-in fossil repository, horizon B (2000 check-ins back) classified after horizon A (1000 back) took 2.5-3.0s and now 1.6s, reading 995 trees instead of 1995; A is unchanged at about 1.3-1.6s. check-in: 13f8021952 user: [email protected] tags: feat/horizon-cones, trunk | |
| 02:20 |
feat(sync): name a history URL in horizon-served
draft-fossil-horizon-00 §6.2.5 lets an origin append to pragma horizon-served the URL of an interface outside the protocol at which history below a horizon can be queried, and §5.5 has a client direct the user there. go-libfossil emitted the level alone and dropped any URL a reply carried. An origin now appends its horizon-history-url setting when set. The value rides the pragma as one raw card token, so SetConfig refuses anything but a single http or https URL with a host and no white space or credentials, and the handler emits nothing for a value stored past that check. The client records a valid URL from any origin in its clone and sync results, exported additively on the public results, and the CLI's clone and cone store/deepen print it. check-in: fe06d02a12 user: [email protected] tags: feat/horizon-cones, trunk | |
| 02:16 |
feat(annotate): report lines from at or below a structural horizon
draft-fossil-horizon-00 §5.5 requires an operation needing history below the horizon to detect the crossing and name the horizon. Annotating back past C stopped at the parent the store does not hold and credited every remaining line to the oldest version it reached, presenting a guess as an attribution. The walk now asks content.BelowHorizon about the parent it could not load; when it lies below the structural horizon, the lines left without credit are marked BelowHorizon and reported against the horizon's check-in. The public AnnotatedLine carries the flag, and repo annotate prints such lines against ^<horizon> with one trailing note naming the horizon and repo cone deepen. A content horizon holds every manifest, so BelowHorizon never fires there; a conformance case clones both depths to show it. HZ-5.5-1 is now covered for every operation it lists. check-in: 1823fe8ca8 user: [email protected] tags: feat/horizon-cones, trunk | |
| 02:13 |
fix(sync): withhold a cluster a horizon request names by gimme
An origin answering a horizon-scoped request already announced no cluster in its clone, pull or sync inventory and answered req-clusters with nothing, but a gimme naming a cluster still got the cluster's file card. A horizon clone that learned a cluster's name anywhere could so still be sent one, against draft-fossil-horizon-00 §4.5 and §6.2. The handler now drops a cluster from a gimme's answer when the request carries a horizon pragma. A new conformance case records the origin's replies to a horizon clone, pull and sync across freshly minted clusters, with and without a cursor, and to every gimme and req-clusters shape, and checks no cluster name crosses; an interop test checks zit's origin the same way. check-in: 5768a5342d user: [email protected] tags: feat/horizon-cones, trunk | |
| 02:05 | conform(horizon): HZ-3.4-1 waits on a wire form, not on step 3 check-in: 265daf2d5f user: [email protected] tags: feat/horizon-cones, trunk | |
| 02:00 | docs(changelog): one statement of the final horizon pin; RepoCloneCmd no longer comparable check-in: 10c8e37095 user: [email protected] tags: feat/horizon-cones, trunk | |
| 01:57 |
Merge branch 'wip/repin-2445' into feat/horizon-cones
The horizon draft re-pinned to zit horizon-rfc 24457c7dd6, the batch agreed with zit's author: the list-form merge-base query (at most two secondaries; silence when a named check-in is unknown), must-ask when either ancestry crosses the horizon, deepen refusing without a common ancestor, boundary labels over every held manifest, the seed's date and label, the structural fallback dropping the seed, and cursors voided when the leaves rule's held set grows below them. check-in: c216f38b6a user: [email protected] tags: feat/horizon-cones, trunk | |
| 01:42 |
docs(changelog): the re-pin to zit 24457c7dd6
Record what the re-pin changed in behavior: the list-form merge-base query and when a merge asks it, the seed's U label, the deepen refusal's remedy, and the cursor voided by a leaves-rule fork. check-in: bb5fc8ebff user: [email protected] tags: feat/horizon-cones, trunk | |
| 01:42 |
test(conformance): the baseline parent label and the fallback with zit
The re-pinned draft makes the parents of a held baseline below C boundary hashes (5.3, 6.2.2) and states the structural store's fallback for a pull or sync (6.2.5, 6.3). go-libfossil already met both; these cases check them against the final text. A case on the delta-baseline fixture (c3 and c4 delta manifests on c2, C = c3) checks that c1, the baseline's parent, is labelled no later than the reply delivering c2 and is held as an elided phantom the clone never asks for. An interop test runs the fallback with zit both ways: zit's structural store pulls from go-libfossil serving as a horizon-ignorant peer, and go-libfossil's pulls from a zit that cannot place C; each falls back to the content horizon, discards its seed and fetches the manifests below C. The recording origin gains an ignorant mode for it. check-in: a553cc1e11 user: [email protected] tags: feat/horizon-cones, trunk | |
| 01:42 |
fix(sync): void a cursor when a fork moves a leaves-rule meeting point
Under the leaves rule a new childless check-in forked below C can move the meeting point of its ancestry with C's deeper, and older check-ins join K together with their blobs. Those sit below every cursor the origin had handed out, so a client holding one was never told of them: the origin recorded a reclassification only when a phantom was filled (6.2.6 requires recording any change of the classification). The origin now also voids a cursor when K holds a check-in at or below it that the held set computed over the check-ins it held through that cursor lacked (horizon.Graph.JoinedAfter, a walk bounded by rid). Like the slot walk's void it is a function of the store, so the memo and a fresh classification agree: TestHorizonMemoEqualsFresh compares them at every cursor and now requires some world to void this way, and a cursor conformance case pulls under the leaves rule across such a fork, the stale cursor answered as if absent once and the next one honoured. check-in: 2c0329ede4 user: [email protected] tags: feat/horizon-cones, trunk | |
| 01:41 |
fix(sync): name the remedy when a deepen has no common ancestor
The re-pinned draft's 5.8 has a deepen that finds no common ancestor of C and its target, here or at the origin, refuse and name the remedy, a fresh clone with an earlier horizon, and never move C to a check-in that is not its ancestor. Deepen already refused with ErrNoDeepenPoint and left C and the seed in place, but its message named no remedy; it now does. A conformance case covers both refusals (the origin answers -, and the origin does not answer) and checks that C, the seed and the derived state stay as they were. check-in: 7a2c0b3bc3 user: [email protected] tags: feat/horizon-cones, trunk | |
| 01:41 |
feat(sync): ask the list-form merge base whenever a merge crosses
The re-pinned draft's 6.2.4 query is a primary and one or two secondaries, `pragma horizon-mergebase <primary> <secondary>...`, and the answer repeats the query in order and appends Fossil's pivot over the set. A two-hash query was answered as the base of a pair, so a merge into a checkout with a merge already pending could be told a base Fossil's merge would not choose. The origin now takes the list (sync.MergeBaseQuery), answers nothing when it lacks any named check-in or is asked more secondaries than a six-token card can repeat, and the client takes only an answer that repeats its query. 5.5 now has the clone ask whenever either side's ancestry crosses the horizon, not only when its own walk does: over part of the graph the common ancestor found need not be the nearest. MergeContext, given a peer, checks the ancestry of the merged-in version, the checkout and its pending plain merges (content.AncestryCrosses), asks the pivot of the merged-in version over the checkout and those merges, and merges over the answer, falling back to Merge when the origin does not answer, as zit's merge does. With two merges already pending the query cannot carry them, and asking without them would name another base, so the merge is refused before anything changes. The deepen point's query names the target over C, as zit asks it. check-in: 78483726d9 user: [email protected] tags: feat/horizon-cones, trunk | |
| 01:40 |
fix(sync): label a seed's U card with the minting implementation
The re-pinned draft (5.2.2) makes a seed's U card the origin's own label for the minting implementation, as zit writes zit-serve, never a user's login: a seed is a tag application no user made. The origin wrote the role name "origin"; it now writes "go-libfossil". The seed-date case is restated to the final text: D is the latest summarized tag event rounded up to a whole second and written with no fraction, checked with an event a quarter second past the second, and the dropped clause (D strictly later than the seed replaced) is no longer asserted. The structural fallback case moves to the new 6.3 row for a store already at the structural horizon. check-in: fe7d47778e user: [email protected] tags: feat/horizon-cones, trunk | |
| 01:40 |
docs(conformance): re-pin the horizon draft to zit 24457c7dd6
zit's horizon-rfc check-in 24457c7dd6 revises the horizon draft; the other six drafts are byte-identical at it and at 3a2df62515. Each change has its own Appendix C bullet: the seed's D card is the latest summarized tag event rounded up to a whole second with no fraction, its U card the origin's label for the minting implementation, and the strictly-later clause is dropped (5.2.2); a merge asks the origin whenever either side's ancestry crosses the horizon (5.5); a deepen with no known common ancestor of C and the target is refused naming the remedy (5.8); the merge-base query takes a list of secondaries (6.2.4); a boundary hash is a parent of any held manifest, a baseline below C included (5.3, 6.2.2); and a store already at the structural horizon falls back to the content horizon, discarding its seed, when its replies lack horizon-served structural (6.3). The 3.4 cost note changes no rule. Restate the rows whose text changed (5.2.1, 5.2.2, 5.3, 5.6, 6.2.2, 6.2.3, 6.2.4, 6.3) and add rows for the new statements (5.3, 5.5, 5.8, 6.2.4, 6.3), so each is checked against the final text. check-in: 9a0f07fe70 user: [email protected] tags: feat/horizon-cones, trunk | |
| 01:16 |
Merge branch 'wip/repin-3a2d' into feat/horizon-cones
The horizon draft re-pinned to zit horizon-rfc 3a2df62515, and its new rule: under either horizon a path cone elides the sub-manifest nodes no in-cone slot reaches, and an origin withholds them. Interoperates with zit at 3a2df62515 both ways. check-in: 113123abae user: [email protected] tags: feat/horizon-cones, trunk | |
|
2026-10-22
| ||
| 23:43 |
feat(sync): withhold out-of-cone nodes of the trees a horizon keeps
Under either horizon with path members an origin now sends only the sub-manifest nodes on paths into the cone of the trees its horizon keeps (every check-in's at the content depth, K's at the structural one), announces none of the rest and answers no gimme for them; a request without path members still receives every node (draft-fossil-horizon-00 §6.2 at zit 3a2df62515). The walk is zit's walkSlots (src/sparse.zig): only slots that may hold an in-cone path are descended, a last slot takes its parent's bound, and a node is walked again only under a wider bound. It runs over the classification's facts, never the store, and the origin's memo keeps it by cone so a later request walks only the roots appended since, falling back to a fresh walk when a root arrives below the ones walked. A clone round sends nodes by the per-check-in tree walk it already uses without a horizon, walking a check-in outside the held set for its nodes alone. Each kept node records the lowest root that keeps it, so a cursor is void (§6.2.6) exactly when some node at or below it is kept only by a root above it: stateless, and the same for a fresh classification and the memo, where zit voids every cursor below the store count at the moment its sidecar sees the change. The memo returned a cone's earlier classification whenever no fact list grew, keeping that store's names, so an artifact appended or filled without becoming a record read as unnamed; it now carries the current facts. Found by the memo equivalence test, which now covers path cones under both horizons, the slot walk, cursor voiding and a tree form that widens a node's bound. check-in: 0de31d6f8e user: [email protected] tags: feat/horizon-cones, trunk | |
| 23:43 |
feat(content): elide out-of-cone nodes under a content horizon
draft-fossil-horizon-00 at zit 3a2df62515 has a path cone elide sub-manifest nodes under either horizon, as the sparse store does (§3.2, §4.1). A content-horizon store held every node of every age, so classification kept a phantom node no in-cone slot reaches as transit and the store kept asking for it. Such a node is now elided, below the horizon too; a node on a path into the cone stays wanted at every age, and a horizon without path members still wants every node. With nodes elided, a check-in's tree is read by the sparse readiness check before its spine is linked, and that check waited on every covered file. Under a horizon a covered file may lie below it, and which do is placed over the very check-ins the check gates, so a check-in below the horizon was never linked and the derivation lost it. Under a horizon the check no longer waits on files: the spine it publishes reads none, and the classification at rest tells an elided file from a transit one. check-in: 63d67666cd user: [email protected] tags: feat/horizon-cones, trunk | |
| 23:19 |
docs(conformance): re-pin the horizon draft to zit 3a2df62515
zit's horizon-rfc check-in 3a2df62515 changes one rule of the horizon draft: a path cone now elides sub-manifest nodes under either horizon, as the sparse store does. Of the trees the horizon keeps (every check-in's under a content horizon) only the nodes on paths into the cone are held, an origin withholds the rest, its walk is bounded and kept like the classification, and a cursor is voided when an appended tree makes an older withheld node kept. The other six drafts are byte-identical at both check-ins. Restate the rows whose text changed (3.2, 4.1, 5.1, 6.2.6), add rows for the new statements (4.1, 4.3, 6.2), and mark the restated rows the code does not meet yet rather than leave them claiming coverage. check-in: bd80910304 user: [email protected] tags: feat/horizon-cones, trunk | |
| 22:54 | docs(changelog): the origin classification memo check-in: abecc56d21 user: [email protected] tags: feat/horizon-cones, trunk | |
| 22:54 |
fix(sync): label the parents of every held manifest at a structural horizon
A structural clone holds the manifests of the held set and everything they reach through B and S cards (draft-fossil-horizon-00 §5.1), so a delta manifest's baseline below C is held. Its parents were labelled by nobody: boundaryOf took the parents of K's check-ins only. The clone then classified such a parent as transit and requested it, and the origin, which withholds manifests below C, never sent it; the clone stalled with one phantom outstanding. zit's origin has the same gap (sparse.zig 758-783), and zit's author agreed the draft will define a boundary hash as an unheld parent of any manifest the clone holds. boundaryOf now labels the parents of K and of every check-in the reach holds. The regression is the HZ-3.1-4 case for a delta manifest in K whose baseline lies below C: the clone completes, the baseline is held but not a leaf, and K's check-ins derive the dense repository's tags. check-in: f0354673e2 user: [email protected] tags: feat/horizon-cones, trunk | |
| 22:48 |
Merge branch 'wip/hz-memo' into feat/horizon-cones
The origin's classification memo (draft-fossil-horizon-00 §6.2): the reference graph and each cone's classification kept per store prefix and extended by appended artifacts, recomputed in full only when an arrival was already cited. A pull after a one-artifact push on fossil-scm.org's repository answers in about 0.25 s instead of 2.7 to 4.5 s. check-in: 7dec3e4fb3 user: [email protected] tags: feat/horizon-cones, trunk | |
| 22:34 |
test(conformance): cover the origin's classification memo
HZ-6.2-9 and HZ-6.2-10 were gaps. The new cases read, from the handler's report of what each request's classification read, that a pull over an unchanged store reads nothing, one after a commit reads what it appended, a blob after its manifest is classified alone, a parent after the child citing it forces a full read, and a cone never classified is classified without re-reading the store; and that every reply announces what a second handle with an empty memo announces. HZ-6.2-10 is partial: a horizon's first classification walks its held set's trees from the store, as zit's does. The case list joins the horizon suite from the coverage table, leaving the horizon case files to the work in flight there. check-in: 6f8176bb93 user: [email protected] tags: feat/horizon-cones, trunk | |
| 22:34 |
fix(content): the non-artifact record appearing extends the memo
A store crosslinked only locally has no non-artifact record until its first received artifact is crosslinked, so the first push to an origin made the memo read the whole store again. The record appearing is every version it names being named at once; those were read raw, and reading file content raw adds nothing, so the extension takes them as files and goes on. Only a version whose raw read added a record or an unreadable name still forces a full read. check-in: bdff158325 user: [email protected] tags: feat/horizon-cones, trunk | |
| 22:31 |
perf(sync): extend the memo by the arrivals' metadata alone
Measured on a dense 68k-artifact, 21k check-in repository, a horizon pull after a one-artifact push still spent most of its time scanning every blob's metadata to extend the memo, and a structural request extended it twice: once to place the horizon ahead of the request's transaction, unpublished, and again inside it. An extension now carries the artifact names over and reads metadata only for the arrivals and the delta ancestors their reads expand. A structural request places its horizon in a read-only transaction of its own, a committed snapshot, and publishes that view, so the inventory finds the memo current. TestCorpusHorizonPullAfterPush (LIBFOSSIL_CORPUS) times it: a pull after a one-artifact push goes from about 2.7s to 0.2s for a content horizon, and from about 4.4s to 0.3s for a structural one. check-in: 62ffc8ff92 user: [email protected] tags: feat/horizon-cones, trunk | |
| 22:26 |
feat(sync): an origin classifies horizon requests through its memo
Each request re-read the store to classify its cone, so a pull after a one-artifact push to a large origin cost a full read. The handler now takes each store's classification memo (one per pool DB, dropped with it) and classifies its inventory and its structural reach through a view of it. The view is published only once the request has committed and stored no artifact: the inventory classifies before received files are crosslinked, so such a view describes a store that never committed. A caller holding its own transaction (a bound DB) classifies afresh. SparseClassified now reports what the classification read: the whole store on a full read, the arrivals on an extension, none when the memo covers the store. A property test drives random commits in every manifest form, pushes in any order and announcements against an origin and checks, after each step, that the memo keeps exactly what a fresh classification keeps for path cones, content and structural horizons under both rules. check-in: 83f8e43ebd user: [email protected] tags: feat/horizon-cones, trunk | |
| 22:26 |
fix(content): the memo re-reads a store whose old rows were linked late
A go-libfossil store is not append-only: a phantom fills at its old rid, and the crosslinker can link an old artifact once what it waited on arrives. An extension by the appended and filled rids alone would miss that, so the memo counts the check-in and plink rows at or below its prefix and reads the store in full when they move other than by a fill, or when a filled phantom is the base of a stored delta (the delta was unreadable before the fill). A new check-in's mlink rows naming an old file version no longer force a full read: reading file content raw adds nothing to the facts, so only a version that did is a change. check-in: 423b5535d4 user: [email protected] tags: feat/horizon-cones, trunk | |
| 22:18 |
feat(content): an origin classification memo extended by appends
An origin re-reads its store for every horizon request: the reference facts behind the path classification and the trees of the whole held set. draft-fossil-horizon-00 §6.2 asks it to keep the classification keyed by cone digest and store prefix, extend it by the artifacts appended since, and recompute in full only when an arrival carries a name a held P, B or S card cited while it was absent. Memo keeps the store's cone-independent references (the reference facts, the check-in graph and the dangling P/B/S names) at a prefix, and per cone digest and per horizon the classification over them. A view extends each by the appended and filled rids: the loader's decisions for those rids alone, classification of the facts' new tails, and the tree walk of the check-ins K gained. Anything other than an append or an uncited fill (a cited arrival, a deleted or phantomized row, a first tree) reads the store in full. Views are copy-on-write and reach the memo only when published, so a rolled-back request leaves nothing behind. check-in: e5bdac3d33 user: [email protected] tags: feat/horizon-cones, trunk | |
| 22:12 |
feat(horizon): a check-in graph a held set extends over
An origin computes K for every horizon request by reading every plink edge and held check-in from the store. draft-fossil-horizon-00 §6.2 asks it to read those references once into a graph keyed to the store prefix and extend that graph by the artifacts appended since. Graph is that graph: loaded once, extended copy-on-write by the rids that changed, and HeldSet over it equals HeldSet over the store. check-in: b1933e5f82 user: [email protected] tags: feat/horizon-cones, trunk | |
| 22:07 |
Merge branch 'wip/hz-small-rows' into feat/horizon-cones
HZ-3.1-2: the leaves rule's cost is stated where it is chosen. HZ-3.4-2: a horizon reaches the wire only as a full hash and a rule name. check-in: c79a113e0c user: [email protected] tags: feat/horizon-cones, trunk | |
| 22:07 |
feat(horizon): state the leaves rule's cost; prove the wire carries only hash and rule
A client that wants to merge across live branches offline should pick the leaves rule knowingly (HZ-3.1-2). The rule holds every descendant of where each childless check-in's history meets C's, which on a repository with old unmerged branches is nearly the whole history. clone and cone store --leaves now warn once on stderr, the flag help says so, and HorizonLeaves documents the cost for library callers. HZ-3.4-2 asks that a horizon never be a count or a date on the wire. A case records the request cards of a horizon clone and pull under both rules and asserts one pragma of a full hash and a rule name; the CLI's clone refuses a name, and its store cone resolves a branch name to the full hash first. check-in: d7a8b71b9b user: [email protected] tags: feat/horizon-cones, trunk | |
| 21:38 | docs(changelog): sparse stores rebuild check-in: e5cedd31cb user: [email protected] tags: feat/horizon-cones, trunk | |
| 21:38 |
Merge branch 'wip/rebuild-perf' into feat/horizon-cones
A sparse or horizon store of a real-sized repository rebuilds: deferred check-ins are linked after the sweep and its non-artifact record, so reference loading answers from the tables instead of reading every blob against the budget. check-in: ba1e308608 user: [email protected] tags: feat/horizon-cones, trunk | |