Skip to content
zit Timeline
Login

Timeline

50 ancestors of fa783773891a55f3

2026-09-18
12:31
soak: a nightly deep DST and fuzz run

scripts/soak.sh passed the optimize flag this Zig rejects, so it has been unrunnable since the toolchain moved; it takes --release=safe now. A scheduled workflow runs it at 03:00 UTC on the x64 runner with the script's nightly budgets, overridable from the dispatch form, keeps the log and any reproduce line as an artifact, and is pinned to the ci toolchain by ci_test like the other workflows. check-in: fa78377389 user: dmestas tags: trunk

12:31
fuzz: reach the igot accept arm again, and sweep at the floor in ci

The adversarial token generator capped fields at 24 bytes, and the igot parser has required a 40- or 64-byte name since the shun work, so the parser's accept arm was unreachable and the harness's own reachability floor said so at 256 rounds. CI swept 100 rounds, below the floor, and never saw it; the first bounded soak did. One field in four is now hash-shaped, and ci sweeps every target at the floor's round count. check-in: af292c3091 user: dmestas tags: trunk

11:37
Release 0.3.23

The transport answers an HTTP Basic challenge; a gated first round is no longer fatal. check-in: 2a3106fcaf user: dmestas tags: trunk, version-0.3.23

11:31
transport: answer an HTTP Basic challenge

A forge that gates a read answers a clone's first request with 401. The login card cannot answer that round, since its secret needs the project code the first reply teaches, and the status was fatal, so the credential never reached the wire. When a verb holds a plaintext password the transport keeps a Basic credential ready, sends nothing until a 401 names the Basic scheme, then repeats the round with the Authorization header and carries it on every later round, as fossil's --httpauth does. A 401 now says what the peer wanted. Proven by an e2e with an authenticating gate in front of zit serve. check-in: 5f541eccc7 user: dmestas tags: trunk

01:52
Release 0.3.22

clone and pull authenticate; a private repository is readable. check-in: d75c7a741d user: dmestas tags: trunk, version-0.3.22

01:45
sync: clone and pull send a login card

Only push and sync signed a login, so a private repository on a forge that gates reads was unreadable by the account that owned it, and a stored login sat unused while pull read anonymously. All four verbs now resolve the login the same way: --user, the URL's user@, then the stored login. A clone holds name and password until the round-0 reply teaches the project code, then signs every later round; a refused round 0 that taught the code is retried with the login, as fossil's client does. The accepted login is stored in the clone. A URL user with no password source is a hint and goes anonymous with a note; --user without a password is an error. Smoke S17 drives a read-gated fossil. check-in: 1d5a6c8fa7 user: dmestas tags: trunk

2026-09-17
19:12
Release 0.3.21

A multi-check-in fossil push lands; --httptrace reaches a zit server. check-in: 575cd3fcdd user: dmestas tags: trunk, version-0.3.21

19:06
serve: accept application/x-fossil-debug

fossil --httptrace sends its cards uncompressed under this type, and the server refused it with a bare 415 on the premise that the reference client never sends it. The exchange now tells handleXfer whether the body is a zlib container or plain cards; the reply is framed as before, and fossil reads the reply's framing off the reply's own content type. x-fossil-uncompressed stays reply-only. check-in: cc96464a51 user: dmestas tags: trunk

19:06
sync: resolve a pushed delta against a basis delivered in the same round

fossil sends a batch of check-ins as one round with each manifest a delta against the previous one, and the scan reconstructed every pushed delta against the committed store, so the basis was never there and the round was refused as a malformed file card. Every multi-check-in push from a stock fossil failed. The scan now parks a delta whose basis is not held and settles it once the whole round is seen, to a fixpoint, so order within the round does not matter. A basis absent from both the store and the round is refused by name: error delta basis not held: TARGET against BASIS. Smoke S16 pushes three fossil check-ins in one round and checks all three landed. check-in: df556319a8 user: dmestas tags: trunk

17:04
Remove issue-tracker numbers from source and documents

The tracker is disposable and does not live with the source, so a number is a dangling pointer. Every zit#NN and #NN reference in code comments, test names, help text, smoke labels, identifiers, release notes and errata is replaced by enough context to stand alone, or by a short plain statement of the problem. No behaviour changed. check-in: 9033c13f21 user: dmestas tags: trunk

16:27
erratum: sync §5.2 spells the bad-command text as bare words

The ABNF and §10.1 already make every error card one fossilized TEXT; the unknown-operator prose writes "error bad command: LINE" and reads as four tokens. Records the wire form and the two-token client rule. check-in: ec7d3fc9f7 user: dmestas tags: trunk

15:29
cli: format the remote URL check check-in: ae396d9bac user: dmestas tags: trunk, version-0.3.20
15:27
Release 0.3.20

Server error cards are encoded so a fossil client reads a refusal; remote refuses a non-URL. check-in: d11082197c user: dmestas tags: trunk

15:21
remote: refuse an argument that is not an http(s) URL

zit remote list saved the word list as the remote and reported success; the loss surfaced at the next push. The help already defines the argument as an http:// or https:// address, and the parser now holds it to that with a usage error that says there are no subcommands. A valid URL is still stored verbatim. check-in: c280025388 user: dmestas tags: trunk

15:21
sync: encode every server error card

The bad-command and malformed-card refusals were written with raw spaces, so on the wire they were four tokens. A stock fossil client reads an error card as two, reported the rest as unknown commands, dropped the refusal and printed its success summary; a refused push looked landed and re-sent the same artifacts on every autosync. Every error card is now assembled at one point with its text encoded, as fossil's own server writes it. A test drives each refusal through the emitter and fails on a raw space after the operator. check-in: 8f5dff81b0 user: dmestas tags: trunk

13:46
Release 0.3.19

log opens once, on the zero-parse image; the open mode is declared, never defaulted. check-in: 61ae22616f user: dmestas tags: trunk, version-0.3.19

13:39
log: open once, on the zero-parse image

log reached past the open funnel and called Repository.open with the default options, which is the full record replay meant for write verbs, and did so twice for a symbolic name. On pkgsrc that was 0.94s before the first row; on the image path it is 0.05-0.10s, output byte-identical on every route. The open mode has no default any more: every direct caller declares it, so the next verb written past the funnel cannot inherit the expensive one silently. An e2e runs every log route under the derive-footprint seam and fails if any of them replays a record. check-in: 0910bc00f5 user: dmestas tags: trunk

00:08
ci: run on every branch push, still never on a tag

A push trigger naming only a tag filter runs for tag pushes alone, so the tags-ignore alone ran nothing for two commits. branches: ['**'] restores every branch; the pin now allows exactly that form. check-in: c004c81e03 user: dmestas tags: trunk

2026-09-16
23:09
ci: keep the Zig cache and toolchain across runs

The runners are self-hosted but checkout cleaned the workspace, so every step recompiled the same sources into its own binary from nothing; unit tests and e2e were 6 and 4 minutes on the ARM64 critical path, mostly compile. Zig's cache is content-addressed, so it now lives outside the workspace and warms across runs, in both workflows. The toolchain tarball is kept the same way and still verified against the pinned digest every run. Also pins the tag-ignore trigger the previous commit introduced, which the workflow pin test had rejected. check-in: 3106100d42 user: dmestas tags: trunk

22:58
ci: do not run the battery a second time on a tag push

A release tag lands on a commit whose branch push already ran ci, and the tag push ran it again on the same three single-slot runners the release workflow was queueing for. Every branch push still runs; the release workflow, triggered by the tag, gates what it ships. check-in: e9e2ba4431 user: dmestas tags: trunk

22:11
Release 0.3.18

The file-history index is built by the derive at ingest, fossil's way. check-in: 09d191e634 user: dmestas tags: trunk, version-0.3.18

22:11
file index: built by the derive, at ingest

fossil fills mlink as each check-in arrives and answers finfo by lookup. zit's derive computed the same row per check-in and dropped it; the index was rebuilt by a separate pass on the first PATH query, 6.8 min on pkgsrc and not resumable. The derive now hands each change set to an index builder as it links, stores it beside the checkpoint, and extends it over the tail on append; a cache hit computes nothing. The on-query build is the fallback and checkpoints every 8192 entries. Index format 4 records completeness so a partial checkpoint is never adopted whole. crosslink reports a tree's files instead of unknown. pkgsrc, fresh clone to first answer: 11.7 min -> 7.1 min + 1s. check-in: df99743ca4 user: dmestas tags: trunk

13:44
Release 0.3.17

A push with no URL keeps its credential; the tree-manifest index build is proportional to what changed. check-in: dbfe2adebc user: dmestas tags: trunk, version-0.3.17

13:43
file index: diff tree manifests by S card

Two tree check-ins were diffed by flattening both to full file lists, files-per-check-in per check-in: 34 minutes once on a pkgsrc mirror. The S-card runs are now walked side by side, skipping every sub-manifest whose hash did not move and resolving only where it did. Deletes are held to the end so a moved partition key cannot make a live file look deleted. Trees flatten on demand only for the root and flat/tree pairings; the derive feed never flattens. pkgsrc: 34.3 min -> 6.8 min. check-in: ea4abe48fd user: dmestas tags: trunk

13:43
push: keep the credential when the URL is omitted

push --user U --password P with no URL parsed to a payload-less no_url, which the verb rebuilt as a bare empty URL, dropping the credential; the push went out anonymous and a peer requiring a login refused it. The parser now mirrors sync: an omitted URL is an empty one, resolved to the saved remote at run time, and the credential comes through. S12 asserts an authenticated no-URL push and a stored-login push both land on fossil. check-in: 4dba18d355 user: dmestas tags: trunk

07:42
Release 0.3.16

File index works on submanifest stores; artifact shows S cards; a saved login authenticates a push that names no user. check-in: 8cfa57ecd3 user: dmestas tags: trunk, version-0.3.16

07:28
push: a saved login authenticates a push that names no user

push --user saved the derived secret, but the resolver returned anonymous the instant --user was absent, so autosync (which pushes with no arguments) always went anonymous and a peer requiring a login refused it. With --user absent the saved login now stands in, never prompting; --user "" still forces anonymous. push, sync and config pull share the resolver. Whether such a push lands on a fossil peer is zit#196, a separate pre-existing matter. check-in: e37768d073 user: dmestas tags: trunk

07:28
log PATH: diff tree manifests in the file index

On a submanifest store every check-in is a tree manifest, which the change-set capture refused as unknown, so the index recorded the whole log as unknown and a PATH query reconstructed per check-in anyway -- 39 minutes on a pkgsrc mirror. Trees now flatten inside the diff memo through the Repository seam, so the untouched diff sees a flat list; a tree that cannot be flattened stays unknown. Index format 2 -> 3 so a stale all-unknown blob is rebuilt once. check-in: 001c73defa user: dmestas tags: trunk

06:05
artifact: render S cards

A tree check-in carries its file set entirely by S cards and the card renderer had no S lane, so it printed as a manifest with no files while ls on the same hash listed thousands. One lane, in card order, checked against --raw. check-in: da2598d1ca user: dmestas tags: trunk

2026-09-15
01:30
Add Submanifest Guide check-in: e88e3d88ed user: dmestas tags: trunk
2026-09-14
16:27
Release 0.3.15

Makes the file-history index durable across commits. check-in: 37e2519ac5 user: dmestas tags: trunk, version-0.3.15

16:12
Extend the file index instead of rebuilding it

The v1 index recorded no coverage, so every commit threw it away and re-walked the whole log. v2 records n_rids, the entry count the index covers, and a later query walks only the tail past it. A fresh query walks the new check-ins; a repeat query walks none. check-in: 788b69f097 user: dmestas tags: trunk

13:43
0.3.14: version bump and release notes check-in: 37a5af6b08 user: dmestas tags: trunk, version-0.3.14
12:48
build: register the cross step with the CI pin

The negative-space pin requires every build.zig step to be either invoked by CI or listed with the reason it is not. The cross step was neither, so it failed all three runners -- the pin doing exactly its job on the commit that added an unregistered step.

CI compiles the real thing on all three shipped targets, so cross-compiling them again proves nothing new; the step is for the developer, before the push. Recorded as such. check-in: 770e641725 user: dmestas tags: trunk

12:14
build: compile for CI's Linux targets in the gate

This project links no libc and macOS links libc always, so a call that demands it compiles on a mac, passes every local lane, and refuses on both Linux runners -- the discovery order the fmt check already exists to prevent, and what zit#188 spent a red main on.

`zig build cross` compiles the CLI for x86_64-linux and aarch64-linux and runs nothing; gate depends on it. Verified to discriminate: reintroducing std.c.getenv fails cross while a plain macOS build still passes. check-in: 31c4850b95 user: dmestas tags: trunk

11:48
log: read the file-index kill switch at main's environ seam, not through libc

std.c.getenv demands libc linkage, which this project does not take, so both Linux runners refused to compile while macOS -- which always links libc -- built it and the local gate passed. cli.Ambient.environ documents exactly this rule; the seam existed and was bypassed.

Read $ZIT_NO_FILE_INDEX in main from init.environ_map like every other seam and hand the runner a flag. Cross-compiles clean to x86_64-linux and aarch64-linux. check-in: 1317aa9864 user: dmestas tags: trunk

02:46
log: answer a PATH filter from a file index, not by reconstruction (zit#188)

The predicate reconstructed a manifest per visited check-in, so one file's history on a mirror of SQLite cost minutes and grew with the history behind it. Persist what the derive already computes and throws away: captureFileFacts is Fossil's mlink row, so invert it by path into .zit-cache/finfo, keyed to the log tail and shun fingerprint like its sibling sidecars.

The PATH walk now traverses the derived adjacency, as the unfiltered walk has since #98, and the index narrows it. The index only NARROWS: an F-card-level diff over-reports a delta that restates a path with the hash it already had (one check-in in 2,113 on SQLite), so every candidate is still confirmed by the B-log-2 predicate and the output is byte-identical. --long reads one manifest per emitted row instead of per visited node.

README.md 72s to 1.6s, btree.c 25s to 1.1s, reconstructions 25,106 to 141. check-in: 17c3b0aaa2 user: dmestas tags: trunk

2026-09-13
23:05
log: memoize the PATH predicate's reconstruction (zit#188)

logChangesPath computed a check-in's effective content for PATH twice — once as `here`, once as the `parent` of the child below it — and each call re-read, re-verified and re-parsed the manifest because fileAt takes neither the walk's plaintext cache nor a baseline memo. One file's history on a mirror of SQLite took 129s.

Thread the walk's cache, a baseline memo, and an answer memo keyed by rid through the predicate, and through timeline -p, which shares the defect. Reconstructions per PATH walk fall from 2N-1 to exactly N; README.md 72s to 24s, btree.c 25s to 9s, timeline -p 16s to 7.5s, output byte-identical. check-in: 184cee5a2e user: dmestas tags: trunk

2026-09-11
20:39
0.3.13: version bump and release notes check-in: 2a427c830b user: dmestas tags: trunk, version-0.3.13
20:29
attach: add/rm/list verbs and the quarry stub (zit#186, zit#187)

The write side of the A card. add stores the blob then the attachment artifact in one commit and prints both hashes; rm ends the association with a sourceless card; list folds latest-per-(target, name). The new attachment-size-limit setting (config key 17) caps add, fossil's semantics with a 1 MiB default. A quarry attachment stub lists as external and zit artifact describes it. Smoke S15 pushes an attachment to real fossil and reads it back from fossil's attachment table. check-in: 170a2e88d0 user: dmestas tags: trunk

18:49
0.3.12: version bump and release notes check-in: 5a962c69ff user: dmestas tags: trunk, version-0.3.12
18:43
serve: mint clusters on a pull reply (zit#184)

Once a pull finds 100 or more held artifacts no cluster names, fold them into cluster artifacts — sorted, 800 M cards each, md5 Z — at fossil's create_cluster point and thresholds. They persist with the round and extend the index; the next reply announces the clusters, not the members. A zit-born repository clusters itself the first time anyone pulls from it: 161 artifacts, one cluster, 201 bytes on the next pull. check-in: 10c905d27b user: dmestas tags: trunk

17:52
sync: announce only what no held cluster names (zit#184)

A pull or push announced every held artifact with igot, every round — 1.25M lines, ~63 MB per direction on pkgsrc. Clusters are igot in bulk: index the held clusters (.zit-cache/clusters, keyed to the log prefix, kept warm by persist), announce held minus what they name, seed the promises they made that this repository never collected, absorb pushed clusters server-side so the reply begs for their members, and honour send-catalog (full inventory) and req-clusters (enclosed cluster igots). A no-op pkgsrc pull now sends 4.7 KB. Cluster promises the peer cannot keep are a warning, not a failed pull. No format changed. check-in: e1ca72a8ef user: dmestas tags: trunk

16:10
pull: persist only what was received, not the whole holdings store (zit#185)

A pull walked every held artifact in persistStore, reading each once from the lazy disk-backed holdings store just to early-return it: 194s of a 197s no-op pull at 1.25M artifacts, reconstructing nothing. Received artifacts are appended beyond the holdings' log-backed prefix, so persist now begins the walk there and processes only the received set. A no-op pull persists nothing; an incremental pull persists what it received, not the millions it already holds. No-op pkgsrc pull 209s to 7s. check-in: a25dcf28f5 user: dmestas tags: trunk

2026-09-10
23:56
0.3.11: version bump and release notes check-in: b1dc991ac0 user: dmestas tags: trunk, version-0.3.11
23:42
serve: suppress announced igot with a set, not a scan per artifact (zit#183)

emitReply walked every held rid and, for each, linear-scanned the peer's announced list to suppress a redundant igot — O(held × announced), so a no-op pull where both sides hold everything cost 760 s of server CPU at 1.25M artifacts. Build the announced/requested names into one set per reply and probe it once per rid. ZIT reports the probe count; a test pins it to the held count at two sizes regardless of what the peer announced. check-in: a93f014321 user: dmestas tags: trunk

2026-09-09
22:53
0.3.10: version bump and release notes check-in: 77f89b0bec user: dmestas tags: trunk, version-0.3.10
20:04
derive: a disposition sidecar skips what cannot contribute a record (zit#182)

One bit per log frame in .zit-cache/dispositions: crosslink-relevant or not. A full derive consults it and never reconstructs the file blobs and SUBMANIFEST tree nodes that were most of a wide store's walk; the walk distills a fresh map either way. Same trust tier as the log-scan index: self-checksummed, keyed to a validated log prefix, stamped with the shun fingerprint, absent on any doubt. pkgsrc submanifest cold derive 302 s to 22 s; a consuming derive's body is byte-identical, pinned by test. check-in: a18d2edd67 user: dmestas tags: trunk

17:58
derive: write the cache in place, never a second copy (zit#182)

repocache.store and checkpoint.store concatenated header + body + image into a full resident copy before writing; both now stream the pieces to the file with the checksum folded incrementally, byte-identical output. The clone path frees its view before the reopen builds a second derived state. ZIT_DERIVE_FOOTPRINT reports the derive body's composition. pkgsrc cold open: peak RSS 2.32 GB to 2.07 GB. check-in: 3107e23e24 user: dmestas tags: trunk

14:26
0.3.9: version bump and release notes check-in: c641321230 user: dmestas tags: trunk, version-0.3.9
14:13
clone: persist each round as it converges, release what is written (zit#172)

A dense clone no longer holds the repository in memory until the exchange ends: each round's artifacts land in the log and their resident bytes are released, the shape of git's index-pack. A delta whose basis has not arrived defers on its hash; a basis first seen as a phantom is revisited when its content lands. Cone-scoped clones keep the batched pass, whose keep set needs the converged store. Streamed and batched logs are byte-identical, pinned by test. ZIT_PERSIST_WAITING reports the drain's peak deferred set. check-in: 533b0a6490 user: dmestas tags: trunk