Measured while writing #83's envelope benchmarks. Reported, not a release blocker yet: the gate
is enforced in Phase 7 (#101) against a reference machine that is not yet defined. But the
distance is large enough that it should be known now rather than discovered at the gate.
The measurement
pnpm bench:envelope, at the file envelope, on AMD Ryzen 9 3900X, 24 cores, 31 GB, Node
24.18.1, Linux x64. Committed as benchmarks/envelope/reference-2026-08-05.json.
"corpus": { "files": 2500, "totalGb": 0.096, "chunks": 39010 },
"measurements": {
"fullBuildMs": 52337,
"noopRebuildP50Ms": 857.76,
"noopRebuildP95Ms": 915.68,
"incrementalRebuildP50Ms": 4561.24,
"incrementalRebuildP95Ms": 5631.62
},
"referenceGates": { "fingerprintP95Ms": 4000, "incrementalRebuildMs": 2000 }
| Gate (section 5.5) |
Target |
Measured p95 |
|
| Fingerprint at 2,500 files |
< 4,000 ms |
916 ms |
comfortably inside |
| Single-document incremental rebuild |
< 2,000 ms |
5,632 ms |
2.8x over |
The fingerprint half is healthy, and that is worth saying: deciding what changed across 2,500
files costs under a second. What costs is everything after that decision.
The machine above is a fast desktop. A reference machine is likely slower, so the real distance
is probably larger, not smaller.
What it is not
- Not the corpus. 2,500 files producing 39,010 chunks is close to section 5.4's 50,000-chunk
figure, so this is the shape the envelope describes.
- Not a cold build. A no-op rebuild of the same corpus is 916 ms, so discovery, fingerprinting
and the cache lookup are not where the time goes. What separates the two runs is one document
reparsed and the index rewritten.
- Not caching failing. The benchmark warns if fewer than
files - 1 artifacts are reused, and
it did not warn.
So the cost is in what a rebuild does after deciding only one artifact changed. Sealing writes
a whole new build database, because a build is immutable and content-addressed; at 39,010 chunks
that is 39,010 rows and an FTS5 index rewritten to change one document's worth.
Why this is a design question, not a slow function
Immutability is invariant 4, and rewriting the catalog is how a build stays a complete,
self-contained, content-addressed artifact. Making an incremental rebuild fast by mutating the
previous build in place would trade the invariant for the number, which is the wrong trade.
So the options are narrower than "optimise it", and the decision belongs on a ticket rather than
in a profiler:
- Copy the previous build database and patch it, rather than building it from nothing. The
result must still be byte-identical for identical inputs, which is what makes this delicate:
identity is logical and excludes physical bytes (build-identity.md), so a copied-and-patched
database is legitimate only if every canonical root is recomputed rather than inherited.
- Accept the number and change the gate, if 2,000 ms was chosen before the sealing cost was
understood. Section 5.5 predates the measurement.
- Narrow the gate's claim to a corpus size it holds for, and say what that size is.
Acceptance criteria
Also worth knowing
Chunks do not merge across headings, so a corpus's chunk count tracks its heading count
rather than its byte count. A first version of this generator wrote 40 KB as ~230 tiny sections
and produced 583,620 chunks from 2,500 files: eleven times the envelope's chunk figure at a tenth
of its byte figure. Anyone writing a corpus generator should set the section length deliberately,
and bench-envelope.mjs records why in a comment.
Measured while writing #83's envelope benchmarks. Reported, not a release blocker yet: the gate
is enforced in Phase 7 (#101) against a reference machine that is not yet defined. But the
distance is large enough that it should be known now rather than discovered at the gate.
The measurement
pnpm bench:envelope, at the file envelope, on AMD Ryzen 9 3900X, 24 cores, 31 GB, Node24.18.1, Linux x64. Committed as
benchmarks/envelope/reference-2026-08-05.json.The fingerprint half is healthy, and that is worth saying: deciding what changed across 2,500
files costs under a second. What costs is everything after that decision.
The machine above is a fast desktop. A reference machine is likely slower, so the real distance
is probably larger, not smaller.
What it is not
figure, so this is the shape the envelope describes.
and the cache lookup are not where the time goes. What separates the two runs is one document
reparsed and the index rewritten.
files - 1artifacts are reused, andit did not warn.
So the cost is in what a rebuild does after deciding only one artifact changed. Sealing writes
a whole new build database, because a build is immutable and content-addressed; at 39,010 chunks
that is 39,010 rows and an FTS5 index rewritten to change one document's worth.
Why this is a design question, not a slow function
Immutability is invariant 4, and rewriting the catalog is how a build stays a complete,
self-contained, content-addressed artifact. Making an incremental rebuild fast by mutating the
previous build in place would trade the invariant for the number, which is the wrong trade.
So the options are narrower than "optimise it", and the decision belongs on a ticket rather than
in a profiler:
result must still be byte-identical for identical inputs, which is what makes this delicate:
identity is logical and excludes physical bytes (
build-identity.md), so a copied-and-patcheddatabase is legitimate only if every canonical root is recomputed rather than inherited.
understood. Section 5.5 predates the measurement.
Acceptance criteria
suite still passes: same id from two paths, on Windows and POSIX, with shuffled enumeration.
docs/says the new number and why, and section 5.5 is amended ratherthan quietly diverged from.
benchmarks/envelope/gains a run at the byte envelope too (1 GB), which this one isnot:
atByteEnvelopeisfalseat 0.096 GB.Also worth knowing
Chunks do not merge across headings, so a corpus's chunk count tracks its heading count
rather than its byte count. A first version of this generator wrote 40 KB as ~230 tiny sections
and produced 583,620 chunks from 2,500 files: eleven times the envelope's chunk figure at a tenth
of its byte figure. Anyone writing a corpus generator should set the section length deliberately,
and
bench-envelope.mjsrecords why in a comment.