diff --git a/corpus/page-lifecycle/offline-creation/README.md b/corpus/page-lifecycle/offline-creation/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..b810c2a3bba92531b56898fc529a803196485726
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/README.md
@@ -0,0 +1,25 @@
+# Offline page creation
+
+Twelve independent replicas start from the same one-page section. Each queues
+a page titled `Same 🦋 é`, a body outline and a dependent body-text edit, then
+reopens its cache and synchronizes. All 36 receipts complete; page identities
+remain distinct and body text identifies each originating replica.
+
+`candidate/pages.one` is the resulting 13-page section. `cold` captures its fresh
+OneNote 2010 reopen. Native XML content/order and 216 explicit character-format
+comparisons pass. Active graphs compare exactly apart from the original seed
+page's previously absent section metadata copy, materialized natively with exact
+page metadata. The creation verifier checks that specific addition.
+`provenance.json` records the capture and removal of clone m6-cc15a5ee.
+
+Set `ONESTORE_OFFLINE_PAGE_OUTPUT` to an absolute, new output directory and run
+`cargo test -p onestore-offline --all-features --test sync
+page::offline_page_creation_rebases_with_dependent_edits_and_duplicate_titles
+-- --exact`. Capture the result with `tools/native_runner.py INPUT OUTPUT
+--expected-pages 13 --collect-notebook`. After building examples,
+`python -m unittest discover -s tools -p test_page_creation.py -v` verifies the
+retained native capture.
+
+This fixture is a serial reconciliation of twelve offline branches. The separate
+`offline_page` sanitizer target varies interleavings, interruption and reopen
+through the shared `tests/support/page_schedule.rs` model.
diff --git a/corpus/page-lifecycle/offline-creation/candidate/pages.one b/corpus/page-lifecycle/offline-creation/candidate/pages.one
new file mode 100644
index 0000000000000000000000000000000000000000..128dcb4b988fd6a590f0b2295ad69e66745727cd
Binary files /dev/null and b/corpus/page-lifecycle/offline-creation/candidate/pages.one differ
diff --git a/corpus/page-lifecycle/offline-creation/cold/notebook/pages.one b/corpus/page-lifecycle/offline-creation/cold/notebook/pages.one
new file mode 100644
index 0000000000000000000000000000000000000000..d2b161ceb75989c1955371ce4b86ad8608fe150e
Binary files /dev/null and b/corpus/page-lifecycle/offline-creation/cold/notebook/pages.one differ
diff --git a/corpus/page-lifecycle/offline-creation/cold/read/hierarchy.xml b/corpus/page-lifecycle/offline-creation/cold/read/hierarchy.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5853659977987a61b6cbfa87659e645a49a4ebf6
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/cold/read/hierarchy.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/page-lifecycle/offline-creation/cold/read/page-000.xml b/corpus/page-lifecycle/offline-creation/cold/read/page-000.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4ab89e04f2fb00acbdd2a18accaccb210afb3fee
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/cold/read/page-000.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/page-lifecycle/offline-creation/cold/read/page-001.xml b/corpus/page-lifecycle/offline-creation/cold/read/page-001.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6b2c687bb6cd5a4847c34c13ab97ccfa8b5942bd
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/cold/read/page-001.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/page-lifecycle/offline-creation/cold/read/page-002.xml b/corpus/page-lifecycle/offline-creation/cold/read/page-002.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0cb80c3d598a97b3cf49f24517a50fe2003ca836
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/cold/read/page-002.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/page-lifecycle/offline-creation/cold/read/page-003.xml b/corpus/page-lifecycle/offline-creation/cold/read/page-003.xml
new file mode 100644
index 0000000000000000000000000000000000000000..624ae72aef85046aae879bee4ba2366afe55af26
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/cold/read/page-003.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/page-lifecycle/offline-creation/cold/read/page-004.xml b/corpus/page-lifecycle/offline-creation/cold/read/page-004.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1866dec8c32dc4c15942d5dfdab07a6e741ab2e1
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/cold/read/page-004.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/page-lifecycle/offline-creation/cold/read/page-005.xml b/corpus/page-lifecycle/offline-creation/cold/read/page-005.xml
new file mode 100644
index 0000000000000000000000000000000000000000..72e2c7cbe3e6e6756b6c083e2e3731efd353de3c
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/cold/read/page-005.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/page-lifecycle/offline-creation/cold/read/page-006.xml b/corpus/page-lifecycle/offline-creation/cold/read/page-006.xml
new file mode 100644
index 0000000000000000000000000000000000000000..77aec796dcc1d229296a99cae0a838ff0f2ede48
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/cold/read/page-006.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/page-lifecycle/offline-creation/cold/read/page-007.xml b/corpus/page-lifecycle/offline-creation/cold/read/page-007.xml
new file mode 100644
index 0000000000000000000000000000000000000000..93079149c7b678adb6d125cbe07702ef714eb97b
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/cold/read/page-007.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/page-lifecycle/offline-creation/cold/read/page-008.xml b/corpus/page-lifecycle/offline-creation/cold/read/page-008.xml
new file mode 100644
index 0000000000000000000000000000000000000000..692f7fc332401b10fbfbfdc37a2a8218d7a7f2d0
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/cold/read/page-008.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/page-lifecycle/offline-creation/cold/read/page-009.xml b/corpus/page-lifecycle/offline-creation/cold/read/page-009.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3795d7efedb3e73366f3ccd9ee86a994658a5e12
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/cold/read/page-009.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/page-lifecycle/offline-creation/cold/read/page-010.xml b/corpus/page-lifecycle/offline-creation/cold/read/page-010.xml
new file mode 100644
index 0000000000000000000000000000000000000000..befb1525c176e8b6807fbf0fc38d5af6c92deaaf
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/cold/read/page-010.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/page-lifecycle/offline-creation/cold/read/page-011.xml b/corpus/page-lifecycle/offline-creation/cold/read/page-011.xml
new file mode 100644
index 0000000000000000000000000000000000000000..84ecfebaf488eeb483fc2710b6b56c2d8d29f33a
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/cold/read/page-011.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/page-lifecycle/offline-creation/cold/read/page-012.xml b/corpus/page-lifecycle/offline-creation/cold/read/page-012.xml
new file mode 100644
index 0000000000000000000000000000000000000000..84591ddf147a5a290d835bc4d34deb0a2a49ffe4
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/cold/read/page-012.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/page-lifecycle/offline-creation/provenance.json b/corpus/page-lifecycle/offline-creation/provenance.json
new file mode 100644
index 0000000000000000000000000000000000000000..09bde4165d3cb3ebc9fe348505bd3f09734a3993
--- /dev/null
+++ b/corpus/page-lifecycle/offline-creation/provenance.json
@@ -0,0 +1,27 @@
+{
+ "run": {
+ "notebook": "/Users/clo/dev/one/evidence/m10/offline-page-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-cc15a5ee",
+ "hostname": "ONE-M6-CC15A5EE"
+ },
+ "teardown": {
+ "absent": true
+ }
+}
diff --git a/crates/onestore-offline/README.md b/crates/onestore-offline/README.md
index 3b7de1d28cae619876fbb2d18d24fcddcdcb599b..59cc0655dced263b3bffe84b9a01a6874feea914 100644
--- a/crates/onestore-offline/README.md
+++ b/crates/onestore-offline/README.md
@@ -32,11 +32,21 @@ the new text for subsequent offline edits. Pending entries expose
`Operation::Text(TextEdit)`, `Operation::Insert(Insertion)`,
`Operation::Format(FormatEdit)`, `Operation::Split(SplitEdit)`,
`Operation::Join(JoinEdit)`, `Operation::Outline(OutlineEdit)` and
-`Operation::Tree(TreeEdit)` through their
+`Operation::Tree(TreeEdit)` and `Operation::CreatePage(PageCreation)` through their
`operation` field. Synchronization applies these in queue order, so an inserted
outline can precede its paragraphs and their later edits. Missing anchors or
existing insertion identities preserve a conflict and the complete local image.
+`create_page` accepts the core `PageCreation` intent and queues its page space
+and section entry as one publication. Subsequent outlines and title edits use
+the intent's stable identities immediately after local acknowledgement.
+Independent page additions rebase against the current section order; duplicate
+titles remain distinct. An unavailable or no-longer-leading insertion anchor
+produces `StructureChanged`. `rebase_page_creation_conflict(id, local, remote,
+before)` reviews a replacement anchor against both cache images while retaining
+the new page and dependent object identities. Existing page identities require
+reconciliation; a matching page alone does not establish a receipt.
+
`split` and `join` accept the core `ParagraphSplit` and `ParagraphJoin` intents.
Splits retain allocated identities when the original UTF-16 boundary rebases;
dependent edits can address `ParagraphSplit::text_object()` immediately after
@@ -116,7 +126,10 @@ remain the same before replay or review; later queued edits keep their targets.
Uncertain tree attempts require their original revision for confirmation, even
when an independent move or deletion has the same visible effect.
-Recognized earlier caches migrate transactionally to version nine. Version-eight
+Recognized earlier caches migrate transactionally to version ten. Publication
+attempts retain every changed space's revision; legacy attempts retain their
+single-space evidence. Earlier recovery archives remain readable without migration.
+Version-eight
deletion observations retain their original identity-sensitive preconditions;
explicit conflict review upgrades them to the immutable-content comparison.
The migration retains images, local IDs, publication attempts, conflicts,
@@ -134,7 +147,8 @@ retrying.
`sync_once(&mut remote)` processes the oldest pending edit through a `Remote`
implementation, returning its ID and `EditStatus`. A durable `Published` receipt
survives reopening. Publication attempts are recorded before network I/O; a retained
-attempted revision requires comparison, flushing and refreshed header version
+attempt requires every recorded revision to remain present, followed by comparison,
+flushing and refreshed header version
metadata before acknowledgement. If a formatting attempt's revision is missing,
the complete requested effect can instead be confirmed on a uniquely aligned
range; its receipt identifies that confirmed current revision. Otherwise the
diff --git a/crates/onestore-offline/examples/cache_probe.rs b/crates/onestore-offline/examples/cache_probe.rs
index d7ab493d9e48d0f610994266a1a9cc32b6cb2f68..9a3f1e4b3d2c729926f79fe3c337c49193dcaa66 100644
--- a/crates/onestore-offline/examples/cache_probe.rs
+++ b/crates/onestore-offline/examples/cache_probe.rs
@@ -139,6 +139,7 @@ fn main() -> Result<(), Box> {
*value as u64 - 6
}
onestore_offline::Operation::Split(_)
+ | onestore_offline::Operation::CreatePage(_)
| onestore_offline::Operation::Join(_)
| onestore_offline::Operation::Outline(_)
| onestore_offline::Operation::Tree(_) => {
diff --git a/crates/onestore-offline/src/lib.rs b/crates/onestore-offline/src/lib.rs
index e95c56c6e1c9c577930580b3fcc09c0715d1c6be..8bad5718bdd02719993f4aec63a5983935b04250 100644
--- a/crates/onestore-offline/src/lib.rs
+++ b/crates/onestore-offline/src/lib.rs
@@ -2,7 +2,7 @@
#![doc = include_str!("../README.md")]
use onestore::{
- ExGuid, Insertion, PreparedEdit, RevisionIndex, Store,
+ ExGuid, Insertion, PageCreation, PreparedEdit, RevisionIndex, Store,
document::{Document, Kind},
};
use rusqlite::{Connection, OpenFlags, TransactionBehavior, params};
@@ -51,7 +51,7 @@ pub enum Error {
type Result = std::result::Result;
const APPLICATION_ID: u32 = 0x4f4e454f;
-const SCHEMA_VERSION: u32 = 9;
+const SCHEMA_VERSION: u32 = 10;
/// Text and its observed precondition, retained across cache reopen and rebasing.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
@@ -66,6 +66,7 @@ pub struct TextEdit {
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub enum Operation {
+ CreatePage(PageCreation),
Text(TextEdit),
Insert(Insertion),
Format(FormatEdit),
@@ -232,6 +233,17 @@ impl Replica {
self.record(source, space, Operation::Insert(insertion.clone()), &edit)
}
+ /// Queues a new page and its section entry with stable identities for dependent edits.
+ pub fn create_page(&self, source: &[u8], page: &PageCreation) -> Result