Repository navigation
ADFA-6683: Store documentation pages in compressed blocks (schema 4.0.0) - #58
davidschachterADFA wants to merge 17 commits into
Conversation
store_pages_in_blocks.py DB --pages-per-block N [--dry-run] writes the format in ADFA-6683-block-format.md, which the app reader follows too: - ContentBlocks(id, data): Brotli q11 w24 against CompressionDictionary of N pages' decoded bytes, concatenated. - ContentBlockItems(contentId, blockId, offset, length), WITHOUT ROWID with declared foreign keys, so rebuild_database.py --pack Content renumbers contentId. - Three sets, each cut in path order (BINARY), the last block of a set shorter, never mixed: a/ templated (not a/devsite/), j/ text/html, k/ templated. Brotli types only; a row with continuation rows and each continuation row stay as they are. - A blocked row keeps path, languageID, contentTypeID and templateId; its content becomes x''. Each block is decoded again before it is stored, and before the commit every item is sliced from its stored block and compared by sha256 with its row's decoded bytes. One transaction, then VACUUM; refuses a database that already has ContentBlockItems; --dry-run rolls every write back. test_store_pages_in_blocks.py covers the seven cases on a synthetic database. Each test was checked to fail against a script broken in the way it names: id order or NOCASE order, mixed sets, no continuation skip, a stuck offset, rows not emptied, templateId cleared, no refusal, a dry run that commits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018CMZQuXT3GT7nEct2w4tg3
…bers blocks Measured on the 2026-10-20 G02 ship-time build (final.db, sha256 c7de1d8a), each database packed with rebuild_database.py --page-size 4096: - Release .br (brotli -q 11 -w 24) 7,904,471 bytes smaller for N = 16 and 10,236,536 for N = 64, of 66,283,003 for the base. - Adding ContentBlocks to --pack: file 823,296 / 200,704 bytes smaller; .br 34,947 bytes larger for 16, 101,421 smaller for 64. - j/'s Javadoc index pages decode to up to 5,065,005 bytes, so two j/ blocks decode to more than the app's 8 MiB cache for N = 16 (largest 14,169,872) and one for N = 64 (28,055,018). The docstring said block ids follow the sets in order. That holds as written, but --pack ContentBlocks renumbers them; nothing reads the order. Comments only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018CMZQuXT3GT7nEct2w4tg3
From documentation version 4.0.0, store_pages_in_blocks.py stores most pages in ContentBlocks, compressed against the dictionary, and empties their Content rows. The re-mint recompresses Content rows and templates, not blocks, so a re-mint of a 4.x file would leave every blocked page undecodable, and the verifier compares no blocks either. Both now refuse a database whose newest DocumentationDatabaseVersion row, read as the app reads it, declares major 4 or later: the re-mint before training and before its backup, the verifier for either database. Each says so on one "error:" line naming the blocks step and the ship-time order: re-mint, verify, store_pages_in_blocks.py, then the pack with ContentBlocks. This is the plan's "re-mint before" variant. B02, which would measure whether a dictionary trained on the blocks stores less, has not run; if it pays, this refusal becomes recompress-and-verify code instead. Tests: ContentBlocksRefusalTest (majors 4 and 5 refused before training with nothing written; an older 4 under a newer 3 re-minted and verified; the verifier refusing either side) and two command-line cases (no backup; an error line and no "Byte-identical"). Each fails against the pair broken that way: no re-mint refusal, the threshold at 5, the greatest major read instead of the newest row, the refusal after training and backup, a message without the order, the verifier checking the re-minted file only, and no verifier refusal. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… by default - Five sets, in this order, each in path order and never mixed in a block: a/ templated JSON (not a/devsite/), j/ HTML, k/ templated JSON, then every Brotli row under a/devsite/ and under i/. Paths are matched with GLOB, case-sensitive as paths are, where LIKE is not. - The cap: an item over 1 MiB decoded stays a row. A block closes at --pages-per-block items, now 64 by default, or before the next item would take it past 4 MiB decoded, so two blocks fit the app's 8 MiB cache. Rows are decoded in the thread pool ahead of the grouping walk, a window at a time, and blocks are built a few ahead of the writes, so no set is held decoded all at once. - Before a row is emptied, its block is decoded back and each item's slice compared byte for byte with the row's decoded bytes. The sha256 pass over the stored blocks before the commit stays. - The summary shows every set with its blocked content types, the pages the cap kept (named, up to 10 a set) and the rows left for their continuation rows. - Docstring: the five sets, the cap, where the step goes, and G02's figures with them, replacing the prototype's 16/64 figures and its reference to a format file the repo does not have. On the 2026-10-20 G02 ship-time build (the prototype's input, sha256 c7de1d8a): 22,478 pages in 356 blocks, rows 34,834,228 -> blocks 24,608,732 bytes; packed with --pack Templates,Content,TooltipTexts,ContentBlocks, the .br went from 66,283,003 to 55,914,367 (10,368,636 smaller). The cap kept 18 pages (5 a/, 12 j/, 1 k/); the largest block decodes to 4,172,562 bytes. integrity_check and foreign_key_check were clean before and after the pack, and the pack's identity check passed. Tests, on the synthetic database: untemplated, non-HTML a/devsite/ and i/ rows blocked; an A/ path left out; nine blocks in set order. On a second database: an item one byte over 1 MiB kept, one exactly at it blocked; a block of exactly 4 MiB, and one closed at 3.5 MiB; 65 k/ pages making 64 + 1 through main()'s default; a codec garbling only the decode-back, and one garbling only the decode before the commit, each refusing with nothing written. Each fails against the script broken that way: three sets, LIKE, sets reordered, a/devsite/ or i/ narrowed, no item cap, the cap at instead of over, on stored size, or at 2 MiB, no block cap, at instead of past, or at 8 MiB, a default of 16 or none, no byte-for-byte check, a length-only check, and no sha256 pass. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Blocks are documentation version 4.0.0, a new major: an app without the reader would show every blocked page empty, and the app reads blocks by the declared major, never by whether the tables exist. 4.0.0 promises everything 3.0.0 does as well (keyed tooltips, compressed templates). - The step reads the major from the newest DocumentationDatabaseVersion row (ORDER BY changeTime DESC, rowid DESC, as the app and remint_dictionary.declared_major do) and refuses no version, a major below 3, or a major of 4 or more, writing nothing. The refusal of a database that already has the tables stays, and comes first. - In the same transaction as the blocks it replaces that table's rows with one, 4.0.0, who and comment naming ADFA-6683, as migrate_tooltip_schema.py declares 3.0.0. A dry run rolls it back with everything else. Tests: no version table, no version row, 2.x, 4.x and 5.x each refused with nothing written; the newest row deciding (an older changeTime on a later rowid, ties broken by rowid, an older 4 under a newer 3); one 4.0.0 row after a run and the old rows after a dry run. Each fails against the script broken that way: no gate, 4.x let through, no version let through, the newest row by rowid, the greatest major, no declaration, a declaration appended instead of replacing, and a dry run that declares. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…E.md python-tests.yaml lists its test files one by one, so without this line CI never runs scripts/test_store_pages_in_blocks.py. CLAUDE.md gets a "Schema 4.0.0: content blocks, in the shipped copy only" section beside the 3.0.0 one: the two tables, the rule that a blocked Content row is empty, the five sets and the cap, the ship-time order (the step after the last re-mint and its verify, before --pack Templates,Content,TooltipTexts,ContentBlocks), which tools refuse 4.x and which run before the step because they do not know blocks, and the release order. The re-mint paragraph says it refuses 4.x, and the repository tour lists the script. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ored-block check Review of B03, F02-F04, F08, F09: - The decode window is read when a set is decoded, not bound as a default argument, so a test can shrink it. New tests decode ten rows four at a time (the next window only once the walk reaches it), and block the synthetic database with windows of 2 and 5, so a/'s seven pages span windows and a block straddles two. - One page to a block with 1 and 2 workers, so a/'s seven blocks pass the 2 * workers built ahead of the writes: every block is written, in path order, with consecutive ids. - check_stored now requires each block as stored to decode to exactly its items' lengths added up. New tests: a decode-back one byte longer (only the decode-back length check sees it), a stored block one byte longer (only the new check sees it), a lost block whose rows were emptied all the same (only the item count sees it), and a blocked row left full (only the "still hold their content" check sees it). Each writes nothing. - --workers below 1 is refused on an error: line; -1 raised a ValueError traceback and 0 ran as 1. - A database declaring no version, or a major below 2, is no longer told to run migrate_tooltip_schema.py, which converts 2.x only and refuses them. A 2.x database still is. Mutants, each failing the tests: the first window only, the whole set in one window, the window as a default argument, the write dropped or written last-in-first-out under the write-ahead limit, no decode-back length check, no item count, no stored-block length check, no "still hold their content" check, --workers unchecked, 0 run as 1, and the migrate_tooltip_schema.py advice for no version or for 1.x. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…cord B02 Review of B03, F10, F01 and F06 (the re-mint's part): - remint_dictionary.py and verify_remint_dictionary.py refused a blocked database only by its declared major. A 4.x file whose version row a later step or a hand edit rewrote to 3 (or dropped) would be re-minted, leaving every block undecodable, and the verifier compares no blocks to notice. Both now also refuse a database holding ContentBlocks or ContentBlockItems, whatever it declares, writing nothing. - B02 has run since 670095f said it had not: re-minting after the blocks step, on the blocks, saved at most 30,880 bytes of .br (25,285 more on the mean of 4 seeds), well inside the docstring's 0.33 MB seed spread, so "re-mint before" stands. The docstring records the measurement, and that whole blocks as zstd samples lose 0.52 to 0.74 MB because zstd trains on a sample's first 128 KiB only. - The ship-time order in the refusal message ends with --page-size 4096, as the pack is run. Tests: the re-mint refuses a 3.x database with both tables, with ContentBlockItems alone, and a database with no version and ContentBlocks, before training and with nothing written; the verifier refuses either side holding ContentBlocks; the command line refuses a 3.x database with both tables, with no backup. The order check includes --page-size 4096. Mutants, each failing the tests: no table check, ContentBlocks only, the table check only with a version declared, the verifier checking the new file only, and the order without --page-size. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ader; record B02
Review of B03, F01 and F05-F07, F11:
- B02 has run: re-minting after the blocks step, on the blocks, saved
at most 30,880 bytes of .br (the mean of 4 seeds 25,285 larger),
against 0.33 MB of seed spread, so the re-mint stays before the step.
Both the 4.0.0 section and the re-mint paragraph say so, and the
re-mint paragraph says the pair also refuses the block tables.
- The other Content tools on a 4.x file: drop_unread_rows.py does read
the version (3 or more means compressed templates) and does not refuse
4. None of them sees a blocked page as empty: x'' is not a Brotli
stream ("corrupt input"), so each refuses, writes what it can read and
exits 1, or stops with a traceback, as listed. migrate_tooltip_schema.py
(nothing to do) and the master-only writers
migrate_content_to_dictionary_brotli.py, insert_optimized_media.py and
sync_kdoc_json_to_db.py (which would overwrite blocked k/ rows) are
added.
- Every ship-time order ends with its pack and --page-size 4096: the
3.0.0 section and the migrate_tooltip_schema.py entry with
--pack Templates,Content,TooltipTexts, as compress_docdb.yml runs it
(a 3.0.0 file has no ContentBlocks for the 4.0.0 pack to name), plus
the 4.0.0 pack with ContentBlocks; the store_pages_in_blocks.py entry
with the 4.0.0 pack.
- The reader is ADFA-6683's CodeOnTheGo PR to stage, not yet numbered
or merged, and a file declaring major 5 would be read as 4.
- python-tests.yaml: the comment no longer names the brotli CLI twice.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
On the copies I have (3.0.0 |
|
DocDB Studio and 4.0.0. The documentation team downloads the database from Google Drive, edits it in DocDB Studio, and pushes it back. Once the Drive copy is upgraded to 4.0.0, Studio has to change in these places (line numbers from this branch):
Once the gate allows 4, everything below still reads or writes
Not affected: the tooltip tables, tooltip CSV import/export, and Templates ( |
|
Workflows that use the Drive copy, once it is 4.0.0. The documentation team downloads the database from Drive, edits it, and pushes it back. If that copy is upgraded to 4.0.0, three workflows run these tools on it. Each was run on a 4.0.0 test copy built with this branch's
|
|
|
|
Other tools on a 4.0.0 database. Each was run on a 4.0.0 test copy built with this branch's Write part of their changes, then exit 1:
Stop without writing:
Read-only, or crash before writing:
Most of these run only on the ship-time copy, before the blocks step, so these results matter mainly if the Drive copy becomes 4.0.0 or the order slips. The partial writers are the ones to watch: they leave a half-changed database and only an exit code to show it. |
|
Thanks for going through this in this much detail. I checked each point against the branch head ( 1. 2. DocDB Studio. Everything you list is there as described: the gate at 3. Workflows on a 4.0.0 Drive copy. On the test database I called 4. 5. Other tools. Run on the test copy: What this means for the PR. Four things are in the text this PR wrote and look mine to fix here: the |
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vh2DAaJxvRXKRYkgiDqwGQ
…me writers run on blocks The ADFA-6806-tracked tools (sync_kdoc_json_to_db.py, insert_optimized_media.py, migrate_content_to_dictionary_brotli.py) are no longer said to write a master that never gets blocks. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vh2DAaJxvRXKRYkgiDqwGQ
…hat each does Run on a 4.0.0 test copy or read from the code, as marked; ADFA-6806, ADFA-6807, ADFA-6808. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vh2DAaJxvRXKRYkgiDqwGQ
…op, and refuses only on a blocked row Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vh2DAaJxvRXKRYkgiDqwGQ
|
Decision on the Drive master: it will become schema 4.0.0 and will not go back to 3.x. That settles the question in my last comment, and it changes two things:
The |
|
Review findings on the 4.0.0 Drive-master plan:
|
Independent review (
|
| ID | Where | Finding | Conclusion |
|---|---|---|---|
| F11 | store_pages_in_blocks.py:263 |
A zero-length stored Brotli row would abort the run: brotli -d -c on empty input exits 1 with "corrupt input" |
Not reachable. Brotli encoding an empty row gives one byte, which decodes to empty (checked), so a stored brotli row is never zero-length. Rows already emptied by this step are excluded by the version check |
| F12 | verify_remint_dictionary.py:150 |
verify() leaves both connections open when it raises |
True, and it never closes them on any path. Only matters when called from a library or test; the CLI exits |
| F13 | store_pages_in_blocks.py:205 |
declared_major copies the same query as remint_dictionary.py:197 and migrate_tooltip_schema.py:489 |
True. A cleanup |
| F14 | store_pages_in_blocks.py:270 |
Decodes queue behind block encodes in one pool | True, but workers stay busy, so no capacity is wasted. No change |
| F15 | store_pages_in_blocks.py:473 |
VACUUM after COMMIT is not handled if it fails |
True, but the database is already committed and valid, and the docstring says "Takes no backup: run it on a copy". Only the space is not reclaimed |
| F16 | several | The ship-time order is written out in about seven places | True: remint_dictionary.py (118, 186), store_pages_in_blocks.py:106, CLAUDE.md (85, 145, 383) |
Posted by Claude Code for David Schachter.
|
Thanks for these. An independent review at
The same review found two smaller things, now ADFA-6870:
Posted by Claude Code for David Schachter. |
| > bytes larger), against a 0.33 MB spread between re-mint seeds; `remint_dictionary.py`'s docstring | ||
| > has the figures. | ||
| > | ||
| > **The Google Drive master database the documentation team edits will become 4.0.0 and will never go |
There was a problem hiding this comment.
This seems to contradict the tools. The copy that ships is made from the Drive master, but store_pages_in_blocks.py accepts only major 3 with no block tables, and remint_dictionary.py / verify_remint_dictionary.py refuse major >= 4 or any block tables. Once the master is 4.0.0, the documented ship-time order (re-mint, verify, blocks, pack) can't run: the dictionary can never be re-minted again, and pages added or edited on the master afterwards stay unblocked rows. It also conflicts with the script docstring ("on the copy that ships only, never the master", store_pages_in_blocks.py:101). Which is the plan: master stays 3.x and only the shipped copy is blocked, or the step learns to re-block a 4.x file?
There was a problem hiding this comment.
Neither, quite: the master goes to 4.0.0, but not before ADFA-6871. The order needs a 3.x copy, so until then the master stays 3.x. ADFA-6871 adds a build step that first turns the copy's blocks back into 3.x rows. Re-mint, verify, blocks and pack then run as written, and pages added or edited on the master since get blocked too. ADFA-6872 teaches those three tools 4.x, after which that step can go. CLAUDE.md (bd39a9e) and the docstring (7a3d63b) now say this, and "never the master" is gone.
There was a problem hiding this comment.
A correction to my answer above: the plan I gave doesn't work, and 583987f fixes the docs. The order starts from a 2.x copy, not a 3.x one. The Kotlin trim and the a/ pages-to-templates step refuse a file declaring 3 or later (trim_kotlin_json.py:427-432, android_pages_to_templates.py:848-854), and migrate_tooltip_schema.py converts only 2.x. The master is 2.x today, as CLAUDE.md:101 and :287 already said. Nothing converts 3.0 back to 2.x, so turning a 4.x copy's blocks back into 3.x rows (ADFA-6871's approach A, which I also gave in my reply of 10-30) would still stop at the trim.
CLAUDE.md and the docstring now say the master is 2.x and that ADFA-6871 has to settle how a 4.x master is built into a copy that ships. The release order also waits on ADFA-6871. ADFA-6871 now lists the options and leaves the choice open. An independent review of bd39a9e caught this, confirmed with dry runs on a copy of the shipped database.
| > upgrade on every tool below that does not refuse 4.x runs on a blocked database. None of them may be | ||
| > left as it is when the Drive copy is upgraded: each must be fixed or guarded first, as ADFA-6806 (the | ||
| > docs pipeline scripts), ADFA-6807 (DocDB Studio) and ADFA-6808 (check-tools) track. | ||
| > |
There was a problem hiding this comment.
The list below has about 20 tools that crash, write part of their work, or silently give wrong answers on a 4.0.0 file (sync_kdoc_json_to_db.py corrupts it), and several of the ones that "refuse" do so only because x'' doesn't decode, not because they check. Since most of them already import optimize_db_media, one shared "refuse major >= 4" helper there, called at startup, would guard them all at once instead of tool by tool, and would make ADFA-6806 a lot smaller. Worth doing in this PR, given the master is meant to go to 4.0.0?
There was a problem hiding this comment.
I'd keep it in ADFA-6806 rather than this PR. The Drive copy isn't upgraded until ADFA-6806, 6807, 6808 and 6871 are done, so no tool here sees a 4.x file before the guard exists. The guard also changes about 20 tools that this PR otherwise leaves alone. ADFA-6806 already plans one shared check in optimize_db_media.py, credited to you, and it can call the declared_major that 7a3d63b put there.
…pair is refused verify() raised its 4.x refusal with both read-only connections still open, so each refused pair left two handles for the collector (and, on Windows, two files that cannot be deleted). Both now close however it returns. A new test fails without the change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vh2DAYjFcUx64N8PMRknzL
…as built, and leave an empty row as it is
- The check before the commit counted ContentBlockItems rows but sliced only the blocks it found,
so a block lost with its items left behind passed, and those pages would ship empty. It now
requires ContentBlocks to hold exactly the blocks written (by sha256) and ContentBlockItems
exactly the items written (block, offset, length). That also ends the second q11 decode of
every block, and blocks are read from the cursor rather than all at once: build_block has
already decoded each block back and matched every item.
- An empty Brotli row stopped the whole run ("corrupt input"). It is now left as a row and
counted in the summary.
- The codec is optimize_db_media.BrotliCodec, which now passes -q 11 -w 24 explicitly (the
CLI's defaults for stdin: the same bytes). declared_major and table_exists move to
optimize_db_media, and declare_version is migrate_tooltip_schema's, now taking the version.
- The docstring says the Drive master stays 3.x until ADFA-6871, as CLAUDE.md now does.
Tests: a lost block with its items kept, a changed block, a moved item, an empty row, and each
block decoded once; the first four fail without the change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vh2DAYjFcUx64N8PMRknzL
…e block reader is CodeOnTheGo #2191 The ship-time order needs a 3.x copy, and the re-mint, its verify and the blocks step each refuse 4.x, so the master is upgraded only once ADFA-6871 turns a 4.x copy's blocks back into rows at build time. ADFA-6872 later teaches those tools 4.x. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vh2DAYjFcUx64N8PMRknzL
…annot start from a 4.x one bd39a9e said the master is 3.x and that once ADFA-6871 turns a 4.x copy's blocks back into 3.x rows the order runs as written. Both are wrong: the Kotlin trim and the a/ pages-to-templates step refuse 3 or later, migrate_tooltip_schema.py converts only 2.x, and nothing converts 3.0 back to 2.x. So the order starts from a 2.x copy, as the master is today, and ADFA-6871 has to settle how a 4.x master is built into a copy that ships. The release order now waits on ADFA-6871 too. The test docstring for the check before the commit no longer says every damage leaves the blocks decoding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vh2DAYjFcUx64N8PMRknzL
Jira: ADFA-6683
Why
The documentation database stores every page as its own Brotli row, so the dictionary can't share what neighbouring pages have in common. Storing pages in compressed blocks (64 pages per block, one Brotli stream against the shared dictionary) cuts the
.brdownload by about 10 MB. This is the ship-time step that makes it, and it declares a new major, 4.0.0, because an app without the reader would show a blocked page as empty. The reader is CodeOnTheGo #2191 (basestage); a 4.0.0 database must not go to dev-assets or Drive before that PR is merged, and the Drive master is upgraded to 4.0.0 only after ADFA-6871 (how a 4.x master is built into a copy that ships: the ship-time order starts from a 2.x copy, as the master is today), ADFA-6806 (docs pipeline scripts refuse or handle 4.0.0), ADFA-6807 (DocDB Studio opens and edits a 4.0.0 database) and ADFA-6808 (check-tools tooltip-table failures on 3.x, blocked pages flagged empty) are done.Change
scripts/store_pages_in_blocks.py: five sets, each inORDER BY path COLLATE BINARY, never mixed in a block:a/templated (nota/devsite/),j/text/html,k/templated, every Brotli row undera/devsite/, every Brotli row underi/. Case-sensitive GLOB.ContentBlocksmust hold exactly the blocks written (by sha256) andContentBlockItemsexactly the items written (block, offset, length); no block is decoded twice. An empty Brotli row stays a row.optimize_db_media.py:declared_majorandtable_exists(moved from this script), andBrotliCodec, which this script now uses, passes-q 11 -w 24explicitly (the CLI's defaults for stdin, so the same bytes).migrate_tooltip_schema.declare_versiontakes the version to declare, and this script reuses it.remint_dictionary.pyandverify_remint_dictionary.pyrefuse a 4.x file, and any file that has the block tables; the verifier closes both databases when it does. B02 measured re-minting on blocks: it saves at most 30,880 B of.br, against 0.33 MB of seed noise and a 25,285 B worse 4-seed mean, so the order stays re-mint, verify, blocks, pack.CLAUDE.md: a "Schema 4.0.0" section (tables, ship-time order,--pack Templates,Content,TooltipTexts,ContentBlocks --page-size 4096, which tools refuse 4.x, release order).python-tests.yamlruns the new test file.Measured
On the 2026-10-20 ship-time build the packed
.brgoes from 66,283,003 to 55,914,367 bytes (10,368,636 smaller): 22,478 pages in 356 blocks, 18 kept as rows by the cap, largest block 4,172,562 bytes decoded and 559,581 bytes compressed.integrity_check,foreign_key_checkand the pack's identity check are clean. On the shipped 3.0.0 database (B02) the saving is 10,231,079 bytes. On a Pixel 6 Pro, 12 of 13 sampled pages were byte-identical to the 3.x layout.Tests
scripts/test_store_pages_in_blocks.py: each set (including unTemplateda/devsite/andi/rows), the cap, the 4 MiB block close, the default of 64, the refusals (no version, 2.x, 4.x) with nothing written, 4.0.0 declared after a run and not after a dry run, a byte mismatch that refuses and writes nothing, more than one decode window, the write-ahead path, and everycheck_storedcheck (a block lost with or without its items, a changed block, a moved item, a row not emptied), an empty row left a row, and each block decoded once.test_remint_dictionary.py: the refusals on the function and the command line, and both connections closed after a refusal.aa4f43f, four fail on the code before it: the block lost with its items kept (the run committed), the empty row (the run stopped), the verifier's connections (both left open) and the single decode.origin/main). The tests of the other scripts that useBrotliCodecpass (450).Not covered
sync_kdoc_json_to_db.pycan silently corrupt a blocked database,insert_optimized_media.pycrashes on the first blocked row, andmigrate_content_to_dictionary_brotli.pyexits 1 with an error per blocked row. Fixing or guarding them is ADFA-6806; DocDB Studio is ADFA-6807; check-tools is ADFA-6808.CLAUDE.mdlists what each tool does on a blocked database. The Drive master is not upgraded before those and ADFA-6871 are done.🤖 Generated with Claude Code