50 descendants and 50 ancestors of c70f48f8251fc6cd
|
2026-08-14
| ||
| 18:27 | import-git: refuse git older than 2.45 by name before any work — first preflight query, without --no-lazy-fetch; CI verifies the minimum beside fossil (#62) check-in: 43efce758a user: dmestas tags: trunk | |
| 18:03 | add sparse.zig: the shared cone classification the previous check-in imports check-in: f031a5283c user: dmestas tags: sparse-cone | |
| 18:02 | cone pragma on the wire (spec 6.1/6.2): client requests scoping via fossil's silently-ignored pragma contract; the server filters clone batches from a digest-keyed classification memo dropped with its snapshot; a cone-aware pair transfers spine + in-cone content only check-in: a52eb95fc0 user: dmestas tags: sparse-cone | |
| 17:34 | sparse-cone spec: the cone request rides pragma cards — fossil answers unknown bare cards with a protocol error (xfer.c) but specifies unknown pragmas as silently ignored, so degradation becomes a documented property rather than assumed tolerance check-in: f7b3d64db8 user: dmestas tags: sparse-cone | |
| 17:33 | merge trunk: autosync + commit fork guard; update grows --no-sync beside --cone/--dense check-in: c50dcc978a user: dmestas tags: sparse-cone | |
| 17:18 | sparse store round trip: verify summarizes elision distinctly from damage (streamed off the existing plaintext pass); commit omits R only when an elided carried blob makes the aggregate uncomputable, and the minted check-in converges to dense peers check-in: 6eecca3136 user: dmestas tags: sparse-cone | |
| 16:57 | commit: fork guard — parent must still be its branch's leaf (--allow-fork to fork); autosync — settings verb, pull before commit/update/merge, push after commit, warn-and-continue (#57) check-in: 7a4dff5d5f user: dmestas tags: trunk | |
| 16:34 | sync on a sparse store: elided phantoms are never gimme'd (Eligibility.elided, distinct from shun) and volunteered content filters at every receive persist via post-run reclassification, so new in-cone commits arrive while declined content stays declined check-in: 4fb1a29e3e user: dmestas tags: sparse-cone | |
| 16:10 | clone --cone: local filtering at persist (spec 6.3) — structural artifacts and in-cone blobs keep, exclusively-out-of-cone blobs elide, count reported; open's preflight classifies absence three ways (foreign / elided / transit) instead of reading a sparse store as foreign check-in: 5a74b96333 user: dmestas tags: sparse-cone | |
| 15:55 | store cone: .zit-cache/store-cone member with loss-heals-to-dense semantics; derived caches proven cone-independent (spec 7 corrected: no cone term in cache keys) check-in: 7c734e7763 user: dmestas tags: sparse-cone | |
| 15:40 | merge trunk: import-git drops std.c (Ambient environ, Io dir fsync); export-git DEST preflight names its own git dir check-in: 7c1fb6e58d user: dmestas tags: sparse-cone | |
| 15:37 | linux: iterate/fsync need real fds — reopen O_PATH dir handles with iterate rights (7 sites + test helpers); runScripted captures into caller-owned storage, ending a use-after-return the Linux stack exposed check-in: a60df1f752 user: dmestas tags: trunk | |
| 15:17 | sparse cone: tree-dialect keystone on an S-card repo, narrowing dematerializes cleanly; README RFC row; open help teaches --cone check-in: 18ad2eea27 user: dmestas tags: sparse-cone | |
| 14:42 | sparse commit synthesis: snapshot inside the cone, parent cards carried verbatim outside; R stays log-sourced; keystone e2e pins reader-indistinguishability check-in: e7c880ac83 user: dmestas tags: sparse-cone | |
| 14:37 | import-git: drop std.c — child environ threads from Init via Ambient, dir fsync goes through Io; Linux builds no longer demand libc check-in: 951b654c5b user: dmestas tags: trunk | |
| 14:33 | sparse worktree verbs: open/update --cone and --dense; add/mv refuse outside the cone; extras/clean/addremove/status cone-scoped; R verified over a dense probe (spec 4.3 verification clause added) check-in: c59a64678f user: dmestas tags: sparse-cone | |
| 14:12 | sparse cone at the seam: MergeWalk filter, cone-aware requireClean, SparseOpt on applyCheckin (record+cone one splice), cross-cone merge refuses fail-closed check-in: 259219f90e user: dmestas tags: sparse-cone | |
| 13:50 | container v3: cone + store_location tags; v2 folds forward, version negative space pinned check-in: 49815f9ac6 user: dmestas tags: sparse-cone | |
| 13:43 | export-git: DEST preflight must name DEST's own git dir — upward discovery adopted the enclosing repo and aimed fast-import at its refs check-in: f451567485 user: dmestas tags: trunk | |
| 13:09 | sparse-cone: RFC draft + cone module (spec 3): sorted-minimal members, covers/coversRange, canonical serialize with fail-closed parse, SHA3 digest check-in: 0fade1c499 user: dmestas tags: sparse-cone | |
| 12:21 | merge sub-manifest: delta-manifest dialect, R cards, diff hunks, server identity, stash, open/log resolution, clone remote+progress, timestamp names; e2e expectations updated check-in: 8a45a021f9 user: dmestas tags: trunk | |
| 10:11 | clone: save the URL as the remote (canonical, password-free, --once opts out); live per-round progress on a TTY stderr (#55 #56) closed check-in: 69c14cd747 user: dmestas tags: sub-manifest | |
| 10:11 | resolve: §8.3 portable T/Z timestamp form and the NAME:TIMESTAMP compound (#60) check-in: f2691f0a23 user: dmestas tags: sub-manifest | |
| 01:57 | open: optional CHECKIN — resolve like info and materialize it directly; no-arg tip behavior unchanged (#54) check-in: 8573f4f57d user: dmestas tags: sub-manifest | |
| 01:33 | log: CHECKIN resolves like info — branch, tag, prefix, reserved forms; full hex IDs keep the stateless fast path check-in: b77d2898ad user: dmestas tags: sub-manifest | |
| 00:11 | stash: save/list/show/apply/pop/drop over a durable .zit-stash sidecar — fail-closed apply (clean targets, same check-in), corruption refuses loudly; the no-stash reflex retires; README rows updated check-in: ba3d1e705b user: dmestas tags: sub-manifest | |
|
2026-08-13
| ||
| 22:12 | commit: keep the delta-manifest dialect — once a repo stores a B-card manifest (durable seen_delta config, ingest-detected), commits emit deltas against the parent's baseline with delete markers, byte-cost guarded; commit also resolves a delta parent's true effective set check-in: 60b3557200 user: dmestas tags: sub-manifest | |
| 20:14 | diff: textual hunks by default — patience alignment, git-compatible unified output (patch -p1 round-trips), -c N context, --brief keeps name-status check-in: ca0f3df379 user: dmestas tags: sub-manifest | |
| 20:13 | sync §9.1: real per-instance servercode — derived from the checkout's absolute path (deriveServerCode, domain-separated from the project code), emitted by serve and all four client verbs; placeholder retired to test fixtures check-in: 59e8660999 user: dmestas tags: sub-manifest | |
| 19:22 | README: five-RFC table, capability overview, and the compatibility framing — what Fossil has, what zit has, and the one break (sub-manifests) check-in: 0f8b901650 user: dmestas tags: sub-manifest | |
| 19:22 | commit: emit the R aggregate checksum on every check-in; update/open verify it fail-closed (Fossil's default repo-cksum posture); diff: rev-to-rev name-status via --from/--to check-in: ad8012ec78 user: dmestas tags: sub-manifest | |
| 18:43 | docs: carry the three errata the code implements ahead of the RFC text (repo-state §5.4 cancel-halt, artifact-format §4.1.2 sterilizer, sync §3.3 content-received OPTION B) — provenance for the divergence until the canon applies them check-in: 062e9a5512 user: dmestas tags: sub-manifest | |
| 18:31 | repostate: cancel-tag propagation halts at a rowless node, matching fossil tag.c (#16); erratum filed for the divergent §5.4 draft text check-in: e286beebd3 user: dmestas tags: sub-manifest | |
| 16:09 | export-git: mirror canonical history into git check-in: ae7c26a1a3 user: dmestas tags: sub-manifest | |
| 16:09 | bench: qualify incremental git export scaling — memo parse bound pinned; althttpd/wapp export clean (fsck 0), phantom-parent lineage refuses naming both hashes check-in: 75fb61025d user: dmestas tags: sub-manifest | |
| 15:18 | e2e: verify git export commit by commit check-in: 08ca21c63c user: dmestas tags: sub-manifest | |
| 15:14 | cli: export git mirrors incrementally check-in: 5b2c6139d5 user: dmestas tags: sub-manifest | |
| 15:03 | export-git: publish mirrors without partial state check-in: 41dfef63b2 user: dmestas tags: sub-manifest | |
| 14:32 | export-git: translate check-ins refs and failures precisely check-in: 74c5c8aba0 user: dmestas tags: sub-manifest | |
| 13:52 | export-git: plan topologically and enumerate through the seam check-in: 491cbc50c5 user: dmestas tags: sub-manifest | |
| 13:35 | export-git: persist a disposable local mark map check-in: 17c02d8950 user: dmestas tags: sub-manifest | |
| 13:23 | export-git: serialize bounded fast-import streams (the writer files the previous commit's message described) check-in: bb1cc4a01b user: dmestas tags: sub-manifest | |
| 13:23 | export-git: serialize bounded fast-import streams check-in: 2450e4f142 user: dmestas tags: sub-manifest | |
| 13:08 | git: gather every interop root into src/git/ — cli.zig joins the zit module (nine importers rerouted), moved roots take parent deps through the module; ~986 duplicate test executions eliminated check-in: 49c08bb191 user: dmestas tags: sub-manifest | |
| 11:41 | submanifest: replay history to pick k (#41) — analyze replays last 200 commits through the real builder+delta encoder (both cost axes, bootstrap split, baseline-charged materialization); enable pins the replayed argmin, snapshot objective as fallback. Advisory only. check-in: b9ceda433f user: dmestas tags: sub-manifest | |
| 03:06 | clone: stop inflating served/received delta containers for their header — prefix-inflate 16B, base frame read header-only; server CPU per 1M-artifact clone 300s+ -> 22s, clone verifies 0 faults check-in: f5eafc6a2f user: dmestas tags: sub-manifest | |
| 01:25 | serve: notice a --force shun/pack generation swap — stat the path beside the held handle, reopen between rounds; shunned bytes stop serving with no restart. Closes #17 check-in: faa8befc3b user: dmestas tags: sub-manifest | |
| 00:16 | serve: scale the write deadline with the reply — 4MB clone batches at the flat 5s cut healthy slow peers mid-write (#22 shape); slow-drain e2e proves it check-in: fa230db9e8 user: dmestas tags: sub-manifest | |
|
2026-08-12
| ||
| 23:21 | timeline: order tied runs by generation, not per-pair ancestry walks — 305k-commit CVS import renders in 5s vs CPU-hours; B1 descendant-first preserved exactly check-in: d823a2811d user: dmestas tags: sub-manifest | |
| 21:47 | clone: byte-budgeted batches (4MB) — 159 rounds vs ~1000 for a 1M-artifact store; warm clones plant the checkpoint (first command 0.06s); print transfer/derive phases. Closes #53 check-in: c32414008b user: dmestas tags: sub-manifest | |
| 19:56 | checkpoint: accept delta manifests; anchor at newest check-in (mainline fold collapsed on real repos); sync: index the pending-delta queue (O(1) hasTarget, O(deps) completeFor) — zit now clones 2GB pkgsrc-2012 from real fossil in 10min, resumes opens in 9s vs 4min derive check-in: c70f48f825 user: dmestas tags: sub-manifest | |
| 09:04 |
checkpoint: retire the tree shape — sidecar only, structurally
The tree shape minted permanent SUBMANIFEST artifacts for benefits already had elsewhere: the rebuild win lives in the prefix record, verify's §6.2 oracle runs buildTree in memory, and the subtree-sharing bytes re-materialize at the first real S-emitting commit at identical cost. Minting early only created unreferenced permanent artifacts and the allow-tree/demotion/gate apparatus needed to keep automation from doing it by accident — all deleted with it. build no longer opens the log for writing or reads the emission settings at all; sidecar layout v3 (old sidecars miss and rebuild in seconds). Proven at scale: SHA-256 of pkgsrc's 593MB store is byte-identical across a full build; cold 4:12, rebuild 3.4s. check-in: 2a8fdcff60 user: dmestas tags: sub-manifest | |
|
2026-08-11
| ||
| 22:45 |
checkpoint: one-pass build, incremental advance, opportunistic refresh
Build paid a bypass derive, a whole-prefix delta scan, and a capture re-derive; a view-free captureRecords (byte-identical to the deriving walk, pinned at every boundary) now does capture and delta detection in one pass, and an existing sidecar's body plus its delta-free attestation reduce a rebuild to the unscanned tail. pkgsrc: cold build 17:58 -> 4:21; rebuild or anchor advance -> 5.1s. Repository.open's healing paths keep an existing checkpoint fresh: witness shape forced (the automatic path can never mint an artifact, whatever §8.1 says — a tree pin advances by demotion and is re-pinned deliberately), gated on 4MB growth AND anchor progress so blob-only growth never churns the sidecar. No checkpoint, no auto-creation: building once is the opt-in. check-in: adae602908 user: dmestas tags: sub-manifest | |
| 20:04 |
repostate: carry the C-card comment in the crosslink record
An unlimited timeline re-read and re-parsed every manifest just to print its comment first-line — 163k inflate+parse cycles on pkgsrc. The comment now rides the crosslink record into the derived state (commentOf; image backing falls back to the manifest read), so the render loop is a lookup per row: full timeline 12.3s -> 1.06s. Record-format consumers bumped so old bodies miss instead of misparsing (repocache v3, checkpoint v2 — one re-derive per repo, then warm). zit log's pinned B-log-1 walk untouched. check-in: 57d8de4120 user: dmestas tags: sub-manifest | |
| 19:07 |
repocache: persist the content log's scan index
The lazy content channel re-walked every frame header on each open — O(store) before the first byte of any comment, cat, or annotate. The scan's product (entries, hash index, shun rows, config cursor, tail key) now persists as .zit-cache/index, self-validating and LogKey-keyed: an exact tail adopts whole, a grown log resumes the walk over the tail alone, and anything else falls back to the authoritative scan (--no-cache never consults it). pkgsrc: info 1.7s -> 0.44s, timeline -n 5 4.3s -> 0.96s. check-in: f2bfd36e74 user: dmestas tags: sub-manifest | |
| 18:17 |
logstore: O(1) LRU eviction in PlaintextCache
Replace the pick-the-minimum-stamp linear scan with an intrusive doubly linked recency list; get/put/evict are all constant-time. The scan was invisible at small occupancy and dominant at full: a pkgsrc clone kept the cache pinned at max_entries and spent over half its client CPU sweeping the map once per stored artifact. Measured: clone 12:38 -> 8:39 wall, client CPU 694s -> 463s; cloned store verifies with 0 faults. check-in: 0df83f350a user: dmestas tags: sub-manifest | |
|
2026-08-10
| ||
| 23:48 |
Move the git-interop implementation into src/git/
Seven files relocate with unchanged basenames: git_fast_export(+test), git_import, git_import_impl, git_import_cli, git_import_verify, git_import_benchmark_support. The six standalone module roots (test, refs_test, cli_test, e2e, benchmark, pipe_helper) stay flat because a Zig module cannot import above its root file's directory — the same rule that keeps main/e2e/dst/smoke at src/. Future export-git files follow the same split: roots flat, implementation in src/git/. Path-string edits only; build.zig unchanged. check-in: ce3c87e25c user: dmestas tags: sub-manifest | |
| 23:12 |
Add zit checkpoint: durable prefix resume for hybrid history
.zit-cache/checkpoint pairs a validated log-prefix key with the prefix's crosslink record body; Repository.open resumes past the flat segment on a cache miss instead of re-parsing it. Witness shape under a closed emission gate creates zero artifacts; tree shape pins the anchor's file set as the canonical S-tree (batch-oracle byte-identical, one interior root node). verify re-derives every claim from real history; --no-cache bypasses both sidecars. New verb: zit checkpoint [show|build|verify|drop]. pkgsrc (163k commits, 60k files): cold open 8.4s vs 352s full derive; verify passes; 366/367 tree nodes deduplicated against the imported tree. check-in: e4026acd7b user: dmestas tags: sub-manifest | |
| 20:17 |
Delete the legacy reserved-name tier (consolidation Task 7)
The policy is three names and a prefix: .zit, .zit-checkout, .zit-cache, .zit-tmp-*. Removed: the absorbed sibling reservations (.zit-managed/-undo/ -bisect), the pre-directory cache/vstat spellings and their read tolerance, the retired scratch spellings, and the import-provenance names (refs, .zit-import-map.jsonl). Behavior shift: in never-re-opened old checkouts these leftovers are now visible to extras and deletable by clean. The container's byte-1 fold stays, so a legacy authoritative sibling still migrates losslessly on first contact (e2e: every-leftover checkout converges to the clean root with membership intact). check-in: c0a116d458 user: dmestas tags: sub-manifest | |
| 19:31 |
Fold managed/undo/bisect into the .zit-checkout container v2 (consolidation Task 6)
Sectioned container (ZITCKOT, tagged table, whole-file CRC); payloads are the existing formats unchanged. Byte-1 dispatch migrates legacy checkouts on first write; siblings dead once a container exists. Undo/redo restore and the commit/merge record+membership writes each collapse to one atomic splice. DST gains a legacy-seeded migration sweep (full-legacy or full-container at every crash cut). Registry: the three names move to the legacy tier. Perf on pkgsrc: adjacent update 6.50s (was 6.8), no-op 5.14s (was 7.69); migration of a 66k-file checkout invisible inside one update. check-in: efa29d234f user: dmestas tags: sub-manifest | |
| 17:25 |
control files: registry, provenance opt-in, one scratch prefix, advisory dir
Tasks 1-5 of the control-file consolidation plan. The checkout root goes from twelve zit-owned names to six live ones; the authoritative-tier container (task 6) and legacy removal (task 7) are deliberately not in this change. Registry: control_paths now carries every reserved root name with its tier -- authoritative, advisory, scratch, legacy -- and is the single authority the case table, the extras exclusions and the ignore-glob coverage pin all derive from or test against. A name added without its glob line or case coverage is a test failure, proven by mutation. The pin caught two live gaps on arrival: a crashed update's transaction directory had no ignore-glob line, and a crashed import's staging directory was never reserved at all. import-git --provenance: the per-object map and the source ref manifest are now instrumentation, published only on request. The default destination is the repository and its checkout record -- fossil's own shape. Both files are still written and verified in staging every run; without the flag they leave with the raw generation, and the staging manifest check enforces whichever set was chosen. Scratch: one prefix. .zit-tmp-txn- (update transactions), .zit-tmp-import- (import staging), .zit-tmp-pack (pack scratch; name only, the truncate-reuse and rewrite-exclusion lock protocol are untouched). One registry prefix row and one glob line cover the tier including names that do not exist yet. Advisory: .zit-cache is a directory -- repo (derived-state cache) and vstat (materialization plan), with the tree-checkpoint's member reserved. A legacy cache FILE is served through a read fallback and migrated by the first write via rename through a scratch name: the derivation survives with no bytes copied and no re-derive. Verified on a 66k-file pkgsrc checkout: identical SHA-256 across migration of a 35.8MB cache, fallback reads at parity, no-op update 7.69s vs 7.74s. Legacy spellings of every renamed or relocated name stay reserved and ignore-globbed until the removal release. check-in: f1ac625093 user: dmestas tags: sub-manifest | |
| 13:41 |
update: cost tracks the diff, not the width
Three fixes, each found by measuring the previous one's residue. markUnchanged: a path byte-identical between baseline and target -- same digest, same mode, with requireClean having already proven the on-disk file matches -- is skipped by every mutation phase: staging both directions, the undo journal, deletion and rewrite. An update used to delete and rewrite the entire tree regardless of how little changed. .zit-vstat: the stat sidecar written at materialization is now the BASELINE PLAN -- version-bound, CRC'd, carrying mode and plan order -- so requireClean rebuilds the baseline with zero tree flattens when it describes the current check-in, and proves most files clean by inode/size/mtime instead of reconstructing their content out of the log. Advisory throughout: any mismatch, staleness or corruption falls back to the flatten and the content compare, which stay authoritative. ManagedSet.fromSortedPaths: building the 66k-entry managed set through insert was quadratic -- an O(n) sorted-position memmove plus the LIVE sortedUnique assert per call -- and measured at 23.9 SECONDS of a pkgsrc update, larger than every real phase combined. The plan's own order is already sorted and unique, so the set is now built in one pass, order enforced per adjacent pair, invariant asserted once. Measured, same store and targets as #52's gate: sqlite (2.2k files): 117.8s flat -> 0.6s adjacent / 1.2s distant pkgsrc (66k files): 42.2s flat -> 6.8s adjacent / 8.9s distant pkgsrc user CPU: ~25s -> 1.3s Residue is stat I/O (two ~66k-file sweeps), not computation. The lockstep flatten prune #52 proposed was never built: the flatten measured 178ms at 66k files once the real costs fell. Tests: dirty AND delete of a skipped-diff file both refuse with the tree untouched; sidecar intact/absent/rebound-to-wrong-check-in all produce identical trees; an unchanged file provably keeps its inode; an updated tree equals a from-scratch materialization. Closes #52. check-in: 8797f53546 user: dmestas tags: sub-manifest | |
| 01:23 |
repocache: extend across appends; import warms at completion
The derived-state cache was all-or-nothing: any log append was a total miss, and the next read re-derived the whole store. On a real 1M-artifact pkgsrc store that was 352s per cold read, re-paid after every commit. Repository.open's miss path now distinguishes growth from rewrite. On growth it replays the cached record body (parse-free), folds only the tail frames through the same crosslink fold a cold derive uses, and rewrites the cache: every append healed by the next read at O(tail). Measured on pkgsrc: 352.31s cold, 2.40s after a commit, output byte-identical to a forced full re-derive. Sound because extension refuses whenever the fold could be wrong rather than stale: validatePrefix proves the old tail frame still verifies at its offset (a rewritten log cannot extend), and a shun fingerprint in the cache header forces the full derive on any shun-table change, since the body was distilled under the old mask. Cache format v2; a v1 cache misses once and rebuilds. import-git now warms the cache at completion, after the atomic rename, against the published store -- clone has done this since warmCacheFromLog existed, so an import-built store no longer defers the whole derive onto its first reader. Tests: extension equals cold derive with a fork and control tag in the tail; a forged-cache canary proving the fold starts from the stored body; shun change takes the full derive with the mask applied. Also backs out 1a4b74f2d5 (parallel staging): measured ~3% on a corpus where staging is ~5s of 149s -- threading complexity the number never justified. Closes #51. check-in: 84d3fe8f32 user: dmestas tags: sub-manifest | |
|
2026-08-09
| ||
| 16:32 |
update: stage plan entries in parallel
Staging reconstructs and writes every file in a plan -- one decompress, delta-chain application and hash verification each -- and on a wide repository that is tens of thousands of files for a single open or update. The work is per-file independent, and the store's read path is offset-addressed pread over an index immutable for the duration, so it parallelises without coordination. The one thing that cannot be written concurrently is last_stage_fault, a module global. Workers keep their own and the winner is chosen by ENTRY INDEX, which is exactly the fault sequential order would have raised, so this is observationally identical to the loop it replaces including which failure gets reported. Below 64 entries it stays sequential, and a thread that fails to spawn is not an error -- its share drains off the same cursor. Measured 2:33.5 to 2:28.8 on a 299 MB sqlite store, about 3%, with CPU never leaving 101%. So staging is NOT the dominant term there: the flatten's per-node reconstruction is, which this does not touch. Committed so it can be measured at pkgsrc scale, where the file count is 27x higher and the size distribution is different. If it does not move that number either, revert it -- it buys complexity for 3%. check-in: 1a4b74f2d5 user: dmestas tags: sub-manifest | |
| 04:46 |
import-git: sub-manifest mode drops on the pair proof, not on reproduction
A POLICY CHANGE, not a bug fix. Gates 5 to 7 prove the mirror's manifest is byte-reproducible from the check-in this import builds. Flat mode claims that; sub-manifest mode restructures the file set behind S cards, so its check-in is a different artifact by construction and claims nothing of the kind. Refusing to release the metadata because a mode failed to reproduce something it never promised is a category error. What makes the drop safe is gate 3 -- the bytes hash to the name a sibling manifest.uuid records, which a project file merely called 'manifest' cannot satisfy. That gate is untouched and still catches the look-alike. Gate 8 still guards the tags member. The B-card and S-card shape checks move with gates 5 to 7: a delta manifest is a real check-in, and refusing it only ever mattered because gate 5 needs a complete F run to compare against. On a sound source the win is small: bench measures 180 retained on sqlite, and this takes that to about 1. The win is on sources holding shunned or phantom content, where the origin's manifests name bytes the origin no longer has and no export can reproduce them -- permanent stranding today. libfossil goes from 293 of 3819 commits validated to 3817, and its store from 35.0 MB to 30.2 MB. Costs, stated rather than discovered later: this retires the only signal that a source repository is incomplete, and it leaves mirrorTreeMatches's tree arm unreachable from classification, annotated in place with the condition for deleting it. check-in: 063a66e4c5 user: dmestas tags: sub-manifest | |
|
2026-08-08
| ||
| 20:53 |
import-git: mint the D card from a proven mirror manifest
Fossil's git export derives the git commit time by truncating a floating-point Julian Day, so a whole-second origin timestamp lands a second early about half the time. Minting from commit.committer meant zit built those check-ins at a provably different instant than the origin, their manifests were never redundant, and their mirror metadata was kept forever: 4533 of 36383 commits on a real sqlite mirror. Where gate 3 has proved a mirror manifest authentic and gate 4 parsed it, the D card is now that manifest's own. Per commit, only where the proof succeeded -- an unproven mirror is never trusted. On the sqlite corpus timestamp_mismatch goes 4533 to 0 and the sha1-named era drops 18373 of 18392. Not under an explicit --attribution author: that flag asks for the author's instants, and adopting the origin's would leave it doing nothing on most commits while still appearing set. Those imports drop nothing, stated rather than silent. Gate 7 also compared against the committer unconditionally while minting the attributed identity, so under --attribution author it could drop a manifest on a proof about a check-in that was never built. It now compares against the instant actually minted. check-in: 7495b2e141 user: dmestas tags: sub-manifest | |
| 18:46 |
import-git: report blobs whose bytes are check-ins; pin sterilizer position
A tracked file can be a complete check-in manifest -- a vendored fossil source tarball is the ordinary route, since fossil stopped sterilizing archive exports in 2.18. The import stores it verbatim rather than mutating it the way fossil's own git import does: the file the user committed should be the file we store, and open already refuses to materialize a check-in whose content is absent. But the operator should hear it at import, not at the first open, so such blobs are counted and reported. Two bytes reject ordinary content before anything is parsed. The --manifest e2e now pins the marker's POSITION, not just its presence: it follows the final card and is not line 1. A refactor moving it to line 1 would still sterilize and would still pass every previous assertion, while silently diverging from fossil and breaking the mirror strip. Errata for artifact-format 4.1.2 filed separately; the draft states the wrong position, the wrong rejection gate, and a literal no fossil code path emits. check-in: 12ce319d18 user: dmestas tags: sub-manifest | |
| 18:07 |
import-git: strip the sterilizer where fossil actually writes it
sterilize_manifest() appends its marker after the last card. On an ordinary manifest that is the end of the blob, but on a PGP-clearsigned one the cards are followed by the signature block and the marker lands immediately before it. The strip only handled the trailing case, so a signed check-in's mirror bytes never hashed to the origin's recorded name, the pair proof refused it, and its manifest/manifest.uuid/ manifest.tags were retained permanently. On a real sqlite mirror that is 1076 check-ins, all 2009-2011. With the splice every one of the corpus's 36383 commits proves: 35307 by the trailing strip, 1076 only by this. Deliberately narrow -- only the two positions fossil writes. A marker anywhere else is still left alone, so a blob that merely contains the line is not reshaped into one that proves. check-in: a161ee719a user: dmestas tags: sub-manifest | |
| 17:40 |
import-git: name artifacts SHA3-256, not SHA-1
The algorithm was a hardcoded .sha1 that mis-named every import from a SHA3-256 Fossil, in both output modes. Sub-manifest mode reproduces nothing by construction, so it has nothing to inherit; flat mode is meant to inherit its source's algorithm, but that is unimplemented and sqlite shows it cannot be one choice anyway -- its history spans the promotion ratchet, so a single manifest there names both 40- and 64-hex blobs. SHA3-256 is the correct answer for a source whose algorithm cannot be derived, and the only defensible one for new addresses after SHATTERED. Now one constant, with the store's default policy and the submanifest k-analysis derived from it rather than guessed. Four name-verification sites in the storage layer used the same constant and rejected every SHA3 name; they now use artifact.verify, which reads the algorithm off the name's own width and cannot go stale. Two test repairs the change exposed: the ISSUE #24 e2e compared parent and child check-in names that both alias one readCheckoutState buffer, passing on the length difference alone, and that fixture no longer produces a mixed-width store -- noted inline, it needs its own fixture. Hashing is ~2.9x slower than SHA-1 on Apple Silicon, which lengthens hash-bound work. check-in: 51afeef206 user: dmestas tags: sub-manifest | |
| 13:42 |
e2e: pin the foreign-check-in refusal end to end
Two CLI-level fixtures for the class behind #42, differing only in their T cards, both dated 2099 so mtime cannot explain the difference: - self-tagged: carries trunk's tag pair, wins the tip, and open must refuse with exit 1, name the class, and leave the tree untouched. - untagged: a leaf and visible in 'zit leaves', but never resolvable, so open succeeds on the real tip. Also repairs assertions the earlier trunk-relabel and open-diagnostic work left stale in the two opt-in e2e steps, which 'zig build test' does not run: branch topology now maps the source's main line to trunk, and open's untracked-file diagnostic includes the resolved check-in line. Closes #44. check-in: 33d9286c19 user: dmestas tags: sub-manifest | |
| 13:02 |
import-git: skip refs outside refs/ instead of aborting the import
git fast-export --all emits a 'reset worktrees/NAME/HEAD' pseudo-ref for every detached-HEAD linked worktree. canonicalRef correctly refuses it, but that refusal unwound the whole import, so one worktree in the source aborted a complete history after all the work was done. Skip it, counted as skipped_foreign_refs. The skip is on the refs/ prefix alone; canonicalRef's traversal refusal still aborts. Fixes #45. check-in: 31346e784b user: dmestas tags: sub-manifest | |
| 00:22 |
import-git: persist a HashPolicy, not a HashAlgo ordinal
The header byte and the config field are both HashPolicy values. The import wrote @intFromEnum(HashAlgo.sha1) into each. The enums overlap numerically and mean different things -- HashAlgo is {sha1=0, sha3_256=1}, HashPolicy is {sha1=0, auto=1, sha3=2, ...} -- so every imported repository advertised policy sha1 by coincidence of ordinal, and the obvious 'switch it to sha3_256' repair would have written 1 = auto, silently arming 7.4's promotion ratchet. Now derived through artifact.defaultPolicy rather than written as a literal. The import mints SHA-1 names throughout, so 7.2's rule gives auto. When the naming algorithm learns to follow its source the predicate changes and the derivation does not. Header byte on a fresh import: 00 to 01. check-in: 4efd2e36d2 user: dmestas tags: sub-manifest | |
| 00:14 |
import-git: a child's parent-proof needs identity, not its parent's other gates
mirrorParentProven required the parent's validated_uuid, set only when a commit FULLY dropped. So proving commit N's parent meant commit N-1 had to clear every gate for itself -- tree shape, timestamp, manifest shape -- none of which bear on whether its UUID is correct. One retain anywhere permanently poisoned every descendant. On a real 36,432-commit sqlite mirror that produced zero drops and 36,252 parent_unproven retentions: its 2000-05-29 genesis commit fails the timestamp gate, and essentially all of history descends from it. Gate 3 already proves identity per commit and cryptographically. That proof is now recorded as attested_uuid whether or not the commit drops, and the parent proof accepts either. Drop eligibility is unchanged -- attestation governs only what a CHILD may rely on. The existing test asserted the defect as intent, comment and all; it now asserts the corrected behaviour. (#43) check-in: e7fd62ec4d user: dmestas tags: sub-manifest | |
|
2026-08-07
| ||
| 21:50 |
update: pin the fail-closed refusal of a foreign check-in
Regression tests for issue 42. A blob whose bytes are a valid check-in manifest IS a check-in -- structure types artifacts, and repo-state 4.4 exempts only what a parser declines to classify -- so a repository that tracks such a file holds check-ins of another repository. That derivation is required; what must not regress is the refusal. Asserts materialization fails with FileContentMissing, names the file, and leaves nothing written -- the fail-closed property stated as a test so it cannot be quietly traded later. Also pins that the diagnostic explains the class and never alleges corruption, since the store is intact and verify says so. Records the T-card grammar that makes such a manifest resolvable: 'T *branch * trunk' self-targets, while 'T *branch trunk' puts a branch name in the UUID field and the manifest is rejected entirely. (#42) check-in: 0369e62f19 user: dmestas tags: sub-manifest | |
| 21:42 |
open/update: refuse a tip whose content this repository never held
The format types artifacts by structure, and repo-state 4.4 exempts only what a parser declines to classify -- so a stored blob whose bytes are a valid check-in manifest IS a check-in, joins the DAG, and may carry its own branch tags. A repository tracking such a file (a Fossil-to-Git mirror's 'manifest' is the common source) therefore holds check-ins of another repository, whose blobs it never had. That derivation is conformant; Fossil does the same. What was not acceptable was spending minutes materializing before finding out, then reporting it as a missing file with no way to see why. open now presence-checks the resolved tip's file set before invoking the engine and refuses naming the class; update prints the same explanation. Advisory only: a files() fault falls through, and stagePlan remains the sole authority, so this cannot let anything through -- it can only refuse earlier. Verified against a foreign self-tagged manifest that genuinely hijacks trunk: exit 1, working tree untouched. (#42) check-in: a81e9a8745 user: dmestas tags: sub-manifest | |
| 21:10 |
update: cover the legacy trailing-separator plan path
parseForImport normalizes a fossil-origin F card into a fresh buffer and the manifest borrows that copy, so every F-card hash the plan reads points into it rather than into the stored bytes. Nothing exercised it: every synthetic corpus here is git-native and never carries the separator, so normalized is null throughout the suite. The path is correct -- the test passes -- but it was covered only by repositories nobody runs the tests against. (#42) check-in: 15db533ea7 user: dmestas tags: sub-manifest | |
| 20:57 |
open: name the check-in it resolved when materialization fails
open picks its own target -- the tip of the configured main branch -- rather than being handed one, so a failure that omits the check-in cannot distinguish materializing the expected one going wrong from resolving a different one than the operator believes is the tip. There was no other way to see which. (#42) check-in: da081a3eca user: dmestas tags: sub-manifest | |
| 18:59 |
update: copy the staging fault's path instead of borrowing freed memory
StageFault held a slice into PlanEntry.path, documented as borrowing a plan that outlives the call. It does not: applyCheckin frees the plan from its own defer on the way out, so the caller rendered freed heap -- ten bytes of garbage where a filename belonged, in the very diagnostic added to make this failure legible. Path is copied now, as the artifact name already was. Adds the end-to-end test that would have caught it: the earlier test hand-built a fault and so never exercised the lifetime that was wrong. check-in: 41dc7067d8 user: dmestas tags: sub-manifest | |
| 18:20 |
update: name the file staging cannot materialize
Three separate sites printed the identical 'cannot read repository: repository is corrupt' -- deriving the view, reopening the log, and applyCheckin. So a materialization failure was indistinguishable from a failure to read the repository, which is a different claim about a different subsystem. stagePlan's three refusals now return FileContentMissing carrying the file path and the artifact name: a file the check-in references has no artifact here, or has none stored, or the stored bytes do not match the name. verify cannot surface any of these -- it checks that stored artifacts are valid, never that referenced ones exist. Being in the closed ApplyError set, the compiler required all four verb mappers to handle it. (#42) check-in: 0b7b4af7fe user: dmestas tags: sub-manifest | |
| 17:49 |
repoview: name the artifact a failed derive rejected
'repository is corrupt' says a repository cannot be read, not what in it is unreadable -- and the walk that discovers it visits every artifact in the store, minutes on a large one. Whoever holds the broken repository was left bisecting it or attaching a debugger. The derive now records which artifact it rejected and why; openRepo reports it, so every verb's open gets the name. Plain data, no allocation, so it survives the errdefer that tears down a half-built view. Repository.open clears it per attempt because callers declare a Repository undefined. Also splits open's two identical diagnostics: deriving the view and reopening the log for materialization both printed the same line, which made a failure in one indistinguishable from the other. (#42) check-in: 37f746e59f user: dmestas tags: sub-manifest | |
| 17:17 |
pack cache: derive the entry cap from the byte budget
A real 53,773-commit import evicted 99,097 times, every one forced by the 4096-entry cap and not one by the 64 MiB byte cap, with the resident set at 23,551 KiB. The cache was held to a third of its own stated size by a second, unrelated number, and the 51 percent hit rate that produced looked like a working-set problem rather than an arithmetic accident. The entry cap now divides the byte budget by a planning entry size, so the two cannot disagree about which one is bounding: bytes bind for normal data, entries only for many tiny items, which is what an entry cap is for. (#40) check-in: ac89bad5fd user: dmestas tags: sub-manifest | |
| 15:29 |
import-git: name the main line trunk, whatever git called it
A git default branch is master or main; this format's is trunk, and so is every place that must name the main line without being told -- a fresh checkout, zit open resolving a tip, a clone initialising its own state. A repository whose main line was called master satisfied none of them: it cloned and verified clean, then could not be opened, because the branch the destination looked for held no commits. labelBranches now takes the name to MATCH on and the name to APPLY separately: the head is still found by its source name, the segment is labelled trunk. Side branches keep their source names, and the git-side refs mapping still records sym-heads/master, so nothing about round-trip identity moves. If a side branch already publishes trunk, the source name is kept rather than colliding two segments under one label. (#42) check-in: 5caf866d86 user: dmestas tags: sub-manifest | |
| 13:03 |
storage: a block cache for FileStorage reads, opt-in and not yet wired
Profiling a real pkgsrc-scale import put readv at over half of all CPU samples. A block cache in front of pread is the shape that addresses it: the store is append-only, so a block is retained only when the read that filled it came back FULL -- which proves those bytes already existed, so appends cannot alter them and need no invalidation. Only truncate does. Deliberately not attached to any caller. Wired into import-git it reached a 99 percent hit rate over 392,421 reads and did NOT make a small corpus faster: at that size the page cache already served those reads, so the syscalls were cheap and the lookup was pure overhead. The prior from the real profile is strong but unproven at scale, and shipping it on 'should help' is how the reverted top-level cache in issue 32 happened. useReadCache is the one-line opt-in for profiling it. (#39) check-in: f3becbedd1 user: dmestas tags: sub-manifest | |
| 11:34 |
pack cache: name WHICH budget forced each eviction
A real 53,773-commit run reported 99,097 evictions against 69,485 lookups -- evicting more than it was asked about. But two caps can force that, max_bytes and max_entries, and they bind under different data, so the count alone said only 'raise something'. Evictions are now split by cause and the resident set is reported, so a run names the budget to raise instead of requiring a blind sweep. (#40) check-in: 88ae8ba619 user: dmestas tags: sub-manifest | |
| 03:25 |
import-git: report the pack plaintext cache the way the memo is reported
PackStats carries the cache's hits, misses, evictions and oversized skips, and import-git prints them. Same reason SubManifestMemo got this in #35: a fixed budget that is wrong for a corpus is invisible from outside the process, and a hit count without its denominator cannot show it. Nothing here gates a decision. (#40) check-in: b6cd09fc11 user: dmestas tags: sub-manifest | |
| 03:24 |
logstore: one read for a frame's payload and its CRC trailer
The trailer is the four bytes immediately after the payload and was read by its own pread. FileStorage is unbuffered, so that was a second kernel transition for adjacent bytes, paid per frame -- and a deltified artifact walks its whole chain frame by frame. Also gives PlaintextCache the counters it was missing. It tracked hits with no denominator, so a hit COUNT could not be read as a hit RATE; misses, evicted and oversized make the budgets observable. (#39, #40) check-in: a4f298d3e1 user: dmestas tags: sub-manifest | |
| 02:37 |
managed: derive the entry and path-table bounds from the format
max_entries was 1 shifted by 20 while its own doc named the u32 count field that carries it, so insert refused a correct set at 1,048,576 paths -- four thousand times below what the format can express. Now the u32 range, with max_file_bytes computed from it rather than a flat 64 MiB, and read sized from stat like every other whole-file read here. Two dependents moved in the same change, which is the point: extras borrowed max_entries as a directory-sweep ceiling. Never the same quantity, and a tracked-set format change would have silently widened a filesystem runaway guard. It has its own now. undo parseSlot allocated count entries BEFORE parsing them, guarded by count over max_files. That guard became vacuous the moment max_files reached the u32 range, leaving a 4-billion-entry claim reaching the allocator. Now bounded by what the remaining payload can back. (#38) check-in: 93adddd49d user: dmestas tags: sub-manifest | |
|
2026-08-06
| ||
| 21:44 |
sync: regression-test a clone past the old round ceiling without a huge fixture
The ceiling was a PRODUCT of round cap and batch size, so shrinking the batch to 1 reaches it at a corpus a unit test can hold: 1124 artifacts, 1124+ rounds, stall budget 4. Records that a converging clone ends exactly one stall deep -- the terminal round carries no cfile -- so the budget can never be 1, and that the count does not grow with the corpus. (#37) check-in: 5aa8e46e47 user: dmestas tags: sub-manifest | |
| 21:44 |
repocache: drop the 2 GiB cap whose rationale the code contradicts
The cap was justified as bounding 'a lying on-disk size before any allocation'. The read path takes stat's size and allocates exactly that, so there was no claim to distrust. What it did do was silently refuse a larger cache -- and a cache is advisory, so that refusal is invisible, degrading to a full rebuild with no diagnostic. Real bounds stay where they bind: the usize cast on read, the u32 image_len field on write. (#38) check-in: c82c04b10d user: dmestas tags: sub-manifest | |
| 21:28 |
update: share one sub-manifest memo across the target and baseline flattens
buildPlan flattened both trees through the non-memoized path, so the baseline walk re-inflated and re-parsed every node the check-in did not touch -- which on a wide repository is nearly all of them. Removes the redundant parse, not the redundant walk: both still enumerate the whole tree. Pruning that needs pack.zig's lockstep descent (#36). check-in: 9fcc75f669 user: dmestas tags: sub-manifest | |
| 19:17 |
commit: name the file-size limit instead of calling it an I/O error
max_tracked_file_bytes is not a policy number: it IS logstore.max_payload, and put/putArtifact assert on it, so a file above it would trip an assert during commit rather than be refused. Tied by a comptime assert in main.zig and documented with its three co-constraints (storage frame, sync decode cap, and the delta format's sub-2^32 integers). Oversize now reports the size and the limit instead of 'input/output error', which named nothing the operator did. Diff keeps the generic path; reaching it means an already-committed file grew past the ceiling. (#34) check-in: 56a44d4010 user: dmestas tags: sub-manifest | |
| 17:33 |
citations: zero broken; CI gates on zero
The last 42 fell in two passes and a hand-repair. Round 6 fed each refuted proposal its reviewer's own objection back -- "off by one at the start", "range too short for a biconditional, it must decide both directions", "evidence stops on the dangling No". Those objections were near-solutions, and 38 of 40 second attempts passed review where the first had failed. Eight were then read and repaired by hand. Seven were ordinary rot. The eighth was not: the reviewer had refused a proposal for the nine-cause fatal-error set because the cited lines "neither state nor deny the claim", and it was right -- no passage states such a set, because it is zit's own enumeration and its members each carry their own citation. It now names the governing section with NO range. Inventing a span to satisfy the checker is the defect this sweep exists to remove. One of the nine was the checker's fault rather than the code's. sync.zig:4599 cites artifact-format section 3.2 L347-350, byte-exact and correct, but the words "artifact-format" sat on the previous line and the draft-name detection only reads the current one. Reflowed the comment. 964 citations, 964 resolving. The baseline is deleted: this started as a two-way ratchet over a 512-citation backlog, and with the backlog gone an allowance is only somewhere for new rot to hide. A citation whose lines fall outside the section it names now fails the build. test 3506/3506, fmt. Issue #18. check-in: 8d3c0927be user: dmestas tags: sub-manifest | |
| 16:55 |
memo: evict by recency instead of freezing at the budget
SubManifestMemo filled to 4 MiB then stopped interning, so the retained set froze on whatever arrived first and later resolves paid a lookup that could not hit. Measured at 30,000 files: working set 7,494 KiB against a 4,096 KiB ceiling, costing 23 points of hit rate (92->69%) and ~27% of wall time. Now per-entry allocation with an LRU list, trimmed in beginWalk. Eviction cannot happen in resolve: TreeWalk frames borrow the views that resolved them, so freeing mid-walk is a use-after-free -- the arena hid that by never freeing. Budget 64 MiB, from the measured working set scaled to pkgsrc. Same corpus: 92%, 0 evicted, 26s->19s. Unchanged where budget isn't binding. repository.zig and git_import_verify.zig halves landed in 76a2905055, swept in by a concurrent commit. (#35) check-in: 9b4f7ae683 user: dmestas tags: sub-manifest | |
| 16:37 |
citations: 126 repairs, most of them impossible to propose before
Every earlier pass asked only for a line re-key WITHIN the cited section. A citation whose claim lived in a different section could therefore only come back "unresolved" -- even when the agent had already found the right answer and written it into its reason field. Four rounds of that read as "these need a human", when what it actually showed was that the prompt never permitted the fix. This pass let each repair choose its own kind. Of 126 confirmed: 63 changed the SECTION, 41 re-keyed a range, and 22 rewrote a claim the drafts no longer make. 104 of those were structurally unproposable before. Unresolved fell from 67 to 5. The claim rewrites are the ones worth reading. reply_body_cap was documented as an RFC-mandated 512 MiB ceiling; section 4 L830-833 states the opposite, that Fossil imposes no such entity limit, so it is now documented as a zit-local cap -- the same repair the 65535-byte line bound got earlier. A verbatim quotation attributed to the draft, "The card MUST consist of exactly two whitespace-separated tokens", does not appear in it; section 10.2 L2118 says "Exactly two visible tokens are required." 42 review verdicts were refused and are not applied. Two refute agents died mid-run; their proposals were left unapplied rather than taken on trust, and one refute agent lost to a stalled stream cost 14 repairs that the completed run later recovered. Broken 172 to 42, 96 percent of citations resolving, 0 unresolvable sections. test 3500/3500, fmt. Issue #18. check-in: 76a2905055 user: dmestas tags: sub-manifest | |
| 14:27 |
citations: anchor appendix citations written as words
The section sign was not the only anchor in the tree. `App. A.2 L3102-3114` and `ABNF App. B.7 L3566-3575` name an appendix by word, so their ranges bound to whatever section sat further left, or to nothing. Three citations were invisible or misattributed for that reason. All three are repaired. The decoded version-gate text is App. A.2 L2547, "Decoded error: Fossil version 2.0 or later required." The transcribed gate-fire response body is L2542-2545 -- the error, clone_seqno, push and timestamp lines. The third keeps no range at all, deliberately. It cites "the seven pragma SP NAME LF productions that carry no argument", and Appendix B is flat now, with those seven SCATTERED through it: uv-push-ok at L2907, send-private at L2925, req-clusters at L2935. No single range covers them, so it names the appendix and stops. A span invented to satisfy the checker would be the same defect this sweep exists to remove, wearing a different costume. Surfacing three and repairing four takes broken from 173 to 172 while the checker gets strictly stronger. 947 citations, 775 correct, 0 unresolvable sections. test 3500/3500, fmt. Issue #18. check-in: 471255d369 user: dmestas tags: sub-manifest | |
| 14:14 |
citations: correct six section banners the sweeps could not see
Six banner comments named sections that do not exist, in the "Section N" spelling rather than the section-sign one. Earlier renumbering sweeps matched the sign and missed these on the very same page, so they are leftovers of those fixes. Each banner carries its own section TITLE, so every target is confirmed by matching that title against the draft's own header rather than by inference: Section 10.3 Configuration Exchange -> 9.3 Section 10.4 Commit-Lock Pragmas -> 9.4 Section 10.5 Private-Content and Discovery Pragmas -> 9.5 Section 3.10 Round Message Layout -> 3.2.1 Section 9.1.1 Unversioned-File Synchronisation -> 8.5.1 Section 9.2.1 Derived Branch, Tag, Leaf, Closed, and Current-Trunk Views -> 8.6.1 Section 3.10 is not a deleted section after all, which both an earlier audit and this author had assumed: it is Round Message Layout, renumbered to 3.2.1. The comments reasoning about its removal, corrected in an earlier commit, remain accurate about the reasoning being void. No "Section N" reference in the tree now names a section absent from its draft, down from six. Baseline unchanged at 173: this spelling is still invisible to the checker, which is its own item. test 3500/3500, fmt. Issue #18. check-in: c8cd73f343 user: dmestas tags: sub-manifest | |
| 13:54 |
citations: catch reversed ranges, and repair two live sites
The containment test asked only that lo be at or after the section start and hi at or before its end. A REVERSED range satisfies both halves independently, so it cleared. sync.zig carried a range running from 1878 back to 1443 -- digits that cannot both be right -- and it had passed as correct since this checker was written. The test now also requires lo to be at or before hi. That site is repaired rather than deferred. The digits were unrecoverable but the claim was not: section 8.2 L1517-1520 states it outright, "An accepting clone reply emits zero or more clone cfile cards in RID order ... then clone_seqno NEXT, push SERVERCODE PROJECTCODE, and final comment", with L1504-1505 for iterating from the supplied RID. 6682409ff0 added a section 8.2 range at L1915-1924 for "the store grew". Section 8.2 ends at L1595; L1915 is config-push payload text. The claim is section 3.7(a) L577-578, "file/cfile, including dangling delta, stored content or igot created a phantom" -- which is what artifact_count_grew measures, and the sibling condition on the line above already cites 3.7(b). Left unfixed this failed the ratchet at 174. Two checker comments asserted things the code does not do: BARE_DEFAULT claimed ambiguity "never manufactures a failure" -- true, while it manufactures roughly 79 silent CLEARANCES -- and the witnesses comment claimed completeness when the loop breaks on first fit. Five draft aliases added so an unrecognised spelling cannot fall through to the all-drafts path unnoticed. 945 citations, 773 correct, 173 broken, 0 unresolvable sections. Baseline holds at 173 while absorbing a new defect and a newly-detectable class. test 3500/3500, fmt. Issue #18. check-in: a9b1ee8fb4 user: dmestas tags: sub-manifest | |
| 13:52 |
undo: journal the files commit accepts
undo's per-file cap read managed.max_file_bytes -- the serialized PATH-TABLE bound (64 MiB) -- as if it were a content ceiling. Any tracked file over 64 MiB (commit allows 1 GiB) failed captureFile, and since a durable pre-image gates any mutation, that aborted checkout/update/merge outright. Per-file cap is now max_tracked_file_bytes, homed in managed so every layer shares one number. Journal read sizes from stat instead of a derived cap that would have refused journals record can write. (#34) check-in: 1cc2d055b9 user: dmestas tags: sub-manifest | |
| 13:21 |
sync: bound stalled rounds, not total rounds
A total-round cap multiplied against the per-round batch size into an accidental ceiling on repository size (~1M artifacts), which a healthy pkgsrc-scale store tripped while converging every round. Guard now counts consecutive rounds that moved no bytes; announcement is not delivery, so a peer announcing phantoms forever still trips it. Also: import-git reports sub-manifest memo hit rate and whether the retention budget latched (#35). check-in: 6682409ff0 user: dmestas tags: sub-manifest | |
| 12:33 |
citations: check every range under a section, not just the first
A citation may carry several ranges: `§9.3 L3278/L3320-3322`, `§7.7 L1465/L1469-1471`, `§4.6.4-m6 (L1014)`. Matching only `§SEC Lnnn` saw the first and was blind to the rest, so half a compound citation could be re-keyed while the other half stayed rotted -- on the same line, looking equally verified. An adversarial reviewer caught that during a re-key pass; the tool could not have. 342 line references sat outside the checker's view. Each now binds to the NEAREST PRECEDING section on its own line, which is the rule that makes this safe: a range written BEFORE any section on the line -- `L545-547 hands clone to §8.1-§8.4, and §8.2 L1504-1506` -- belongs to earlier prose, not to a section further right, and is left alone rather than misattributed. 122 became checkable; 12 leading ranges were correctly skipped. Ranges are never carried across a line break for the same reason. This also surfaced §10.5, which the old pattern could not see at all because a requirement id sits between section and range (`§10.5 r43 L2477-2479`). §10 is Error Handling with only §10.1; the send-private and client-url claims are §9.5 Private-Content and Discovery Pragmas (L1983-1985: "send-private enables private sync only with x ... Clients repeat it every private round"). Same -1 downshift already verified for §10.3 and §10.4. 21 sites renumbered, and not-a-section is back to 0. Baseline 80 to 173: 93 defects that were always there and are now visible. The population it measures grew 821 to 943. test 3495/3495, fmt. Issue #18. check-in: 43c89e841f user: dmestas tags: sub-manifest | |
| 01:33 |
citations: re-key 20 against the corrected sections
d4e0e0d6da fixed six renumbered sections and deliberately left their line ranges stale, on the reasoning that a correct section turns a stale range into an ordinary lookup. This is that lookup: 20 of 100 survived review. The 20 percent hit rate is lower than the pass before it, and the reason is worth recording. It is NOT that the renumbering targets were wrong -- §11.1 L2604-2607 reads identically before and after that commit, so those citations were never renumbered by it. What the pass found instead is a second, older layer of mis-sectioning underneath the first: claims about server-emitted error cards citing §11.1 Credential Replay when they belong in §10.1 Error Card. Fixing one generation of drift exposed the next. Two refusals name failure modes the applier could not have caught. One proposal re-keyed half of a COMPOUND citation -- sync.zig:961 carries `§9.3 L3278/L3320-3322` -- which would have welded a freshly verified range to a still-rotted one on the same line. Another submitted a quote that stopped mid-word, with the half of the claim it needed sitting in the sentence it cut off. The 66 unresolved are now the honest core: three independent adversarial passes have each declined to invent an answer for them. Baseline 100 to 80; 90 percent of citations now resolve. test 3495/3495, fmt. Issue #18. check-in: dcd6c48df9 user: dmestas tags: sub-manifest | |