Artifact de39217b31ab766faccefabbc26b03daba56269e5ae1d827c26fa65c6855efc9:
- File
internal/sync/session.go
-
2026-10-23 20:05:29
— part of check-in
[612f2e7acc]
on branch main
— 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. (user: [email protected], size: 20041) [annotate] [blame] [check-ins using]
-
2026-10-23 20:13:35
— part of check-in
[4a439c6de6]
on branch main
— Merge pull request #468 from danmestas/feat/signing
feat(signing): sign check-ins and horizon seeds with gpg or ssh (user: [email protected], size: 20041) [annotate] [blame] [check-ins using]
-
2026-10-24 04:58:32
— part of check-in
[6083693442]
on branch main
— 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. (user: [email protected], size: 20041) [annotate] [blame] [check-ins using]
-
2026-10-24 05:21:29
— part of check-in
[5b43d4fe38]
on branch feat/horizon-dfloor-receive
— 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). (user: [email protected], size: 20041) [annotate] [blame] [check-ins using]
-
2026-10-23 20:05:29
— part of check-in
[612f2e7acc]
on branch main
— feat(signing): sign check-ins and horizon seeds with gpg or ssh