Phase 2 of docs/chapter-uploads.md, and a fix to something wrong today.
MAX_BATCH_SIZE bounds how many events a batch holds. Nothing bounded how
large they are, and those are not the same limit: sixty-four dialects are a
few kilobytes and sixty-four chunks of a chapter are eighty. The proposal is
the largest message in this protocol by an order of magnitude -- a signer's
nonce payload is k*133 bytes, their partial k*65, the proposal k whole events
-- so a legal batch could exceed what a group event carries, and when it did
the failure was silent: the proposal went out and the session stalled with
nothing to blame.
MAX_PROPOSAL_BYTES sits beside MAX_BATCH_SIZE and is enforced the same way:
when proposing, against the real encoding, and independently in
acceptProposal, before parsing. That second check is the one that matters,
for the reason the count check already gives -- the proposal is where a
remote party decides how much work everybody else does.
The chapter form needed the same limit early enough to be useful, which the
exact answer cannot be: it needs the chapter built and the chunks cut, one
line from opening a session. So ChapterProposal estimates it. Measured over
chapters from 1 KiB to 24 KiB and 1 to 63 chunks, the per-event cost of an
encoded proposal runs 357-388 bytes; the estimate pays 448, because
over-estimating costs an author a shorter chapter and under-estimating costs
the group a stalled session. A test asserts it never falls under the real
encoding and stays within a tenth of it.
The plan asked for a flat MAX_ORIGINAL_TEXT_BYTES and the measurement says
that is wrong by half: a 63-chunk chapter fits about 20 KiB of markdown and a
10-chunk chapter about 30, because the envelopes are what varies. The chunk
count is known as the text is typed, so the limit is too.
While the screen was open, its last two hardcoded English literals moved to
the catalogue, and the too-long message compares like with like -- what
proposing would send against what a group message carries -- rather than a
UTF-16 character count against a byte budget, which is what the first draft
of it did.
944 tests, no failures; m3Audit's budgets all met.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mirrors mantra.press's multi-file chapter upload under this app's rule that
the group, not the uploader, authors the work.
The web app's shape -- N files in, N chapters out, one transaction, one
redirect -- is the one shape this app cannot have. A signing session is one
quorum, all-or-nothing, 64 items and about 64 KiB on the wire, so twelve
chapters in one batch does not fit, should not fit, and would ask every
member to read twelve chapters in a single approval gate. One upload is N
sessions, one per chapter, and that cost belongs in front of whoever is
picking files.
Reading both trees turned up four things the single-chapter path gets away
with at N = 1 and an upload does not: the index is read from rows and no
proposed chapter is a row yet, so twelve proposals all sign the same index;
nothing checks the proposal's size, which puts a silent ceiling near 28 KiB
of markdown; there is no file picker in the app at all; and translation
scaffolding opens a session per chapter where one batch would do.
The splitter is measured rather than assumed. Run side by side, markdown-it's
block tokens and this app's blank-line rule disagree on ten cases in eleven,
and on the text as well as the boundaries -- markdown-it strips the block
marker, so `# Chapter one` is `"Chapter one"`. Phases 7 and 8 adopt it, with
three deliberate departures (no chunk for code blocks, one chunk per table,
none for a link reference definition) and a producer, which the app has never
had.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>