From 09ee1d6454d9862cd2b895fea88eee8541e6caaf Mon Sep 17 00:00:00 2001 From: clover caruso Date: Fri, 11 Sep 2026 01:40:12 -0700 Subject: [PATCH] refactor: merge the notebook, offline, SMB and diagnostic crates into notebook One application-facing crate: notebook discovery (notebook::discover), the SQLite replica with reconciliation, assets and recovery at the crate root, embedded SMB access behind the smb feature (notebook::smb, including the SmbRemote adapter), and the onestore-diagnostic bridge as a binary. Tools, fuzz targets and documentation follow the new paths; the diagnostic binary keeps its name for the Python tooling. Validation: 457 workspace tests (nextest), 5 README doctests, Clippy and fmt; the fuzz workspace builds. Assisted-by: claude-fable-5.1 --- Cargo.lock | 64 +++------- crates/notebook/Cargo.toml | 37 ++++++ .../{onestore-offline => notebook}/README.md | 112 ++++++++++++++++-- .../examples/cache_probe.rs | 22 ++-- .../examples/discover.rs | 6 +- .../examples/document.rs | 6 +- .../examples/externalize_fixture.rs | 0 .../examples/recovery_probe.rs | 4 +- .../examples/smb_concurrent_client.rs | 2 +- .../examples/smb_offline_client.rs | 6 +- .../examples/smb_reconnect_client.rs | 2 +- .../examples/support/view.rs | 0 .../src/assets.rs | 4 +- .../src/bin/onestore-diagnostic.rs} | 6 +- .../src/lib.rs => notebook/src/discover.rs} | 3 +- .../src => notebook/src/discover}/source.rs | 4 +- .../src/formatting.rs | 0 .../{onestore-offline => notebook}/src/lib.rs | 10 +- .../src/outline.rs | 0 .../src/pages.rs | 0 .../src/paragraph.rs | 0 .../src/rebase.rs | 0 .../src/recovery.rs | 0 .../src/schema.rs | 0 .../src => notebook/src/smb}/directory.rs | 0 .../src/lib.rs => notebook/src/smb/mod.rs} | 5 +- .../src/smb.rs => notebook/src/smb/remote.rs} | 2 +- .../src => notebook/src/smb}/tests.rs | 0 .../src => notebook/src/smb}/tests/faults.rs | 0 .../src/sync.rs | 0 .../src/tree.rs | 0 .../src/worker.rs | 0 .../tests/assets.rs | 36 +++--- .../tests/cache.rs | 23 ++-- .../tests/discovery.rs | 6 +- .../tests/external_assets.rs} | 16 +-- .../tests/support/page_schedule.rs | 2 +- .../tests/support/tree_schedule.rs | 2 +- .../tests/sync.rs | 19 ++- .../tests/sync/outline.rs | 4 +- .../tests/sync/page.rs | 2 +- .../tests/sync/pages.rs | 2 +- .../tests/sync/tree.rs | 2 +- crates/onestore-diagnostic/Cargo.toml | 11 -- crates/onestore-notebook/Cargo.toml | 20 ---- crates/onestore-notebook/README.md | 25 ---- crates/onestore-offline/Cargo.toml | 23 ---- crates/onestore-smb/Cargo.toml | 13 -- crates/onestore-smb/README.md | 63 ---------- crates/onestore/README.md | 13 +- fuzz/Cargo.lock | 38 +++--- fuzz/Cargo.toml | 2 +- fuzz/fuzz_targets/offline_page.rs | 2 +- fuzz/fuzz_targets/offline_paragraph.rs | 2 +- fuzz/fuzz_targets/offline_tree.rs | 2 +- tools/TESTING.md | 2 +- tools/native_maintenance.py | 2 +- tools/smb_faults.py | 6 +- tools/test_offline_document_history.py | 2 +- tools/test_smb_directory.py | 6 +- 60 files changed, 286 insertions(+), 355 deletions(-) create mode 100644 crates/notebook/Cargo.toml rename crates/{onestore-offline => notebook}/README.md (76%) rename crates/{onestore-offline => notebook}/examples/cache_probe.rs (92%) rename crates/{onestore-notebook => notebook}/examples/discover.rs (90%) rename crates/{onestore-notebook => notebook}/examples/document.rs (96%) rename crates/{onestore-notebook => notebook}/examples/externalize_fixture.rs (100%) rename crates/{onestore-offline => notebook}/examples/recovery_probe.rs (95%) rename crates/{onestore-smb => notebook}/examples/smb_concurrent_client.rs (93%) rename crates/{onestore-offline => notebook}/examples/smb_offline_client.rs (99%) rename crates/{onestore-smb => notebook}/examples/smb_reconnect_client.rs (99%) rename crates/{onestore-offline => notebook}/examples/support/view.rs (100%) rename crates/{onestore-offline => notebook}/src/assets.rs (97%) rename crates/{onestore-diagnostic/src/main.rs => notebook/src/bin/onestore-diagnostic.rs} (96%) rename crates/{onestore-notebook/src/lib.rs => notebook/src/discover.rs} (99%) rename crates/{onestore-notebook/src => notebook/src/discover}/source.rs (96%) rename crates/{onestore-offline => notebook}/src/formatting.rs (100%) rename crates/{onestore-offline => notebook}/src/lib.rs (99%) rename crates/{onestore-offline => notebook}/src/outline.rs (100%) rename crates/{onestore-offline => notebook}/src/pages.rs (100%) rename crates/{onestore-offline => notebook}/src/paragraph.rs (100%) rename crates/{onestore-offline => notebook}/src/rebase.rs (100%) rename crates/{onestore-offline => notebook}/src/recovery.rs (100%) rename crates/{onestore-offline => notebook}/src/schema.rs (100%) rename crates/{onestore-smb/src => notebook/src/smb}/directory.rs (100%) rename crates/{onestore-smb/src/lib.rs => notebook/src/smb/mod.rs} (99%) rename crates/{onestore-offline/src/smb.rs => notebook/src/smb/remote.rs} (97%) rename crates/{onestore-smb/src => notebook/src/smb}/tests.rs (100%) rename crates/{onestore-smb/src => notebook/src/smb}/tests/faults.rs (100%) rename crates/{onestore-offline => notebook}/src/sync.rs (100%) rename crates/{onestore-offline => notebook}/src/tree.rs (100%) rename crates/{onestore-offline => notebook}/src/worker.rs (100%) rename crates/{onestore-offline => notebook}/tests/assets.rs (93%) rename crates/{onestore-offline => notebook}/tests/cache.rs (97%) rename crates/{onestore-notebook => notebook}/tests/discovery.rs (98%) rename crates/{onestore-notebook/tests/assets.rs => notebook/tests/external_assets.rs} (92%) rename crates/{onestore-offline => notebook}/tests/support/page_schedule.rs (99%) rename crates/{onestore-offline => notebook}/tests/support/tree_schedule.rs (99%) rename crates/{onestore-offline => notebook}/tests/sync.rs (99%) rename crates/{onestore-offline => notebook}/tests/sync/outline.rs (99%) rename crates/{onestore-offline => notebook}/tests/sync/page.rs (99%) rename crates/{onestore-offline => notebook}/tests/sync/pages.rs (99%) rename crates/{onestore-offline => notebook}/tests/sync/tree.rs (99%) delete mode 100644 crates/onestore-diagnostic/Cargo.toml delete mode 100644 crates/onestore-notebook/Cargo.toml delete mode 100644 crates/onestore-notebook/README.md delete mode 100644 crates/onestore-offline/Cargo.toml delete mode 100644 crates/onestore-smb/Cargo.toml delete mode 100644 crates/onestore-smb/README.md diff --git a/Cargo.lock b/Cargo.lock index 83fa0aca68d4f841b8bb4b95a13d153854fd6cd0..773eff13833a5ec5fa04a8116edd92ce7c27b988 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1561,6 +1561,22 @@ dependencies = [ "libc", ] +[[package]] +name = "notebook" +version = "0.1.0" +dependencies = [ + "onestore", + "rusqlite", + "serde", + "serde_json", + "sha2", + "smb2", + "tempfile", + "thiserror 2.0.20", + "tokio", + "zeroize", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1923,54 +1939,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "onestore-diagnostic" -version = "0.1.0" -dependencies = [ - "onestore", - "onestore-notebook", - "serde", - "serde_json", -] - -[[package]] -name = "onestore-notebook" -version = "0.1.0" -dependencies = [ - "onestore", - "onestore-smb", - "serde", - "serde_json", - "tempfile", - "thiserror 2.0.20", - "zeroize", -] - -[[package]] -name = "onestore-offline" -version = "0.1.0" -dependencies = [ - "onestore", - "onestore-notebook", - "onestore-smb", - "rusqlite", - "serde", - "serde_json", - "sha2", - "tempfile", - "thiserror 2.0.20", -] - -[[package]] -name = "onestore-smb" -version = "0.1.0" -dependencies = [ - "onestore", - "serde_json", - "smb2", - "tokio", -] - [[package]] name = "orbclient" version = "0.3.55" diff --git a/crates/notebook/Cargo.toml b/crates/notebook/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..6c7a47eadcdb9156907dd56c131fe16884961f10 --- /dev/null +++ b/crates/notebook/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "notebook" +version = "0.1.0" +edition = "2024" +publish = false + +[features] +smb = ["dep:smb2", "dep:tokio"] +protected = ["onestore/protected", "dep:zeroize"] + +[dependencies] +onestore = { path = "../onestore" } +rusqlite = { version = "=0.40.2", features = ["bundled", "backup"] } +thiserror = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tempfile = "3" +sha2 = "0.11" +smb2 = { version = "=0.21.0", optional = true } +tokio = { version = "1", features = ["rt-multi-thread", "time"], optional = true } +zeroize = { version = "1.9.0", optional = true } + +[dev-dependencies] +serde_json = "1" +tempfile = "3" + +[[example]] +name = "smb_offline_client" +required-features = ["smb"] + +[[example]] +name = "smb_reconnect_client" +required-features = ["smb"] + +[[example]] +name = "smb_concurrent_client" +required-features = ["smb"] diff --git a/crates/onestore-offline/README.md b/crates/notebook/README.md similarity index 76% rename from crates/onestore-offline/README.md rename to crates/notebook/README.md index fd4aa367554e978a9910f58f70608df58ffa3d03..7c6a6a3d4371e36f277e891cb5265351e6061968 100644 --- a/crates/onestore-offline/README.md +++ b/crates/notebook/README.md @@ -1,6 +1,8 @@ -# onestore-offline +# notebook -Durable local editing for a OneNote file, in an optional sibling crate. The current +The application-facing crate: notebook discovery, a durable local replica with +reconnect reconciliation, external-asset caching, recovery export and optional +embedded SMB access. Durable local editing for a OneNote file: the current foundation stores a complete working image and typed editing intents in a local SQLite database. `sync_once` provides a reconciliation step and `start_sync` owns automatic polling and reconnects. Local success does not @@ -8,7 +10,7 @@ acknowledge publication to a shared notebook. ```no_run use onestore::ExGuid; -use onestore_offline::Replica; +use notebook::Replica; # fn example(path: &std::path::Path, source: &[u8], space: ExGuid, text: ExGuid) # -> Result<(), Box> { // `space` and `text` are identities from the supplied section's document model. @@ -93,7 +95,7 @@ uncertain publication attempts cannot be repositioned through conflict review. ```no_run use onestore::{ExGuid, Insertion, TextAttribute}; -use onestore_offline::{EditStatus, Replica}; +use notebook::{EditStatus, Replica}; # fn add_outline(cache: &Replica, space: ExGuid, page: ExGuid) # -> Result<(), Box> { let outline = Insertion::outline(page, 144.0, 216.0, "Offline outline", "Author")? @@ -200,7 +202,7 @@ cannot be rebased, and selecting text that already equals the replacement does not create a publication acknowledgement. With the optional `smb` feature, `SmbRemote::new(client, path, limit)` binds an -`onestore_smb::Client` to one share-relative file and snapshot limit. Remote identity uses the logical root +`notebook::smb::Client` to one share-relative file and snapshot limit. Remote identity uses the logical root object space, which survives the tested native compaction that replaces the file ID. An `Arc` can own one background worker. Supply a connection factory, poll @@ -218,10 +220,10 @@ statuses; durable edit state remains available through `status`. ```no_run # #[cfg(feature = "smb")] -# fn example(cache: std::sync::Arc, username: String, password: String) +# fn example(cache: std::sync::Arc, username: String, password: String) # -> Result<(), Box> { -use onestore_offline::SmbRemote; -use onestore_smb::{Client, Credentials}; +use notebook::SmbRemote; +use notebook::smb::{Client, Credentials}; use std::time::Duration; let worker = cache.start_sync( @@ -281,7 +283,7 @@ edit. Archives contain notebook content and use a separate database identity, so `Replica::open` rejects them as writable caches. ```no_run -use onestore_offline::{Recovery, Replica}; +use notebook::{Recovery, Replica}; # fn example(cache: &Replica) -> Result<(), Box> { cache.export_recovery("review.sqlite")?; let review = Recovery::open("review.sqlite")?; @@ -304,7 +306,7 @@ archive is evidence for a reviewed recovery decision, not a second active queue. ## Downloaded media `fetch_asset(source, section, filename, limit)` resolves a declared external -payload through `onestore-notebook::Source` and durably caches its exact bytes. +payload through `notebook::discover::Source` and durably caches its exact bytes. The section path is relative to the source root; the filename comes from a `FileDataReference::External` in the retained working or remote image. Local and SMB sources use the same API. Downloads release the cache mutex during network @@ -321,3 +323,93 @@ identity return `AssetChanged` and preserve the previous download. Downloads do not change pending edits, publication attempts or receipts. Recovery archives include cached media and expose the same bounded `cached_asset` lookup. Schema-4 archives remain readable without migration and contain no media cache. + +## Discovery + +`notebook::discover` provides read-only notebook discovery over a caller-supplied +root, keeping directory traversal out of the single-file storage parser. + +Discovery returns ordered sections and nested groups with file identities and +share-relative paths. Section display-name overrides remain distinct from file +names. TOC references whose identities are absent from the directory remain +inspectable; a cached filename never substitutes for an identity match. +Reserved `_onefiles` directories are excluded from section-group traversal. +Encrypted sections and valid storage with an unreadable document graph retain +their identity as `Locked` or `Unreadable`, without being presented as empty pages. +Malformed storage, failed reads and ambiguous identities reject the discovery. + +Each file read must be a consistent, bounded snapshot. The result is an +observation across multiple files, not an atomic notebook transaction or +authorization to publish an edit. Refresh rejects observed topology changes and +duplicate physical files with the same logical identity. Retain the last accepted +catalog if discovery fails; a connection failure does not mean files were deleted. + +`read_external_asset` resolves a validated UUID `.onebin` filename beneath the +selected section's sibling `_onefiles` folder and returns exact bounded bytes. +Missing files, permissions and size failures retain their I/O error kinds; an +empty payload is a successful empty buffer. Embedded payloads remain available +directly from the core document model. + +## SMB (feature `smb`) + +`notebook::smb` provides blocking SMB access for OneNote sections and +table-of-contents files. The core `onestore` crate remains independent of network runtimes. This is an +experimental Rust API with native interoperability evidence in the repository's +[Milestone 9](../../evidence/MILESTONE9.md). + +```no_run +use notebook::smb::{Client, Credentials}; +use std::time::Duration; + +let client = Client::connect( + "server:445", + "notes", + Credentials { username: "user", password: "password", domain: "" }, + Duration::from_secs(5), +)?; +let snapshot = client.read("Personal/Video.one", 64 * 1024 * 1024)?; +let store = onestore::Store::parse(&snapshot)?; +let revisions = onestore::RevisionIndex::parse(&store)?; +let document = onestore::document::Document::parse(&revisions)?; +# Ok::<(), Box>(()) +``` + +Paths are relative to the share. The read limit bounds the complete physical +snapshot. Call from a background thread outside a Tokio runtime. Use identities +from the document and the same snapshot with `Client::commit_text` or +`Client::commit_property_bytes`; their errors retain `onestore::CommitState`. +`PreparedEdit::{text,insert,format}` separate preparation from I/O: inspect the immutable image +and persist the intended revision identity before `Client::commit_prepared`. +`Client::confirm_snapshot` compares and flushes an observed image, then refreshes +its header version metadata without adding a revision. The caller must first +establish which intents that image contains and reread before another commit. + +Readers use shared native guards while writers publish under native write-open +and byte-lock exclusion. Maintenance is excluded during each operation; pathname +identity is checked after acquiring the guards. Connection loss retires the +client. Reconnect for subsequent operations, and reconcile an `Unknown` edit +before retrying it. The transport does not automatically replay requests. + +`Client::read_dir(path, entry_limit)` enumerates a directory, including the share +root with an empty path. It follows every response page and returns no partial +list on interruption, entry-limit overflow or close failure. Entries retain exact +Unicode names, observed sizes and MS-FSCC attributes, including directory/reparse +flags. Concurrent directory changes are not an atomic snapshot; repeated names +are rejected with `ResourceBusy`. Notebook identities come from the files, not +directory names or sizes. Missing paths, denied access and non-directory paths +have distinct I/O error kinds. + +`Client::read_asset(path, byte_limit)` reads an external payload under a read-only +share handle that excludes writes and deletion. Empty files succeed; limits, +interrupted reads and failed close never return partial bytes. This payload read +does not parse a OneStore header or acquire its reader-coordination bytes. + +`python3 tools/test_smb_directory.py VM OUTPUT` checks a caller-owned disposable +Linux lab VM against its filesystem listing and interrupts directory requests, +responses and close. It creates synthetic files in that VM; the caller retains +responsibility for VM teardown. The parser also has bounded-record/truncation +tests independent of the server. + +Device and simulator builds link for iOS. Native acceptance uses disposable +OneNote 2010 clients and Samba; it does not establish on-device execution or +physical power-loss durability. diff --git a/crates/onestore-offline/examples/cache_probe.rs b/crates/notebook/examples/cache_probe.rs similarity index 92% rename from crates/onestore-offline/examples/cache_probe.rs rename to crates/notebook/examples/cache_probe.rs index c27e00cf46fcc8da856d280dd9874cea30f89cca..aef2d6e75b0d81db0965cedf76778955b6d73208 100644 --- a/crates/onestore-offline/examples/cache_probe.rs +++ b/crates/notebook/examples/cache_probe.rs @@ -1,8 +1,8 @@ +use notebook::Replica; use onestore::{ ExGuid, Insertion, RevisionIndex, Store, TextAttribute, document::{Document, Kind}, }; -use onestore_offline::Replica; use std::{ io::{self, BufRead, Write}, path::Path, @@ -86,7 +86,7 @@ fn main() -> Result<(), Box> { let mut font_size = None; for pending in cache.pending()? { let operation = match pending.operation { - onestore_offline::Operation::Text(edit) => { + notebook::Operation::Text(edit) => { assert_eq!(operation_kind, "text"); assert_eq!(edit.object, target); assert_eq!(edit.before, expected); @@ -99,7 +99,7 @@ fn main() -> Result<(), Box> { assert_eq!(edit.replacement, expected); operation } - onestore_offline::Operation::Insert(insertion) => { + notebook::Operation::Insert(insertion) => { assert_eq!(operation_kind, "insert"); let Kind::RichText { text, .. } = &revision.nodes[&insertion.text_object()].kind @@ -123,7 +123,7 @@ fn main() -> Result<(), Box> { assert!(revision.nodes[&page].children.contains(&insertion.object())); operation } - onestore_offline::Operation::Format(edit) => { + notebook::Operation::Format(edit) => { assert_eq!(operation_kind, "format"); assert_eq!(edit.object, target); assert_eq!(edit.before, expected); @@ -138,12 +138,12 @@ fn main() -> Result<(), Box> { font_size = Some(*value); *value as u64 - 6 } - onestore_offline::Operation::Split(_) - | onestore_offline::Operation::CreatePage(_) - | onestore_offline::Operation::Pages(_) - | onestore_offline::Operation::Join(_) - | onestore_offline::Operation::Outline(_) - | onestore_offline::Operation::Tree(_) => { + notebook::Operation::Split(_) + | notebook::Operation::CreatePage(_) + | notebook::Operation::Pages(_) + | notebook::Operation::Join(_) + | notebook::Operation::Outline(_) + | notebook::Operation::Tree(_) => { panic!("Unexpected operation for this fixture") } }; @@ -178,7 +178,7 @@ fn main() -> Result<(), Box> { } assert_eq!(mode, "edit"); assert!( - matches!(Replica::open(path), Err(onestore_offline::Error::Database(error)) if error.sqlite_error_code() == Some(rusqlite::ErrorCode::DatabaseBusy)) + matches!(Replica::open(path), Err(notebook::Error::Database(error)) if error.sqlite_error_code() == Some(rusqlite::ErrorCode::DatabaseBusy)) ); println!("ready"); io::stdout().flush()?; diff --git a/crates/onestore-notebook/examples/discover.rs b/crates/notebook/examples/discover.rs similarity index 90% rename from crates/onestore-notebook/examples/discover.rs rename to crates/notebook/examples/discover.rs index fb73310d27b998ff66f3dde3a4f2dfc364e0829b..c243228318a5733038cf1eee4accf536ad8eae66 100644 --- a/crates/onestore-notebook/examples/discover.rs +++ b/crates/notebook/examples/discover.rs @@ -1,4 +1,4 @@ -use onestore_notebook::{Limits, Local, discover}; +use notebook::discover::{Limits, Local, discover}; fn main() -> Result<(), Box> { let args: Vec<_> = std::env::args_os().skip(1).collect(); @@ -11,7 +11,7 @@ fn main() -> Result<(), Box> { [root] => discover(&mut Local::open(root)?, limits)?, #[cfg(feature = "smb")] [flag, address, share, root] if flag == "--smb" => { - use onestore_smb::{Client, Credentials}; + use notebook::smb::{Client, Credentials}; let username = std::env::var("ONESTORE_SMB_USERNAME").unwrap_or_default(); let password = std::env::var("ONESTORE_SMB_PASSWORD").unwrap_or_default(); let domain = std::env::var("ONESTORE_SMB_DOMAIN").unwrap_or_default(); @@ -26,7 +26,7 @@ fn main() -> Result<(), Box> { std::time::Duration::from_secs(5), )?; discover( - &mut onestore_notebook::Smb::new( + &mut notebook::discover::Smb::new( &client, root.to_str().ok_or("Use a UTF-8 notebook path")?, )?, diff --git a/crates/onestore-notebook/examples/document.rs b/crates/notebook/examples/document.rs similarity index 96% rename from crates/onestore-notebook/examples/document.rs rename to crates/notebook/examples/document.rs index f38d1d5e6ad757f9faed31a8e013ecd12638fd9a..d1a68175db8f8b35932e59946be4ff683f581a7b 100644 --- a/crates/onestore-notebook/examples/document.rs +++ b/crates/notebook/examples/document.rs @@ -90,7 +90,7 @@ fn main() -> Result<(), Box> { .parent() .filter(|parent| !parent.as_os_str().is_empty()) .unwrap_or_else(|| Path::new(".")); - let mut source = onestore_notebook::Local::open(parent)?; + let mut source = notebook::discover::Local::open(parent)?; let section = source_path .file_name() .and_then(|name| name.to_str()) @@ -117,7 +117,7 @@ fn main() -> Result<(), Box> { assets.push(serde_json::json!({"reference":reference,"path":null,"error":{"kind":"Unsupported","message":"Protected external payload export is not supported"}})); continue; } - match onestore_notebook::read_external_asset( + match notebook::discover::read_external_asset( &mut source, section, filename, @@ -126,7 +126,7 @@ fn main() -> Result<(), Box> { Ok(bytes) => Cow::Owned(bytes), Err(error) => { let kind = match &error { - onestore_notebook::Error::Io { error, .. } => { + notebook::discover::Error::Io { error, .. } => { format!("{:?}", error.kind()) } _ => "InvalidData".into(), diff --git a/crates/onestore-notebook/examples/externalize_fixture.rs b/crates/notebook/examples/externalize_fixture.rs similarity index 100% rename from crates/onestore-notebook/examples/externalize_fixture.rs rename to crates/notebook/examples/externalize_fixture.rs diff --git a/crates/onestore-offline/examples/recovery_probe.rs b/crates/notebook/examples/recovery_probe.rs similarity index 95% rename from crates/onestore-offline/examples/recovery_probe.rs rename to crates/notebook/examples/recovery_probe.rs index 60fc68a028e5a3a72bf6b2cf9ac861960a861556..a573a56a213c052f6e00ede4e86e5241fcd4b925 100644 --- a/crates/onestore-offline/examples/recovery_probe.rs +++ b/crates/notebook/examples/recovery_probe.rs @@ -3,8 +3,8 @@ mod support { } use support::view::view; +use notebook::{EditStatus, Remote, Replica}; use onestore::{CommitError, CommitIo, PreparedEdit}; -use onestore_offline::{EditStatus, Remote, Replica}; use serde_json::json; use std::{ env, @@ -98,7 +98,7 @@ fn report(cache: &Replica, root: &Path) -> Result<(), Box json!({"event":"state", "status":status, "revision":revision, "local_text":local.text, "remote_text":remote.text, "remote_revision":remote.revision.to_string(), "pending":cache.pending()?.iter().map(|pending|match &pending.operation { - onestore_offline::Operation::Text(edit) => json!({"id":pending.id,"before":edit.before,"replacement":edit.replacement,"range":[edit.range.start,edit.range.end]}), + notebook::Operation::Text(edit) => json!({"id":pending.id,"before":edit.before,"replacement":edit.replacement,"range":[edit.range.start,edit.range.end]}), operation => json!({"id":pending.id,"operation":operation}), }).collect::>() }) ); diff --git a/crates/onestore-smb/examples/smb_concurrent_client.rs b/crates/notebook/examples/smb_concurrent_client.rs similarity index 93% rename from crates/onestore-smb/examples/smb_concurrent_client.rs rename to crates/notebook/examples/smb_concurrent_client.rs index c54cc91159f7125667d07638bc77ebc4f148f9ab..9c55d513e961b1c553f0b0957032c8fb94cd6d2e 100644 --- a/crates/onestore-smb/examples/smb_concurrent_client.rs +++ b/crates/notebook/examples/smb_concurrent_client.rs @@ -1,7 +1,7 @@ #[path = "../../onestore/examples/support/concurrent.rs"] mod concurrent; -use onestore_smb::{Client, Credentials}; +use notebook::smb::{Client, Credentials}; use std::{env, time::Duration}; fn main() -> Result<(), Box> { diff --git a/crates/onestore-offline/examples/smb_offline_client.rs b/crates/notebook/examples/smb_offline_client.rs similarity index 99% rename from crates/onestore-offline/examples/smb_offline_client.rs rename to crates/notebook/examples/smb_offline_client.rs index 4440ebb379af1b2f9c7c252b0f098919ff6edb41..aa68d9082ca051bc17989c20f05b555e9678e48b 100644 --- a/crates/onestore-offline/examples/smb_offline_client.rs +++ b/crates/notebook/examples/smb_offline_client.rs @@ -1,12 +1,12 @@ #[path = "../../onestore/examples/support/concurrent.rs"] mod concurrent; +use notebook::smb::{Client, Credentials}; +use notebook::{EditStatus, Error, Remote, Replica, SmbRemote}; use onestore::{ CommitError, CommitState, ExGuid, Insertion, ParagraphJoin, ParagraphSplit, PreparedEdit, RevisionIndex, Store, TextAttribute, TreeEdit, document::Document, }; -use onestore_offline::{EditStatus, Error, Remote, Replica, SmbRemote}; -use onestore_smb::{Client, Credentials}; use serde_json::json; use std::{ env, @@ -606,7 +606,7 @@ fn main() -> Result<(), Box> { let local = cache.snapshot()?; let remote = cache.remote_snapshot()?; let current = view(&remote)?; - let onestore_offline::Operation::Text(edit) = &intent.operation else { + let notebook::Operation::Text(edit) = &intent.operation else { return Err(format!( "Document intent {} requires review: {:?}", intent.id, diff --git a/crates/onestore-smb/examples/smb_reconnect_client.rs b/crates/notebook/examples/smb_reconnect_client.rs similarity index 99% rename from crates/onestore-smb/examples/smb_reconnect_client.rs rename to crates/notebook/examples/smb_reconnect_client.rs index 1c88874791fe610c2b3fb6f7221014bbd0de45ae..d72a42c9c0a88db608811c8e5b08601222c2f8ee 100644 --- a/crates/onestore-smb/examples/smb_reconnect_client.rs +++ b/crates/notebook/examples/smb_reconnect_client.rs @@ -1,11 +1,11 @@ #[path = "../../onestore/examples/support/concurrent.rs"] mod concurrent; +use notebook::smb::{Client, Credentials}; use onestore::{ CommitError, CommitState, ExGuid, RevisionIndex, Store, document::{Document, Kind}, }; -use onestore_smb::{Client, Credentials}; use serde_json::json; use std::{ cell::RefCell, diff --git a/crates/onestore-offline/examples/support/view.rs b/crates/notebook/examples/support/view.rs similarity index 100% rename from crates/onestore-offline/examples/support/view.rs rename to crates/notebook/examples/support/view.rs diff --git a/crates/onestore-offline/src/assets.rs b/crates/notebook/src/assets.rs similarity index 97% rename from crates/onestore-offline/src/assets.rs rename to crates/notebook/src/assets.rs index 9f61197f3a188cfb90d3bc60dc65502595b8a7a9..fd74b47164ef17cd5230e20d8b0aca938eae9c01 100644 --- a/crates/onestore-offline/src/assets.rs +++ b/crates/notebook/src/assets.rs @@ -20,7 +20,7 @@ impl Replica { /// Network I/O does not hold the cache mutex; a stale reference fails before local publication. pub fn fetch_asset( &self, - source: &mut impl onestore_notebook::Source, + source: &mut impl crate::discover::Source, section: &str, filename: &str, limit: usize, @@ -39,7 +39,7 @@ impl Replica { .into()); } } - let bytes = onestore_notebook::read_external_asset(source, section, filename, limit)?; + let bytes = crate::discover::read_external_asset(source, section, filename, limit)?; let mut connection = self .connection .lock() diff --git a/crates/onestore-diagnostic/src/main.rs b/crates/notebook/src/bin/onestore-diagnostic.rs similarity index 96% rename from crates/onestore-diagnostic/src/main.rs rename to crates/notebook/src/bin/onestore-diagnostic.rs index 5b39a4f147a8221205d3a636b59d6d46934080bd..cb5fbe8ecd9e720d60238cfec56dd06c59a8ad46 100644 --- a/crates/onestore-diagnostic/src/main.rs +++ b/crates/notebook/src/bin/onestore-diagnostic.rs @@ -44,9 +44,9 @@ fn run(args: &[std::ffi::OsString]) -> Result> if let [mode, root] = args && mode == "catalog" { - let catalog = onestore_notebook::discover( - &mut onestore_notebook::Local::open(root)?, - onestore_notebook::Limits { + let catalog = notebook::discover::discover( + &mut notebook::discover::Local::open(root)?, + notebook::discover::Limits { entries: 100_000, bytes_per_file: 256 * 1024 * 1024, depth: 64, diff --git a/crates/onestore-notebook/src/lib.rs b/crates/notebook/src/discover.rs similarity index 99% rename from crates/onestore-notebook/src/lib.rs rename to crates/notebook/src/discover.rs index 68a31a7c239c406376e6f23a0e014af8f3f7e120..57f57db439cb559fa66b96fdc1a1428b2cbcc387 100644 --- a/crates/onestore-notebook/src/lib.rs +++ b/crates/notebook/src/discover.rs @@ -1,5 +1,4 @@ -#![forbid(unsafe_code)] -#![doc = include_str!("../README.md")] +//! Read-only notebook discovery over a caller-supplied root. use onestore::{ FileType, RevisionIndex, Store, diff --git a/crates/onestore-notebook/src/source.rs b/crates/notebook/src/discover/source.rs similarity index 96% rename from crates/onestore-notebook/src/source.rs rename to crates/notebook/src/discover/source.rs index 4bceacd8600fe84c2497dfc43faa7d8c69b2a308..47af7fe9e61b680237776e7976303fe3b5269fa9 100644 --- a/crates/onestore-notebook/src/source.rs +++ b/crates/notebook/src/discover/source.rs @@ -65,13 +65,13 @@ impl Source for Local { #[cfg(feature = "smb")] /// A share-relative root on an existing blocking SMB connection. pub struct Smb<'a> { - client: &'a onestore_smb::Client, + client: &'a crate::smb::Client, root: String, } #[cfg(feature = "smb")] impl<'a> Smb<'a> { - pub fn new(client: &'a onestore_smb::Client, root: &str) -> io::Result { + pub fn new(client: &'a crate::smb::Client, root: &str) -> io::Result { let root = root.replace('\\', "/"); if !root.is_empty() && !root.split('/').all(component) { return Err(io::ErrorKind::InvalidInput.into()); diff --git a/crates/onestore-offline/src/formatting.rs b/crates/notebook/src/formatting.rs similarity index 100% rename from crates/onestore-offline/src/formatting.rs rename to crates/notebook/src/formatting.rs diff --git a/crates/onestore-offline/src/lib.rs b/crates/notebook/src/lib.rs similarity index 99% rename from crates/onestore-offline/src/lib.rs rename to crates/notebook/src/lib.rs index 58a57badb609e2b9223be5f3d70d81c645050297..a768226d5ff9fba55f5408b8bd7d3953be5c804f 100644 --- a/crates/onestore-offline/src/lib.rs +++ b/crates/notebook/src/lib.rs @@ -1,6 +1,10 @@ #![forbid(unsafe_code)] #![doc = include_str!("../README.md")] +pub mod discover; +#[cfg(feature = "smb")] +pub mod smb; + use onestore::{ ExGuid, Insertion, PageCreation, PreparedEdit, RevisionIndex, Store, document::{Document, Kind}, @@ -26,11 +30,9 @@ pub use tree::TreeEdit; mod sync; pub use sync::{ConflictKind, EditStatus, Remote}; mod worker; -pub use worker::SyncWorker; -#[cfg(feature = "smb")] -mod smb; #[cfg(feature = "smb")] pub use smb::SmbRemote; +pub use worker::SyncWorker; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -45,7 +47,7 @@ pub enum Error { #[error(transparent)] RemoteIo(io::Error), #[error(transparent)] - Notebook(#[from] onestore_notebook::Error), + Discovery(#[from] discover::Error), #[error("External payload identity now refers to different bytes")] AssetChanged, } diff --git a/crates/onestore-offline/src/outline.rs b/crates/notebook/src/outline.rs similarity index 100% rename from crates/onestore-offline/src/outline.rs rename to crates/notebook/src/outline.rs diff --git a/crates/onestore-offline/src/pages.rs b/crates/notebook/src/pages.rs similarity index 100% rename from crates/onestore-offline/src/pages.rs rename to crates/notebook/src/pages.rs diff --git a/crates/onestore-offline/src/paragraph.rs b/crates/notebook/src/paragraph.rs similarity index 100% rename from crates/onestore-offline/src/paragraph.rs rename to crates/notebook/src/paragraph.rs diff --git a/crates/onestore-offline/src/rebase.rs b/crates/notebook/src/rebase.rs similarity index 100% rename from crates/onestore-offline/src/rebase.rs rename to crates/notebook/src/rebase.rs diff --git a/crates/onestore-offline/src/recovery.rs b/crates/notebook/src/recovery.rs similarity index 100% rename from crates/onestore-offline/src/recovery.rs rename to crates/notebook/src/recovery.rs diff --git a/crates/onestore-offline/src/schema.rs b/crates/notebook/src/schema.rs similarity index 100% rename from crates/onestore-offline/src/schema.rs rename to crates/notebook/src/schema.rs diff --git a/crates/onestore-smb/src/directory.rs b/crates/notebook/src/smb/directory.rs similarity index 100% rename from crates/onestore-smb/src/directory.rs rename to crates/notebook/src/smb/directory.rs diff --git a/crates/onestore-smb/src/lib.rs b/crates/notebook/src/smb/mod.rs similarity index 99% rename from crates/onestore-smb/src/lib.rs rename to crates/notebook/src/smb/mod.rs index 7f0d3dc562aa53b9e0c7f60707e8355c247847ee..e8ccdff841bd0e37acf6d4c57eb1198a76d7c859 100644 --- a/crates/onestore-smb/src/lib.rs +++ b/crates/notebook/src/smb/mod.rs @@ -1,5 +1,4 @@ -#![forbid(unsafe_code)] -#![doc = include_str!("../README.md")] +//! Blocking SMB access to OneNote sections, table-of-contents files and payloads. use onestore::{CommitError, CommitIo, CommitState, ExGuid}; use smb2::{ @@ -26,7 +25,9 @@ use std::{io, ops::Range, sync::Mutex, time::Duration}; use tokio::runtime::{Handle, Runtime}; mod directory; +mod remote; pub use directory::DirectoryEntry; +pub use remote::SmbRemote; #[derive(Default)] pub struct Credentials<'a> { diff --git a/crates/onestore-offline/src/smb.rs b/crates/notebook/src/smb/remote.rs similarity index 97% rename from crates/onestore-offline/src/smb.rs rename to crates/notebook/src/smb/remote.rs index 6076ee03937af7bce342e751daa9e65cd447dad7..7dc3c146d32c21ada33ebedc6e4123232b953a3c 100644 --- a/crates/onestore-offline/src/smb.rs +++ b/crates/notebook/src/smb/remote.rs @@ -1,6 +1,6 @@ +use super::Client; use crate::Remote; use onestore::{CommitError, PreparedEdit}; -use onestore_smb::Client; use std::io; /// Binds every reconciliation operation to one share-relative file and read limit. diff --git a/crates/onestore-smb/src/tests.rs b/crates/notebook/src/smb/tests.rs similarity index 100% rename from crates/onestore-smb/src/tests.rs rename to crates/notebook/src/smb/tests.rs diff --git a/crates/onestore-smb/src/tests/faults.rs b/crates/notebook/src/smb/tests/faults.rs similarity index 100% rename from crates/onestore-smb/src/tests/faults.rs rename to crates/notebook/src/smb/tests/faults.rs diff --git a/crates/onestore-offline/src/sync.rs b/crates/notebook/src/sync.rs similarity index 100% rename from crates/onestore-offline/src/sync.rs rename to crates/notebook/src/sync.rs diff --git a/crates/onestore-offline/src/tree.rs b/crates/notebook/src/tree.rs similarity index 100% rename from crates/onestore-offline/src/tree.rs rename to crates/notebook/src/tree.rs diff --git a/crates/onestore-offline/src/worker.rs b/crates/notebook/src/worker.rs similarity index 100% rename from crates/onestore-offline/src/worker.rs rename to crates/notebook/src/worker.rs diff --git a/crates/onestore-offline/tests/assets.rs b/crates/notebook/tests/assets.rs similarity index 93% rename from crates/onestore-offline/tests/assets.rs rename to crates/notebook/tests/assets.rs index 3186efe6def0171e62db0640966f925ca9ec0f11..378c08b225879aaf6a246ca0e3b745c242030c98 100644 --- a/crates/onestore-offline/tests/assets.rs +++ b/crates/notebook/tests/assets.rs @@ -1,4 +1,4 @@ -use onestore_offline::{EditStatus, Error, Operation, Recovery, Replica}; +use notebook::{EditStatus, Error, Operation, Recovery, Replica}; use std::{ fs, io, path::Path, @@ -29,8 +29,8 @@ fn payload(size: usize) -> (String, Vec) { } struct Payload(Option>>); -impl onestore_notebook::Source for Payload { - fn entries(&mut self, _: &str, _: usize) -> io::Result> { +impl notebook::discover::Source for Payload { + fn entries(&mut self, _: &str, _: usize) -> io::Result> { panic!("Unexpected enumeration") } fn read(&mut self, _: &str, _: usize) -> io::Result> { @@ -55,7 +55,7 @@ fn downloaded_media_survives_migration_reopen_and_recovery_with_the_queue_intact uncertain, Some(EditStatus::AwaitingConfirmation { .. }) )); - let mut source = onestore_notebook::Local::open(FIXTURE).unwrap(); + let mut source = notebook::discover::Local::open(FIXTURE).unwrap(); for size in [0, 1024] { let (name, bytes) = payload(size); assert!(cache.cached_asset(&name, size).unwrap().is_none()); @@ -132,7 +132,7 @@ fn unsuccessful_refreshes_and_changed_identity_data_preserve_the_downloaded_payl let mut unavailable = Payload(Some(Err(io::ErrorKind::ConnectionReset.into()))); assert!( matches!(cache.fetch_asset(&mut unavailable, "synthetic.one", &name, 1024), - Err(Error::Notebook(onestore_notebook::Error::Io { error, .. })) if error.kind() == io::ErrorKind::ConnectionReset) + Err(Error::Discovery(notebook::discover::Error::Io { error, .. })) if error.kind() == io::ErrorKind::ConnectionReset) ); for size in [0, 1024, 2048] { let mut changed = Payload(Some(Ok(vec![9; size]))); @@ -175,8 +175,8 @@ fn download_network_wait_does_not_block_local_edits() { released: mpsc::Receiver<()>, bytes: Vec, } - impl onestore_notebook::Source for Waiting { - fn entries(&mut self, _: &str, _: usize) -> io::Result> { + impl notebook::discover::Source for Waiting { + fn entries(&mut self, _: &str, _: usize) -> io::Result> { panic!("Unexpected enumeration") } fn read(&mut self, _: &str, _: usize) -> io::Result> { @@ -231,7 +231,7 @@ fn concurrent_downloads_publish_one_immutable_cache_entry() { std::thread::scope(|scope| { for _ in 0..8 { scope.spawn(|| { - let mut source = onestore_notebook::Local::open(FIXTURE).unwrap(); + let mut source = notebook::discover::Local::open(FIXTURE).unwrap(); ready.wait(); assert_eq!( cache @@ -248,7 +248,7 @@ fn concurrent_downloads_publish_one_immutable_cache_entry() { #[test] fn a_native_refresh_removing_the_reference_rejects_an_inflight_download() { struct Native; - impl onestore_offline::Remote for Native { + impl notebook::Remote for Native { fn read(&mut self) -> io::Result> { fs::read("../../corpus/native-external-assets/native/synthetic.one") } @@ -260,8 +260,8 @@ fn a_native_refresh_removing_the_reference_rejects_an_inflight_download() { } } struct Refresh<'a>(&'a Replica); - impl onestore_notebook::Source for Refresh<'_> { - fn entries(&mut self, _: &str, _: usize) -> io::Result> { + impl notebook::discover::Source for Refresh<'_> { + fn entries(&mut self, _: &str, _: usize) -> io::Result> { panic!("Unexpected enumeration") } fn read(&mut self, _: &str, _: usize) -> io::Result> { @@ -294,7 +294,7 @@ fn local_failure_and_bad_cached_bytes_never_become_successful_downloads() { drop(connection); let (name, bytes) = payload(1024); let cache = Replica::open(&path).unwrap(); - let mut source = onestore_notebook::Local::open(FIXTURE).unwrap(); + let mut source = notebook::discover::Local::open(FIXTURE).unwrap(); assert!(matches!( cache.fetch_asset(&mut source, "synthetic.one", &name, 1024), Err(Error::Database(_)) @@ -337,8 +337,8 @@ fn abrupt_process_exit_retains_only_completed_downloads_and_archives() { const CHILD: &str = "ONESTORE_ASSET_EXIT_CASE"; if let Ok(phase) = std::env::var(CHILD) { struct ExitDuringRead; - impl onestore_notebook::Source for ExitDuringRead { - fn entries(&mut self, _: &str, _: usize) -> io::Result> { + impl notebook::discover::Source for ExitDuringRead { + fn entries(&mut self, _: &str, _: usize) -> io::Result> { panic!("Unexpected enumeration") } fn read(&mut self, _: &str, _: usize) -> io::Result> { @@ -356,7 +356,7 @@ fn abrupt_process_exit_retains_only_completed_downloads_and_archives() { } cache .fetch_asset( - &mut onestore_notebook::Local::open(FIXTURE).unwrap(), + &mut notebook::discover::Local::open(FIXTURE).unwrap(), "synthetic.one", &name, bytes.len(), @@ -410,15 +410,15 @@ fn abrupt_process_exit_retains_only_completed_downloads_and_archives() { fn live_smb_downloads_survive_disconnect_and_cache_reopen() { let root = tempfile::tempdir().unwrap(); let cache = copied_cache(root.path()); - let client = onestore_smb::Client::connect( + let client = notebook::smb::Client::connect( &std::env::var("ONESTORE_SMB_LAB").unwrap(), "agent", - onestore_smb::Credentials::default(), + notebook::smb::Credentials::default(), Duration::from_secs(5), ) .unwrap(); let notebook = std::env::var("ONESTORE_SMB_NOTEBOOK").unwrap(); - let mut source = onestore_notebook::Smb::new(&client, ¬ebook).unwrap(); + let mut source = notebook::discover::Smb::new(&client, ¬ebook).unwrap(); for size in [0, 771, 1024] { let (name, bytes) = payload(size); assert_eq!( diff --git a/crates/onestore-offline/tests/cache.rs b/crates/notebook/tests/cache.rs similarity index 97% rename from crates/onestore-offline/tests/cache.rs rename to crates/notebook/tests/cache.rs index 554e50de35068d76ea96c781fb4eb53785136459..36d48874caa846b77725c861db4260ac59e626f5 100644 --- a/crates/onestore-offline/tests/cache.rs +++ b/crates/notebook/tests/cache.rs @@ -1,8 +1,8 @@ +use notebook::{Error, Replica}; use onestore::{ ExGuid, RevisionIndex, Store, document::{Document, Kind}, }; -use onestore_offline::{Error, Replica}; use std::{ collections::BTreeSet, fs, @@ -58,7 +58,7 @@ fn cache_reopen_preserves_exact_images_and_intents() { let intents = replica.pending().unwrap(); assert_eq!(intents.len(), 1); assert_eq!(intents[0].id, first); - let onestore_offline::Operation::Text(first_edit) = &intents[0].operation else { + let notebook::Operation::Text(first_edit) = &intents[0].operation else { panic!() }; assert_eq!(first_edit.before, "café 🦀"); @@ -74,8 +74,7 @@ fn cache_reopen_preserves_exact_images_and_intents() { .unwrap() .unwrap(); assert!(second > first); - let onestore_offline::Operation::Text(second_edit) = &replica.pending().unwrap()[1].operation - else { + let notebook::Operation::Text(second_edit) = &replica.pending().unwrap()[1].operation else { panic!() }; assert_eq!(second_edit.before, "café 🐈 日本語"); @@ -114,7 +113,7 @@ fn failed_edits_preserve_both_intent_queue_and_working_image() { #[test] fn concurrent_recovery_exports_capture_one_complete_acknowledged_queue() { - use onestore_offline::{Operation, Recovery}; + use notebook::{Operation, Recovery}; let directory = tempfile::tempdir().unwrap(); let source = onestore::create_section("recovery.one", "base", "Fixture").unwrap(); @@ -287,7 +286,7 @@ fn twelve_local_editors_reject_stale_ranges_and_preserve_every_acknowledgement() let content = target(&final_bytes).2; let mut expected = "Shared café 🦀".to_owned(); for pending in replica.pending().unwrap() { - let onestore_offline::Operation::Text(edit) = pending.operation else { + let notebook::Operation::Text(edit) = pending.operation else { panic!() }; assert_eq!(edit.before, expected); @@ -356,12 +355,12 @@ fn seeded_unicode_edits_and_restarts_match_an_independent_text_model() { assert!( intents .last() - .is_none_or(|edit: &onestore_offline::PendingEdit| edit.id < id) + .is_none_or(|edit: ¬ebook::PendingEdit| edit.id < id) ); - intents.push(onestore_offline::PendingEdit { + intents.push(notebook::PendingEdit { id, space, - operation: onestore_offline::Operation::Text(onestore_offline::TextEdit { + operation: notebook::Operation::Text(notebook::TextEdit { object, before: text, range, @@ -397,7 +396,7 @@ fn seeded_unicode_edits_and_restarts_match_an_independent_text_model() { #[test] #[ignore = "migrates a fresh copy of a retained version-two or version-three cache"] fn migrate_retained_cache_copy() { - use onestore_offline::{EditStatus, Operation, PendingEdit, TextEdit}; + use notebook::{EditStatus, Operation, PendingEdit, TextEdit}; let source = std::path::PathBuf::from(std::env::var_os("ONESTORE_MIGRATION_SOURCE").unwrap()); let output = std::path::PathBuf::from(std::env::var_os("ONESTORE_MIGRATION_OUTPUT").unwrap()); assert!(source.is_absolute() && output.is_absolute()); @@ -526,7 +525,7 @@ fn migrate_retained_cache_copy() { assert_eq!(kind, 0); states.insert( u64::try_from(id).unwrap(), - EditStatus::Conflict(onestore_offline::ConflictKind::TextChanged), + EditStatus::Conflict(notebook::ConflictKind::TextChanged), ); } drop(query); @@ -798,7 +797,7 @@ fn unrecognized_persisted_operations_are_rejected_without_dropping_fields() { #[test] fn prior_schema_migrations_retain_queue_evidence_assets_and_enable_content_conflicts() { for (version, ceiling) in [(5, 3), (6, 4), (7, 5), (8, 6), (9, 6)] { - use onestore_offline::{ConflictKind, EditStatus, Recovery}; + use notebook::{ConflictKind, EditStatus, Recovery}; use sha2::{Digest, Sha256}; let directory = tempfile::tempdir().unwrap(); let path = directory.path().join("previous.sqlite"); diff --git a/crates/onestore-notebook/tests/discovery.rs b/crates/notebook/tests/discovery.rs similarity index 98% rename from crates/onestore-notebook/tests/discovery.rs rename to crates/notebook/tests/discovery.rs index 89a6f21debf9b218b64f59dad76fae89c6262830..f61babffc362c846cda2e541593ec5a9ae584352 100644 --- a/crates/onestore-notebook/tests/discovery.rs +++ b/crates/notebook/tests/discovery.rs @@ -1,4 +1,4 @@ -use onestore_notebook::{Entry, Error, Limits, Local, Source, discover}; +use notebook::discover::{Entry, Error, Limits, Local, Source, discover}; use std::{fs, io, path::Path}; fn limits() -> Limits { @@ -107,11 +107,11 @@ fn locked_and_unreadable_sections_retain_their_storage_identity() { assert!(catalog.toc.is_none()); assert!(matches!( catalog.sections[0].state, - onestore_notebook::SectionState::Locked + notebook::discover::SectionState::Locked )); assert!(matches!( catalog.sections[1].state, - onestore_notebook::SectionState::Unreadable(_) + notebook::discover::SectionState::Unreadable(_) )); for section in catalog.sections { let bytes = fs::read(root.path().join(section.path)).unwrap(); diff --git a/crates/onestore-notebook/tests/assets.rs b/crates/notebook/tests/external_assets.rs similarity index 92% rename from crates/onestore-notebook/tests/assets.rs rename to crates/notebook/tests/external_assets.rs index 47f50d2e80a1a5aeed909bbcfc8e99b850796987..8b7dc36e86a0757ab4a72903adfada28c4a724ea 100644 --- a/crates/onestore-notebook/tests/assets.rs +++ b/crates/notebook/tests/external_assets.rs @@ -1,4 +1,4 @@ -use onestore_notebook::{Error, Local, read_external_asset}; +use notebook::discover::{Error, Local, read_external_asset}; use std::{fs, io}; #[test] @@ -35,8 +35,8 @@ fn nested_external_payloads_are_exact_and_empty_is_distinct_from_missing() { #[test] fn invalid_asset_locations_fail_before_accessing_the_source() { struct Unused; - impl onestore_notebook::Source for Unused { - fn entries(&mut self, _: &str, _: usize) -> io::Result> { + impl notebook::discover::Source for Unused { + fn entries(&mut self, _: &str, _: usize) -> io::Result> { panic!("Unexpected enumeration") } fn read(&mut self, _: &str, _: usize) -> io::Result> { @@ -89,9 +89,9 @@ fn reserved_payload_directories_are_not_notebook_groups() { b"payload", ) .unwrap(); - let catalog = onestore_notebook::discover( + let catalog = notebook::discover::discover( &mut Local::open(root.path()).unwrap(), - onestore_notebook::Limits { + notebook::discover::Limits { entries: 4, bytes_per_file: 1 << 20, depth: 1, @@ -122,15 +122,15 @@ fn live_external_payloads() { let store = Store::parse(&bytes).unwrap(); let index = RevisionIndex::parse(&store).unwrap(); let document = Document::parse(&index).unwrap(); - let client = onestore_smb::Client::connect( + let client = notebook::smb::Client::connect( &std::env::var("ONESTORE_SMB_LAB").unwrap(), "agent", - onestore_smb::Credentials::default(), + notebook::smb::Credentials::default(), std::time::Duration::from_secs(5), ) .unwrap(); let mut remote = - onestore_notebook::Smb::new(&client, &std::env::var("ONESTORE_SMB_NOTEBOOK").unwrap()) + notebook::discover::Smb::new(&client, &std::env::var("ONESTORE_SMB_NOTEBOOK").unwrap()) .unwrap(); let mut local = Local::open(root).unwrap(); let mut seen = std::collections::BTreeSet::new(); diff --git a/crates/onestore-offline/tests/support/page_schedule.rs b/crates/notebook/tests/support/page_schedule.rs similarity index 99% rename from crates/onestore-offline/tests/support/page_schedule.rs rename to crates/notebook/tests/support/page_schedule.rs index fe02c8a42adcd80da6c9446132ecb762ecf97261..af00a40b0cf1d0b5bf3cc9865783fcd16c290cdb 100644 --- a/crates/onestore-offline/tests/support/page_schedule.rs +++ b/crates/notebook/tests/support/page_schedule.rs @@ -1,9 +1,9 @@ use crate::disk; +use notebook::{EditStatus, Operation, Remote, Replica}; use onestore::{ CommitError, ExGuid, Insertion, PageEdit, PagePosition, PreparedEdit, RevisionIndex, Store, document::{Document, Kind}, }; -use onestore_offline::{EditStatus, Operation, Remote, Replica}; use std::{io, sync::LazyLock}; #[path = "../../../onestore/tests/support/current.rs"] diff --git a/crates/onestore-offline/tests/support/tree_schedule.rs b/crates/notebook/tests/support/tree_schedule.rs similarity index 99% rename from crates/onestore-offline/tests/support/tree_schedule.rs rename to crates/notebook/tests/support/tree_schedule.rs index 8032dba58d40dfd020373396b0c058f667615bbb..170b19af59783786ee119301b7900e869d7536b4 100644 --- a/crates/onestore-offline/tests/support/tree_schedule.rs +++ b/crates/notebook/tests/support/tree_schedule.rs @@ -1,9 +1,9 @@ use crate::disk; +use notebook::{EditStatus, Operation, Remote, Replica}; use onestore::{ CommitError, ExGuid, Insertion, PreparedEdit, RevisionIndex, Store, TreeEdit, document::{Document, Kind}, }; -use onestore_offline::{EditStatus, Operation, Remote, Replica}; use std::{io, sync::LazyLock}; static SOURCE: LazyLock<(Vec, ExGuid, ExGuid)> = LazyLock::new(|| { diff --git a/crates/onestore-offline/tests/sync.rs b/crates/notebook/tests/sync.rs similarity index 99% rename from crates/onestore-offline/tests/sync.rs rename to crates/notebook/tests/sync.rs index a41fcf03c1353f7cf1fde00c90fd083650d72876..f03a39611443d7f5649768e4b993db46df84a3ac 100644 --- a/crates/onestore-offline/tests/sync.rs +++ b/crates/notebook/tests/sync.rs @@ -1,8 +1,8 @@ +use notebook::{ConflictKind, EditStatus, Error, Remote, Replica}; use onestore::{ CommitError, CommitIo, CommitState, ExGuid, PreparedEdit, RevisionIndex, Store, document::{Document, Kind}, }; -use onestore_offline::{ConflictKind, EditStatus, Error, Remote, Replica}; use std::io; #[path = "../../onestore/tests/support/disk.rs"] @@ -185,8 +185,7 @@ mod paragraph { cache .rebase_conflict(id, &local, &remote, offset..offset) .unwrap(); - let onestore_offline::Operation::Split(edit) = - &cache.pending().unwrap()[0].operation + let notebook::Operation::Split(edit) = &cache.pending().unwrap()[0].operation else { panic!() }; @@ -585,7 +584,7 @@ mod paragraph { .rebase_conflict(id, &local, &server.visible, 2..2) .unwrap(); let pending = cache.pending().unwrap(); - let onestore_offline::Operation::Split(edit) = &pending[0].operation else { + let notebook::Operation::Split(edit) = &pending[0].operation else { panic!() }; assert_eq!(edit.intent, split); @@ -1089,8 +1088,8 @@ fn text(source: &[u8]) -> (ExGuid, ExGuid, String) { #[test] fn recovery_archive_preserves_typed_queue_uncertainty_and_receipts_without_becoming_a_writer() { + use notebook::{Recovery, RecoverySummary}; use onestore::{Insertion, TextAttribute}; - use onestore_offline::{Recovery, RecoverySummary}; let directory = tempfile::tempdir().unwrap(); let path = directory.path().join("live.sqlite"); @@ -1231,7 +1230,7 @@ fn recovery_archive_preserves_typed_queue_uncertainty_and_receipts_without_becom #[test] fn recovery_archive_retains_conflict_images_and_rejects_foreign_or_future_archives() { - use onestore_offline::Recovery; + use notebook::Recovery; let directory = tempfile::tempdir().unwrap(); let source = onestore::create_section("recovery.one", "Original", "Fixture").unwrap(); @@ -1741,10 +1740,10 @@ fn reviewed_conflict_rebase_preserves_twelve_dependent_edits_and_survives_reopen let rebased = cache.pending().unwrap(); assert_eq!(&rebased[1..], &pending[1..]); assert_eq!(rebased[0].id, first); - let onestore_offline::Operation::Text(updated) = &rebased[0].operation else { + let notebook::Operation::Text(updated) = &rebased[0].operation else { panic!() }; - let onestore_offline::Operation::Text(previous) = &pending[0].operation else { + let notebook::Operation::Text(previous) = &pending[0].operation else { panic!() }; assert_eq!(updated.replacement, previous.replacement); @@ -2014,7 +2013,7 @@ fn remote_changes_after_review_cannot_be_overwritten_by_the_reviewed_placement() assert_eq!(remote.server.visible, changed); assert_eq!(remote.server.publications, usize::from(after_read)); assert_eq!(cache.snapshot().unwrap(), local); - let onestore_offline::Operation::Text(edit) = &cache.pending().unwrap()[0].operation else { + let notebook::Operation::Text(edit) = &cache.pending().unwrap()[0].operation else { panic!() }; assert_eq!(edit.replacement, "L"); @@ -3712,8 +3711,8 @@ fn every_visual_attribute_rebases_with_an_independent_remote_attribute() { #[test] fn reviewed_insertion_placements_preserve_identity_and_dependent_operations() { + use notebook::Operation; use onestore::{Insertion, TextAttribute as A}; - use onestore_offline::Operation; for outline_case in [false, true] { let directory = tempfile::tempdir().unwrap(); let path = directory.path().join("placement.sqlite"); diff --git a/crates/onestore-offline/tests/sync/outline.rs b/crates/notebook/tests/sync/outline.rs similarity index 99% rename from crates/onestore-offline/tests/sync/outline.rs rename to crates/notebook/tests/sync/outline.rs index 112402c85421ca57f5fe842fd3baf0e358b2df0d..7863cb221020478fff27934fe832ec49f95933c6 100644 --- a/crates/onestore-offline/tests/sync/outline.rs +++ b/crates/notebook/tests/sync/outline.rs @@ -346,7 +346,7 @@ fn twelve_offline_writers_preserve_all_layout_intents_through_review_and_restart let path = directory.path().join(format!("{actor}.sqlite")); let cache = Replica::open(&path).unwrap(); let first = cache.pending().unwrap()[0].clone(); - let onestore_offline::Operation::Outline(edit) = first.operation else { + let notebook::Operation::Outline(edit) = first.operation else { panic!() }; let local = cache.snapshot().unwrap(); @@ -446,7 +446,7 @@ fn uncertain_layout_is_confirmed_by_revision_and_never_by_converged_values() { assert!(cache.snapshot().unwrap() == local); let archive = directory.path().join("recovery.sqlite"); cache.export_recovery(&archive).unwrap(); - let recovery = onestore_offline::Recovery::open(&archive).unwrap(); + let recovery = notebook::Recovery::open(&archive).unwrap(); assert_eq!(recovery.pending().unwrap(), cache.pending().unwrap()); assert_eq!(recovery.status(id).unwrap(), Some(status)); assert_eq!(server.confirmations, 0); diff --git a/crates/onestore-offline/tests/sync/page.rs b/crates/notebook/tests/sync/page.rs similarity index 99% rename from crates/onestore-offline/tests/sync/page.rs rename to crates/notebook/tests/sync/page.rs index 711facd5b6e82585dbcad80b9ea9630dfeb42dc4..5cae6719919da89b9e2939df52f6ffb306cf804f 100644 --- a/crates/onestore-offline/tests/sync/page.rs +++ b/crates/notebook/tests/sync/page.rs @@ -1,6 +1,6 @@ use super::*; +use notebook::{Operation, Recovery}; use onestore::{Insertion, PageCreation}; -use onestore_offline::{Operation, Recovery}; use std::collections::BTreeMap; #[test] diff --git a/crates/onestore-offline/tests/sync/pages.rs b/crates/notebook/tests/sync/pages.rs similarity index 99% rename from crates/onestore-offline/tests/sync/pages.rs rename to crates/notebook/tests/sync/pages.rs index 45d447d7cada7a959c5c7cb8f48e2dc474867af2..a751c6740c86a288e41a106020ed21dffe2b0c4b 100644 --- a/crates/onestore-offline/tests/sync/pages.rs +++ b/crates/notebook/tests/sync/pages.rs @@ -1,6 +1,6 @@ use super::*; +use notebook::{Operation, Recovery}; use onestore::{PageEdit, PagePosition}; -use onestore_offline::{Operation, Recovery}; use std::collections::BTreeMap; const SOURCE: &[u8] = diff --git a/crates/onestore-offline/tests/sync/tree.rs b/crates/notebook/tests/sync/tree.rs similarity index 99% rename from crates/onestore-offline/tests/sync/tree.rs rename to crates/notebook/tests/sync/tree.rs index b55b304ca3921551c45b1d17173faaed27c1fd60..a0681214118c92dad8f5a6e2cb6e024407a978d7 100644 --- a/crates/onestore-offline/tests/sync/tree.rs +++ b/crates/notebook/tests/sync/tree.rs @@ -270,7 +270,7 @@ fn deletion_requires_review_of_remote_content_and_preserves_later_work() { assert!(cache.rebase_conflict(id, &local, &remote, 0..0).is_err()); let archive = directory.path().join("recovery.sqlite"); cache.export_recovery(&archive).unwrap(); - let recovery = onestore_offline::Recovery::open(&archive).unwrap(); + let recovery = notebook::Recovery::open(&archive).unwrap(); assert_eq!(recovery.pending().unwrap(), queue); assert_eq!(recovery.status(id).unwrap(), cache.status(id).unwrap()); assert!(recovery.snapshot().unwrap() == local); diff --git a/crates/onestore-diagnostic/Cargo.toml b/crates/onestore-diagnostic/Cargo.toml deleted file mode 100644 index cc4b96f0e146f2cff6bab02ca13e8d29d920c1bb..0000000000000000000000000000000000000000 --- a/crates/onestore-diagnostic/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "onestore-diagnostic" -version = "0.1.0" -edition = "2024" -publish = false - -[dependencies] -onestore = { path = "../onestore" } -onestore-notebook = { path = "../onestore-notebook" } -serde = { version = "1.0.229", features = ["derive"] } -serde_json = "1.0.151" diff --git a/crates/onestore-notebook/Cargo.toml b/crates/onestore-notebook/Cargo.toml deleted file mode 100644 index 476d94e25b04ebe68f127dc163221eb4a63840ed..0000000000000000000000000000000000000000 --- a/crates/onestore-notebook/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "onestore-notebook" -version = "0.1.0" -edition = "2024" -publish = false - -[features] -smb = ["dep:onestore-smb"] -protected = ["onestore/protected", "dep:zeroize"] - -[dependencies] -onestore = { path = "../onestore" } -onestore-smb = { path = "../onestore-smb", optional = true } -serde = { version = "1", features = ["derive"] } -thiserror = "2" -zeroize = { version = "1.9.0", optional = true } - -[dev-dependencies] -serde_json = "1" -tempfile = "3" diff --git a/crates/onestore-notebook/README.md b/crates/onestore-notebook/README.md deleted file mode 100644 index f60e6b49b3a66c82955ed3545fb85400a3e385d0..0000000000000000000000000000000000000000 --- a/crates/onestore-notebook/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# onestore-notebook - -Read-only notebook discovery over a caller-supplied root. This experimental -sibling crate keeps directory traversal out of the single-file storage parser. - -Discovery returns ordered sections and nested groups with file identities and -share-relative paths. Section display-name overrides remain distinct from file -names. TOC references whose identities are absent from the directory remain -inspectable; a cached filename never substitutes for an identity match. -Reserved `_onefiles` directories are excluded from section-group traversal. -Encrypted sections and valid storage with an unreadable document graph retain -their identity as `Locked` or `Unreadable`, without being presented as empty pages. -Malformed storage, failed reads and ambiguous identities reject the discovery. - -Each file read must be a consistent, bounded snapshot. The result is an -observation across multiple files, not an atomic notebook transaction or -authorization to publish an edit. Refresh rejects observed topology changes and -duplicate physical files with the same logical identity. Retain the last accepted -catalog if discovery fails; a connection failure does not mean files were deleted. - -`read_external_asset` resolves a validated UUID `.onebin` filename beneath the -selected section's sibling `_onefiles` folder and returns exact bounded bytes. -Missing files, permissions and size failures retain their I/O error kinds; an -empty payload is a successful empty buffer. Embedded payloads remain available -directly from the core document model. diff --git a/crates/onestore-offline/Cargo.toml b/crates/onestore-offline/Cargo.toml deleted file mode 100644 index 8fe583780935ca0b1f0ba3c470361f964299b4fa..0000000000000000000000000000000000000000 --- a/crates/onestore-offline/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "onestore-offline" -version = "0.1.0" -edition = "2024" -publish = false - -[features] -smb = ["dep:onestore-smb", "onestore-notebook/smb"] - -[dependencies] -onestore = { path = "../onestore" } -onestore-notebook = { path = "../onestore-notebook" } -onestore-smb = { path = "../onestore-smb", optional = true } -rusqlite = { version = "=0.40.2", features = ["bundled", "backup"] } -thiserror = "2" -serde = { version = "1", features = ["derive"] } -serde_json = "1" -tempfile = "3" -sha2 = "0.11" - -[[example]] -name = "smb_offline_client" -required-features = ["smb"] diff --git a/crates/onestore-smb/Cargo.toml b/crates/onestore-smb/Cargo.toml deleted file mode 100644 index 65a3eb6a84d2f313991f0ce6247ee7d1399d2c0c..0000000000000000000000000000000000000000 --- a/crates/onestore-smb/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "onestore-smb" -version = "0.1.0" -edition = "2024" -publish = false - -[dependencies] -onestore = { path = "../onestore" } -smb2 = "=0.21.0" -tokio = { version = "1", features = ["rt-multi-thread", "time"] } - -[dev-dependencies] -serde_json = "1" diff --git a/crates/onestore-smb/README.md b/crates/onestore-smb/README.md deleted file mode 100644 index f13eb815e9f14c23e83fa10a1e3a17b74b41a642..0000000000000000000000000000000000000000 --- a/crates/onestore-smb/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# onestore-smb - -Optional blocking SMB access for OneNote sections and table-of-contents files. -The core `onestore` crate remains independent of network runtimes. This is an -experimental Rust API with native interoperability evidence in the repository's -[Milestone 9](../../evidence/MILESTONE9.md). - -```no_run -use onestore_smb::{Client, Credentials}; -use std::time::Duration; - -let client = Client::connect( - "server:445", - "notes", - Credentials { username: "user", password: "password", domain: "" }, - Duration::from_secs(5), -)?; -let snapshot = client.read("Personal/Video.one", 64 * 1024 * 1024)?; -let store = onestore::Store::parse(&snapshot)?; -let revisions = onestore::RevisionIndex::parse(&store)?; -let document = onestore::document::Document::parse(&revisions)?; -# Ok::<(), Box>(()) -``` - -Paths are relative to the share. The read limit bounds the complete physical -snapshot. Call from a background thread outside a Tokio runtime. Use identities -from the document and the same snapshot with `Client::commit_text` or -`Client::commit_property_bytes`; their errors retain `onestore::CommitState`. -`PreparedEdit::{text,insert,format}` separate preparation from I/O: inspect the immutable image -and persist the intended revision identity before `Client::commit_prepared`. -`Client::confirm_snapshot` compares and flushes an observed image, then refreshes -its header version metadata without adding a revision. The caller must first -establish which intents that image contains and reread before another commit. - -Readers use shared native guards while writers publish under native write-open -and byte-lock exclusion. Maintenance is excluded during each operation; pathname -identity is checked after acquiring the guards. Connection loss retires the -client. Reconnect for subsequent operations, and reconcile an `Unknown` edit -before retrying it. The transport does not automatically replay requests. - -`Client::read_dir(path, entry_limit)` enumerates a directory, including the share -root with an empty path. It follows every response page and returns no partial -list on interruption, entry-limit overflow or close failure. Entries retain exact -Unicode names, observed sizes and MS-FSCC attributes, including directory/reparse -flags. Concurrent directory changes are not an atomic snapshot; repeated names -are rejected with `ResourceBusy`. Notebook identities come from the files, not -directory names or sizes. Missing paths, denied access and non-directory paths -have distinct I/O error kinds. - -`Client::read_asset(path, byte_limit)` reads an external payload under a read-only -share handle that excludes writes and deletion. Empty files succeed; limits, -interrupted reads and failed close never return partial bytes. This payload read -does not parse a OneStore header or acquire its reader-coordination bytes. - -`python3 tools/test_smb_directory.py VM OUTPUT` checks a caller-owned disposable -Linux lab VM against its filesystem listing and interrupts directory requests, -responses and close. It creates synthetic files in that VM; the caller retains -responsibility for VM teardown. The parser also has bounded-record/truncation -tests independent of the server. - -Device and simulator builds link for iOS. Native acceptance uses disposable -OneNote 2010 clients and Samba; it does not establish on-device execution or -physical power-loss durability. diff --git a/crates/onestore/README.md b/crates/onestore/README.md index b650d80518cf468679e985b4e6cc7b94c41bb86e..ca543ea889f24a1c7c5d763f3f8792775dbc9881 100644 --- a/crates/onestore/README.md +++ b/crates/onestore/README.md @@ -23,10 +23,9 @@ OneNote verification of all 3200 editing intents. prototypes belong in sibling directories under `crates/` and depend on `onestore = { path = "../onestore" }`. The root manifest discovers these crates. The consumer boundary and API tradeoffs are recorded in [API-AUDIT.md](../../evidence/API-AUDIT.md). -`crates/onestore-diagnostic` backs the [HTML diagnostic editor](../../evidence/DIAGNOSTIC.md). -[`onestore-smb`](../onestore-smb/README.md) provides optional embedded network -access; [`onestore-offline`](../onestore-offline/README.md) provides local -SQLite persistence and reconnect reconciliation for text, insertion and formatting. +`onestore-diagnostic` in the `notebook` crate backs the [HTML diagnostic editor](../../evidence/DIAGNOSTIC.md). +[`notebook`](../notebook/README.md) provides notebook discovery, optional embedded +network access (feature `smb`) and local SQLite persistence and reconnect reconciliation for text, insertion and formatting. Shared native fixtures, specifications, evidence and Python/VM tools stay at the repository root; `fuzz/` remains an independent cargo-fuzz workspace. @@ -143,7 +142,7 @@ Ambiguous ancestry, unsupported indentation transitions and unknown implicit font/language inheritance reject before I/O. This is a logical join, so keyboard actions that only change list or indentation state remain separate operations. Generated fields, protected targets and unsupported run-data boundary changes are -rejected before publication. The [offline crate](../onestore-offline/README.md) +rejected before publication. The [notebook crate](../notebook/README.md) documents durable local operations and reconciliation. The [document-writer acceptance](../../evidence/MILESTONE9.md#document-writer-and-offline-acceptance) includes twelve mixed native/Rust clients, outages, lost replies and native revision retirement. @@ -161,7 +160,7 @@ cargo run --example create_notebook -- /tmp/one-demo 'Hello from Rust.' 'Example cargo run --example inventory -- /tmp/one-demo/synthetic.one cargo run --example inspect -- /tmp/one-demo/synthetic.one cargo run --example document -- /tmp/one-demo/synthetic.one /tmp/one-model -cargo build -p onestore-diagnostic +cargo build -p notebook --bin onestore-diagnostic python3 tools/notebook_report.py /tmp/one-demo /tmp/one-report --timezone America/Los_Angeles ``` @@ -249,7 +248,7 @@ The evidence covers transport failures, not physical server power loss or every filesystem's lock implementation. For shared network notebooks, use the optional -[`onestore-smb`](../onestore-smb/README.md) crate. It uses native share modes, +[`notebook::smb`](../notebook/README.md) module. It uses native share modes, shared reader guards, writer exclusion and fresh pathname identity checks without an OS-mounted share. Its [coordination acceptance](../../evidence/MILESTONE9.md) covers native maintenance, mixed readers/writers, reconnects and uncertain publication. The diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 71d81cf9569121fd47613819f1e67dc553d67584..aeb18075979bba53c2030be9bfa8db908cc91fd4 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -577,6 +577,19 @@ dependencies = [ "libc", ] +[[package]] +name = "notebook" +version = "0.1.0" +dependencies = [ + "onestore", + "rusqlite", + "serde", + "serde_json", + "sha2", + "tempfile", + "thiserror", +] + [[package]] name = "objc2" version = "0.6.4" @@ -651,35 +664,12 @@ dependencies = [ "canvas", "libfuzzer-sys", "md5", + "notebook", "onestore", - "onestore-offline", "serde_json", "tempfile", ] -[[package]] -name = "onestore-notebook" -version = "0.1.0" -dependencies = [ - "onestore", - "serde", - "thiserror", -] - -[[package]] -name = "onestore-offline" -version = "0.1.0" -dependencies = [ - "onestore", - "onestore-notebook", - "rusqlite", - "serde", - "serde_json", - "sha2", - "tempfile", - "thiserror", -] - [[package]] name = "parlance" version = "0.1.0" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 702328b67f6bb61b70d2b31a8e824ca2d7a4e9bb..48acc1b606f5b318cda25c8baa4bfdfc13f00624 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,7 +10,7 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.4" onestore = { path = "../crates/onestore", features = ["protected"] } -onestore-offline = { path = "../crates/onestore-offline" } +notebook = { path = "../crates/notebook" } canvas = { path = "../crates/canvas" } tempfile = "3" md5 = "0.8.1" diff --git a/fuzz/fuzz_targets/offline_page.rs b/fuzz/fuzz_targets/offline_page.rs index 103617b5c15807aef9f01363225b4384d4d366e8..ef91755840c0c6ddee9eff5d1d69750fca87ad99 100644 --- a/fuzz/fuzz_targets/offline_page.rs +++ b/fuzz/fuzz_targets/offline_page.rs @@ -3,7 +3,7 @@ use libfuzzer_sys::fuzz_target; #[path = "../../crates/onestore/tests/support/disk.rs"] mod disk; -#[path = "../../crates/onestore-offline/tests/support/page_schedule.rs"] +#[path = "../../crates/notebook/tests/support/page_schedule.rs"] mod page_schedule; fuzz_target!(|input: &[u8]| page_schedule::run(input)); diff --git a/fuzz/fuzz_targets/offline_paragraph.rs b/fuzz/fuzz_targets/offline_paragraph.rs index 3ad34c04feb29be8e5f91212f66b9df55d80a380..0d8bf5e5dfc6e567c7ee9922f6a3f552c43f7d64 100644 --- a/fuzz/fuzz_targets/offline_paragraph.rs +++ b/fuzz/fuzz_targets/offline_paragraph.rs @@ -5,7 +5,7 @@ use onestore::{ Store, document::{Document, Kind}, }; -use onestore_offline::{EditStatus, Operation, Remote, Replica}; +use notebook::{EditStatus, Operation, Remote, Replica}; use std::{io, sync::LazyLock}; #[path = "../../crates/onestore/tests/support/disk.rs"] diff --git a/fuzz/fuzz_targets/offline_tree.rs b/fuzz/fuzz_targets/offline_tree.rs index 5463e3709d22564ad0e2e12b0e5ee14aa6cb4d6a..2ab663e639f852eb8bb94e8d5dba9b75acac8aec 100644 --- a/fuzz/fuzz_targets/offline_tree.rs +++ b/fuzz/fuzz_targets/offline_tree.rs @@ -3,7 +3,7 @@ use libfuzzer_sys::fuzz_target; #[path = "../../crates/onestore/tests/support/disk.rs"] mod disk; -#[path = "../../crates/onestore-offline/tests/support/tree_schedule.rs"] +#[path = "../../crates/notebook/tests/support/tree_schedule.rs"] mod tree_schedule; fuzz_target!(|input: &[u8]| tree_schedule::run(input)); diff --git a/tools/TESTING.md b/tools/TESTING.md index 90be63e2462e80ffc6de0fe3462c38b7dfbadaf2..6b96a7e66780c72867c2a5d57c974cc2ea2a6504 100644 --- a/tools/TESTING.md +++ b/tools/TESTING.md @@ -39,7 +39,7 @@ the run's commands, inputs, outputs and teardown evidence. | Native authoring and cold reopen | `native_runner.py --help` | Independent OneNote capture; exact expected page count when known; owned clone teardown | | Mixed native/Rust/offline writers | `native_collaboration.py --help` | Recorded intents, durable receipts, independent server state and cold native comparison | | SMB directory pagination | `test_smb_directory.py --help` | Caller-owned Linux VM, native filesystem oracle, interrupted-page rejection | -| SMB publication and payload interruptions | Ignored tests in `onestore-smb` | Explicit `ONESTORE_SMB_*` lab inputs, retained protocol traces and independent recovery checks | +| SMB publication and payload interruptions | Ignored tests in `notebook` (feature `smb`) | Explicit `ONESTORE_SMB_*` lab inputs, retained protocol traces and independent recovery checks | | Retained cache migration | Ignored `migrate_retained_cache_copy` test | New destination, unchanged source, images, intent IDs, attempts and receipts | To compare an additional **already captured** notebook hierarchy without running diff --git a/tools/native_maintenance.py b/tools/native_maintenance.py index c7b4d61b7e31f94eaafb8877a8ef6104a5ee238f..ac27df8db79f43b5025e08c0faa68375c802f2b2 100644 --- a/tools/native_maintenance.py +++ b/tools/native_maintenance.py @@ -125,7 +125,7 @@ Sleep(1000) hold.mkdir() config = json.loads((output / 'linux.json').read_text()) with (hold / 'run.log').open('w') as guardian_log: - guardian = subprocess.Popen(['cargo', 'test', '-p', 'onestore-smb', 'live_reader_hold', '--', '--ignored', '--nocapture'], + guardian = subprocess.Popen(['cargo', 'test', '-p', 'notebook', '--features', 'smb', 'live_reader_hold', '--', '--ignored', '--nocapture'], cwd=ROOT, stdout=guardian_log, stderr=subprocess.STDOUT, env={**os.environ, 'ONESTORE_SMB_LAB': f'127.0.0.1:{config["samba_port"]}', 'ONESTORE_SMB_PATH': 'm6-collaboration/synthetic.one', 'ONESTORE_SMB_HOLD': str(hold)}) diff --git a/tools/smb_faults.py b/tools/smb_faults.py index 32e897b2edac2c8410ea9c9e056a0619f5d0dec8..f271b9ca01bec4675f3169a7e2ff5e5891a9c10e 100644 --- a/tools/smb_faults.py +++ b/tools/smb_faults.py @@ -20,8 +20,8 @@ def run(output, server): output = output.resolve() output.mkdir(parents=True, exist_ok=False) sources = ['tools/smb_faults.py', 'tools/smb-proxy.py', 'Cargo.lock', - 'crates/onestore-smb/src/lib.rs', 'crates/onestore-smb/src/tests.rs', - 'crates/onestore-smb/src/tests/faults.rs', 'crates/onestore/src/commit.rs', + 'crates/notebook/src/smb/mod.rs', 'crates/notebook/src/smb/tests.rs', + 'crates/notebook/src/smb/tests/faults.rs', 'crates/onestore/src/commit.rs', 'crates/onestore/src/snapshot.rs'] for name in sources: target = output / 'harness' / name @@ -37,7 +37,7 @@ def run(output, server): config = linux_vm.load_instance(server) (output / 'linux.json').write_text(json.dumps(config, indent=2)) with (output / 'test.log').open('w') as log: - process = subprocess.Popen(['cargo', 'test', '-p', 'onestore-smb', 'live_message_loss', '--', '--ignored', '--nocapture'], + process = subprocess.Popen(['cargo', 'test', '-p', 'notebook', '--features', 'smb', 'live_message_loss', '--', '--ignored', '--nocapture'], cwd=ROOT, stdout=log, stderr=subprocess.STDOUT, start_new_session=True, env={**os.environ, 'ONESTORE_SMB_LAB': f'127.0.0.1:{config["samba_port"]}', 'ONESTORE_SMB_EVIDENCE': str(output / 'cases')}) diff --git a/tools/test_offline_document_history.py b/tools/test_offline_document_history.py index 59e33f449ab2da2dc9c33542a36acb61fa74e56c..95698e545b9bd17d42c3a7d9e558df3282cb49ae 100644 --- a/tools/test_offline_document_history.py +++ b/tools/test_offline_document_history.py @@ -141,7 +141,7 @@ class DocumentHistoryTests(unittest.TestCase): document_history(logs, 2) def test_production_cache_workload_matches_the_independent_history_model(self): - result = subprocess.run(['cargo', 'test', '--locked', '-p', 'onestore-offline', '--features', 'smb', + result = subprocess.run(['cargo', 'test', '--locked', '-p', 'notebook', '--features', 'smb', '--example', 'smb_offline_client', 'tests::document_workload_retains_dependencies_and_receipts_across_reopen', '--', '--exact', '--nocapture'], diff --git a/tools/test_smb_directory.py b/tools/test_smb_directory.py index 6b91864b0582385ff14b534e78253ad1a2ae5213..b1bdcee2e4e18a19bd5eb40f9c2216aadf9aaa49 100644 --- a/tools/test_smb_directory.py +++ b/tools/test_smb_directory.py @@ -67,9 +67,9 @@ print(json.dumps(dict(path=root.name, entries=entries), ensure_ascii=False)) time.sleep(.05) env = dict(os.environ, ONESTORE_SMB_LAB=f'127.0.0.1:{port}', ONESTORE_SMB_DIRECTORY=root, ONESTORE_SMB_DIRECTORY_ORACLE=str(output / 'oracle.json')) - test = 'tests::live_directory_interruption' if control else 'tests::live_directory' + test = 'tests::live_directory_interruption' if control else 'smb::tests::live_directory' with (output / f'{name}.log').open('w') as result: - subprocess.run(['cargo', 'test', '-p', 'onestore-smb', test, '--', '--ignored', '--exact'], + subprocess.run(['cargo', 'test', '-p', 'notebook', '--features', 'smb', test, '--', '--ignored', '--exact'], env=env, stdout=result, stderr=result, check=True, timeout=120) finally: proxy.terminate() @@ -84,7 +84,7 @@ print(json.dumps(dict(path=root.name, entries=entries), ensure_ascii=False)) env = dict(os.environ, ONESTORE_SMB_LAB=f'127.0.0.1:{config["samba_port"]}', ONESTORE_SMB_DIRECTORY_ORACLE=str(output / 'oracle.json')) with (output / 'reconnected.log').open('w') as result: - subprocess.run(['cargo', 'test', '-p', 'onestore-smb', 'tests::live_directory', '--', '--ignored', '--exact'], + subprocess.run(['cargo', 'test', '-p', 'notebook', '--features', 'smb', 'smb::tests::live_directory', '--', '--ignored', '--exact'], env=env, stdout=result, stderr=result, check=True, timeout=120) print('reconnected: passed', flush=True) -- 2.54.0