fix(log): pad-frame construction is total; the at-ack sync-failure compensation splits by phase — a production adoption's two write-path defects, cured at their roots
An adopter's full suite found two v2 write-path defects on fresh brains,
reproduced with stacks; both cured and both pinned with their exact
production shapes:
1. PAD-FRAME CONSTRUCTIBILITY: a single msgpack bin filler steps its
header by one byte at each size class (bin8→bin16→bin32), leaving one
unreachable payload size per boundary — the sealer requested a
291-byte pad, the encoder threw 'not constructible', and sync() died
whole. Construction is now TOTAL: the class-boundary holes bridge with
a trailing fixint beside the bin ({bin(n)} ∪ {bin(n)+fixint} covers
every size ≥ minimum). Pinned exhaustively: every size from the
minimum through a full sector plus boundary spill constructs
byte-exact and decodes as reader-invisible filler.
2. THE NON-MONOTONIC REFUSAL LOOP: the append-failure compensation
rewound the generation counter on ANY throw — including a covering
SYNC failure after a SUCCESSFUL append. The log carried generation N
while the counter re-minted N, and every later append refused
'non-monotonic (N ≤ head N)' — the write path wedged in a refusal
loop through deferred-embed retries and flush backoff. The
compensation now splits by phase: an append failure (log never took
the fact) fully compensates — un-buffer and rewind; a sync failure
after append earns the rewind ONLY if the appended fact is provably
dropped, otherwise the generation stays consumed and buffered — the
counter never re-mints a number the log may carry. Pinned: an
injected one-shot sync failure fails its write loudly and the very
next write mints fresh and succeeds, with the log scanning strictly
ascending end to end.
Also probed against the adopter's carried report: the 9.0 vfs.rename
stale-ghost shape does NOT reproduce on this head (old path cleanly
unresolvable on exists/stat/readdir after rename).
Gates: unit 2067/2067 (160 files) · integration 833 (97 files) ·
conformance 36/36.
This commit is contained in:
parent
7b67db4d0c
commit
cbe34d115e
4 changed files with 173 additions and 14 deletions
|
|
@ -1204,6 +1204,30 @@ function buildPadFrame(totalBytes: number): Uint8Array {
|
|||
fillerLength += diff
|
||||
if (fillerLength < 0) break
|
||||
}
|
||||
if (!converged) {
|
||||
// Class-boundary holes: a single bin filler steps its header by one
|
||||
// byte at each msgpack size class (bin8→bin16→bin32), leaving exactly
|
||||
// one unreachable payload size per boundary (the 291-byte production
|
||||
// case). Bridge with a trailing fixint (+1 byte) beside the bin —
|
||||
// {bin(n)} ∪ {bin(n) + fixint} covers every size ≥ minimum.
|
||||
let bridged = Math.max(0, targetPayload - payload.length - 2)
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const candidate = attempt([
|
||||
LOG_RECORD_TYPES.PAD,
|
||||
LOG_RECORD_VERSION,
|
||||
new Uint8Array(bridged),
|
||||
0
|
||||
])
|
||||
const diff = targetPayload - candidate.length
|
||||
if (diff === 0) {
|
||||
payload = candidate
|
||||
converged = true
|
||||
break
|
||||
}
|
||||
bridged += diff
|
||||
if (bridged < 0) break
|
||||
}
|
||||
}
|
||||
if (!converged) {
|
||||
throw new Error(`fact log v2: a pad frame of ${totalBytes} bytes is not constructible`)
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue