From 44df465c5e6e99c1a98916fa6cbcbde01a666164 Mon Sep 17 00:00:00 2001 From: clover caruso Date: Tue, 8 Sep 2026 21:17:46 -0700 Subject: [PATCH] feat: create pages in existing sections atomically Retain page identities and publish the new space with section navigation in one transaction. Cover empty and duplicate Unicode titles, native edits and Rust follow-ups, 521-page checkpoint/counter stress, historical asset preservation, and interrupted publication. Validation: 137 core, 99 offline and 150 Python tests; 29,328 ASan executions; workspace Clippy/formatting; both iOS targets compile. Five native clones removed. Acceptance: evidence/m10/PAGE-CREATION.md. Assisted-by: gpt-6-astra --- corpus/page-lifecycle/creation/README.md | 37 ++ .../creation/candidate/Lifecycle.one | Bin 0 -> 60368 bytes .../creation/cold/notebook/Lifecycle.one | Bin 0 -> 60368 bytes .../creation/cold/read/hierarchy.xml | 2 + .../creation/cold/read/page-000.xml | 4 + .../creation/cold/read/page-001.xml | 2 + .../creation/cold/read/page-002.xml | 2 + .../creation/cold/read/page-003.xml | 2 + .../creation/cold/read/page-004.xml | 2 + .../creation/cold/read/page-005.xml | 2 + .../creation/cold/read/page-006.xml | 4 + .../creation/cold/read/page-007.xml | 4 + .../creation/cold/read/page-008.xml | 2 + .../creation/cold/read/page-009.xml | 4 + .../creation/cold/read/page-010.xml | 4 + .../creation/cold/read/page-011.xml | 2 + .../creation/cold/read/page-012.xml | 4 + .../creation/followup/Lifecycle.one | Bin 0 -> 72088 bytes .../followup/cold/notebook/Lifecycle.one | Bin 0 -> 72088 bytes .../creation/followup/cold/read/hierarchy.xml | 2 + .../creation/followup/cold/read/page-000.xml | 4 + .../creation/followup/cold/read/page-001.xml | 2 + .../creation/followup/cold/read/page-002.xml | 2 + .../creation/followup/cold/read/page-003.xml | 2 + .../creation/followup/cold/read/page-004.xml | 2 + .../creation/followup/cold/read/page-005.xml | 2 + .../creation/followup/cold/read/page-006.xml | 4 + .../creation/followup/cold/read/page-007.xml | 4 + .../creation/followup/cold/read/page-008.xml | 2 + .../creation/followup/cold/read/page-009.xml | 4 + .../creation/followup/cold/read/page-010.xml | 4 + .../creation/followup/cold/read/page-011.xml | 2 + .../creation/followup/cold/read/page-012.xml | 4 + .../native/cold/notebook/Lifecycle.one | 1 + .../creation/native/cold/read/hierarchy.xml | 2 + .../creation/native/cold/read/page-000.xml | 2 + .../creation/native/cold/read/page-001.xml | 4 + .../creation/native/cold/read/page-002.xml | 4 + .../creation/native/cold/read/page-003.xml | 2 + .../creation/native/cold/read/page-004.xml | 4 + .../creation/native/cold/read/page-005.xml | 2 + .../creation/native/cold/read/page-006.xml | 2 + .../creation/native/cold/read/page-007.xml | 2 + .../creation/native/cold/read/page-008.xml | 2 + .../creation/native/cold/read/page-009.xml | 2 + .../creation/native/cold/read/page-010.xml | 4 + .../creation/native/cold/read/page-011.xml | 4 + .../creation/native/cold/read/page-012.xml | 4 + .../creation/native/notebook/Lifecycle.one | Bin 0 -> 69000 bytes .../creation/native/read/hierarchy.xml | 2 + .../creation/native/read/page-000.xml | 4 + .../creation/native/read/page-001.xml | 2 + .../creation/native/read/page-002.xml | 4 + .../creation/native/read/page-003.xml | 4 + .../creation/native/read/page-004.xml | 2 + .../creation/native/read/page-005.xml | 2 + .../creation/native/read/page-006.xml | 4 + .../creation/native/read/page-007.xml | 4 + .../creation/native/read/page-008.xml | 2 + .../creation/native/read/page-009.xml | 2 + .../creation/native/read/page-010.xml | 2 + .../creation/native/read/page-011.xml | 4 + .../creation/native/read/page-012.xml | 2 + .../page-lifecycle/creation/provenance.json | 115 ++++++ crates/onestore/README.md | 8 + crates/onestore/src/commit.rs | 8 + crates/onestore/src/lib.rs | 2 + crates/onestore/src/page.rs | 260 +++++++++++++ crates/onestore/src/write.rs | 228 +++++++---- crates/onestore/src/write/tests/multi.rs | 14 +- crates/onestore/tests/page.rs | 356 ++++++++++++++++++ fuzz/Cargo.toml | 7 + fuzz/fuzz_targets/page.rs | 138 +++++++ tools/native/created-pages.ps1 | 35 ++ tools/test_page_creation.py | 48 +++ tools/verify_page_creation.py | 73 ++++ 76 files changed, 1417 insertions(+), 73 deletions(-) create mode 100644 corpus/page-lifecycle/creation/README.md create mode 100644 corpus/page-lifecycle/creation/candidate/Lifecycle.one create mode 100644 corpus/page-lifecycle/creation/cold/notebook/Lifecycle.one create mode 100644 corpus/page-lifecycle/creation/cold/read/hierarchy.xml create mode 100644 corpus/page-lifecycle/creation/cold/read/page-000.xml create mode 100644 corpus/page-lifecycle/creation/cold/read/page-001.xml create mode 100644 corpus/page-lifecycle/creation/cold/read/page-002.xml create mode 100644 corpus/page-lifecycle/creation/cold/read/page-003.xml create mode 100644 corpus/page-lifecycle/creation/cold/read/page-004.xml create mode 100644 corpus/page-lifecycle/creation/cold/read/page-005.xml create mode 100644 corpus/page-lifecycle/creation/cold/read/page-006.xml create mode 100644 corpus/page-lifecycle/creation/cold/read/page-007.xml create mode 100644 corpus/page-lifecycle/creation/cold/read/page-008.xml create mode 100644 corpus/page-lifecycle/creation/cold/read/page-009.xml create mode 100644 corpus/page-lifecycle/creation/cold/read/page-010.xml create mode 100644 corpus/page-lifecycle/creation/cold/read/page-011.xml create mode 100644 corpus/page-lifecycle/creation/cold/read/page-012.xml create mode 100644 corpus/page-lifecycle/creation/followup/Lifecycle.one create mode 100644 corpus/page-lifecycle/creation/followup/cold/notebook/Lifecycle.one create mode 100644 corpus/page-lifecycle/creation/followup/cold/read/hierarchy.xml create mode 100644 corpus/page-lifecycle/creation/followup/cold/read/page-000.xml create mode 100644 corpus/page-lifecycle/creation/followup/cold/read/page-001.xml create mode 100644 corpus/page-lifecycle/creation/followup/cold/read/page-002.xml create mode 100644 corpus/page-lifecycle/creation/followup/cold/read/page-003.xml create mode 100644 corpus/page-lifecycle/creation/followup/cold/read/page-004.xml create mode 100644 corpus/page-lifecycle/creation/followup/cold/read/page-005.xml create mode 100644 corpus/page-lifecycle/creation/followup/cold/read/page-006.xml create mode 100644 corpus/page-lifecycle/creation/followup/cold/read/page-007.xml create mode 100644 corpus/page-lifecycle/creation/followup/cold/read/page-008.xml create mode 100644 corpus/page-lifecycle/creation/followup/cold/read/page-009.xml create mode 100644 corpus/page-lifecycle/creation/followup/cold/read/page-010.xml create mode 100644 corpus/page-lifecycle/creation/followup/cold/read/page-011.xml create mode 100644 corpus/page-lifecycle/creation/followup/cold/read/page-012.xml create mode 120000 corpus/page-lifecycle/creation/native/cold/notebook/Lifecycle.one create mode 100644 corpus/page-lifecycle/creation/native/cold/read/hierarchy.xml create mode 100644 corpus/page-lifecycle/creation/native/cold/read/page-000.xml create mode 100644 corpus/page-lifecycle/creation/native/cold/read/page-001.xml create mode 100644 corpus/page-lifecycle/creation/native/cold/read/page-002.xml create mode 100644 corpus/page-lifecycle/creation/native/cold/read/page-003.xml create mode 100644 corpus/page-lifecycle/creation/native/cold/read/page-004.xml create mode 100644 corpus/page-lifecycle/creation/native/cold/read/page-005.xml create mode 100644 corpus/page-lifecycle/creation/native/cold/read/page-006.xml create mode 100644 corpus/page-lifecycle/creation/native/cold/read/page-007.xml create mode 100644 corpus/page-lifecycle/creation/native/cold/read/page-008.xml create mode 100644 corpus/page-lifecycle/creation/native/cold/read/page-009.xml create mode 100644 corpus/page-lifecycle/creation/native/cold/read/page-010.xml create mode 100644 corpus/page-lifecycle/creation/native/cold/read/page-011.xml create mode 100644 corpus/page-lifecycle/creation/native/cold/read/page-012.xml create mode 100644 corpus/page-lifecycle/creation/native/notebook/Lifecycle.one create mode 100644 corpus/page-lifecycle/creation/native/read/hierarchy.xml create mode 100644 corpus/page-lifecycle/creation/native/read/page-000.xml create mode 100644 corpus/page-lifecycle/creation/native/read/page-001.xml create mode 100644 corpus/page-lifecycle/creation/native/read/page-002.xml create mode 100644 corpus/page-lifecycle/creation/native/read/page-003.xml create mode 100644 corpus/page-lifecycle/creation/native/read/page-004.xml create mode 100644 corpus/page-lifecycle/creation/native/read/page-005.xml create mode 100644 corpus/page-lifecycle/creation/native/read/page-006.xml create mode 100644 corpus/page-lifecycle/creation/native/read/page-007.xml create mode 100644 corpus/page-lifecycle/creation/native/read/page-008.xml create mode 100644 corpus/page-lifecycle/creation/native/read/page-009.xml create mode 100644 corpus/page-lifecycle/creation/native/read/page-010.xml create mode 100644 corpus/page-lifecycle/creation/native/read/page-011.xml create mode 100644 corpus/page-lifecycle/creation/native/read/page-012.xml create mode 100644 corpus/page-lifecycle/creation/provenance.json create mode 100644 crates/onestore/src/page.rs create mode 100644 crates/onestore/tests/page.rs create mode 100644 fuzz/fuzz_targets/page.rs create mode 100644 tools/native/created-pages.ps1 create mode 100644 tools/test_page_creation.py create mode 100644 tools/verify_page_creation.py diff --git a/corpus/page-lifecycle/creation/README.md b/corpus/page-lifecycle/creation/README.md new file mode 100644 index 0000000000000000000000000000000000000000..fa19f5425ee7f7d144d312df11408a4f36baec8b --- /dev/null +++ b/corpus/page-lifecycle/creation/README.md @@ -0,0 +1,37 @@ +# Page creation + +`candidate` adds four pages to `../03-renamed/notebook/Lifecycle.one`: an appended +page without a title node, a prepended page with an empty title field, and two +appended pages titled `New 🦋 é`. All thirteen ordered page identities survive +each fresh OneNote 2010 reopen. Identical artifacts are relative symlinks. + +`cold` captures the first reopen. `native` adds a distinct body paragraph to each +duplicate-title page through `tools/native/created-pages.ps1`; `native/cold` +reopens that result in another cache. `followup` prefixes those bodies with +`Rust + ` and the titles with `Reviewed `; `followup/cold` reopens that result. +The retained `provenance.json` records the native capture inputs and clone removal. + +After building workspace examples, run `python -m unittest discover -s tools +-p test_page_creation.py -v`. It compares native XML content and character formats, +ordered identities, and complete active object graphs across each cold reopen. +These thirteen-page comparisons need no graph normalization. + +Export fresh Rust candidates by setting each variable to a new output directory: + +| Core integration test (`cargo test -p onestore --test page TEST -- --exact`) | Variable | +| --- | --- | +| `native_section_accepts_empty_and_titled_pages_with_preserved_history` | `ONESTORE_PAGE_CREATION_OUTPUT` | +| `native_changes_on_created_pages_accept_rust_followups` | `ONESTORE_PAGE_FOLLOWUP_OUTPUT` | +| `repeated_page_creation_crosses_root_fragments_counters_and_section_checkpoints` | `ONESTORE_PAGE_STRESS_OUTPUT` | + +The last test produces 521 pages, crosses a section revision checkpoint and +transaction-counter rollover, and checks that every unpublished candidate still +exposes the complete preceding page order. Its large native capture is local +evidence. Reproduce with `tools/native_runner.py INPUT OUTPUT --expected-pages 521 +--collect-notebook`, then `PYTHONPATH=tools python tools/verify_page_creation.py +INPUT OUTPUT`. That comparison permits only exact native materialization of a +previously absent section metadata copy; all other graph fields compare exactly. + +This core operation deliberately creates one optional title outline without +generated date/time text or template content. MS-ONE 2.2.50 permits a title with +one or two outlines. The fixture establishes this explicit empty-page contract. diff --git a/corpus/page-lifecycle/creation/candidate/Lifecycle.one b/corpus/page-lifecycle/creation/candidate/Lifecycle.one new file mode 100644 index 0000000000000000000000000000000000000000..3dac230dfcad0e261e9327e945c79ed84af025b7 GIT binary patch literal 60368 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 Rd$X2oPsY-@TG?vl{2%i`gDU_4 literal 0 HcmV?d00001 diff --git a/corpus/page-lifecycle/creation/cold/notebook/Lifecycle.one b/corpus/page-lifecycle/creation/cold/notebook/Lifecycle.one new file mode 100644 index 0000000000000000000000000000000000000000..776a303621a5dc0fe041cfb0f6bcfb29784f5212 GIT binary patch literal 60368 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 PTCzPEP3LN5tCjPAPs4rA literal 0 HcmV?d00001 diff --git a/corpus/page-lifecycle/creation/cold/read/hierarchy.xml b/corpus/page-lifecycle/creation/cold/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..5f5f4541a71288ce6f039105b1bef2b6cc92bbce --- /dev/null +++ b/corpus/page-lifecycle/creation/cold/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/cold/read/page-000.xml b/corpus/page-lifecycle/creation/cold/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..b74db02740bf5f6664bec471f6ceb4edfa69e947 --- /dev/null +++ b/corpus/page-lifecycle/creation/cold/read/page-000.xml @@ -0,0 +1,4 @@ + +Body child 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/cold/read/page-001.xml b/corpus/page-lifecycle/creation/cold/read/page-001.xml new file mode 100644 index 0000000000000000000000000000000000000000..62991791c86033a1286a58fba2ca36558e3150e6 --- /dev/null +++ b/corpus/page-lifecycle/creation/cold/read/page-001.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/cold/read/page-002.xml b/corpus/page-lifecycle/creation/cold/read/page-002.xml new file mode 100644 index 0000000000000000000000000000000000000000..a4888c4c3a60c26e6a1e7527a818fa76c0000738 --- /dev/null +++ b/corpus/page-lifecycle/creation/cold/read/page-002.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/cold/read/page-003.xml b/corpus/page-lifecycle/creation/cold/read/page-003.xml new file mode 100644 index 0000000000000000000000000000000000000000..0a4fa38b36604ea31858926fc28563e3d48fce5e --- /dev/null +++ b/corpus/page-lifecycle/creation/cold/read/page-003.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/cold/read/page-004.xml b/corpus/page-lifecycle/creation/cold/read/page-004.xml new file mode 100644 index 0000000000000000000000000000000000000000..36b3f48ded7416caa1a1a3493c5ce738d378dd59 --- /dev/null +++ b/corpus/page-lifecycle/creation/cold/read/page-004.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/cold/read/page-005.xml b/corpus/page-lifecycle/creation/cold/read/page-005.xml new file mode 100644 index 0000000000000000000000000000000000000000..2841ac8de01c2941a57d6a5781785ce87c58f233 --- /dev/null +++ b/corpus/page-lifecycle/creation/cold/read/page-005.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/cold/read/page-006.xml b/corpus/page-lifecycle/creation/cold/read/page-006.xml new file mode 100644 index 0000000000000000000000000000000000000000..9e25e11773ae0c3c673cf196ba3c8d353f9cf201 --- /dev/null +++ b/corpus/page-lifecycle/creation/cold/read/page-006.xml @@ -0,0 +1,4 @@ + +Body grandchild 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/cold/read/page-007.xml b/corpus/page-lifecycle/creation/cold/read/page-007.xml new file mode 100644 index 0000000000000000000000000000000000000000..d4d158edf3e7f04a47d7e6fe30985c6e43b4eba8 --- /dev/null +++ b/corpus/page-lifecycle/creation/cold/read/page-007.xml @@ -0,0 +1,4 @@ + +Body parent 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/cold/read/page-008.xml b/corpus/page-lifecycle/creation/cold/read/page-008.xml new file mode 100644 index 0000000000000000000000000000000000000000..8f25f7fb29c1de845d267ae0a7432186062da69a --- /dev/null +++ b/corpus/page-lifecycle/creation/cold/read/page-008.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/cold/read/page-009.xml b/corpus/page-lifecycle/creation/cold/read/page-009.xml new file mode 100644 index 0000000000000000000000000000000000000000..9bb9dde27336654424657d5c9e644849d564507d --- /dev/null +++ b/corpus/page-lifecycle/creation/cold/read/page-009.xml @@ -0,0 +1,4 @@ + +Body duplicate 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/cold/read/page-010.xml b/corpus/page-lifecycle/creation/cold/read/page-010.xml new file mode 100644 index 0000000000000000000000000000000000000000..265b775930e7d1769f636d1662b677a553696018 --- /dev/null +++ b/corpus/page-lifecycle/creation/cold/read/page-010.xml @@ -0,0 +1,4 @@ + +Body automatic 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/cold/read/page-011.xml b/corpus/page-lifecycle/creation/cold/read/page-011.xml new file mode 100644 index 0000000000000000000000000000000000000000..7c03f6ae8fca64df7719f2b108cb98ceb5ba8e83 --- /dev/null +++ b/corpus/page-lifecycle/creation/cold/read/page-011.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/cold/read/page-012.xml b/corpus/page-lifecycle/creation/cold/read/page-012.xml new file mode 100644 index 0000000000000000000000000000000000000000..9890c342b59f523cac95cb9193722bad8b6ca62e --- /dev/null +++ b/corpus/page-lifecycle/creation/cold/read/page-012.xml @@ -0,0 +1,4 @@ + +Body trailing 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/followup/Lifecycle.one b/corpus/page-lifecycle/creation/followup/Lifecycle.one new file mode 100644 index 0000000000000000000000000000000000000000..b6f78fe9a2da590e540d55a842742eb36fe7d173 GIT binary patch literal 72088 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 rCr-URZeqKpd+zQ!F{xe2WZ~-M(-(HU- literal 0 HcmV?d00001 diff --git a/corpus/page-lifecycle/creation/followup/cold/read/hierarchy.xml b/corpus/page-lifecycle/creation/followup/cold/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..f7d9ebde7686be747df76426cc89501e37dcee14 --- /dev/null +++ b/corpus/page-lifecycle/creation/followup/cold/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/followup/cold/read/page-000.xml b/corpus/page-lifecycle/creation/followup/cold/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..4742bf7a49b14bfeaec07003c7b4f461063d324a --- /dev/null +++ b/corpus/page-lifecycle/creation/followup/cold/read/page-000.xml @@ -0,0 +1,4 @@ + +Body child 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/followup/cold/read/page-001.xml b/corpus/page-lifecycle/creation/followup/cold/read/page-001.xml new file mode 100644 index 0000000000000000000000000000000000000000..a2161a614bca5619a0c11e013b97c1610daeb6c0 --- /dev/null +++ b/corpus/page-lifecycle/creation/followup/cold/read/page-001.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/followup/cold/read/page-002.xml b/corpus/page-lifecycle/creation/followup/cold/read/page-002.xml new file mode 100644 index 0000000000000000000000000000000000000000..8ae4e38d376aafaf3afe54f4fc83bcb156f694ef --- /dev/null +++ b/corpus/page-lifecycle/creation/followup/cold/read/page-002.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/followup/cold/read/page-003.xml b/corpus/page-lifecycle/creation/followup/cold/read/page-003.xml new file mode 100644 index 0000000000000000000000000000000000000000..8068f394f7b8bfa45dc97905c4c1f3e69ce07354 --- /dev/null +++ b/corpus/page-lifecycle/creation/followup/cold/read/page-003.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/followup/cold/read/page-004.xml b/corpus/page-lifecycle/creation/followup/cold/read/page-004.xml new file mode 100644 index 0000000000000000000000000000000000000000..9fa30270c14770a10545f36ef6b13e509c2bb1e0 --- /dev/null +++ b/corpus/page-lifecycle/creation/followup/cold/read/page-004.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/followup/cold/read/page-005.xml b/corpus/page-lifecycle/creation/followup/cold/read/page-005.xml new file mode 100644 index 0000000000000000000000000000000000000000..0edd9bcab1194c3d9d1e8e688c7975b2beebd093 --- /dev/null +++ b/corpus/page-lifecycle/creation/followup/cold/read/page-005.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/followup/cold/read/page-006.xml b/corpus/page-lifecycle/creation/followup/cold/read/page-006.xml new file mode 100644 index 0000000000000000000000000000000000000000..4b64cee072f1fd557c142d26e335f3d8307cd2f1 --- /dev/null +++ b/corpus/page-lifecycle/creation/followup/cold/read/page-006.xml @@ -0,0 +1,4 @@ + +Body parent 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/followup/cold/read/page-007.xml b/corpus/page-lifecycle/creation/followup/cold/read/page-007.xml new file mode 100644 index 0000000000000000000000000000000000000000..d4fad8b51dc23795d32144dea512a0308d6bb8f1 --- /dev/null +++ b/corpus/page-lifecycle/creation/followup/cold/read/page-007.xml @@ -0,0 +1,4 @@ + +Body grandchild 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/followup/cold/read/page-008.xml b/corpus/page-lifecycle/creation/followup/cold/read/page-008.xml new file mode 100644 index 0000000000000000000000000000000000000000..d26253e67ff7df8de34f3f04b9c19fd71b09ee97 --- /dev/null +++ b/corpus/page-lifecycle/creation/followup/cold/read/page-008.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/followup/cold/read/page-009.xml b/corpus/page-lifecycle/creation/followup/cold/read/page-009.xml new file mode 100644 index 0000000000000000000000000000000000000000..5457a4e122714c0a67224ed80bc9ec1a7aba3b4f --- /dev/null +++ b/corpus/page-lifecycle/creation/followup/cold/read/page-009.xml @@ -0,0 +1,4 @@ + +Body duplicate 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/followup/cold/read/page-010.xml b/corpus/page-lifecycle/creation/followup/cold/read/page-010.xml new file mode 100644 index 0000000000000000000000000000000000000000..e756008ceb91813adc6f64b434c7f1a84ac5bf4a --- /dev/null +++ b/corpus/page-lifecycle/creation/followup/cold/read/page-010.xml @@ -0,0 +1,4 @@ + +Body automatic 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/followup/cold/read/page-011.xml b/corpus/page-lifecycle/creation/followup/cold/read/page-011.xml new file mode 100644 index 0000000000000000000000000000000000000000..42ea81a0966b70ee621212a3552ae3ae90be0989 --- /dev/null +++ b/corpus/page-lifecycle/creation/followup/cold/read/page-011.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/followup/cold/read/page-012.xml b/corpus/page-lifecycle/creation/followup/cold/read/page-012.xml new file mode 100644 index 0000000000000000000000000000000000000000..9abc381b9155abd5962d1a4f8c41d4d78862067d --- /dev/null +++ b/corpus/page-lifecycle/creation/followup/cold/read/page-012.xml @@ -0,0 +1,4 @@ + +Body trailing 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/native/cold/notebook/Lifecycle.one b/corpus/page-lifecycle/creation/native/cold/notebook/Lifecycle.one new file mode 120000 index 0000000000000000000000000000000000000000..0c82f1045162a6bde2dc5f5fe0881c0130aca14d --- /dev/null +++ b/corpus/page-lifecycle/creation/native/cold/notebook/Lifecycle.one @@ -0,0 +1 @@ +../../notebook/Lifecycle.one \ No newline at end of file diff --git a/corpus/page-lifecycle/creation/native/cold/read/hierarchy.xml b/corpus/page-lifecycle/creation/native/cold/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..4836e778e6af3fff3797afe9cad4ffcee75bb427 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/cold/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/native/cold/read/page-000.xml b/corpus/page-lifecycle/creation/native/cold/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..30e03f8c5d7c32095c10ab54d530f2834febf2c7 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/cold/read/page-000.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/native/cold/read/page-001.xml b/corpus/page-lifecycle/creation/native/cold/read/page-001.xml new file mode 100644 index 0000000000000000000000000000000000000000..686a57f9a0006bf55f61dffc6ef0b48bd1ff2c31 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/cold/read/page-001.xml @@ -0,0 +1,4 @@ + +Body grandchild 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/native/cold/read/page-002.xml b/corpus/page-lifecycle/creation/native/cold/read/page-002.xml new file mode 100644 index 0000000000000000000000000000000000000000..bf6f1f3285ced61f86352de7d9720ce3902b66a5 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/cold/read/page-002.xml @@ -0,0 +1,4 @@ + +Body parent 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/native/cold/read/page-003.xml b/corpus/page-lifecycle/creation/native/cold/read/page-003.xml new file mode 100644 index 0000000000000000000000000000000000000000..b4a0399dc12d1b401c358ef95898a6f7e7e9dd02 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/cold/read/page-003.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/native/cold/read/page-004.xml b/corpus/page-lifecycle/creation/native/cold/read/page-004.xml new file mode 100644 index 0000000000000000000000000000000000000000..bdde6f6c2dafbe40a1af2fdc132ae2ad526a51b0 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/cold/read/page-004.xml @@ -0,0 +1,4 @@ + +Body child 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/native/cold/read/page-005.xml b/corpus/page-lifecycle/creation/native/cold/read/page-005.xml new file mode 100644 index 0000000000000000000000000000000000000000..556edf47723978c3db8c7ed78d6d841ad73fdde9 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/cold/read/page-005.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/native/cold/read/page-006.xml b/corpus/page-lifecycle/creation/native/cold/read/page-006.xml new file mode 100644 index 0000000000000000000000000000000000000000..a7314ac9046523033e145cb67980fbb75e4b4cd1 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/cold/read/page-006.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/native/cold/read/page-007.xml b/corpus/page-lifecycle/creation/native/cold/read/page-007.xml new file mode 100644 index 0000000000000000000000000000000000000000..1af4f16e7bd0db14f4ba7f8d1679d336822795b3 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/cold/read/page-007.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/native/cold/read/page-008.xml b/corpus/page-lifecycle/creation/native/cold/read/page-008.xml new file mode 100644 index 0000000000000000000000000000000000000000..7464cc7dc7fea87e99960b6ad14fb2cceff20be4 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/cold/read/page-008.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/native/cold/read/page-009.xml b/corpus/page-lifecycle/creation/native/cold/read/page-009.xml new file mode 100644 index 0000000000000000000000000000000000000000..c8d4a0a8ed5b1ad9a362afce806bc09ed4008621 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/cold/read/page-009.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/native/cold/read/page-010.xml b/corpus/page-lifecycle/creation/native/cold/read/page-010.xml new file mode 100644 index 0000000000000000000000000000000000000000..2ac049eeed39fea44ae2da23e392b7591f1b06b7 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/cold/read/page-010.xml @@ -0,0 +1,4 @@ + +Body trailing 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/native/cold/read/page-011.xml b/corpus/page-lifecycle/creation/native/cold/read/page-011.xml new file mode 100644 index 0000000000000000000000000000000000000000..43386377e0063d58ee24dd56f0f58a8a856daccd --- /dev/null +++ b/corpus/page-lifecycle/creation/native/cold/read/page-011.xml @@ -0,0 +1,4 @@ + +Body duplicate 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/native/cold/read/page-012.xml b/corpus/page-lifecycle/creation/native/cold/read/page-012.xml new file mode 100644 index 0000000000000000000000000000000000000000..d8b4366fe460f248ae133fd9ec8fd8e3e7c70568 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/cold/read/page-012.xml @@ -0,0 +1,4 @@ + +Body automatic 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/native/notebook/Lifecycle.one b/corpus/page-lifecycle/creation/native/notebook/Lifecycle.one new file mode 100644 index 0000000000000000000000000000000000000000..b60dfff4780a6d8dd3826fc2a515033d2e5f0cae GIT binary patch literal 69000 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 DlF8bj literal 0 HcmV?d00001 diff --git a/corpus/page-lifecycle/creation/native/read/hierarchy.xml b/corpus/page-lifecycle/creation/native/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..46f7de2e422eaa62f29644d33dd752fb0527c358 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/native/read/page-000.xml b/corpus/page-lifecycle/creation/native/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..b8cab291fd9a7f09e47daa1b311825a5035b039e --- /dev/null +++ b/corpus/page-lifecycle/creation/native/read/page-000.xml @@ -0,0 +1,4 @@ + +Body trailing 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/native/read/page-001.xml b/corpus/page-lifecycle/creation/native/read/page-001.xml new file mode 100644 index 0000000000000000000000000000000000000000..3015c50816186e839b87dc7dc7d59d73f27fc535 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/read/page-001.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/native/read/page-002.xml b/corpus/page-lifecycle/creation/native/read/page-002.xml new file mode 100644 index 0000000000000000000000000000000000000000..d7f2c174e35a8ae7f7eb175293702d85748e8bf5 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/read/page-002.xml @@ -0,0 +1,4 @@ + +Body duplicate 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/native/read/page-003.xml b/corpus/page-lifecycle/creation/native/read/page-003.xml new file mode 100644 index 0000000000000000000000000000000000000000..e9d43bf99f32856357e6bbd970d6bcc248cd6111 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/read/page-003.xml @@ -0,0 +1,4 @@ + +Body automatic 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/native/read/page-004.xml b/corpus/page-lifecycle/creation/native/read/page-004.xml new file mode 100644 index 0000000000000000000000000000000000000000..8ed9596a931a9c3ce235802b6c4b981ca388cb89 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/read/page-004.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/native/read/page-005.xml b/corpus/page-lifecycle/creation/native/read/page-005.xml new file mode 100644 index 0000000000000000000000000000000000000000..3b4a678ee36c1430ad8c7de33bb226d2b7ec9b0f --- /dev/null +++ b/corpus/page-lifecycle/creation/native/read/page-005.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/native/read/page-006.xml b/corpus/page-lifecycle/creation/native/read/page-006.xml new file mode 100644 index 0000000000000000000000000000000000000000..4693795ee03c300bd507cb4737d8c9550f6d24bf --- /dev/null +++ b/corpus/page-lifecycle/creation/native/read/page-006.xml @@ -0,0 +1,4 @@ + +Body grandchild 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/native/read/page-007.xml b/corpus/page-lifecycle/creation/native/read/page-007.xml new file mode 100644 index 0000000000000000000000000000000000000000..e818ae26b54fd7bd9b60249de373bceaaa087704 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/read/page-007.xml @@ -0,0 +1,4 @@ + +Body parent 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/native/read/page-008.xml b/corpus/page-lifecycle/creation/native/read/page-008.xml new file mode 100644 index 0000000000000000000000000000000000000000..b4ce7a9d9152ced7d6a6790478ac895cf31e2af5 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/read/page-008.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/native/read/page-009.xml b/corpus/page-lifecycle/creation/native/read/page-009.xml new file mode 100644 index 0000000000000000000000000000000000000000..5ef8f5a66daa4a3e9e8d60818bfff6dccb6a319f --- /dev/null +++ b/corpus/page-lifecycle/creation/native/read/page-009.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/native/read/page-010.xml b/corpus/page-lifecycle/creation/native/read/page-010.xml new file mode 100644 index 0000000000000000000000000000000000000000..34434e16da993550ce35f3520fcfa45a2178e281 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/read/page-010.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/native/read/page-011.xml b/corpus/page-lifecycle/creation/native/read/page-011.xml new file mode 100644 index 0000000000000000000000000000000000000000..b97f3354d80313f0e6a5c44e57a4b0a46c29f6c8 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/read/page-011.xml @@ -0,0 +1,4 @@ + +Body child 🦀 é preserved]]> diff --git a/corpus/page-lifecycle/creation/native/read/page-012.xml b/corpus/page-lifecycle/creation/native/read/page-012.xml new file mode 100644 index 0000000000000000000000000000000000000000..5873da638217cf192a44a47a46ceb3eb70532f23 --- /dev/null +++ b/corpus/page-lifecycle/creation/native/read/page-012.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/page-lifecycle/creation/provenance.json b/corpus/page-lifecycle/creation/provenance.json new file mode 100644 index 0000000000000000000000000000000000000000..09523b2d417a016d27dd110a7251ccc4491d729b --- /dev/null +++ b/corpus/page-lifecycle/creation/provenance.json @@ -0,0 +1,115 @@ +{ + "cold": { + "capture": "evidence/m10/page-create-cold", + "run": { + "notebook": "/Users/clo/dev/one/evidence/m10/page-create-candidate", + "expected_pages": 13, + "author": null, + "author_timeout_seconds": 600, + "inspect": false, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" + } + }, + "machine": { + "name": "m6-4b25125e", + "hostname": "ONE-M6-4B25125E" + }, + "teardown": { + "absent": true + } + }, + "native": { + "capture": "evidence/m10/page-create-native-edit", + "run": { + "notebook": "/Users/clo/dev/one/evidence/m10/page-create-cold/notebook", + "expected_pages": 13, + "author": "tools/native/created-pages.ps1", + "author_timeout_seconds": 600, + "inspect": false, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "author.ps1": "f7d94cfa418a971c2911bb3f57b9a9991c9e0b91d0f7d9c7526ca7c8cd5adc42", + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" + } + }, + "machine": { + "name": "m6-71dafa89", + "hostname": "ONE-M6-71DAFA89" + }, + "teardown": { + "absent": true + } + }, + "native/cold": { + "capture": "evidence/m10/page-create-native-edit-cold", + "run": { + "notebook": "/Users/clo/dev/one/evidence/m10/page-create-native-edit/notebook", + "expected_pages": 13, + "author": null, + "author_timeout_seconds": 600, + "inspect": false, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" + } + }, + "machine": { + "name": "m6-eabc83c1", + "hostname": "ONE-M6-EABC83C1" + }, + "teardown": { + "absent": true + } + }, + "followup/cold": { + "capture": "evidence/m10/page-create-rust-followup-cold", + "run": { + "notebook": "/Users/clo/dev/one/evidence/m10/page-create-rust-followup", + "expected_pages": 13, + "author": null, + "author_timeout_seconds": 600, + "inspect": false, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" + } + }, + "machine": { + "name": "m6-5a91d9d1", + "hostname": "ONE-M6-5A91D9D1" + }, + "teardown": { + "absent": true + } + } +} diff --git a/crates/onestore/README.md b/crates/onestore/README.md index d4ae6e6a8e6a3a2eb81c0c2ec42fac14f1a6ec21..ef5de8c3d810f34f1293aae8bddf90ae6fef35f9 100644 --- a/crates/onestore/README.md +++ b/crates/onestore/README.md @@ -45,6 +45,7 @@ harness also accepts `--client-profile release`. | `document::Document`, `Revision::text_runs` | Interpret document objects and inherited text formatting while retaining unknown properties and revision identities | | `protected::UnlockedSection` (optional feature) | Own decoded buffers for explicit known-password inspection; clear those buffers on drop; derived document strings/exports remain caller-owned | | `create_section` | Create one page containing one plain-text paragraph and an author, including Unicode | +| `PageCreation`, `PreparedEdit::create_page` | Add an empty top-level page and its section entry atomically, retaining page identities across retries | | `create_table_of_contents` | Create ordered section entries from filenames and file identities | | `replace_property_bytes` | Append one scalar-property revision; preserve prior revisions and unrelated property values and references | | `replace_text`, `commit_text`, `commit_file_text` | Replace a UTF-16 range across ordinary text runs; publish text, run boundaries and modification time together | @@ -73,6 +74,13 @@ sections retain their encrypted structure and payloads. With the optional AES-128/CBC, SHA-1 password wrappers into a borrowed document view. Incorrect passwords, unsupported protection profiles and work-limit failures remain distinct. The source stays encrypted; protected writes are rejected. +`PageCreation::new` appends, or inserts before the first page space of an existing +series. `Some("")` creates an empty title field; `None` omits the title node. +The page has no body outlines, generated date/time text or applied template. +Retain the intent to preserve its page, title and space identities; existing +identities require reconciliation before retry. Body insertion and title edits +use those identities through the existing APIs. [Native page-creation fixtures](../../corpus/page-lifecycle/creation/README.md) +cover duplicate Unicode titles, native edits and Rust follow-up edits. Insertions update child references, reference counts, modification times and automatic titles atomically. Paragraphs can be nested or inserted into table cells; outline coordinates use points. `Insertion::with_formatting` includes nonoverlapping UTF-16 diff --git a/crates/onestore/src/commit.rs b/crates/onestore/src/commit.rs index 4e3d2507a46971d073f8375baddd705eae20a8ed..49aa9d68405c10697d372f37cebbd3b15110aa36 100644 --- a/crates/onestore/src/commit.rs +++ b/crates/onestore/src/commit.rs @@ -183,6 +183,14 @@ pub struct PreparedEdit<'a> { } impl<'a> PreparedEdit<'a> { + /// Creates a page and its section entry in one transaction, retaining the intent's identities. + pub fn create_page(source: &'a [u8], page: &crate::PageCreation) -> Result { + Ok(Self { + source, + written: page.apply(source)?, + }) + } + /// Moves or removes a subtree and normalizes its containers in one revision. pub fn tree( source: &'a [u8], diff --git a/crates/onestore/src/lib.rs b/crates/onestore/src/lib.rs index c5e2f825aa45f9d0bdddb9e643ff4ab2451b2691..5334f874fc04fd30ede8640713bf77bdfe8749c3 100644 --- a/crates/onestore/src/lib.rs +++ b/crates/onestore/src/lib.rs @@ -12,6 +12,7 @@ mod formatting; mod insertion; mod objects; mod outline; +mod page; mod paragraph; mod properties; #[cfg(feature = "protected")] @@ -35,6 +36,7 @@ pub use formatting::TextAttribute; pub use insertion::Insertion; pub use objects::{Object, ObjectData, ObjectReferences, ResolvedRevision}; pub use outline::OutlineEdit; +pub use page::PageCreation; pub use paragraph::{ParagraphJoin, ParagraphSplit}; pub use properties::{IdStream, Property, PropertySets, Value}; pub use revisions::{ExGuid, ObjectSpace, Revision, RevisionIndex}; diff --git a/crates/onestore/src/page.rs b/crates/onestore/src/page.rs new file mode 100644 index 0000000000000000000000000000000000000000..bf44af9cf36e99aa638570ab4770adf2108157c2 --- /dev/null +++ b/crates/onestore/src/page.rs @@ -0,0 +1,260 @@ +use crate::{ + Error, ExGuid, + create::{current_timestamps, properties, string}, + document::{Document, Kind}, + write::{PropertyObject, RevisionEdit, fresh_guid, write_revisions}, +}; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, sync::Arc}; + +fn invalid(message: &'static str) -> Error { + Error { offset: 0, message } +} + +/// An empty top-level page with stable identities and creation time. +/// Retain the intent across retries; an existing page identity rejects duplicate creation. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct PageCreation { + guid: [u8; 16], + series_guid: [u8; 16], + before: Option, + title: Option, + author: String, + created: u32, +} + +impl PageCreation { + /// Appends a page, or inserts before the first page space of an existing series. + /// `Some("")` creates an empty title field; `None` creates a page without a title node. + /// The page has no body outlines or generated date/time text and does not apply a template. + pub fn new(before: Option, title: Option<&str>, author: &str) -> Result { + let page = Self { + guid: fresh_guid()?, + series_guid: fresh_guid()?, + before, + title: title.map(str::to_owned), + author: author.to_owned(), + created: current_timestamps()?.0, + }; + page.validate()?; + Ok(page) + } + + pub fn space(&self) -> ExGuid { + ExGuid { + guid: self.guid, + n: 1, + } + } + + pub fn object(&self) -> ExGuid { + ExGuid { + guid: self.guid, + n: 12, + } + } + + pub fn title_object(&self) -> Option { + self.title.as_ref().map(|_| ExGuid { + guid: self.guid, + n: 16, + }) + } + + fn validate(&self) -> Result<(), Error> { + if self.guid == [0; 16] + || self.series_guid == [0; 16] + || self.guid == self.series_guid + || self.author.contains('\0') + || self.before.is_some_and(|id| id.guid == [0; 16]) + || self + .title + .as_ref() + .is_some_and(|title| title.contains(['\0', '\r', '\n', '\u{fffc}', '\u{fddf}'])) + { + return Err(invalid( + "Use a new page identity, an existing page anchor, and ordinary single-line title text", + )); + } + Ok(()) + } + + pub(crate) fn apply(&self, source: &[u8]) -> Result, Error> { + self.validate()?; + write_revisions(source, |index| { + if index.spaces.contains_key(&self.space()) { + return Err(invalid( + "This page identity already exists; reconcile the original creation", + )); + } + let document = Document::parse(index)?; + document.pages()?; + let section = &document.spaces[&document.root]; + let view = §ion.revisions[§ion.contexts[&ExGuid::default()]]; + let section_id = view.roots[&1]; + let section_node = &view.nodes[§ion_id]; + if !matches!(section_node.kind, Kind::Section { .. }) + || section_node.extra[0] + .iter() + .any(|field| field.id == 0x88001cde) + { + return Err(invalid("Choose an editable section for the new page")); + } + let position = match self.before { + None => section_node.children.len(), + Some(before) => section_node + .children + .iter() + .position(|id| view.nodes[id].spaces.first() == Some(&before)) + .ok_or_else(|| { + invalid("Insert before the first page of an existing series, or append") + })?, + }; + let raw = index.resolve(document.root, section.contexts[&ExGuid::default()])?; + let id = |n| ExGuid { guid: self.guid, n }; + let reference = |n: u32| n.to_le_bytes().to_vec(); + let timestamp = ((u64::from(self.created) + 315532800 + 11644473600) * 10000000) + .to_le_bytes() + .to_vec(); + let modified = || (0x14001d7a, self.created.to_le_bytes().to_vec()); + let mut table = BTreeMap::from([(0, self.guid)]); + let mut metadata_id = id(1); + for (byte, salt) in metadata_id.guid.iter_mut().zip([ + 0x31, 0xc0, 0xa8, 0x22, 0, 0x36, 0xee, 0x42, 0xb7, 0x14, 0xd7, 0xac, 0xda, 0x24, + 0x35, 0xe8, + ]) { + *byte ^= salt; + } + table.insert(1, metadata_id.guid); + let table = Arc::new(table); + let object = |jcid, values: Vec<_>| -> Result<_, Error> { + Ok(PropertyObject { + jcid, + bytes: properties(&values)?, + global_ids: Arc::clone(&table), + }) + }; + let title = self.title.as_deref().unwrap_or_default().trim_start(); + let metadata = vec![ + (0x1c001c30, self.guid.to_vec()), + (0x1c001cf3, string(title)), + (0x14001d82, reference(40)), + (0x1400348b, reference(40)), + (0x14001dff, reference(1)), + (0x18001c65, timestamp.clone()), + ]; + let mut page = vec![ + modified(), + (0x1c001d75, string(&self.author)), + (0x1c001d3c, string("")), + ]; + if self.title.is_some() { + page.push((0x24001d5f, reference(13))); + } + let mut objects = BTreeMap::from([ + (id(10), object(0x60037, vec![(0x24001c1f, reference(12))])?), + (id(11), object(0x20030, metadata.clone())?), + (id(12), object(0x6000b, page)?), + ]); + if let Some(title) = &self.title { + for (n, jcid, values) in [ + ( + 13, + 0x6002c, + vec![ + modified(), + (0x24001c20, reference(14)), + (0x14001c14, 0_f32.to_le_bytes().to_vec()), + (0x14001c15, 0_f32.to_le_bytes().to_vec()), + ], + ), + ( + 14, + 0x6000c, + vec![ + modified(), + (0x24001c20, reference(15)), + (0x0c001c03, vec![1]), + ], + ), + ( + 15, + 0x6000d, + vec![ + modified(), + (0x24001c1f, reference(16)), + (0x0c001c03, vec![1]), + (0x20001d78, reference(17)), + (0x20001d79, reference(17)), + (0x14001d09, self.created.to_le_bytes().to_vec()), + (0x88001cb4, vec![]), + ], + ), + ( + 16, + 0x6000e, + vec![ + modified(), + (0x1c001c22, string(title)), + (0x24001e13, reference(19)), + (0x2000342c, reference(18)), + (0x10001cfe, 0x409_u16.to_le_bytes().to_vec()), + (0x88001cb4, vec![]), + ], + ), + (17, 0x120001, vec![(0x1c001d75, string(&self.author))]), + ( + 18, + 0x12004d, + vec![ + (0x1c00345a, string("PageTitle")), + (0x1c001c0a, string("Calibri")), + (0x10001c0b, 34_u16.to_le_bytes().to_vec()), + ], + ), + (19, 0x12004d, vec![(0x14001c3b, reference(0x409))]), + ] { + objects.insert(id(n), object(jcid, values)?); + } + } + let series = object( + 0x60008, + vec![ + (0x1c001c30, self.series_guid.to_vec()), + (0x18001c65, timestamp), + (0x2c001d63, reference(1)), + (0x24003442, reference(257)), + ], + )?; + if raw.objects.contains_key(&id(2)) || raw.objects.contains_key(&metadata_id) { + return Err(invalid("The new page's section identities already exist")); + } + let mut parent = PropertyObject::from_object(&raw.objects[§ion_id])?; + let mut children = section_node.children.clone(); + children.insert(position, id(2)); + let mut references = Vec::new(); + for child in children { + references.extend_from_slice(&parent.reference(child)?); + } + parent.set(&[(0x24001c20, &references)])?; + Ok(BTreeMap::from([ + ( + document.root, + RevisionEdit::Update(BTreeMap::from([ + (section_id, parent), + (id(2), series), + (metadata_id, object(0x20030, metadata)?), + ])), + ), + ( + self.space(), + RevisionEdit::Create { + roots: BTreeMap::from([(1, id(10)), (2, id(11))]), + objects, + }, + ), + ])) + }) + } +} diff --git a/crates/onestore/src/write.rs b/crates/onestore/src/write.rs index aea01dddb259348307d1dd7e34eefe1762710257..6f3b7b67ce8f5681d1597d3ef3245939a9c6490d 100644 --- a/crates/onestore/src/write.rs +++ b/crates/onestore/src/write.rs @@ -354,6 +354,14 @@ pub(crate) struct PropertyObject { pub global_ids: Arc>, } +pub(crate) enum RevisionEdit { + Update(BTreeMap), + Create { + roots: BTreeMap, + objects: BTreeMap, + }, +} + impl PropertyObject { pub fn from_object(object: &crate::Object<'_>) -> Result { let ObjectData::Properties(bytes) = object.data else { @@ -597,13 +605,49 @@ pub(crate) fn write_revision( message: "Object space has no active default revision", })?; let revision = index.resolve(space, rid)?; - Ok(BTreeMap::from([(space, edit(&revision)?)])) + Ok(BTreeMap::from([( + space, + RevisionEdit::Update(edit(&revision)?), + )])) }) } +fn append_fragment( + source: &[u8], + output: &mut Vec, + list: &crate::NodeList, + nodes: &[Vec], +) -> Result<(u32, usize)> { + let last_fragment = *list.fragments.last().unwrap(); + let list_start = usize::try_from(last_fragment.offset).unwrap(); + let list_id = u32::from_le_bytes(source[list_start + 8..list_start + 12].try_into().unwrap()); + let chunk = append_list(output, list_id, nodes)?; + let start = usize::try_from(chunk.offset).unwrap(); + let sequence = u32::try_from(list.fragments.len()).map_err(|_| Error { + offset: list_start, + message: "File-node fragment sequences are exhausted", + })?; + output[start + 12..start + 16].copy_from_slice(&sequence.to_le_bytes()); + let last_node = list.nodes.last().unwrap(); + let node_header = u32::from_le_bytes( + source[last_node.offset..last_node.offset + 4] + .try_into() + .unwrap(), + ); + let nodes_end = last_node.offset + usize::try_from((node_header >> 10) & 0x1fff).unwrap(); + let tail = list_start + usize::try_from(last_fragment.length).unwrap() - 20; + if tail - nodes_end >= 4 { + output[nodes_end..nodes_end + 4].copy_from_slice(&node(0xff, None, &[])?); + } + output[tail..tail + 8].copy_from_slice(&chunk.offset.to_le_bytes()); + output[tail + 8..tail + 12].copy_from_slice(&(chunk.length as u32).to_le_bytes()); + + Ok((list_id, list.nodes.len() + nodes.len())) +} + pub(crate) fn write_revisions( source: &[u8], - edit: impl FnOnce(&RevisionIndex<'_>) -> Result>>, + edit: impl FnOnce(&RevisionIndex<'_>) -> Result>, ) -> Result> { let store = Store::parse(source)?; let is_section = store.header.file_type == FileType::Section; @@ -617,25 +661,63 @@ pub(crate) fn write_revisions( index.validate_current()?; let changes = edit(&index)?; let mut output = source.to_vec(); - let maximum = store + let mut maximum = store .transaction_fragments .iter() .flat_map(|fragment| fragment.entries.chunks_exact(8)) .map(|entry| u32::from_le_bytes(entry[..4].try_into().unwrap())) .max() .unwrap(); + let mut allocate_list = || { + maximum = maximum.checked_add(1).ok_or(Error { + offset: 0, + message: "File-node list identities are exhausted", + })?; + Ok::<_, Error>(maximum) + }; let mut counts = Vec::new(); - for (space, mut replacements) in changes { - let rid = *index - .spaces - .get(&space) - .and_then(|space| space.labels.get(&(ExGuid::default(), 1))) - .ok_or(Error { - offset: 0, - message: "Object space has no active default revision", - })?; - let mut revision = index.resolve(space, rid)?; - let reachable = revision.reachable()?; + let mut root_nodes = Vec::new(); + for (space, change) in changes { + let (rid, mut revision, mut replacements) = match change { + RevisionEdit::Update(objects) => { + let rid = *index + .spaces + .get(&space) + .and_then(|space| space.labels.get(&(ExGuid::default(), 1))) + .ok_or(Error { + offset: 0, + message: "Object space has no active default revision", + })?; + (Some(rid), index.resolve(space, rid)?, objects) + } + RevisionEdit::Create { roots, objects } => { + if !is_section || space.guid == [0; 16] || index.spaces.contains_key(&space) { + return Err(Error { + offset: 0, + message: "Choose a new object-space identity in a section file", + }); + } + if roots.is_empty() || objects.is_empty() { + return Err(Error { + offset: 0, + message: "New object space needs roots and objects", + }); + } + ( + None, + crate::ResolvedRevision { + roots, + objects: BTreeMap::new(), + }, + objects, + ) + } + }; + let reachable = if rid.is_some() { + revision.reachable()? + } else { + BTreeSet::new() + }; for (id, replacement) in &replacements { if let Some(object) = revision.objects.get(id) { if !reachable.contains(id) { @@ -769,11 +851,10 @@ pub(crate) fn write_revisions( } // Native cold-open fails on long dependency chains; cap their depth at 512. - let checkpoint = std::iter::successors(Some(rid), |id| { - index.spaces[&space].revisions[id].dependency - }) - .nth(511) - .is_some(); + let checkpoint = rid.is_none() + || std::iter::successors(rid, |id| index.spaces[&space].revisions[id].dependency) + .nth(511) + .is_some(); let selected: Vec<_> = revision .objects .iter() @@ -821,13 +902,25 @@ pub(crate) fn write_revisions( }; let mut start = Vec::new(); new_rid.encode(&mut start); - if checkpoint { ExGuid::default() } else { rid }.encode(&mut start); + if checkpoint { + ExGuid::default() + } else { + rid.unwrap() + } + .encode(&mut start); if !is_section { start.extend_from_slice(&0_u64.to_le_bytes()); } start.extend_from_slice(&1_u32.to_le_bytes()); start.extend_from_slice(&0_u16.to_le_bytes()); - let mut manifest = vec![node(if is_section { 0x1e } else { 0x1b }, None, &start)?]; + let mut manifest = Vec::new(); + if rid.is_none() { + let mut payload = Vec::new(); + space.encode(&mut payload); + payload.extend_from_slice(&0_u32.to_le_bytes()); + manifest.push(node(0x14, None, &payload)?); + } + manifest.push(node(if is_section { 0x1e } else { 0x1b }, None, &start)?); for (table, objects) in groups { let mut payload = Vec::new(); let mut group = if is_section { @@ -941,14 +1034,7 @@ pub(crate) fn write_revisions( } if is_section { group.push(node(0xb8, None, &[])?); - let group_id = u32::try_from(counts.len()) - .ok() - .and_then(|n| n.checked_add(1)) - .and_then(|n| maximum.checked_add(n)) - .ok_or(Error { - offset: 0, - message: "File-node list identities are exhausted", - })?; + let group_id = allocate_list()?; let chunk = append_list(&mut output, group_id, &group)?; counts.push((group_id, group.len())); manifest.push(node(0xb0, Some(Reference::NodeList(chunk)), &payload)?); @@ -979,47 +1065,51 @@ pub(crate) fn write_revisions( } } manifest.push(node(0x1c, None, &[])?); - let root = store.list(store.header.root)?; - let space_node = root - .nodes - .iter() - .find(|node| node.id == 8 && node.fields(&store).exguid() == Ok(space)) - .ok_or(Error { - offset: 0, - message: "Object space is absent from the root list", - })?; - let space_list = space_node.referenced_list(&store)?; - let revision_node = space_list.iter().rfind(|node| node.id == 0x10).unwrap(); - let Some(Reference::NodeList(manifest_reference)) = revision_node.reference else { - unreachable!() - }; - let revision_list = store.list(manifest_reference)?; - let last_fragment = *revision_list.fragments.last().unwrap(); - let list_start = usize::try_from(last_fragment.offset).unwrap(); - let list_id = - u32::from_le_bytes(source[list_start + 8..list_start + 12].try_into().unwrap()); - let manifest_chunk = append_list(&mut output, list_id, &manifest)?; - let manifest_start = usize::try_from(manifest_chunk.offset).unwrap(); - let sequence = u32::try_from(revision_list.fragments.len()).map_err(|_| Error { - offset: list_start, - message: "File-node fragment sequences are exhausted", - })?; - output[manifest_start + 12..manifest_start + 16].copy_from_slice(&sequence.to_le_bytes()); - let last_node = revision_list.nodes.last().unwrap(); - let node_header = u32::from_le_bytes( - source[last_node.offset..last_node.offset + 4] - .try_into() - .unwrap(), - ); - let nodes_end = last_node.offset + usize::try_from((node_header >> 10) & 0x1fff).unwrap(); - let tail = list_start + usize::try_from(last_fragment.length).unwrap() - 20; - if tail - nodes_end >= 4 { - output[nodes_end..nodes_end + 4].copy_from_slice(&node(0xff, None, &[])?); + if rid.is_none() { + let list_id = allocate_list()?; + let chunk = append_list(&mut output, list_id, &manifest)?; + counts.push((list_id, manifest.len())); + let mut payload = Vec::new(); + space.encode(&mut payload); + let nodes = vec![ + node(0xc, None, &payload)?, + node(0x10, Some(Reference::NodeList(chunk)), &[])?, + ]; + let list_id = allocate_list()?; + let chunk = append_list(&mut output, list_id, &nodes)?; + counts.push((list_id, nodes.len())); + root_nodes.push(node(8, Some(Reference::NodeList(chunk)), &payload)?); + } else { + let root = store.list(store.header.root)?; + let space_node = root + .nodes + .iter() + .find(|node| node.id == 8 && node.fields(&store).exguid() == Ok(space)) + .ok_or(Error { + offset: 0, + message: "Object space is absent from the root list", + })?; + let space_list = space_node.referenced_list(&store)?; + let revision_node = space_list.iter().rfind(|node| node.id == 0x10).unwrap(); + let Some(Reference::NodeList(manifest_reference)) = revision_node.reference else { + unreachable!() + }; + let revision_list = store.list(manifest_reference)?; + counts.push(append_fragment( + source, + &mut output, + revision_list, + &manifest, + )?); } - output[tail..tail + 8].copy_from_slice(&manifest_chunk.offset.to_le_bytes()); - output[tail + 8..tail + 12].copy_from_slice(&(manifest_chunk.length as u32).to_le_bytes()); - - counts.push((list_id, revision_list.nodes.len() + manifest.len())); + } + if !root_nodes.is_empty() { + counts.push(append_fragment( + source, + &mut output, + store.list(store.header.root)?, + &root_nodes, + )?); } if counts.is_empty() { return Ok(source.to_vec()); diff --git a/crates/onestore/src/write/tests/multi.rs b/crates/onestore/src/write/tests/multi.rs index 191a5d88846f581bba983d8c2eac71cb0fad0e70..e3bec7129975a4c305656a2b0fdfda4fcda071c9 100644 --- a/crates/onestore/src/write/tests/multi.rs +++ b/crates/onestore/src/write/tests/multi.rs @@ -1,7 +1,7 @@ use crate::{ ExGuid, RevisionIndex, Store, document::{Document, Kind}, - write::{PropertyObject, write_revisions}, + write::{PropertyObject, RevisionEdit, write_revisions}, }; use std::collections::BTreeMap; @@ -90,7 +90,10 @@ fn nest(source: &[u8]) -> Vec { object.set(&[(0x24001c20, &children)])?; replacements.insert(root, object); changes.insert(index.root, replacements); - Ok(changes) + Ok(changes + .into_iter() + .map(|(sid, objects)| (sid, RevisionEdit::Update(objects))) + .collect()) }) .unwrap() } @@ -213,7 +216,7 @@ fn empty_batches_and_invalid_spaces_do_not_produce_an_edit() { assert!( write_revisions(SOURCE, |_| Ok(BTreeMap::from([( ExGuid::default(), - BTreeMap::new() + RevisionEdit::Update(BTreeMap::new()) )]))) .is_err() ); @@ -247,7 +250,10 @@ fn repeated_multi_space_edits_cross_counter_carries_and_checkpoint_each_space() object.set(&[(property, &step.to_le_bytes())])?; changes.insert(*sid, BTreeMap::from([(id, object)])); } - Ok(changes) + Ok(changes + .into_iter() + .map(|(sid, objects)| (sid, RevisionEdit::Update(objects))) + .collect()) }) .unwrap(); let store = Store::parse(&written).unwrap(); diff --git a/crates/onestore/tests/page.rs b/crates/onestore/tests/page.rs new file mode 100644 index 0000000000000000000000000000000000000000..f2ecc7d5c779d76dbca635b7252f8a0a01fa7393 --- /dev/null +++ b/crates/onestore/tests/page.rs @@ -0,0 +1,356 @@ +use onestore::{ + ExGuid, Insertion, PageCreation, PreparedEdit, RevisionIndex, Store, + document::{Document, Kind}, +}; + +#[path = "support/current.rs"] +mod current; +#[path = "support/disk.rs"] +mod disk; + +const SOURCE: &[u8] = + include_bytes!("../../../corpus/page-lifecycle/03-renamed/notebook/Lifecycle.one"); + +#[test] +fn native_section_accepts_empty_and_titled_pages_with_preserved_history() { + let original = Store::parse(SOURCE).unwrap(); + let old_index = RevisionIndex::parse(&original).unwrap(); + let original_pages = Document::parse(&old_index).unwrap().pages().unwrap(); + let mut expected = original_pages.clone(); + let mut source = SOURCE.to_vec(); + for (ordinal, title) in [None, Some(""), Some("New 🦋 é"), Some("New 🦋 é")] + .into_iter() + .enumerate() + { + let before = (ordinal == 1).then_some(original_pages[0].0); + let intent = PageCreation::new(before, title, "Rust author").unwrap(); + let serialized = serde_json::to_vec(&intent).unwrap(); + let retained = serde_json::from_slice(&serialized).unwrap(); + assert_eq!(intent, retained); + let prepared = PreparedEdit::create_page(&source, &retained).unwrap(); + let store = Store::parse(prepared.as_bytes()).unwrap(); + assert_eq!( + store.header.transaction_count, + original.header.transaction_count + ordinal as u32 + 1 + ); + assert_eq!(store.header.file_id, original.header.file_id); + let index = RevisionIndex::parse(&store).unwrap(); + index.validate_current().unwrap(); + let document = Document::parse(&index).unwrap(); + let at = if ordinal == 1 { 0 } else { expected.len() }; + expected.insert(at, (intent.space(), intent.object())); + assert_eq!(document.pages().unwrap(), expected); + let view = &document.spaces[&intent.space()]; + let view = &view.revisions[&view.contexts[&ExGuid::default()]]; + assert!(view.nodes[&intent.object()].children.is_empty()); + assert_eq!( + view.nodes[&intent.object()].structure.len(), + usize::from(title.is_some()) + ); + let Kind::Metadata { + title: actual, + level, + } = &view.nodes[&view.roots[&2]].kind + else { + panic!() + }; + assert_eq!(actual.as_deref(), Some(title.unwrap_or_default())); + assert_eq!(*level, Some(1)); + if let Some(text) = intent.title_object() { + let Kind::RichText { + text, boilerplate, .. + } = &view.nodes[&text].kind + else { + panic!() + }; + assert_eq!(text, title.unwrap()); + assert!(!boilerplate); + } + for (sid, space) in &old_index.spaces { + for rid in space.revisions.keys() { + assert_eq!( + format!("{:?}", old_index.resolve(*sid, *rid).unwrap()), + format!("{:?}", index.resolve(*sid, *rid).unwrap()) + ); + } + if *sid != old_index.root { + assert_eq!(space.labels, index.spaces[sid].labels); + } + } + assert!(PreparedEdit::create_page(prepared.as_bytes(), &intent).is_err()); + source = prepared.as_bytes().to_vec(); + } + if let Some(output) = std::env::var_os("ONESTORE_PAGE_CREATION_OUTPUT") { + std::fs::create_dir(&output).unwrap(); + std::fs::write(std::path::Path::new(&output).join("Lifecycle.one"), &source).unwrap(); + } +} + +#[test] +fn created_pages_support_title_edits_and_body_insertion() { + let mut source = onestore::create_section("pages.one", "Original", "Author").unwrap(); + for title in [None, Some(""), Some("Explicit 🦋 é")] { + let intent = PageCreation::new(None, title, "Author").unwrap(); + source = PreparedEdit::create_page(&source, &intent) + .unwrap() + .as_bytes() + .to_vec(); + let insertion = + Insertion::outline(intent.object(), 36.0, 36.0, "Body 🦀 é", "Author").unwrap(); + source = PreparedEdit::insert(&source, intent.space(), &insertion) + .unwrap() + .as_bytes() + .to_vec(); + let store = Store::parse(&source).unwrap(); + let index = RevisionIndex::parse(&store).unwrap(); + let document = Document::parse(&index).unwrap(); + let view = &document.spaces[&intent.space()]; + let view = &view.revisions[&view.contexts[&ExGuid::default()]]; + let Kind::Metadata { title: actual, .. } = &view.nodes[&view.roots[&2]].kind else { + panic!() + }; + assert_eq!( + actual.as_deref(), + Some(title.filter(|s| !s.is_empty()).unwrap_or("Body 🦀 é")) + ); + if let Some(text) = intent.title_object() { + source = PreparedEdit::text( + &source, + intent.space(), + text, + 0..title.unwrap().encode_utf16().count() as u32, + "Renamed", + ) + .unwrap() + .as_bytes() + .to_vec(); + let store = Store::parse(&source).unwrap(); + let index = RevisionIndex::parse(&store).unwrap(); + let document = Document::parse(&index).unwrap(); + let view = &document.spaces[&intent.space()]; + let view = &view.revisions[&view.contexts[&ExGuid::default()]]; + let Kind::Metadata { title, .. } = &view.nodes[&view.roots[&2]].kind else { + panic!() + }; + assert_eq!(title.as_deref(), Some("Renamed")); + } + } +} + +#[test] +fn invalid_page_intents_and_nonleading_anchors_reject_before_publication() { + for title in ["a\nb", "\r", "\0", "\u{fffc}", "\u{fddf}"] { + assert!(PageCreation::new(None, Some(title), "Author").is_err()); + } + assert!(PageCreation::new(None, None, "\0").is_err()); + assert!(PageCreation::new(Some(ExGuid::default()), None, "Author").is_err()); + let source = include_bytes!("../../../corpus/page-lifecycle/04-nested/notebook/Lifecycle.one"); + let store = Store::parse(source).unwrap(); + let index = RevisionIndex::parse(&store).unwrap(); + let pages = Document::parse(&index).unwrap().pages().unwrap(); + let intent = PageCreation::new(Some(pages[4].0), Some("Child"), "Author").unwrap(); + assert!(PreparedEdit::create_page(source, &intent).is_err()); + let mut value = serde_json::to_value(PageCreation::new(None, None, "Author").unwrap()).unwrap(); + value["guid"] = serde_json::to_value([0_u8; 16]).unwrap(); + let invalid: PageCreation = serde_json::from_value(value.clone()).unwrap(); + assert!(PreparedEdit::create_page(source, &invalid).is_err()); + value["unknown"] = true.into(); + assert!(serde_json::from_value::(value).is_err()); +} + +#[test] +fn new_space_and_section_entry_publish_as_one_complete_action() { + let source = onestore::create_section("pages.one", "Original", "Author").unwrap(); + let intent = PageCreation::new(None, Some("New 🦋 é"), "Author").unwrap(); + let prepared = PreparedEdit::create_page(&source, &intent).unwrap(); + let old = current::current(&source); + let new = current::current(prepared.as_bytes()); + assert_eq!(new.len(), old.len() + 1); + for write_limit in [1, 17, 4096] { + let mut complete = disk::Disk { + visible: source.clone(), + durable: source.clone(), + operation: 0, + fail_at: None, + write_limit, + random: 1954, + }; + prepared.commit(&mut complete).unwrap(); + assert_eq!(complete.durable, prepared.as_bytes()); + for fail_at in 1..=complete.operation { + let mut interrupted = disk::Disk { + visible: source.clone(), + durable: source.clone(), + operation: 0, + fail_at: Some(fail_at), + write_limit, + random: 1954 + fail_at as u64, + }; + prepared.commit(&mut interrupted).unwrap_err(); + let observed = current::current(&interrupted.durable); + assert!( + observed == old || observed == new, + "{write_limit}:{fail_at}" + ); + } + } +} + +#[test] +fn repeated_page_creation_crosses_root_fragments_counters_and_section_checkpoints() { + let mut source = onestore::create_section("PageStress.one", "Original", "Author").unwrap(); + let original = source.clone(); + let original_store = Store::parse(&original).unwrap(); + let original_index = RevisionIndex::parse(&original_store).unwrap(); + let mut expected = Document::parse(&original_index).unwrap().pages().unwrap(); + let mut checkpoints = 0; + for step in 1..=520 { + let title = format!("Page {} 🦋 é", step % 7); + let intent = PageCreation::new( + None, + (step % 2 != 0).then_some(title.as_str()), + "Stress author", + ) + .unwrap(); + let prepared = PreparedEdit::create_page(&source, &intent).unwrap(); + let mut unpublished = prepared.as_bytes().to_vec(); + unpublished[96..100].copy_from_slice(&source[96..100]); + let old_store = Store::parse(&unpublished).unwrap(); + let old_index = RevisionIndex::parse(&old_store).unwrap(); + assert_eq!( + Document::parse(&old_index).unwrap().pages().unwrap(), + expected + ); + let store = Store::parse(prepared.as_bytes()).unwrap(); + assert_eq!(store.header.root, original_store.header.root); + assert_eq!( + store.header.transaction_count, + original_store.header.transaction_count + step + ); + let index = RevisionIndex::parse(&store).unwrap(); + assert_eq!( + index.spaces.len(), + original_index.spaces.len() + step as usize + ); + expected.push((intent.space(), intent.object())); + assert_eq!(Document::parse(&index).unwrap().pages().unwrap(), expected); + let section = &index.spaces[&index.root]; + let rid = section.labels[&(ExGuid::default(), 1)]; + checkpoints += usize::from(section.revisions[&rid].dependency.is_none()); + for (sid, space) in &original_index.spaces { + for rid in space.revisions.keys() { + assert_eq!( + format!("{:?}", original_index.resolve(*sid, *rid).unwrap()), + format!("{:?}", index.resolve(*sid, *rid).unwrap()) + ); + } + } + source = prepared.as_bytes().to_vec(); + } + assert_eq!(checkpoints, 1); + if let Some(output) = std::env::var_os("ONESTORE_PAGE_STRESS_OUTPUT") { + std::fs::create_dir(&output).unwrap(); + std::fs::write( + std::path::Path::new(&output).join("PageStress.one"), + &source, + ) + .unwrap(); + } +} + +#[test] +fn native_changes_on_created_pages_accept_rust_followups() { + let initial = include_bytes!( + "../../../corpus/page-lifecycle/creation/native/cold/notebook/Lifecycle.one" + ); + let store = Store::parse(initial).unwrap(); + let index = RevisionIndex::parse(&store).unwrap(); + let document = Document::parse(&index).unwrap(); + let mut targets = Vec::new(); + for (sid, _) in document.pages().unwrap() { + let space = &document.spaces[&sid]; + let view = &space.revisions[&space.contexts[&ExGuid::default()]]; + let mut title = None; + let mut body = None; + for (id, node) in &view.nodes { + if let Kind::RichText { text, .. } = &node.kind { + if text == "New 🦋 é" { + title = Some(*id); + } + if text.starts_with("Native body ") { + body = Some(*id); + } + } + } + if let (Some(title), Some(body)) = (title, body) { + targets.push((sid, title, body)); + } + } + assert_eq!(targets.len(), 2); + let mut source = initial.to_vec(); + for (sid, title, body) in targets { + for (object, prefix) in [(body, "Rust + "), (title, "Reviewed ")] { + source = PreparedEdit::text(&source, sid, object, 0..0, prefix) + .unwrap() + .as_bytes() + .to_vec(); + } + let store = Store::parse(&source).unwrap(); + let index = RevisionIndex::parse(&store).unwrap(); + let document = Document::parse(&index).unwrap(); + let view = &document.spaces[&sid]; + let view = &view.revisions[&view.contexts[&ExGuid::default()]]; + assert!( + matches!(&view.nodes[&title].kind, Kind::RichText { text, .. } if text == "Reviewed New 🦋 é") + ); + assert!( + matches!(&view.nodes[&body].kind, Kind::RichText { text, .. } if text.starts_with("Rust + Native body ")) + ); + assert!( + matches!(&view.nodes[&view.roots[&2]].kind, Kind::Metadata { title: Some(title), .. } if title == "Reviewed New 🦋 é") + ); + } + if let Some(output) = std::env::var_os("ONESTORE_PAGE_FOLLOWUP_OUTPUT") { + std::fs::create_dir(&output).unwrap(); + std::fs::write(std::path::Path::new(&output).join("Lifecycle.one"), &source).unwrap(); + } +} + +#[test] +fn page_creation_preserves_native_features_and_file_data() { + for source in [ + include_bytes!("../../../corpus/native-ink/20260905-ui/notebook/synthetic.one").as_slice(), + include_bytes!("../../../corpus/native-external-assets/notebook/synthetic.one").as_slice(), + include_bytes!("../../../corpus/m6/native-features-01/notebook/Features.one").as_slice(), + ] { + let before = Store::parse(source).unwrap(); + let old = RevisionIndex::parse(&before).unwrap(); + let original = Document::parse(&old).unwrap(); + let intent = PageCreation::new(None, Some("Preserved features"), "Author").unwrap(); + let prepared = PreparedEdit::create_page(source, &intent).unwrap(); + let after = Store::parse(prepared.as_bytes()).unwrap(); + let new = RevisionIndex::parse(&after).unwrap(); + let document = Document::parse(&new).unwrap(); + assert_eq!( + document.pages().unwrap().len(), + original.pages().unwrap().len() + 1 + ); + for (sid, space) in &old.spaces { + for rid in space.revisions.keys() { + let original = old.resolve(*sid, *rid).unwrap(); + let retained = new.resolve(*sid, *rid).unwrap(); + assert_eq!(format!("{original:?}"), format!("{retained:?}")); + for object in original.objects.values() { + if let Some(onestore::FileDataReference::Internal(id)) = + object.file_reference().unwrap() + { + assert_eq!(before.file_data(id).unwrap(), after.file_data(id).unwrap()); + } + } + } + if *sid != old.root { + assert_eq!(space.labels, new.spaces[sid].labels); + } + } + } +} diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 05afdb31cbee19b3952f956d79943754d5acb87a..13a8e908866d6a6d86414023b19f09314deaeb60 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -114,3 +114,10 @@ path = "fuzz_targets/offline_tree.rs" test = false doc = false bench = false + +[[bin]] +name = "page" +path = "fuzz_targets/page.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/page.rs b/fuzz/fuzz_targets/page.rs new file mode 100644 index 0000000000000000000000000000000000000000..36b68f756092379a905784e3ec5a61597cf04b08 --- /dev/null +++ b/fuzz/fuzz_targets/page.rs @@ -0,0 +1,138 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use onestore::{ + CommitState, ExGuid, Insertion, PageCreation, PreparedEdit, RevisionIndex, Store, + document::{Document, Kind}, +}; +use std::sync::LazyLock; + +#[path = "../../crates/onestore/tests/support/current.rs"] +mod current; +#[path = "../../crates/onestore/tests/support/disk.rs"] +mod disk; + +static SOURCE: LazyLock> = LazyLock::new(|| { + onestore::create_section("pages.one", "Original 🦀 é 東京", "Author").unwrap() +}); + +fuzz_target!(|input: &[u8]| { + if let Ok(intent) = serde_json::from_slice::(input) + && let Ok(prepared) = PreparedEdit::create_page(&SOURCE, &intent) + { + current::current(prepared.as_bytes()); + } + let mut persisted = SOURCE.clone(); + let mut caches = std::array::from_fn::<_, 12, _>(|_| SOURCE.clone()); + for step in input.chunks_exact(8).take(24) { + let actor = usize::from(step[0]) % caches.len(); + if step[1] % 3 == 0 { + caches[actor].clone_from(&persisted); + } + let source = &caches[actor]; + let store = Store::parse(source).unwrap(); + let index = RevisionIndex::parse(&store).unwrap(); + let document = Document::parse(&index).unwrap(); + let mut pages = document.pages().unwrap(); + let (sid, page) = pages[usize::from(step[2]) % pages.len()]; + let space = &document.spaces[&sid]; + let view = &space.revisions[&space.contexts[&ExGuid::default()]]; + let text = ["", "Same title", "é 🦋 東京", " spaces "][usize::from(step[3]) % 4]; + let titles: Vec<_> = view + .nodes + .iter() + .filter_map(|(id, node)| { + (node.extra[0].iter().any(|field| field.id == 0x88001cb4) + && matches!( + node.kind, + Kind::RichText { + boilerplate: false, + .. + } + )) + .then_some(*id) + }) + .collect(); + let (edit, title_update) = if step[1] & 8 != 0 && !titles.is_empty() { + let object = titles[0]; + let Kind::RichText { text: original, .. } = &view.nodes[&object].kind else { + unreachable!() + }; + ( + PreparedEdit::text( + source, + sid, + object, + 0..original.encode_utf16().count() as u32, + text, + ) + .unwrap(), + Some((sid, object, text)), + ) + } else if step[1] & 16 != 0 { + let intent = Insertion::outline(page, 36.0, 36.0, text, "Page fuzz").unwrap(); + ( + PreparedEdit::insert(source, sid, &intent).unwrap(), + Some((sid, intent.text_object(), text)), + ) + } else { + let before = (step[2] & 1 != 0).then_some(sid); + let title = (step[3] & 4 == 0).then_some(text); + let intent = PageCreation::new(before, title, "Page fuzz").unwrap(); + let restored = serde_json::from_value(serde_json::to_value(&intent).unwrap()).unwrap(); + assert_eq!(intent, restored); + let edit = PreparedEdit::create_page(source, &restored).unwrap(); + let position = before.map_or(pages.len(), |sid| { + pages.iter().position(|p| p.0 == sid).unwrap() + }); + pages.insert(position, (intent.space(), intent.object())); + ( + edit, + intent + .title_object() + .map(|object| (intent.space(), object, text)), + ) + }; + let after_store = Store::parse(edit.as_bytes()).unwrap(); + let after_index = RevisionIndex::parse(&after_store).unwrap(); + let after_document = Document::parse(&after_index).unwrap(); + assert_eq!(after_document.pages().unwrap(), pages); + if let Some((sid, object, text)) = title_update { + let space = &after_document.spaces[&sid]; + let view = &space.revisions[&space.contexts[&ExGuid::default()]]; + assert!( + matches!(&view.nodes[&object].kind, Kind::RichText { text: actual, .. } if actual == text) + ); + } + for (sid, space) in &index.spaces { + let rid = space.labels[&(ExGuid::default(), 1)]; + assert_eq!( + format!("{:?}", index.resolve(*sid, rid).unwrap()), + format!("{:?}", after_index.resolve(*sid, rid).unwrap()) + ); + } + let before = current::current(&persisted); + let after = current::current(edit.as_bytes()); + let mut disk = disk::Disk { + visible: persisted.clone(), + durable: persisted.clone(), + operation: 0, + fail_at: (step[4] != 0).then_some(usize::from(u16::from_le_bytes([step[4], step[5]]))), + write_limit: if step[6] & 1 == 0 { 17 } else { 4096 }, + random: u64::from(step[7]) + 1, + }; + let result = edit.commit(&mut disk); + let observed = current::current(&disk.durable); + match result { + Ok(()) => assert_eq!(observed, after), + Err(error) => { + assert!(observed == before || observed == after); + match error.state { + CommitState::NotCommitted => assert_eq!(observed, before), + CommitState::Committed => assert_eq!(observed, after), + CommitState::Unknown => {} + } + } + } + persisted = disk.durable; + } +}); diff --git a/tools/native/created-pages.ps1 b/tools/native/created-pages.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..b77bdeecbc2c657398800e2ec865444325fe0bae --- /dev/null +++ b/tools/native/created-pages.ps1 @@ -0,0 +1,35 @@ +param([Parameter(Mandatory=$true)][string]$Root, [Parameter(Mandatory=$true)][string]$CloneHost) +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +& "$PSScriptRoot\cold-current.ps1" -Root $Root -CloneHost $CloneHost +$app = New-Object -ComObject OneNote.Application +$notebook = '' +try { + $app.OpenHierarchy((Join-Path $Root 'notebook'), '', [ref]$notebook, 0) + $section = '' + $app.OpenHierarchy('Lifecycle.one', $notebook, [ref]$section, 0) + $deadline = [DateTime]::UtcNow.AddSeconds(30) + do { + $hierarchy = '' + $app.GetHierarchy($section, 4, [ref]$hierarchy, 1) + [xml]$tree = $hierarchy + $pages = @($tree.SelectNodes('//*[local-name()="Page"]')) + if ($pages.Count -eq 13) { break } + Start-Sleep -Milliseconds 200 + } while ([DateTime]::UtcNow -lt $deadline) + $targets = @($pages | Where-Object { $_.GetAttribute('name').StartsWith('New ') }) + if ($pages.Count -ne 13 -or $targets.Count -ne 2) { throw 'Expected thirteen pages and two created title controls.' } + $inputs = Join-Path $Root 'inputs' + [void](New-Item -ItemType Directory -Path $inputs) + for ($i = 0; $i -lt $targets.Count; $i++) { + $pageId = [Security.SecurityElement]::Escape($targets[$i].GetAttribute('ID')) + $xml = "Native body $i after Rust page creation." + [IO.File]::WriteAllText((Join-Path $inputs ("native-$i.xml")), $xml, [Text.Encoding]::UTF8) + $app.UpdatePageContent($xml, [DateTime]::MinValue, 1, $false) + } + $app.SyncHierarchy($notebook) +} finally { + [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app) + [GC]::Collect() + [GC]::WaitForPendingFinalizers() +} diff --git a/tools/test_page_creation.py b/tools/test_page_creation.py new file mode 100644 index 0000000000000000000000000000000000000000..5a0c777299347384402c658db116e7fa874901d5 --- /dev/null +++ b/tools/test_page_creation.py @@ -0,0 +1,48 @@ +import json +from pathlib import Path +import subprocess +from tempfile import TemporaryDirectory +import unittest + +from document_model import EXPORTER, ordered_pages, walk +from verify_page_creation import compare + +FIXTURE = Path(__file__).resolve().parent.parent / 'corpus/page-lifecycle/creation' + + +class PageCreationTest(unittest.TestCase): + def test_created_pages_and_both_writers_survive_native_cold_reopens(self): + for source, capture in [('candidate', 'cold'), ('native/notebook', 'native/cold'), ('followup', 'followup/cold')]: + with self.subTest(source=source): + compare(FIXTURE / source, FIXTURE / capture) + + def test_duplicate_titles_keep_distinct_pages_through_native_and_rust_edits(self): + with TemporaryDirectory() as temporary: + identities = None + for ordinal, path in enumerate(('candidate', 'native/cold/notebook', 'followup/cold/notebook')): + output = Path(temporary) / str(ordinal) + subprocess.run([EXPORTER, FIXTURE / path / 'Lifecycle.one', output], check=True) + document = json.loads((output / 'document.json').read_text()) + pages = list(ordered_pages(document)) + self.assertEqual(len(pages), 13) + current = [(sid, page) for sid, _, _, page in pages] + self.assertEqual(len(set(current)), 13) + if identities is None: + identities = current + self.assertEqual(identities, current) + for index, (_, _, view, page) in enumerate(pages[-2:]): + title = ('Reviewed ' if ordinal == 2 else '') + 'New 🦋 é' + self.assertEqual(view['nodes'][view['roots']['2']]['kind']['title'], title) + expected = [title] + if ordinal: + expected.append(('Rust + ' if ordinal == 2 else '') + f'Native body {index} after Rust page creation.') + self.assertEqual([node['kind']['text'] for _, node in walk(view, page) + if node['kind']['type'] == 'RichText'], expected) + for at, titled in [(0, True), (10, False)]: + _, _, view, page = pages[at] + self.assertEqual(bool(view['nodes'][page]['structure']), titled) + self.assertEqual(view['nodes'][page]['children'], []) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/verify_page_creation.py b/tools/verify_page_creation.py new file mode 100644 index 0000000000000000000000000000000000000000..01d3d6a533550f36ba45d1cdd2ee7d44ea2eb208 --- /dev/null +++ b/tools/verify_page_creation.py @@ -0,0 +1,73 @@ +"""Compare created pages with native XML and cold-reopened object graphs.""" +import argparse +from copy import deepcopy +import json +from pathlib import Path +import runpy +import shutil +import subprocess +from tempfile import TemporaryDirectory +from uuid import UUID + +from document_model import EXPORTER, ordered_pages, view + +compare_document = runpy.run_path(str(Path(__file__).with_name('verify-document.py')))['compare'] + + +def compare(source, capture): + files = list(source.glob('*.one')) + assert len(files) == 1 + with TemporaryDirectory() as temporary: + temporary = Path(temporary) + native = temporary / 'native/read' + shutil.copytree(capture / 'read', native) + notebook = temporary / 'notebook' + notebook.mkdir() + (notebook / files[0].name).symlink_to(files[0].resolve()) + compare_document(notebook, native) + models = [] + for ordinal, file in enumerate((files[0], capture / 'notebook' / files[0].name)): + output = temporary / str(ordinal) + subprocess.run([EXPORTER, file, output], check=True) + models.append(json.loads((output / 'document.json').read_text())) + before, after = models + pages = list(ordered_pages(before)) + assert [(sid, page) for sid, _, _, page in pages] == [ + (sid, page) for sid, _, _, page in ordered_pages(after)] + assert before['root'] == after['root'] + assert before['spaces'].keys() == after['spaces'].keys() + filled = 0 + for sid in before['spaces']: + _, original = view(before, sid) + _, observed = view(after, sid) + expected = deepcopy(original['nodes']) + if sid == before['root']: + for series_id in original['nodes'][original['roots']['1']]['children']: + series = expected[series_id] + existing = [p for p in series['extra'][0] if p['id'] == 0x24003442] + added = [p for p in observed['nodes'][series_id]['extra'][0] if p['id'] == 0x24003442] + if existing or not added: + continue + copies = [] + for page_sid in series['spaces']: + (_, _, page_view, _), = [p for p in pages if p[0] == page_sid] + # Native uses the space GUID when it differs from the page-node GUID. + guid = UUID(page_sid.split('}')[0].strip('{')).bytes_le + salt = UUID('22a8c031-3600-42ee-b714-d7acda2435e8').bytes_le + metadata = '{' + str(UUID(bytes_le=bytes(a ^ b for a, b in zip(guid, salt, strict=True)))).upper() + '},1' + assert metadata not in expected + expected[metadata] = page_view['nodes'][page_view['roots']['2']] + copies.append(metadata) + filled += 1 + series['extra'][0].append({'id': 0x24003442, 'value': {'Objects': copies}}) + assert original['roots'] == observed['roots'] + assert expected == observed['nodes'], sid + print(f'Passed: {len(pages)} ordered pages and preserved active graphs; {filled} missing section metadata copies filled natively') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('source', type=Path) + parser.add_argument('capture', type=Path) + args = parser.parse_args() + compare(args.source, args.capture) -- 2.54.0