diff --git a/corpus/conflict-page/README.md b/corpus/conflict-page/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..ffe296770ee5a58ef7a9d29c5301215a1dbf9bfa
--- /dev/null
+++ b/corpus/conflict-page/README.md
@@ -0,0 +1,77 @@
+# Conflict pages
+
+What OneNote 2010 does when a sync finds that two clients changed the same object, and
+that it reads and deletes the conflict pages Rust writes. `tools/native_conflict.py`
+made every capture; `tools/test_conflict_page.py` checks them without a VM.
+
+`native` is two OneNote clients on one Samba notebook (`native_conflict.py sync`, fixture
+`native-ink/cold-ui-ink`): client A, offline, rewrote the first outline's paragraph and the
+second outline's; client B rewrote the same first paragraph and the left table cell and
+published (`b-published`); A reconnected and synced (`conflict`). A kept B's version of the
+paragraph both changed and merged its own disjoint edit into the page; its own version of the
+page became a conflict page:
+
+- its own object space, listed only in the page manifest's `ChildGraphSpaceElementNodes`
+ (`0x2c001d63`), never in the page series, with a copy of its metadata in the manifest's
+ `0x24003442` whose identity is the space's XOR the metadata salt (MS-ONE 2.2.81);
+- metadata root `jcidConflictPageMetaData` (`0x20038`): `CachedTitleString`, a fresh
+ `NotebookManagementEntityGuid`, `PageLevel`, both schema revisions (`0x28`),
+ `TopologyCreationTimeStamp` of the merge, `ConflictingUserName` and
+ `ConflictingUserInitials` of the client whose version it is;
+- a page node with `IsReadOnly`, `Deletable` and `IsConflictPage`, holding A's whole page;
+ only the rich text of the paragraph both changed has `IsConflictObjectForSelection` and
+ `IsConflictObjectForRender`;
+- the page's metadata and the section's copy of it gain `HasConflictPages`.
+
+Both clients show the same UI (`a/`, `b/`): the page's tab carries a conflict mark and a
+yellow bar reads "This page has changes that could not be merged during synchronization.
+Click here to show versions of the page with unmerged changes." Clicking it lists the
+conflict pages under the page, each as its date and user ("9/26/2026 snow"), and opens the
+first; that page's bar reads "Conflicting changes are highlighted in red. This page cannot be
+edited, but you can copy changes to the primary page. Click here for more options." over a
+light red highlight (RGB 255, 214, 214), and its menu offers Delete Conflict Page, Copy Page
+To..., Select Previous/Next Conflicting Change and Collapse Conflict Pages.
+
+`native-three` is `evidence/m8/conflict-abrupt-cold-01`: three clients' conflict pages under
+one page. OneNote appends each to `ChildGraphSpaceElementNodes` and lists them last first
+(`conflicts.png`); the metadata copies are in their own order.
+
+`native-delete` is OneNote's Delete Conflict Page on `native/conflict` in a fresh clone
+(`native_conflict.py cold --delete`): the manifest loses `ChildGraphSpaceElementNodes` (its
+last entry) but keeps the metadata copy, the page's metadata and the section's copy lose
+`HasConflictPages`, and the conflict page's space is left as it was.
+
+`native-pages` and `native-restore` are page-list merges (`native_conflict.py pages`,
+`restore`): client A, offline, moved pages through COM and edited `Target`'s body while
+client B moved pages and deleted `Target`; A reconnected (`merged`, both clients' `listed.json`
+and screenshots). OneNote merges page series, not page positions. A page a client moves
+gets a series of its own, and a COM move also re-series every page it jumps over (a drag
+re-series only the dragged pages, `page-lifecycle/movement`). The merge keeps each page in
+the series of the client that re-seriesed it, the server's where both did, leaving the
+other's series empty (`native-pages` keeps two, which MS-ONE's one-page minimum does not
+allow); series only A made go after the server's. `Target` comes back as a new page space
+holding A's edit, in a new series where A had it; B's version stays in the recycle bin.
+
+- `native-pages`: from One, Two, Three, Four, Target, A moved Four before One and Two last
+ (re-seriesing One, Two, Three), B moved Four last and Three before One (re-seriesing One,
+ Two, Four): One, Two, Four, Three, Target.
+- `native-restore`: from One, Two, Target, Three, Four, A moved Four before One
+ (re-seriesing One, Two, Target, Three) and B deleted Target: Four, One, Two, Target, Three.
+
+`page_moves_and_a_removed_page_merge_as_onenote_merges_them` in
+`crates/notebook/tests/sync_pages.rs` replays both with Snowbound's queue, A's moves as
+moves of the pages A re-seriesed, and reaches OneNote's order.
+
+`candidate` is `a_conflict_page_is_stored_as_onenote_stores_one` in
+`crates/onestore/tests/conflict.rs` (`ONESTORE_CONFLICT_EXPORT`): the remote renamed the
+first word of the fixture's first paragraph, this machine did too, and `SectionOp::Conflict`
+kept this machine's page for "Clover Snow", whose author objects carry her initials beside
+her name as OneNote's do. `cold` is OneNote's fresh read and the same UI
+walk, ending in Delete Conflict Page on the Rust-written page: its bar and list entry show,
+the conflict page opens (window title) with the paragraph highlighted, and the deletion
+stores what `native-delete` did.
+
+`candidate-delete` is `deleting_a_conflict_page_stores_what_onenote_stores`
+(`ONESTORE_CONFLICT_DELETE_EXPORT`): `SectionOp::Delete` of `native/conflict`'s conflict page,
+which stores the objects OneNote's deletion did; `cold-delete` is its fresh read, without a
+bar.
diff --git a/corpus/conflict-page/candidate-delete/Open Notebook.onetoc2 b/corpus/conflict-page/candidate-delete/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..c7beb44bf509f935090fe55e7549e4b50e0ae0e9
Binary files /dev/null and b/corpus/conflict-page/candidate-delete/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/candidate-delete/synthetic.one b/corpus/conflict-page/candidate-delete/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..ea2ecf61b9d305de11c694334fd94369e99b7eea
Binary files /dev/null and b/corpus/conflict-page/candidate-delete/synthetic.one differ
diff --git a/corpus/conflict-page/candidate/Open Notebook.onetoc2 b/corpus/conflict-page/candidate/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..c7beb44bf509f935090fe55e7549e4b50e0ae0e9
Binary files /dev/null and b/corpus/conflict-page/candidate/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/candidate/synthetic.one b/corpus/conflict-page/candidate/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..647cd9bc181046ab29ee4528aebd31fbb9dba4fd
Binary files /dev/null and b/corpus/conflict-page/candidate/synthetic.one differ
diff --git a/corpus/conflict-page/cold-delete/commands.jsonl b/corpus/conflict-page/cold-delete/commands.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..059e40e61d665797d4683acc66d6459966fff285
--- /dev/null
+++ b/corpus/conflict-page/cold-delete/commands.jsonl
@@ -0,0 +1,3 @@
+{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-4D3235A0 -ExpectedPages 1 -KeepOpen", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\read -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
diff --git a/corpus/conflict-page/cold-delete/machine.json b/corpus/conflict-page/cold-delete/machine.json
new file mode 100644
index 0000000000000000000000000000000000000000..a1f05d8179bfaf87806c989ea93c89a231bd45af
--- /dev/null
+++ b/corpus/conflict-page/cold-delete/machine.json
@@ -0,0 +1 @@
+{"name": "m6-4d3235a0", "hostname": "ONE-M6-4D3235A0"}
diff --git a/corpus/conflict-page/cold-delete/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment b/corpus/conflict-page/cold-delete/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment
new file mode 100644
index 0000000000000000000000000000000000000000..66b6151e91f90df6a61a19ef2f4447da278e07b2
--- /dev/null
+++ b/corpus/conflict-page/cold-delete/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment
@@ -0,0 +1 @@
+Fictitious attachment for native corpus.
\ No newline at end of file
diff --git a/corpus/conflict-page/cold-delete/read/environment.json b/corpus/conflict-page/cold-delete/read/environment.json
new file mode 100644
index 0000000000000000000000000000000000000000..d03335c154a59437cf873f38ab49810353d580b1
--- /dev/null
+++ b/corpus/conflict-page/cold-delete/read/environment.json
@@ -0,0 +1,7 @@
+{
+ "powershell": "5.1.14409.1005",
+ "schema": "xs2010",
+ "hostname": "ONE-M6-4D3235A0",
+ "cold": true,
+ "onenote": "14.0.4763.1000"
+}
diff --git a/corpus/conflict-page/cold-delete/read/hierarchy.xml b/corpus/conflict-page/cold-delete/read/hierarchy.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8e71ea46462fed2239de01f765d60863222f3739
--- /dev/null
+++ b/corpus/conflict-page/cold-delete/read/hierarchy.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/cold-delete/read/page-000.navigation.json b/corpus/conflict-page/cold-delete/read/page-000.navigation.json
new file mode 100644
index 0000000000000000000000000000000000000000..668f002c22e396d7530675ec44f659f85b6f6a86
--- /dev/null
+++ b/corpus/conflict-page/cold-delete/read/page-000.navigation.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Client B conflicting edit. - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/cold-delete/read/page-000.png b/corpus/conflict-page/cold-delete/read/page-000.png
new file mode 100644
index 0000000000000000000000000000000000000000..c6d238e1b156fda4d252289eb13f6a8ed3ff0906
Binary files /dev/null and b/corpus/conflict-page/cold-delete/read/page-000.png differ
diff --git a/corpus/conflict-page/cold-delete/read/page-000.xml b/corpus/conflict-page/cold-delete/read/page-000.xml
new file mode 100644
index 0000000000000000000000000000000000000000..37567a5be70a94b48ee71b7e9676a697078dd76e
--- /dev/null
+++ b/corpus/conflict-page/cold-delete/read/page-000.xml
@@ -0,0 +1,21 @@
+
+AK4FHQSiAaIBAWAqABofi0JwRYR1gL7QRdkE+oeVMvLsF0G4wBa/8yv0et67E4vlXrNInJuhJVq3
+URK/7HHMzCKxQYkOuxMs7nqhWM9UiuaXxU+PBvi60uGbIsRmbacNslhJmh1OQDQGbBADBkgQRSNG
+IwUIC2VPZmdoZGkZFDIIAIAeAiHi4kEzCADwEAKLxeJBZAMVRgBACpUE/QGH81d3mq7mkzgM0mcB
+mk1gEBgEBgEBgE1mUAgMzmUAmsygE1mUzmkBmc0gMzmk1mUAmsyTOaTWZQCazKZgALSmiazKATWZ
+QCAwCAwBAZpM4DAJlNYBMprAJlNZpM4DNJmmU1mkzgM0mcBmkzmU1gEyAAFpTSAzOaQGZzRAYBAY
+BAYBMprAIDNJnAZpM0yms0mcBmkzgM0mcymsAmU1gEyms0maZAACZTSawCZTWATKawCAoBAYBAZn
+NIDAJrMoBNZlAJrMpnMoBNZlAJrMpnNIDM5pAZnNJrMkzmk1AACH8C7XgWnmkzgM0msAmU1gEBgE
+BgEBTOaQGATWZQCazKATWZTOaTWZTNNJrMpmmUzTSaplM00mqZTOaTVMpmmk1TKATKazSawCAzSZ
+wGAQFAIDAJrMoBAZnNIDM5pAZnMpnNJrMpmmk1mUzTSaplM00mqZTNNJrMpmmk1TKZplhuAzKATW
+ZQCazKAIDAIDAIDNJnAYBMprAJlM4DNJnMprNJnMk1mkzmSazRM5kms0TOZJnMprNEzmSazQtKaQ
+GZzRNZlAIDAIDAIDAJlNYAmU1gEymsAmU1mkzmU1miZzKazRM5kmcyTWaJnMk1mkzmSazRM5kAUU
+RgBAAAAJBAUURgAAAAASABEgXhT+65lz/EiPi+8SFvjqCAk+gEcDAa/89Qd0AxVGAEAKABEg2R87
+QQrSFUA=AOECHQJSegFgKgAaH4tCcEWEdYC+0EXZBPqHlTLy7BdBuMAWv/Mr9HreuxOL5V6zSJyboSVat1ES
+v+xxzMwisUGJDrsTLO56oVjPVIrml8VPjwb4utLhmyLEZm2nDbJYSZodTkA0BmwQAwZIEEUjRiMF
+CAtlT2ZnaGRpGRQyCACAHgIh4uJBMwgA8BACi8XiQWQDFUYAQArKAUuH88s3nkzmkzgM0mcBmkzg
+KAQGAQGATWZQCAzOaQGZzKATWZTOaQGZzSAzOaTWZQCazJM5pNZlAJrMpmAAAACH8D9XgeBJlM0z
+mRNZpNZpM4DAJlNYBAUAgMAgMzmkBgE1mUAmsyTOaTWZTOaTVMpnNJqmUzTSaplM00mqZTOZTNNJ
+qmUzTSagBRRGAIAAAAkEBRRGAAAAABIAESDPTQ4xwIZyTqQ8FVDbHxeKCj9AI5rARvPPUDugAxVG
+AEAKABEgLMlfQdkfI0A=iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9J1uoA
+AAAASUVORK5CYII=
diff --git a/corpus/conflict-page/cold-delete/read/payloads.json b/corpus/conflict-page/cold-delete/read/payloads.json
new file mode 100644
index 0000000000000000000000000000000000000000..de140b63b49c11437c4b611e7bf1ba60bd5f31b3
--- /dev/null
+++ b/corpus/conflict-page/cold-delete/read/payloads.json
@@ -0,0 +1,10 @@
+[
+ {
+ "sha256": "af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7",
+ "name": "fictitious-attachment.txt",
+ "object": "{577D18B7-BF93-4AA8-9305-80E5A4F26E9B}{11}{B0}",
+ "kind": "InsertedFile",
+ "page": "{DE321CF1-6751-492B-959C-42BC27DEE80E}{1}{B0}",
+ "bytes": 43
+ }
+]
\ No newline at end of file
diff --git a/corpus/conflict-page/cold-delete/run.json b/corpus/conflict-page/cold-delete/run.json
new file mode 100644
index 0000000000000000000000000000000000000000..7c3bfe43bd28d9130a244a2123cb5a1665748154
--- /dev/null
+++ b/corpus/conflict-page/cold-delete/run.json
@@ -0,0 +1,18 @@
+{
+ "notebook": "/private/tmp/gate/candidate-delete",
+ "expected_pages": 1,
+ "author": null,
+ "author_timeout_seconds": 600,
+ "inspect": false,
+ "collect_notebook": false,
+ "base": {
+ "file": "win7-office-base.qcow2",
+ "format": "qcow2",
+ "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346",
+ "virtual_size": 68719476736
+ },
+ "scripts": {
+ "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331",
+ "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41"
+ }
+}
diff --git a/corpus/conflict-page/cold-delete/scripts/cold.ps1 b/corpus/conflict-page/cold-delete/scripts/cold.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..a0b99e7aeb86f4dc10032537800501a52ad65b4f
--- /dev/null
+++ b/corpus/conflict-page/cold-delete/scripts/cold.ps1
@@ -0,0 +1,27 @@
+param([Parameter(Mandatory=$true)][string]$Root, [string]$CloneHost = '')
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
+if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
+ throw 'Choose a run directly below C:\one-tests\runs.'
+}
+if (Get-Process ONENOTE -ErrorAction SilentlyContinue) { throw 'Close OneNote before resetting its test cache.' }
+$key = 'HKCU:\Software\Microsoft\Office\14.0\OneNote'
+if ($CloneHost) {
+ if ($CloneHost -notmatch '^ONE-[A-Z0-9-]+$' -or [Environment]::MachineName -ne $CloneHost) {
+ throw 'The disposable clone hostname does not match this machine.'
+ }
+ New-Item "$key\Options\Paths" -Force | Out-Null
+ New-ItemProperty "$key\Options\Paths" -Name UnfiledNotesSection -PropertyType ExpandString -Value 'C:\one-tests\Loose.one' -Force | Out-Null
+} elseif ((Get-ItemProperty "$key\Options\Paths").UnfiledNotesSection -ne 'C:\one-tests\Loose.one' -or
+ -not (Test-Path 'C:\one-tests\profile-original-cache')) {
+ throw 'Park the personal OneNote profile before resetting the test cache.'
+}
+$cache = Join-Path $env:LOCALAPPDATA 'Microsoft\OneNote\14.0'
+$parked = Join-Path 'C:\one-tests\caches' ([IO.Path]::GetFileName($root))
+if (Test-Path $parked) { throw 'Choose a new run; its parked cache already exists.' }
+New-Item -ItemType Directory -Path 'C:\one-tests\caches' -Force | Out-Null
+if (Test-Path $cache) { Move-Item -LiteralPath $cache -Destination $parked }
+if (Test-Path "$key\OpenNotebooks") { Remove-Item "$key\OpenNotebooks" -Recurse }
+New-Item "$key\OpenNotebooks" | Out-Null
+New-ItemProperty "$key\OpenNotebooks" -Name '1' -PropertyType String -Value "$root\notebook" | Out-Null
diff --git a/corpus/conflict-page/cold-delete/scripts/read.ps1 b/corpus/conflict-page/cold-delete/scripts/read.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..476094ab1e0c47077988a6461db2080cf208642b
--- /dev/null
+++ b/corpus/conflict-page/cold-delete/scripts/read.ps1
@@ -0,0 +1,142 @@
+param(
+ [Parameter(Mandatory=$true)][string]$Root,
+ [int]$ExpectedPages = -1,
+ [switch]$UseCurrentCache,
+ [switch]$Pdf,
+ [switch]$KeepOpen,
+ [string]$CloneHost = ''
+)
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
+if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
+ throw 'Choose a run directly below C:\one-tests\runs.'
+}
+$notebook = Join-Path $root 'notebook'
+$output = Join-Path $root 'read'
+if (Test-Path $output) { throw 'Choose a new read destination.' }
+if ($UseCurrentCache) {
+ if ((Get-ItemProperty 'HKCU:\Software\Microsoft\Office\14.0\OneNote\Options\Paths').UnfiledNotesSection -ne 'C:\one-tests\Loose.one') {
+ throw 'Park the personal OneNote profile before reading test notebooks.'
+ }
+} else {
+ & "$PSScriptRoot\cold-current.ps1" -Root $root -CloneHost $CloneHost
+}
+New-Item -ItemType Directory -Path $output | Out-Null
+$app = New-Object -ComObject OneNote.Application
+$notebookId = ''
+$failure = $null
+try {
+ $app.OpenHierarchy($notebook, '', [ref]$notebookId, 0)
+ $process = Get-Process ONENOTE
+ @{ hostname = [Environment]::MachineName; onenote = $process.MainModule.FileVersionInfo.FileVersion;
+ powershell = $PSVersionTable.PSVersion.ToString(); schema = 'xs2010'; cold = (-not $UseCurrentCache.IsPresent) } |
+ ConvertTo-Json | Set-Content (Join-Path $output 'environment.json') -Encoding UTF8
+ $sections = @()
+ foreach ($file in @(Get-ChildItem $notebook -Recurse | Where-Object { $_.Extension -eq '.one' })) {
+ $id = ''
+ $app.OpenHierarchy($file.FullName, '', [ref]$id, 0)
+ $sections += $id
+ }
+ $deadline = [DateTime]::UtcNow.AddSeconds(300)
+ $previous = ''
+ $lastChange = ''
+ $stableSince = [DateTime]::UtcNow
+ $settled = $false
+ do {
+ $pages = @{}
+ foreach ($section in $sections) {
+ $hierarchy = ''
+ $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
+ [xml]$xml = $hierarchy
+ foreach ($node in $xml.SelectNodes('//*[@path]')) {
+ if (-not $node.GetAttribute('path').StartsWith("$notebook\", [StringComparison]::OrdinalIgnoreCase)) {
+ throw 'OneNote opened a section outside the copied notebook.'
+ }
+ }
+ foreach ($node in $xml.SelectNodes('//*[local-name()="Page"]')) {
+ $id = $node.GetAttribute('ID')
+ $content = ''
+ $app.GetPageContent($id, [ref]$content, 1, 1)
+ $pages[$id] = $content
+ }
+ }
+ $signature = [String]::Join('|', @($pages.Keys | Sort-Object | ForEach-Object { $_ + $pages[$_] }))
+ if ($signature -ne $previous) {
+ $lastChange = $previous
+ $previous = $signature
+ $stableSince = [DateTime]::UtcNow
+ }
+ if ((($ExpectedPages -ge 0 -and $pages.Count -eq $ExpectedPages) -or
+ ($ExpectedPages -lt 0 -and $pages.Count -gt 0)) -and
+ ([DateTime]::UtcNow - $stableSince).TotalSeconds -ge 2) { $settled = $true; break }
+ Start-Sleep -Milliseconds 250
+ } while ([DateTime]::UtcNow -lt $deadline)
+ if (-not $settled -or ($ExpectedPages -ge 0 -and $pages.Count -ne $ExpectedPages) -or ($ExpectedPages -lt 0 -and $pages.Count -eq 0)) {
+ [IO.File]::WriteAllText((Join-Path $output 'previous-signature.txt'), $lastChange, [Text.Encoding]::UTF8)
+ $index = 0
+ foreach ($id in @($pages.Keys | Sort-Object)) {
+ [IO.File]::WriteAllText((Join-Path $output ('unsettled-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8)
+ $index++
+ }
+ $hierarchy = ''
+ $app.GetHierarchy($notebookId, 4, [ref]$hierarchy, 1)
+ [IO.File]::WriteAllText((Join-Path $output 'unsettled-hierarchy.xml'), $hierarchy, [Text.Encoding]::UTF8)
+ throw "Expected $ExpectedPages stable pages; OneNote returned $($pages.Count), settled=$settled."
+ }
+ $index = 0
+ $payloads = @()
+ foreach ($id in @($pages.Keys | Sort-Object)) {
+ [IO.File]::WriteAllText((Join-Path $output ('page-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8)
+ if ($Pdf) {
+ $pdfPath = Join-Path $output ('page-{0:d3}.pdf' -f $index)
+ $app.NavigateTo($id, '', $false)
+ $app.Publish($id, $pdfPath, 3, '')
+ if (-not (Test-Path $pdfPath) -or (Get-Item $pdfPath).Length -eq 0) { throw 'OneNote did not publish the page PDF.' }
+ }
+ [xml]$page = $pages[$id]
+ foreach ($file in $page.SelectNodes('//*[local-name()="InsertedFile" or local-name()="MediaFile"]')) {
+ $bytes = [IO.File]::ReadAllBytes($file.GetAttribute('pathCache'))
+ $hash = [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash($bytes)).Replace('-', '').ToLowerInvariant()
+ [IO.File]::WriteAllBytes((Join-Path $output ($hash + '.attachment')), $bytes)
+ $payloads += @{ page = $id; object = $file.ParentNode.GetAttribute('objectID');
+ kind = $file.LocalName; name = $file.GetAttribute('preferredName');
+ sha256 = $hash; bytes = $bytes.Length }
+ }
+ $index++
+ }
+ [IO.File]::WriteAllText((Join-Path $output 'payloads.json'), (ConvertTo-Json -InputObject $payloads -Depth 4), [Text.Encoding]::UTF8)
+ $all = ''
+ $app.GetHierarchy($notebookId, 4, [ref]$all, 1)
+ [xml]$finalTree = $all
+ $finalIds = @($finalTree.SelectNodes('//*[local-name()="Page"]') | ForEach-Object { $_.GetAttribute('ID') } | Sort-Object -Unique)
+ if ($finalIds.Count -ne $pages.Count -or @($finalIds | Where-Object { -not $pages.ContainsKey($_) }).Count -ne 0) {
+ throw 'The notebook hierarchy changed while collecting page evidence; repeat the cold read.'
+ }
+ [IO.File]::WriteAllText((Join-Path $output 'hierarchy.xml'), $all, [Text.Encoding]::UTF8)
+ Write-Output "Read $($sections.Count) sections and $($pages.Count) pages."
+} catch {
+ $failure = $_
+ [IO.File]::WriteAllText((Join-Path $output 'failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8)
+ throw
+} finally {
+ try {
+ try {
+ if ($notebookId -and $CloneHost) { $app.SyncHierarchy($notebookId) }
+ if ($notebookId -and -not $KeepOpen -and (-not $UseCurrentCache -or $CloneHost)) {
+ $app.CloseNotebook($notebookId, $false)
+ }
+ } catch {
+ if ($null -eq $failure) { throw }
+ [IO.File]::WriteAllText((Join-Path $output 'cleanup-failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8)
+ }
+ } finally {
+ [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
+ }
+ $app = $null
+ [GC]::Collect()
+ [GC]::WaitForPendingFinalizers()
+ if (-not $UseCurrentCache -and -not $CloneHost) {
+ Get-Process ONENOTE -ErrorAction SilentlyContinue | Wait-Process -Timeout 10
+ }
+}
diff --git a/corpus/conflict-page/cold-delete/source.json b/corpus/conflict-page/cold-delete/source.json
new file mode 100644
index 0000000000000000000000000000000000000000..c1ef9fe53decc033de306870451ed968b741f78c
--- /dev/null
+++ b/corpus/conflict-page/cold-delete/source.json
@@ -0,0 +1,14 @@
+[
+ {
+ "path": "Open Notebook.onetoc2",
+ "bytes": 4944,
+ "sha256": "36274ad5cc077c6b1ad51ee4c4b90d3b6798a45ce446cbfd7c9d7ac504776b28",
+ "mtime_ns": 1790473161246592184
+ },
+ {
+ "path": "synthetic.one",
+ "bytes": 41728,
+ "sha256": "78e247a18c1fe2c8d4de66dd8bf79bf918636bfb42933a5533a91c8f21939de0",
+ "mtime_ns": 1790473248313861507
+ }
+]
diff --git a/corpus/conflict-page/cold-delete/teardown.json b/corpus/conflict-page/cold-delete/teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/conflict-page/cold-delete/teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/conflict-page/cold/before-read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment b/corpus/conflict-page/cold/before-read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment
new file mode 100644
index 0000000000000000000000000000000000000000..66b6151e91f90df6a61a19ef2f4447da278e07b2
--- /dev/null
+++ b/corpus/conflict-page/cold/before-read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment
@@ -0,0 +1 @@
+Fictitious attachment for native corpus.
\ No newline at end of file
diff --git a/corpus/conflict-page/cold/before-read/environment.json b/corpus/conflict-page/cold/before-read/environment.json
new file mode 100644
index 0000000000000000000000000000000000000000..6f6316cb1e6b9b42bb515e8c9bb2673249eca696
--- /dev/null
+++ b/corpus/conflict-page/cold/before-read/environment.json
@@ -0,0 +1,7 @@
+{
+ "powershell": "5.1.14409.1005",
+ "schema": "xs2010",
+ "hostname": "ONE-M6-FA8F1E62",
+ "cold": true,
+ "onenote": "14.0.4763.1000"
+}
diff --git a/corpus/conflict-page/cold/before-read/hierarchy.xml b/corpus/conflict-page/cold/before-read/hierarchy.xml
new file mode 100644
index 0000000000000000000000000000000000000000..712236a875d72e75cb8e64fa22404a7e7ad56182
--- /dev/null
+++ b/corpus/conflict-page/cold/before-read/hierarchy.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/cold/before-read/page-000.xml b/corpus/conflict-page/cold/before-read/page-000.xml
new file mode 100644
index 0000000000000000000000000000000000000000..eb35bbfb1c1b8d416a12b3d07500528f3d87ad66
--- /dev/null
+++ b/corpus/conflict-page/cold/before-read/page-000.xml
@@ -0,0 +1,26 @@
+
+Remote: café, 東京, مرحبا]]>AK4FHQSiAaIBAWAqABofi0JwRYR1gL7QRdkE+oeVMvLsF0G4wBa/8yv0et67E4vlXrNInJuhJVq3
+URK/7HHMzCKxQYkOuxMs7nqhWM9UiuaXxU+PBvi60uGbIsRmbacNslhJmh1OQDQGbBADBkgQRSNG
+IwUIC2VPZmdoZGkZFDIIAIAeAiHi4kEzCADwEAKLxeJBZAMVRgBACpUE/QGH81d3mq7mkzgM0mcB
+mk1gEBgEBgEBgE1mUAgMzmUAmsygE1mUzmkBmc0gMzmk1mUAmsyTOaTWZQCazKZgALSmiazKATWZ
+QCAwCAwBAZpM4DAJlNYBMprAJlNZpM4DNJmmU1mkzgM0mcBmkzmU1gEyAAFpTSAzOaQGZzRAYBAY
+BAYBMprAIDNJnAZpM0yms0mcBmkzgM0mcymsAmU1gEyms0maZAACZTSawCZTWATKawCAoBAYBAZn
+NIDAJrMoBNZlAJrMpnMoBNZlAJrMpnNIDM5pAZnNJrMkzmk1AACH8C7XgWnmkzgM0msAmU1gEBgE
+BgEBTOaQGATWZQCazKATWZTOaTWZTNNJrMpmmUzTSaplM00mqZTOaTVMpmmk1TKATKazSawCAzSZ
+wGAQFAIDAJrMoBAZnNIDM5pAZnMpnNJrMpmmk1mUzTSaplM00mqZTNNJrMpmmk1TKZplhuAzKATW
+ZQCazKAIDAIDAIDNJnAYBMprAJlM4DNJnMprNJnMk1mkzmSazRM5kms0TOZJnMprNEzmSazQtKaQ
+GZzRNZlAIDAIDAIDAJlNYAmU1gEymsAmU1mkzmU1miZzKazRM5kmcyTWaJnMk1mkzmSazRM5kAUU
+RgBAAAAJBAUURgAAAAASABEgXhT+65lz/EiPi+8SFvjqCAk+gEcDAa/89Qd0AxVGAEAKABEg2R87
+QQrSFUA=AOECHQJSegFgKgAaH4tCcEWEdYC+0EXZBPqHlTLy7BdBuMAWv/Mr9HreuxOL5V6zSJyboSVat1ES
+v+xxzMwisUGJDrsTLO56oVjPVIrml8VPjwb4utLhmyLEZm2nDbJYSZodTkA0BmwQAwZIEEUjRiMF
+CAtlT2ZnaGRpGRQyCACAHgIh4uJBMwgA8BACi8XiQWQDFUYAQArKAUuH88s3nkzmkzgM0mcBmkzg
+KAQGAQGATWZQCAzOaQGZzKATWZTOaQGZzSAzOaTWZQCazJM5pNZlAJrMpmAAAACH8D9XgeBJlM0z
+mRNZpNZpM4DAJlNYBAUAgMAgMzmkBgE1mUAmsyTOaTWZTOaTVMpnNJqmUzTSaplM00mqZTOZTNNJ
+qmUzTSagBRRGAIAAAAkEBRRGAAAAABIAESDPTQ4xwIZyTqQ8FVDbHxeKCj9AI5rARvPPUDugAxVG
+AEAKABEgLMlfQdkfI0A=iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9J1uoA
+AAAASUVORK5CYII=
diff --git a/corpus/conflict-page/cold/before-read/payloads.json b/corpus/conflict-page/cold/before-read/payloads.json
new file mode 100644
index 0000000000000000000000000000000000000000..88ae247d1cd3dee9c2ac3f05912170135543e7cb
--- /dev/null
+++ b/corpus/conflict-page/cold/before-read/payloads.json
@@ -0,0 +1,10 @@
+[
+ {
+ "sha256": "af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7",
+ "name": "fictitious-attachment.txt",
+ "object": "{EEA4864A-EC4F-48DD-94E4-A54023F53C64}{11}{B0}",
+ "kind": "InsertedFile",
+ "page": "{67EB820C-348D-4B5E-927D-6719A0D9BAF1}{1}{B0}",
+ "bytes": 43
+ }
+]
\ No newline at end of file
diff --git a/corpus/conflict-page/cold/cold-conflict.json b/corpus/conflict-page/cold/cold-conflict.json
new file mode 100644
index 0000000000000000000000000000000000000000..2797bae39ee1a51a132ddf62bd0f0a7b5fb6f9e9
--- /dev/null
+++ b/corpus/conflict-page/cold/cold-conflict.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Snowbound: caf\u00e9, \u6771\u4eac, \u0645\u0631\u062d\u0628\u0627 - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/cold/cold-conflict.png b/corpus/conflict-page/cold/cold-conflict.png
new file mode 100644
index 0000000000000000000000000000000000000000..3e581162febb7372e249109b1cc0fbc31e841ff2
Binary files /dev/null and b/corpus/conflict-page/cold/cold-conflict.png differ
diff --git a/corpus/conflict-page/cold/cold-delete.json b/corpus/conflict-page/cold/cold-delete.json
new file mode 100644
index 0000000000000000000000000000000000000000..61e9e7317ecda208a0500463bc8537e2a0328ff0
--- /dev/null
+++ b/corpus/conflict-page/cold/cold-delete.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Remote: caf\u00e9, \u6771\u4eac, \u0645\u0631\u062d\u0628\u0627 - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/cold/cold-delete.png b/corpus/conflict-page/cold/cold-delete.png
new file mode 100644
index 0000000000000000000000000000000000000000..8d35fc6da74741e9c963466bd316d601d603ca2c
Binary files /dev/null and b/corpus/conflict-page/cold/cold-delete.png differ
diff --git a/corpus/conflict-page/cold/cold-main.json b/corpus/conflict-page/cold/cold-main.json
new file mode 100644
index 0000000000000000000000000000000000000000..61e9e7317ecda208a0500463bc8537e2a0328ff0
--- /dev/null
+++ b/corpus/conflict-page/cold/cold-main.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Remote: caf\u00e9, \u6771\u4eac, \u0645\u0631\u062d\u0628\u0627 - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/cold/cold-main.png b/corpus/conflict-page/cold/cold-main.png
new file mode 100644
index 0000000000000000000000000000000000000000..07962cb3e707d017176cdf0394b5e6866c78699b
Binary files /dev/null and b/corpus/conflict-page/cold/cold-main.png differ
diff --git a/corpus/conflict-page/cold/cold-menu.json b/corpus/conflict-page/cold/cold-menu.json
new file mode 100644
index 0000000000000000000000000000000000000000..f43854a7a42d5bb99c0b443513bc0c0993c2716c
--- /dev/null
+++ b/corpus/conflict-page/cold/cold-menu.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "",
+ "class": "Net UI Tool Window",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/cold/cold-menu.png b/corpus/conflict-page/cold/cold-menu.png
new file mode 100644
index 0000000000000000000000000000000000000000..ae05c88ae8f99bca435602c2848815a8db493a97
Binary files /dev/null and b/corpus/conflict-page/cold/cold-menu.png differ
diff --git a/corpus/conflict-page/cold/cold-shown.json b/corpus/conflict-page/cold/cold-shown.json
new file mode 100644
index 0000000000000000000000000000000000000000..2797bae39ee1a51a132ddf62bd0f0a7b5fb6f9e9
--- /dev/null
+++ b/corpus/conflict-page/cold/cold-shown.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Snowbound: caf\u00e9, \u6771\u4eac, \u0645\u0631\u062d\u0628\u0627 - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/cold/cold-shown.png b/corpus/conflict-page/cold/cold-shown.png
new file mode 100644
index 0000000000000000000000000000000000000000..28b4db4798984a0b42158552a903406f50099aab
Binary files /dev/null and b/corpus/conflict-page/cold/cold-shown.png differ
diff --git a/corpus/conflict-page/cold/commands.jsonl b/corpus/conflict-page/cold/commands.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..88a0fe416fa51778997f253463110777c3747341
--- /dev/null
+++ b/corpus/conflict-page/cold/commands.jsonl
@@ -0,0 +1,6 @@
+{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-FA8F1E62 -ExpectedPages -1 -KeepOpen", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Rename-Item C:\\one-tests\\runs\\capture\\read before-read\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-FA8F1E62 -UseCurrentCache -ExpectedPages -1", "exit": 0, "stdout": "Read 2 sections and 1 pages.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
diff --git a/corpus/conflict-page/cold/machine.json b/corpus/conflict-page/cold/machine.json
new file mode 100644
index 0000000000000000000000000000000000000000..d0177b47e02f051df52127aafaa35425381a2411
--- /dev/null
+++ b/corpus/conflict-page/cold/machine.json
@@ -0,0 +1 @@
+{"name": "m6-fa8f1e62", "hostname": "ONE-M6-FA8F1E62"}
diff --git a/corpus/conflict-page/cold/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one b/corpus/conflict-page/cold/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one
new file mode 100644
index 0000000000000000000000000000000000000000..ffe604aa40452c5694188fa0042a52d1d9cf7da8
Binary files /dev/null and b/corpus/conflict-page/cold/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one differ
diff --git a/corpus/conflict-page/cold/notebook/OneNote_RecycleBin/Open Notebook.onetoc2 b/corpus/conflict-page/cold/notebook/OneNote_RecycleBin/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..e795d08b1ebc6d904ffba085f27ed57f2dfe90f6
Binary files /dev/null and b/corpus/conflict-page/cold/notebook/OneNote_RecycleBin/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/cold/notebook/Open Notebook.onetoc2 b/corpus/conflict-page/cold/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..6e5dfc82f4398ec9c92ff1009ceb7b20c199f700
Binary files /dev/null and b/corpus/conflict-page/cold/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/cold/notebook/synthetic.one b/corpus/conflict-page/cold/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..deff07779000e935c7af2c07cc425e9a57e8c0bc
Binary files /dev/null and b/corpus/conflict-page/cold/notebook/synthetic.one differ
diff --git a/corpus/conflict-page/cold/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment b/corpus/conflict-page/cold/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment
new file mode 100644
index 0000000000000000000000000000000000000000..66b6151e91f90df6a61a19ef2f4447da278e07b2
--- /dev/null
+++ b/corpus/conflict-page/cold/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment
@@ -0,0 +1 @@
+Fictitious attachment for native corpus.
\ No newline at end of file
diff --git a/corpus/conflict-page/cold/read/environment.json b/corpus/conflict-page/cold/read/environment.json
new file mode 100644
index 0000000000000000000000000000000000000000..7c72a2249c5bedec250b192ce0de0762aaa1dbde
--- /dev/null
+++ b/corpus/conflict-page/cold/read/environment.json
@@ -0,0 +1,7 @@
+{
+ "powershell": "5.1.14409.1005",
+ "schema": "xs2010",
+ "hostname": "ONE-M6-FA8F1E62",
+ "cold": false,
+ "onenote": "14.0.4763.1000"
+}
diff --git a/corpus/conflict-page/cold/read/hierarchy.xml b/corpus/conflict-page/cold/read/hierarchy.xml
new file mode 100644
index 0000000000000000000000000000000000000000..129b3d3fff6dbcbe8697a747320aeb5ae880165b
--- /dev/null
+++ b/corpus/conflict-page/cold/read/hierarchy.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/cold/read/page-000.xml b/corpus/conflict-page/cold/read/page-000.xml
new file mode 100644
index 0000000000000000000000000000000000000000..631b9d5bd28c3cfa00181deedba99f442f148656
--- /dev/null
+++ b/corpus/conflict-page/cold/read/page-000.xml
@@ -0,0 +1,26 @@
+
+Remote: café, 東京, مرحبا]]>AK4FHQSiAaIBAWAqABofi0JwRYR1gL7QRdkE+oeVMvLsF0G4wBa/8yv0et67E4vlXrNInJuhJVq3
+URK/7HHMzCKxQYkOuxMs7nqhWM9UiuaXxU+PBvi60uGbIsRmbacNslhJmh1OQDQGbBADBkgQRSNG
+IwUIC2VPZmdoZGkZFDIIAIAeAiHi4kEzCADwEAKLxeJBZAMVRgBACpUE/QGH81d3mq7mkzgM0mcB
+mk1gEBgEBgEBgE1mUAgMzmUAmsygE1mUzmkBmc0gMzmk1mUAmsyTOaTWZQCazKZgALSmiazKATWZ
+QCAwCAwBAZpM4DAJlNYBMprAJlNZpM4DNJmmU1mkzgM0mcBmkzmU1gEyAAFpTSAzOaQGZzRAYBAY
+BAYBMprAIDNJnAZpM0yms0mcBmkzgM0mcymsAmU1gEyms0maZAACZTSawCZTWATKawCAoBAYBAZn
+NIDAJrMoBNZlAJrMpnMoBNZlAJrMpnNIDM5pAZnNJrMkzmk1AACH8C7XgWnmkzgM0msAmU1gEBgE
+BgEBTOaQGATWZQCazKATWZTOaTWZTNNJrMpmmUzTSaplM00mqZTOaTVMpmmk1TKATKazSawCAzSZ
+wGAQFAIDAJrMoBAZnNIDM5pAZnMpnNJrMpmmk1mUzTSaplM00mqZTNNJrMpmmk1TKZplhuAzKATW
+ZQCazKAIDAIDAIDNJnAYBMprAJlM4DNJnMprNJnMk1mkzmSazRM5kms0TOZJnMprNEzmSazQtKaQ
+GZzRNZlAIDAIDAIDAJlNYAmU1gEymsAmU1mkzmU1miZzKazRM5kmcyTWaJnMk1mkzmSazRM5kAUU
+RgBAAAAJBAUURgAAAAASABEgXhT+65lz/EiPi+8SFvjqCAk+gEcDAa/89Qd0AxVGAEAKABEg2R87
+QQrSFUA=AOECHQJSegFgKgAaH4tCcEWEdYC+0EXZBPqHlTLy7BdBuMAWv/Mr9HreuxOL5V6zSJyboSVat1ES
+v+xxzMwisUGJDrsTLO56oVjPVIrml8VPjwb4utLhmyLEZm2nDbJYSZodTkA0BmwQAwZIEEUjRiMF
+CAtlT2ZnaGRpGRQyCACAHgIh4uJBMwgA8BACi8XiQWQDFUYAQArKAUuH88s3nkzmkzgM0mcBmkzg
+KAQGAQGATWZQCAzOaQGZzKATWZTOaQGZzSAzOaTWZQCazJM5pNZlAJrMpmAAAACH8D9XgeBJlM0z
+mRNZpNZpM4DAJlNYBAUAgMAgMzmkBgE1mUAmsyTOaTWZTOaTVMpnNJqmUzTSaplM00mqZTOZTNNJ
+qmUzTSagBRRGAIAAAAkEBRRGAAAAABIAESDPTQ4xwIZyTqQ8FVDbHxeKCj9AI5rARvPPUDugAxVG
+AEAKABEgLMlfQdkfI0A=iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9J1uoA
+AAAASUVORK5CYII=
diff --git a/corpus/conflict-page/cold/read/payloads.json b/corpus/conflict-page/cold/read/payloads.json
new file mode 100644
index 0000000000000000000000000000000000000000..88ae247d1cd3dee9c2ac3f05912170135543e7cb
--- /dev/null
+++ b/corpus/conflict-page/cold/read/payloads.json
@@ -0,0 +1,10 @@
+[
+ {
+ "sha256": "af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7",
+ "name": "fictitious-attachment.txt",
+ "object": "{EEA4864A-EC4F-48DD-94E4-A54023F53C64}{11}{B0}",
+ "kind": "InsertedFile",
+ "page": "{67EB820C-348D-4B5E-927D-6719A0D9BAF1}{1}{B0}",
+ "bytes": 43
+ }
+]
\ No newline at end of file
diff --git a/corpus/conflict-page/cold/run.json b/corpus/conflict-page/cold/run.json
new file mode 100644
index 0000000000000000000000000000000000000000..376f7384b0892afab7194adc1def657f070b7909
--- /dev/null
+++ b/corpus/conflict-page/cold/run.json
@@ -0,0 +1,18 @@
+{
+ "notebook": "/private/tmp/gate3/candidate",
+ "expected_pages": -1,
+ "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"
+ }
+}
diff --git a/corpus/conflict-page/cold/scripts/cold.ps1 b/corpus/conflict-page/cold/scripts/cold.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..a0b99e7aeb86f4dc10032537800501a52ad65b4f
--- /dev/null
+++ b/corpus/conflict-page/cold/scripts/cold.ps1
@@ -0,0 +1,27 @@
+param([Parameter(Mandatory=$true)][string]$Root, [string]$CloneHost = '')
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
+if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
+ throw 'Choose a run directly below C:\one-tests\runs.'
+}
+if (Get-Process ONENOTE -ErrorAction SilentlyContinue) { throw 'Close OneNote before resetting its test cache.' }
+$key = 'HKCU:\Software\Microsoft\Office\14.0\OneNote'
+if ($CloneHost) {
+ if ($CloneHost -notmatch '^ONE-[A-Z0-9-]+$' -or [Environment]::MachineName -ne $CloneHost) {
+ throw 'The disposable clone hostname does not match this machine.'
+ }
+ New-Item "$key\Options\Paths" -Force | Out-Null
+ New-ItemProperty "$key\Options\Paths" -Name UnfiledNotesSection -PropertyType ExpandString -Value 'C:\one-tests\Loose.one' -Force | Out-Null
+} elseif ((Get-ItemProperty "$key\Options\Paths").UnfiledNotesSection -ne 'C:\one-tests\Loose.one' -or
+ -not (Test-Path 'C:\one-tests\profile-original-cache')) {
+ throw 'Park the personal OneNote profile before resetting the test cache.'
+}
+$cache = Join-Path $env:LOCALAPPDATA 'Microsoft\OneNote\14.0'
+$parked = Join-Path 'C:\one-tests\caches' ([IO.Path]::GetFileName($root))
+if (Test-Path $parked) { throw 'Choose a new run; its parked cache already exists.' }
+New-Item -ItemType Directory -Path 'C:\one-tests\caches' -Force | Out-Null
+if (Test-Path $cache) { Move-Item -LiteralPath $cache -Destination $parked }
+if (Test-Path "$key\OpenNotebooks") { Remove-Item "$key\OpenNotebooks" -Recurse }
+New-Item "$key\OpenNotebooks" | Out-Null
+New-ItemProperty "$key\OpenNotebooks" -Name '1' -PropertyType String -Value "$root\notebook" | Out-Null
diff --git a/corpus/conflict-page/cold/scripts/read.ps1 b/corpus/conflict-page/cold/scripts/read.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..476094ab1e0c47077988a6461db2080cf208642b
--- /dev/null
+++ b/corpus/conflict-page/cold/scripts/read.ps1
@@ -0,0 +1,142 @@
+param(
+ [Parameter(Mandatory=$true)][string]$Root,
+ [int]$ExpectedPages = -1,
+ [switch]$UseCurrentCache,
+ [switch]$Pdf,
+ [switch]$KeepOpen,
+ [string]$CloneHost = ''
+)
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
+if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
+ throw 'Choose a run directly below C:\one-tests\runs.'
+}
+$notebook = Join-Path $root 'notebook'
+$output = Join-Path $root 'read'
+if (Test-Path $output) { throw 'Choose a new read destination.' }
+if ($UseCurrentCache) {
+ if ((Get-ItemProperty 'HKCU:\Software\Microsoft\Office\14.0\OneNote\Options\Paths').UnfiledNotesSection -ne 'C:\one-tests\Loose.one') {
+ throw 'Park the personal OneNote profile before reading test notebooks.'
+ }
+} else {
+ & "$PSScriptRoot\cold-current.ps1" -Root $root -CloneHost $CloneHost
+}
+New-Item -ItemType Directory -Path $output | Out-Null
+$app = New-Object -ComObject OneNote.Application
+$notebookId = ''
+$failure = $null
+try {
+ $app.OpenHierarchy($notebook, '', [ref]$notebookId, 0)
+ $process = Get-Process ONENOTE
+ @{ hostname = [Environment]::MachineName; onenote = $process.MainModule.FileVersionInfo.FileVersion;
+ powershell = $PSVersionTable.PSVersion.ToString(); schema = 'xs2010'; cold = (-not $UseCurrentCache.IsPresent) } |
+ ConvertTo-Json | Set-Content (Join-Path $output 'environment.json') -Encoding UTF8
+ $sections = @()
+ foreach ($file in @(Get-ChildItem $notebook -Recurse | Where-Object { $_.Extension -eq '.one' })) {
+ $id = ''
+ $app.OpenHierarchy($file.FullName, '', [ref]$id, 0)
+ $sections += $id
+ }
+ $deadline = [DateTime]::UtcNow.AddSeconds(300)
+ $previous = ''
+ $lastChange = ''
+ $stableSince = [DateTime]::UtcNow
+ $settled = $false
+ do {
+ $pages = @{}
+ foreach ($section in $sections) {
+ $hierarchy = ''
+ $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
+ [xml]$xml = $hierarchy
+ foreach ($node in $xml.SelectNodes('//*[@path]')) {
+ if (-not $node.GetAttribute('path').StartsWith("$notebook\", [StringComparison]::OrdinalIgnoreCase)) {
+ throw 'OneNote opened a section outside the copied notebook.'
+ }
+ }
+ foreach ($node in $xml.SelectNodes('//*[local-name()="Page"]')) {
+ $id = $node.GetAttribute('ID')
+ $content = ''
+ $app.GetPageContent($id, [ref]$content, 1, 1)
+ $pages[$id] = $content
+ }
+ }
+ $signature = [String]::Join('|', @($pages.Keys | Sort-Object | ForEach-Object { $_ + $pages[$_] }))
+ if ($signature -ne $previous) {
+ $lastChange = $previous
+ $previous = $signature
+ $stableSince = [DateTime]::UtcNow
+ }
+ if ((($ExpectedPages -ge 0 -and $pages.Count -eq $ExpectedPages) -or
+ ($ExpectedPages -lt 0 -and $pages.Count -gt 0)) -and
+ ([DateTime]::UtcNow - $stableSince).TotalSeconds -ge 2) { $settled = $true; break }
+ Start-Sleep -Milliseconds 250
+ } while ([DateTime]::UtcNow -lt $deadline)
+ if (-not $settled -or ($ExpectedPages -ge 0 -and $pages.Count -ne $ExpectedPages) -or ($ExpectedPages -lt 0 -and $pages.Count -eq 0)) {
+ [IO.File]::WriteAllText((Join-Path $output 'previous-signature.txt'), $lastChange, [Text.Encoding]::UTF8)
+ $index = 0
+ foreach ($id in @($pages.Keys | Sort-Object)) {
+ [IO.File]::WriteAllText((Join-Path $output ('unsettled-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8)
+ $index++
+ }
+ $hierarchy = ''
+ $app.GetHierarchy($notebookId, 4, [ref]$hierarchy, 1)
+ [IO.File]::WriteAllText((Join-Path $output 'unsettled-hierarchy.xml'), $hierarchy, [Text.Encoding]::UTF8)
+ throw "Expected $ExpectedPages stable pages; OneNote returned $($pages.Count), settled=$settled."
+ }
+ $index = 0
+ $payloads = @()
+ foreach ($id in @($pages.Keys | Sort-Object)) {
+ [IO.File]::WriteAllText((Join-Path $output ('page-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8)
+ if ($Pdf) {
+ $pdfPath = Join-Path $output ('page-{0:d3}.pdf' -f $index)
+ $app.NavigateTo($id, '', $false)
+ $app.Publish($id, $pdfPath, 3, '')
+ if (-not (Test-Path $pdfPath) -or (Get-Item $pdfPath).Length -eq 0) { throw 'OneNote did not publish the page PDF.' }
+ }
+ [xml]$page = $pages[$id]
+ foreach ($file in $page.SelectNodes('//*[local-name()="InsertedFile" or local-name()="MediaFile"]')) {
+ $bytes = [IO.File]::ReadAllBytes($file.GetAttribute('pathCache'))
+ $hash = [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash($bytes)).Replace('-', '').ToLowerInvariant()
+ [IO.File]::WriteAllBytes((Join-Path $output ($hash + '.attachment')), $bytes)
+ $payloads += @{ page = $id; object = $file.ParentNode.GetAttribute('objectID');
+ kind = $file.LocalName; name = $file.GetAttribute('preferredName');
+ sha256 = $hash; bytes = $bytes.Length }
+ }
+ $index++
+ }
+ [IO.File]::WriteAllText((Join-Path $output 'payloads.json'), (ConvertTo-Json -InputObject $payloads -Depth 4), [Text.Encoding]::UTF8)
+ $all = ''
+ $app.GetHierarchy($notebookId, 4, [ref]$all, 1)
+ [xml]$finalTree = $all
+ $finalIds = @($finalTree.SelectNodes('//*[local-name()="Page"]') | ForEach-Object { $_.GetAttribute('ID') } | Sort-Object -Unique)
+ if ($finalIds.Count -ne $pages.Count -or @($finalIds | Where-Object { -not $pages.ContainsKey($_) }).Count -ne 0) {
+ throw 'The notebook hierarchy changed while collecting page evidence; repeat the cold read.'
+ }
+ [IO.File]::WriteAllText((Join-Path $output 'hierarchy.xml'), $all, [Text.Encoding]::UTF8)
+ Write-Output "Read $($sections.Count) sections and $($pages.Count) pages."
+} catch {
+ $failure = $_
+ [IO.File]::WriteAllText((Join-Path $output 'failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8)
+ throw
+} finally {
+ try {
+ try {
+ if ($notebookId -and $CloneHost) { $app.SyncHierarchy($notebookId) }
+ if ($notebookId -and -not $KeepOpen -and (-not $UseCurrentCache -or $CloneHost)) {
+ $app.CloseNotebook($notebookId, $false)
+ }
+ } catch {
+ if ($null -eq $failure) { throw }
+ [IO.File]::WriteAllText((Join-Path $output 'cleanup-failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8)
+ }
+ } finally {
+ [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
+ }
+ $app = $null
+ [GC]::Collect()
+ [GC]::WaitForPendingFinalizers()
+ if (-not $UseCurrentCache -and -not $CloneHost) {
+ Get-Process ONENOTE -ErrorAction SilentlyContinue | Wait-Process -Timeout 10
+ }
+}
diff --git a/corpus/conflict-page/cold/source.json b/corpus/conflict-page/cold/source.json
new file mode 100644
index 0000000000000000000000000000000000000000..94cafa9cd7aff191a1fc72f639a1d752940f8906
--- /dev/null
+++ b/corpus/conflict-page/cold/source.json
@@ -0,0 +1,14 @@
+[
+ {
+ "path": "Open Notebook.onetoc2",
+ "bytes": 4944,
+ "sha256": "36274ad5cc077c6b1ad51ee4c4b90d3b6798a45ce446cbfd7c9d7ac504776b28",
+ "mtime_ns": 1788585164198018949
+ },
+ {
+ "path": "synthetic.one",
+ "bytes": 43112,
+ "sha256": "ab763188e885724957e2440a78eb29c457f47d538f78c3cc40e9c1a396be5c3b",
+ "mtime_ns": 1790489509247358867
+ }
+]
diff --git a/corpus/conflict-page/cold/teardown.json b/corpus/conflict-page/cold/teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/conflict-page/cold/teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/conflict-page/native-delete/before-read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment b/corpus/conflict-page/native-delete/before-read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment
new file mode 100644
index 0000000000000000000000000000000000000000..66b6151e91f90df6a61a19ef2f4447da278e07b2
--- /dev/null
+++ b/corpus/conflict-page/native-delete/before-read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment
@@ -0,0 +1 @@
+Fictitious attachment for native corpus.
\ No newline at end of file
diff --git a/corpus/conflict-page/native-delete/before-read/environment.json b/corpus/conflict-page/native-delete/before-read/environment.json
new file mode 100644
index 0000000000000000000000000000000000000000..9048ca4bd4e5ef3cdfc99810e0455a385a8556d2
--- /dev/null
+++ b/corpus/conflict-page/native-delete/before-read/environment.json
@@ -0,0 +1,7 @@
+{
+ "powershell": "5.1.14409.1005",
+ "schema": "xs2010",
+ "hostname": "ONE-M6-7691E04E",
+ "cold": true,
+ "onenote": "14.0.4763.1000"
+}
diff --git a/corpus/conflict-page/native-delete/before-read/hierarchy.xml b/corpus/conflict-page/native-delete/before-read/hierarchy.xml
new file mode 100644
index 0000000000000000000000000000000000000000..22ff82ae69fa8483dea86f371739b36373c76aa2
--- /dev/null
+++ b/corpus/conflict-page/native-delete/before-read/hierarchy.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/native-delete/before-read/page-000.xml b/corpus/conflict-page/native-delete/before-read/page-000.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0fe04aa3c60ab810ec3f3dda7268469e20ca8065
--- /dev/null
+++ b/corpus/conflict-page/native-delete/before-read/page-000.xml
@@ -0,0 +1,21 @@
+
+AK4FHQSiAaIBAWAqABofi0JwRYR1gL7QRdkE+oeVMvLsF0G4wBa/8yv0et67E4vlXrNInJuhJVq3
+URK/7HHMzCKxQYkOuxMs7nqhWM9UiuaXxU+PBvi60uGbIsRmbacNslhJmh1OQDQGbBADBkgQRSNG
+IwUIC2VPZmdoZGkZFDIIAIAeAiHi4kEzCADwEAKLxeJBZAMVRgBACpUE/QGH81d3mq7mkzgM0mcB
+mk1gEBgEBgEBgE1mUAgMzmUAmsygE1mUzmkBmc0gMzmk1mUAmsyTOaTWZQCazKZgALSmiazKATWZ
+QCAwCAwBAZpM4DAJlNYBMprAJlNZpM4DNJmmU1mkzgM0mcBmkzmU1gEyAAFpTSAzOaQGZzRAYBAY
+BAYBMprAIDNJnAZpM0yms0mcBmkzgM0mcymsAmU1gEyms0maZAACZTSawCZTWATKawCAoBAYBAZn
+NIDAJrMoBNZlAJrMpnMoBNZlAJrMpnNIDM5pAZnNJrMkzmk1AACH8C7XgWnmkzgM0msAmU1gEBgE
+BgEBTOaQGATWZQCazKATWZTOaTWZTNNJrMpmmUzTSaplM00mqZTOaTVMpmmk1TKATKazSawCAzSZ
+wGAQFAIDAJrMoBAZnNIDM5pAZnMpnNJrMpmmk1mUzTSaplM00mqZTNNJrMpmmk1TKZplhuAzKATW
+ZQCazKAIDAIDAIDNJnAYBMprAJlM4DNJnMprNJnMk1mkzmSazRM5kms0TOZJnMprNEzmSazQtKaQ
+GZzRNZlAIDAIDAIDAJlNYAmU1gEymsAmU1mkzmU1miZzKazRM5kmcyTWaJnMk1mkzmSazRM5kAUU
+RgBAAAAJBAUURgAAAAASABEgXhT+65lz/EiPi+8SFvjqCAk+gEcDAa/89Qd0AxVGAEAKABEg2R87
+QQrSFUA=AOECHQJSegFgKgAaH4tCcEWEdYC+0EXZBPqHlTLy7BdBuMAWv/Mr9HreuxOL5V6zSJyboSVat1ES
+v+xxzMwisUGJDrsTLO56oVjPVIrml8VPjwb4utLhmyLEZm2nDbJYSZodTkA0BmwQAwZIEEUjRiMF
+CAtlT2ZnaGRpGRQyCACAHgIh4uJBMwgA8BACi8XiQWQDFUYAQArKAUuH88s3nkzmkzgM0mcBmkzg
+KAQGAQGATWZQCAzOaQGZzKATWZTOaQGZzSAzOaTWZQCazJM5pNZlAJrMpmAAAACH8D9XgeBJlM0z
+mRNZpNZpM4DAJlNYBAUAgMAgMzmkBgE1mUAmsyTOaTWZTOaTVMpnNJqmUzTSaplM00mqZTOZTNNJ
+qmUzTSagBRRGAIAAAAkEBRRGAAAAABIAESDPTQ4xwIZyTqQ8FVDbHxeKCj9AI5rARvPPUDugAxVG
+AEAKABEgLMlfQdkfI0A=iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9J1uoA
+AAAASUVORK5CYII=
diff --git a/corpus/conflict-page/native-delete/before-read/payloads.json b/corpus/conflict-page/native-delete/before-read/payloads.json
new file mode 100644
index 0000000000000000000000000000000000000000..f4ee299a7df5c6b52e3098b2bc5840a1996ec7e2
--- /dev/null
+++ b/corpus/conflict-page/native-delete/before-read/payloads.json
@@ -0,0 +1,10 @@
+[
+ {
+ "sha256": "af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7",
+ "name": "fictitious-attachment.txt",
+ "object": "{19686511-7C8F-4B47-BC86-55344C46A4DA}{11}{B0}",
+ "kind": "InsertedFile",
+ "page": "{90276157-A44D-48C4-BA1F-976DCF6A224F}{1}{B0}",
+ "bytes": 43
+ }
+]
\ No newline at end of file
diff --git a/corpus/conflict-page/native-delete/cold-conflict.json b/corpus/conflict-page/native-delete/cold-conflict.json
new file mode 100644
index 0000000000000000000000000000000000000000..2fd05a4a7938577ce0347213e7f3ed53dab2c0a4
--- /dev/null
+++ b/corpus/conflict-page/native-delete/cold-conflict.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Client A conflicting edit. - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-delete/cold-conflict.png b/corpus/conflict-page/native-delete/cold-conflict.png
new file mode 100644
index 0000000000000000000000000000000000000000..8d522c209a274b0ad1076843186dd469c683d028
Binary files /dev/null and b/corpus/conflict-page/native-delete/cold-conflict.png differ
diff --git a/corpus/conflict-page/native-delete/cold-delete.json b/corpus/conflict-page/native-delete/cold-delete.json
new file mode 100644
index 0000000000000000000000000000000000000000..668f002c22e396d7530675ec44f659f85b6f6a86
--- /dev/null
+++ b/corpus/conflict-page/native-delete/cold-delete.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Client B conflicting edit. - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-delete/cold-delete.png b/corpus/conflict-page/native-delete/cold-delete.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa8b2818d72f6d10d67a3bfe8c11c73c2bb34d9c
Binary files /dev/null and b/corpus/conflict-page/native-delete/cold-delete.png differ
diff --git a/corpus/conflict-page/native-delete/cold-main.json b/corpus/conflict-page/native-delete/cold-main.json
new file mode 100644
index 0000000000000000000000000000000000000000..668f002c22e396d7530675ec44f659f85b6f6a86
--- /dev/null
+++ b/corpus/conflict-page/native-delete/cold-main.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Client B conflicting edit. - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-delete/cold-main.png b/corpus/conflict-page/native-delete/cold-main.png
new file mode 100644
index 0000000000000000000000000000000000000000..d82c666ffe306025e3f33d57ad6e47433bad0a59
Binary files /dev/null and b/corpus/conflict-page/native-delete/cold-main.png differ
diff --git a/corpus/conflict-page/native-delete/cold-menu.json b/corpus/conflict-page/native-delete/cold-menu.json
new file mode 100644
index 0000000000000000000000000000000000000000..f43854a7a42d5bb99c0b443513bc0c0993c2716c
--- /dev/null
+++ b/corpus/conflict-page/native-delete/cold-menu.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "",
+ "class": "Net UI Tool Window",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-delete/cold-menu.png b/corpus/conflict-page/native-delete/cold-menu.png
new file mode 100644
index 0000000000000000000000000000000000000000..c740dbf75d89b9f347e3cead98f6b4f188fe0b1f
Binary files /dev/null and b/corpus/conflict-page/native-delete/cold-menu.png differ
diff --git a/corpus/conflict-page/native-delete/cold-shown.json b/corpus/conflict-page/native-delete/cold-shown.json
new file mode 100644
index 0000000000000000000000000000000000000000..2fd05a4a7938577ce0347213e7f3ed53dab2c0a4
--- /dev/null
+++ b/corpus/conflict-page/native-delete/cold-shown.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Client A conflicting edit. - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-delete/cold-shown.png b/corpus/conflict-page/native-delete/cold-shown.png
new file mode 100644
index 0000000000000000000000000000000000000000..3257684d36d68a5b5ff42b439cef7a97c793ad62
Binary files /dev/null and b/corpus/conflict-page/native-delete/cold-shown.png differ
diff --git a/corpus/conflict-page/native-delete/commands.jsonl b/corpus/conflict-page/native-delete/commands.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..bf51e5ed90a7bdb52ff561231782da39c4669557
--- /dev/null
+++ b/corpus/conflict-page/native-delete/commands.jsonl
@@ -0,0 +1,6 @@
+{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-7691E04E -ExpectedPages -1 -KeepOpen", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Rename-Item C:\\one-tests\\runs\\capture\\read before-read\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-7691E04E -UseCurrentCache -ExpectedPages -1", "exit": 0, "stdout": "Read 2 sections and 1 pages.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
diff --git a/corpus/conflict-page/native-delete/machine.json b/corpus/conflict-page/native-delete/machine.json
new file mode 100644
index 0000000000000000000000000000000000000000..a5cd39156d6dc01d0e333458bf636f41fdff0721
--- /dev/null
+++ b/corpus/conflict-page/native-delete/machine.json
@@ -0,0 +1 @@
+{"name": "m6-7691e04e", "hostname": "ONE-M6-7691E04E"}
diff --git a/corpus/conflict-page/native-delete/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one b/corpus/conflict-page/native-delete/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one
new file mode 100644
index 0000000000000000000000000000000000000000..1cd05d71d4fe210c8010abf2a4a08bfd8c4abba9
Binary files /dev/null and b/corpus/conflict-page/native-delete/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one differ
diff --git a/corpus/conflict-page/native-delete/notebook/OneNote_RecycleBin/Open Notebook.onetoc2 b/corpus/conflict-page/native-delete/notebook/OneNote_RecycleBin/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..94d5ec410cd4d8d0de874baaa40fe256f0a686bb
Binary files /dev/null and b/corpus/conflict-page/native-delete/notebook/OneNote_RecycleBin/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native-delete/notebook/Open Notebook.onetoc2 b/corpus/conflict-page/native-delete/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..5d8fb2d3a1d1c39967d3a3260efd997006aaf554
Binary files /dev/null and b/corpus/conflict-page/native-delete/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native-delete/notebook/synthetic.one b/corpus/conflict-page/native-delete/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..2d9ecaa8a40f17d391c48d15af3eb5ca56563ca4
Binary files /dev/null and b/corpus/conflict-page/native-delete/notebook/synthetic.one differ
diff --git a/corpus/conflict-page/native-delete/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment b/corpus/conflict-page/native-delete/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment
new file mode 100644
index 0000000000000000000000000000000000000000..66b6151e91f90df6a61a19ef2f4447da278e07b2
--- /dev/null
+++ b/corpus/conflict-page/native-delete/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment
@@ -0,0 +1 @@
+Fictitious attachment for native corpus.
\ No newline at end of file
diff --git a/corpus/conflict-page/native-delete/read/environment.json b/corpus/conflict-page/native-delete/read/environment.json
new file mode 100644
index 0000000000000000000000000000000000000000..5f02dc66163bb072e2494050996e4b456ee3b69a
--- /dev/null
+++ b/corpus/conflict-page/native-delete/read/environment.json
@@ -0,0 +1,7 @@
+{
+ "powershell": "5.1.14409.1005",
+ "schema": "xs2010",
+ "hostname": "ONE-M6-7691E04E",
+ "cold": false,
+ "onenote": "14.0.4763.1000"
+}
diff --git a/corpus/conflict-page/native-delete/read/hierarchy.xml b/corpus/conflict-page/native-delete/read/hierarchy.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d9c3291f114855dddea2bd070799775265dd86e0
--- /dev/null
+++ b/corpus/conflict-page/native-delete/read/hierarchy.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/native-delete/read/page-000.xml b/corpus/conflict-page/native-delete/read/page-000.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6c687224b62d5cb559633435c7772dc6de9e40a4
--- /dev/null
+++ b/corpus/conflict-page/native-delete/read/page-000.xml
@@ -0,0 +1,21 @@
+
+AK4FHQSiAaIBAWAqABofi0JwRYR1gL7QRdkE+oeVMvLsF0G4wBa/8yv0et67E4vlXrNInJuhJVq3
+URK/7HHMzCKxQYkOuxMs7nqhWM9UiuaXxU+PBvi60uGbIsRmbacNslhJmh1OQDQGbBADBkgQRSNG
+IwUIC2VPZmdoZGkZFDIIAIAeAiHi4kEzCADwEAKLxeJBZAMVRgBACpUE/QGH81d3mq7mkzgM0mcB
+mk1gEBgEBgEBgE1mUAgMzmUAmsygE1mUzmkBmc0gMzmk1mUAmsyTOaTWZQCazKZgALSmiazKATWZ
+QCAwCAwBAZpM4DAJlNYBMprAJlNZpM4DNJmmU1mkzgM0mcBmkzmU1gEyAAFpTSAzOaQGZzRAYBAY
+BAYBMprAIDNJnAZpM0yms0mcBmkzgM0mcymsAmU1gEyms0maZAACZTSawCZTWATKawCAoBAYBAZn
+NIDAJrMoBNZlAJrMpnMoBNZlAJrMpnNIDM5pAZnNJrMkzmk1AACH8C7XgWnmkzgM0msAmU1gEBgE
+BgEBTOaQGATWZQCazKATWZTOaTWZTNNJrMpmmUzTSaplM00mqZTOaTVMpmmk1TKATKazSawCAzSZ
+wGAQFAIDAJrMoBAZnNIDM5pAZnMpnNJrMpmmk1mUzTSaplM00mqZTNNJrMpmmk1TKZplhuAzKATW
+ZQCazKAIDAIDAIDNJnAYBMprAJlM4DNJnMprNJnMk1mkzmSazRM5kms0TOZJnMprNEzmSazQtKaQ
+GZzRNZlAIDAIDAIDAJlNYAmU1gEymsAmU1mkzmU1miZzKazRM5kmcyTWaJnMk1mkzmSazRM5kAUU
+RgBAAAAJBAUURgAAAAASABEgXhT+65lz/EiPi+8SFvjqCAk+gEcDAa/89Qd0AxVGAEAKABEg2R87
+QQrSFUA=AOECHQJSegFgKgAaH4tCcEWEdYC+0EXZBPqHlTLy7BdBuMAWv/Mr9HreuxOL5V6zSJyboSVat1ES
+v+xxzMwisUGJDrsTLO56oVjPVIrml8VPjwb4utLhmyLEZm2nDbJYSZodTkA0BmwQAwZIEEUjRiMF
+CAtlT2ZnaGRpGRQyCACAHgIh4uJBMwgA8BACi8XiQWQDFUYAQArKAUuH88s3nkzmkzgM0mcBmkzg
+KAQGAQGATWZQCAzOaQGZzKATWZTOaQGZzSAzOaTWZQCazJM5pNZlAJrMpmAAAACH8D9XgeBJlM0z
+mRNZpNZpM4DAJlNYBAUAgMAgMzmkBgE1mUAmsyTOaTWZTOaTVMpnNJqmUzTSaplM00mqZTOZTNNJ
+qmUzTSagBRRGAIAAAAkEBRRGAAAAABIAESDPTQ4xwIZyTqQ8FVDbHxeKCj9AI5rARvPPUDugAxVG
+AEAKABEgLMlfQdkfI0A=iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9J1uoA
+AAAASUVORK5CYII=
diff --git a/corpus/conflict-page/native-delete/read/payloads.json b/corpus/conflict-page/native-delete/read/payloads.json
new file mode 100644
index 0000000000000000000000000000000000000000..f4ee299a7df5c6b52e3098b2bc5840a1996ec7e2
--- /dev/null
+++ b/corpus/conflict-page/native-delete/read/payloads.json
@@ -0,0 +1,10 @@
+[
+ {
+ "sha256": "af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7",
+ "name": "fictitious-attachment.txt",
+ "object": "{19686511-7C8F-4B47-BC86-55344C46A4DA}{11}{B0}",
+ "kind": "InsertedFile",
+ "page": "{90276157-A44D-48C4-BA1F-976DCF6A224F}{1}{B0}",
+ "bytes": 43
+ }
+]
\ No newline at end of file
diff --git a/corpus/conflict-page/native-delete/run.json b/corpus/conflict-page/native-delete/run.json
new file mode 100644
index 0000000000000000000000000000000000000000..8bbfce1033d107da6ca6bc60347fa5c2614f6a2e
--- /dev/null
+++ b/corpus/conflict-page/native-delete/run.json
@@ -0,0 +1,18 @@
+{
+ "notebook": "/private/tmp/gate/native-conflict",
+ "expected_pages": -1,
+ "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"
+ }
+}
diff --git a/corpus/conflict-page/native-delete/scripts/cold.ps1 b/corpus/conflict-page/native-delete/scripts/cold.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..a0b99e7aeb86f4dc10032537800501a52ad65b4f
--- /dev/null
+++ b/corpus/conflict-page/native-delete/scripts/cold.ps1
@@ -0,0 +1,27 @@
+param([Parameter(Mandatory=$true)][string]$Root, [string]$CloneHost = '')
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
+if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
+ throw 'Choose a run directly below C:\one-tests\runs.'
+}
+if (Get-Process ONENOTE -ErrorAction SilentlyContinue) { throw 'Close OneNote before resetting its test cache.' }
+$key = 'HKCU:\Software\Microsoft\Office\14.0\OneNote'
+if ($CloneHost) {
+ if ($CloneHost -notmatch '^ONE-[A-Z0-9-]+$' -or [Environment]::MachineName -ne $CloneHost) {
+ throw 'The disposable clone hostname does not match this machine.'
+ }
+ New-Item "$key\Options\Paths" -Force | Out-Null
+ New-ItemProperty "$key\Options\Paths" -Name UnfiledNotesSection -PropertyType ExpandString -Value 'C:\one-tests\Loose.one' -Force | Out-Null
+} elseif ((Get-ItemProperty "$key\Options\Paths").UnfiledNotesSection -ne 'C:\one-tests\Loose.one' -or
+ -not (Test-Path 'C:\one-tests\profile-original-cache')) {
+ throw 'Park the personal OneNote profile before resetting the test cache.'
+}
+$cache = Join-Path $env:LOCALAPPDATA 'Microsoft\OneNote\14.0'
+$parked = Join-Path 'C:\one-tests\caches' ([IO.Path]::GetFileName($root))
+if (Test-Path $parked) { throw 'Choose a new run; its parked cache already exists.' }
+New-Item -ItemType Directory -Path 'C:\one-tests\caches' -Force | Out-Null
+if (Test-Path $cache) { Move-Item -LiteralPath $cache -Destination $parked }
+if (Test-Path "$key\OpenNotebooks") { Remove-Item "$key\OpenNotebooks" -Recurse }
+New-Item "$key\OpenNotebooks" | Out-Null
+New-ItemProperty "$key\OpenNotebooks" -Name '1' -PropertyType String -Value "$root\notebook" | Out-Null
diff --git a/corpus/conflict-page/native-delete/scripts/read.ps1 b/corpus/conflict-page/native-delete/scripts/read.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..476094ab1e0c47077988a6461db2080cf208642b
--- /dev/null
+++ b/corpus/conflict-page/native-delete/scripts/read.ps1
@@ -0,0 +1,142 @@
+param(
+ [Parameter(Mandatory=$true)][string]$Root,
+ [int]$ExpectedPages = -1,
+ [switch]$UseCurrentCache,
+ [switch]$Pdf,
+ [switch]$KeepOpen,
+ [string]$CloneHost = ''
+)
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
+if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
+ throw 'Choose a run directly below C:\one-tests\runs.'
+}
+$notebook = Join-Path $root 'notebook'
+$output = Join-Path $root 'read'
+if (Test-Path $output) { throw 'Choose a new read destination.' }
+if ($UseCurrentCache) {
+ if ((Get-ItemProperty 'HKCU:\Software\Microsoft\Office\14.0\OneNote\Options\Paths').UnfiledNotesSection -ne 'C:\one-tests\Loose.one') {
+ throw 'Park the personal OneNote profile before reading test notebooks.'
+ }
+} else {
+ & "$PSScriptRoot\cold-current.ps1" -Root $root -CloneHost $CloneHost
+}
+New-Item -ItemType Directory -Path $output | Out-Null
+$app = New-Object -ComObject OneNote.Application
+$notebookId = ''
+$failure = $null
+try {
+ $app.OpenHierarchy($notebook, '', [ref]$notebookId, 0)
+ $process = Get-Process ONENOTE
+ @{ hostname = [Environment]::MachineName; onenote = $process.MainModule.FileVersionInfo.FileVersion;
+ powershell = $PSVersionTable.PSVersion.ToString(); schema = 'xs2010'; cold = (-not $UseCurrentCache.IsPresent) } |
+ ConvertTo-Json | Set-Content (Join-Path $output 'environment.json') -Encoding UTF8
+ $sections = @()
+ foreach ($file in @(Get-ChildItem $notebook -Recurse | Where-Object { $_.Extension -eq '.one' })) {
+ $id = ''
+ $app.OpenHierarchy($file.FullName, '', [ref]$id, 0)
+ $sections += $id
+ }
+ $deadline = [DateTime]::UtcNow.AddSeconds(300)
+ $previous = ''
+ $lastChange = ''
+ $stableSince = [DateTime]::UtcNow
+ $settled = $false
+ do {
+ $pages = @{}
+ foreach ($section in $sections) {
+ $hierarchy = ''
+ $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
+ [xml]$xml = $hierarchy
+ foreach ($node in $xml.SelectNodes('//*[@path]')) {
+ if (-not $node.GetAttribute('path').StartsWith("$notebook\", [StringComparison]::OrdinalIgnoreCase)) {
+ throw 'OneNote opened a section outside the copied notebook.'
+ }
+ }
+ foreach ($node in $xml.SelectNodes('//*[local-name()="Page"]')) {
+ $id = $node.GetAttribute('ID')
+ $content = ''
+ $app.GetPageContent($id, [ref]$content, 1, 1)
+ $pages[$id] = $content
+ }
+ }
+ $signature = [String]::Join('|', @($pages.Keys | Sort-Object | ForEach-Object { $_ + $pages[$_] }))
+ if ($signature -ne $previous) {
+ $lastChange = $previous
+ $previous = $signature
+ $stableSince = [DateTime]::UtcNow
+ }
+ if ((($ExpectedPages -ge 0 -and $pages.Count -eq $ExpectedPages) -or
+ ($ExpectedPages -lt 0 -and $pages.Count -gt 0)) -and
+ ([DateTime]::UtcNow - $stableSince).TotalSeconds -ge 2) { $settled = $true; break }
+ Start-Sleep -Milliseconds 250
+ } while ([DateTime]::UtcNow -lt $deadline)
+ if (-not $settled -or ($ExpectedPages -ge 0 -and $pages.Count -ne $ExpectedPages) -or ($ExpectedPages -lt 0 -and $pages.Count -eq 0)) {
+ [IO.File]::WriteAllText((Join-Path $output 'previous-signature.txt'), $lastChange, [Text.Encoding]::UTF8)
+ $index = 0
+ foreach ($id in @($pages.Keys | Sort-Object)) {
+ [IO.File]::WriteAllText((Join-Path $output ('unsettled-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8)
+ $index++
+ }
+ $hierarchy = ''
+ $app.GetHierarchy($notebookId, 4, [ref]$hierarchy, 1)
+ [IO.File]::WriteAllText((Join-Path $output 'unsettled-hierarchy.xml'), $hierarchy, [Text.Encoding]::UTF8)
+ throw "Expected $ExpectedPages stable pages; OneNote returned $($pages.Count), settled=$settled."
+ }
+ $index = 0
+ $payloads = @()
+ foreach ($id in @($pages.Keys | Sort-Object)) {
+ [IO.File]::WriteAllText((Join-Path $output ('page-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8)
+ if ($Pdf) {
+ $pdfPath = Join-Path $output ('page-{0:d3}.pdf' -f $index)
+ $app.NavigateTo($id, '', $false)
+ $app.Publish($id, $pdfPath, 3, '')
+ if (-not (Test-Path $pdfPath) -or (Get-Item $pdfPath).Length -eq 0) { throw 'OneNote did not publish the page PDF.' }
+ }
+ [xml]$page = $pages[$id]
+ foreach ($file in $page.SelectNodes('//*[local-name()="InsertedFile" or local-name()="MediaFile"]')) {
+ $bytes = [IO.File]::ReadAllBytes($file.GetAttribute('pathCache'))
+ $hash = [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash($bytes)).Replace('-', '').ToLowerInvariant()
+ [IO.File]::WriteAllBytes((Join-Path $output ($hash + '.attachment')), $bytes)
+ $payloads += @{ page = $id; object = $file.ParentNode.GetAttribute('objectID');
+ kind = $file.LocalName; name = $file.GetAttribute('preferredName');
+ sha256 = $hash; bytes = $bytes.Length }
+ }
+ $index++
+ }
+ [IO.File]::WriteAllText((Join-Path $output 'payloads.json'), (ConvertTo-Json -InputObject $payloads -Depth 4), [Text.Encoding]::UTF8)
+ $all = ''
+ $app.GetHierarchy($notebookId, 4, [ref]$all, 1)
+ [xml]$finalTree = $all
+ $finalIds = @($finalTree.SelectNodes('//*[local-name()="Page"]') | ForEach-Object { $_.GetAttribute('ID') } | Sort-Object -Unique)
+ if ($finalIds.Count -ne $pages.Count -or @($finalIds | Where-Object { -not $pages.ContainsKey($_) }).Count -ne 0) {
+ throw 'The notebook hierarchy changed while collecting page evidence; repeat the cold read.'
+ }
+ [IO.File]::WriteAllText((Join-Path $output 'hierarchy.xml'), $all, [Text.Encoding]::UTF8)
+ Write-Output "Read $($sections.Count) sections and $($pages.Count) pages."
+} catch {
+ $failure = $_
+ [IO.File]::WriteAllText((Join-Path $output 'failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8)
+ throw
+} finally {
+ try {
+ try {
+ if ($notebookId -and $CloneHost) { $app.SyncHierarchy($notebookId) }
+ if ($notebookId -and -not $KeepOpen -and (-not $UseCurrentCache -or $CloneHost)) {
+ $app.CloseNotebook($notebookId, $false)
+ }
+ } catch {
+ if ($null -eq $failure) { throw }
+ [IO.File]::WriteAllText((Join-Path $output 'cleanup-failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8)
+ }
+ } finally {
+ [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
+ }
+ $app = $null
+ [GC]::Collect()
+ [GC]::WaitForPendingFinalizers()
+ if (-not $UseCurrentCache -and -not $CloneHost) {
+ Get-Process ONENOTE -ErrorAction SilentlyContinue | Wait-Process -Timeout 10
+ }
+}
diff --git a/corpus/conflict-page/native-delete/source.json b/corpus/conflict-page/native-delete/source.json
new file mode 100644
index 0000000000000000000000000000000000000000..c7bc810c8304aa1ca14df7b60ab0c9dcfaffb63c
--- /dev/null
+++ b/corpus/conflict-page/native-delete/source.json
@@ -0,0 +1,14 @@
+[
+ {
+ "path": "Open Notebook.onetoc2",
+ "bytes": 4944,
+ "sha256": "36274ad5cc077c6b1ad51ee4c4b90d3b6798a45ce446cbfd7c9d7ac504776b28",
+ "mtime_ns": 1790472520423354873
+ },
+ {
+ "path": "synthetic.one",
+ "bytes": 40600,
+ "sha256": "e8d43dd57e02fe76e88c8ef45a7e18487bbf28928d0baf077288a4bb1415692c",
+ "mtime_ns": 1790472520423873581
+ }
+]
diff --git a/corpus/conflict-page/native-delete/teardown.json b/corpus/conflict-page/native-delete/teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/conflict-page/native-delete/teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/conflict-page/native-pages/a/a-merged.json b/corpus/conflict-page/native-pages/a/a-merged.json
new file mode 100644
index 0000000000000000000000000000000000000000..2f394e7eaf1defcfe3e06e321b5af76be4f84054
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/a-merged.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Fictitious: caf\u00e9, \u6771\u4eac, \u0645\u0631\u062d\u0628\u0627 - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/a-merged.png b/corpus/conflict-page/native-pages/a/a-merged.png
new file mode 100644
index 0000000000000000000000000000000000000000..2bf9f7a8336aac9401d21ad0614e03e51551d83e
Binary files /dev/null and b/corpus/conflict-page/native-pages/a/a-merged.png differ
diff --git a/corpus/conflict-page/native-pages/a/command-0001.json b/corpus/conflict-page/native-pages/a/command-0001.json
new file mode 100644
index 0000000000000000000000000000000000000000..d79cc0093fa7c789c925d193b3110c4da6523951
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/command-0001.json
@@ -0,0 +1 @@
+{"action": "pages", "titles": ["One", "Two", "Three", "Four", "Target"]}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/command-0002.json b/corpus/conflict-page/native-pages/a/command-0002.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/command-0002.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/command-0003.json b/corpus/conflict-page/native-pages/a/command-0003.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/command-0003.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/command-0004.json b/corpus/conflict-page/native-pages/a/command-0004.json
new file mode 100644
index 0000000000000000000000000000000000000000..8bd5839cfa379b0117fb5154acd9d20bc06b5cda
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/command-0004.json
@@ -0,0 +1 @@
+{"action": "move", "page": "Four", "before": "One"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/command-0005.json b/corpus/conflict-page/native-pages/a/command-0005.json
new file mode 100644
index 0000000000000000000000000000000000000000..39953577e616d0ad2af01c7227449b427efdf5e5
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/command-0005.json
@@ -0,0 +1 @@
+{"action": "move", "page": "Two", "before": null}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/command-0006.json b/corpus/conflict-page/native-pages/a/command-0006.json
new file mode 100644
index 0000000000000000000000000000000000000000..3f6f2524a6523bec79f8152f3340d2ae649729a0
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/command-0006.json
@@ -0,0 +1 @@
+{"action": "edit", "expected": "Body Target.", "text": "Body Target edited offline."}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/command-0007.json b/corpus/conflict-page/native-pages/a/command-0007.json
new file mode 100644
index 0000000000000000000000000000000000000000..554ff0ef8a35478d74f3e5388f75789140b158b8
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/command-0007.json
@@ -0,0 +1 @@
+{"action": "snapshot"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/command-0008.json b/corpus/conflict-page/native-pages/a/command-0008.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/command-0008.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/command-0009.json b/corpus/conflict-page/native-pages/a/command-0009.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/command-0009.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/command-0010.json b/corpus/conflict-page/native-pages/a/command-0010.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/command-0010.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/command-0011.json b/corpus/conflict-page/native-pages/a/command-0011.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/command-0011.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/command-0012.json b/corpus/conflict-page/native-pages/a/command-0012.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/command-0012.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/command-0013.json b/corpus/conflict-page/native-pages/a/command-0013.json
new file mode 100644
index 0000000000000000000000000000000000000000..554ff0ef8a35478d74f3e5388f75789140b158b8
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/command-0013.json
@@ -0,0 +1 @@
+{"action": "snapshot"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/command-0014.json b/corpus/conflict-page/native-pages/a/command-0014.json
new file mode 100644
index 0000000000000000000000000000000000000000..7adf980027ef43cc77767d4fd1b1d67ad1541ab4
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/command-0014.json
@@ -0,0 +1 @@
+{"action": "close"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/commands.jsonl b/corpus/conflict-page/native-pages/a/commands.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..a0ec79a71dfef56ca2cfd608ba91f4a17561c1c6
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/commands.jsonl
@@ -0,0 +1,16 @@
+{"command": "mkdir C:\\one-tests\\runs\\capture", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -ExecutionPolicy Bypass -File C:\\one-tests\\network.ps1 -LabMac 52:54:02:a8:e9:55", "exit": 0, "stdout": "{\"renewalResult\":0,\"releaseResult\":0,\"mac\":\"52:54:02:a8:e9:55\",\"address\":\"192.168.77.42\"}\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0001.tmp C:\\one-tests\\runs\\capture\\inbox\\0001.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0002.tmp C:\\one-tests\\runs\\capture\\inbox\\0002.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0003.tmp C:\\one-tests\\runs\\capture\\inbox\\0003.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0004.tmp C:\\one-tests\\runs\\capture\\inbox\\0004.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0005.tmp C:\\one-tests\\runs\\capture\\inbox\\0005.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0006.tmp C:\\one-tests\\runs\\capture\\inbox\\0006.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0007.tmp C:\\one-tests\\runs\\capture\\inbox\\0007.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0008.tmp C:\\one-tests\\runs\\capture\\inbox\\0008.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0009.tmp C:\\one-tests\\runs\\capture\\inbox\\0009.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0010.tmp C:\\one-tests\\runs\\capture\\inbox\\0010.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0011.tmp C:\\one-tests\\runs\\capture\\inbox\\0011.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0012.tmp C:\\one-tests\\runs\\capture\\inbox\\0012.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0013.tmp C:\\one-tests\\runs\\capture\\inbox\\0013.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0014.tmp C:\\one-tests\\runs\\capture\\inbox\\0014.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
diff --git a/corpus/conflict-page/native-pages/a/hierarchy-0007.xml b/corpus/conflict-page/native-pages/a/hierarchy-0007.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ef20a1ea257cde476594b8c089fec60f01448292
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/hierarchy-0007.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/native-pages/a/hierarchy-0013.xml b/corpus/conflict-page/native-pages/a/hierarchy-0013.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1b7acda08daec45d584097dfb24cc17574ae24c1
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/hierarchy-0013.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/native-pages/a/listed.json b/corpus/conflict-page/native-pages/a/listed.json
new file mode 100644
index 0000000000000000000000000000000000000000..67a675a4f64d48e4266aa24db7ca7731d86ad8c6
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/listed.json
@@ -0,0 +1 @@
+["Fictitious: café, 東京, مرحبا", "One", "Two", "Four", "Three", "Target"]
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/a/machine.json b/corpus/conflict-page/native-pages/a/machine.json
new file mode 100644
index 0000000000000000000000000000000000000000..8078699df47e7da61dfbd6d7fcafb6d2f7114e2d
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/machine.json
@@ -0,0 +1 @@
+{"name": "m6-327953db", "hostname": "ONE-M6-327953DB"}
diff --git a/corpus/conflict-page/native-pages/a/teardown.json b/corpus/conflict-page/native-pages/a/teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/conflict-page/native-pages/a/teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/conflict-page/native-pages/b-published/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one b/corpus/conflict-page/native-pages/b-published/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one
new file mode 100644
index 0000000000000000000000000000000000000000..4af78d7a2dbe5360b92dcd980204e841a09ba37d
Binary files /dev/null and b/corpus/conflict-page/native-pages/b-published/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one differ
diff --git a/corpus/conflict-page/native-pages/b-published/notebook/OneNote_RecycleBin/Open Notebook.onetoc2 b/corpus/conflict-page/native-pages/b-published/notebook/OneNote_RecycleBin/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..6e4b6c9be779af4e6515e1c9facf29cf6c5b792e
Binary files /dev/null and b/corpus/conflict-page/native-pages/b-published/notebook/OneNote_RecycleBin/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native-pages/b-published/notebook/Open Notebook.onetoc2 b/corpus/conflict-page/native-pages/b-published/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..c5257a0970c051ac5c96357e05cdd9b0f242ee38
Binary files /dev/null and b/corpus/conflict-page/native-pages/b-published/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native-pages/b-published/notebook/synthetic.one b/corpus/conflict-page/native-pages/b-published/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..1d3f7dc3f9b4db4d434241133067f9942301fb88
Binary files /dev/null and b/corpus/conflict-page/native-pages/b-published/notebook/synthetic.one differ
diff --git a/corpus/conflict-page/native-pages/b/b-merged.json b/corpus/conflict-page/native-pages/b/b-merged.json
new file mode 100644
index 0000000000000000000000000000000000000000..2f394e7eaf1defcfe3e06e321b5af76be4f84054
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/b-merged.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Fictitious: caf\u00e9, \u6771\u4eac, \u0645\u0631\u062d\u0628\u0627 - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/b/b-merged.png b/corpus/conflict-page/native-pages/b/b-merged.png
new file mode 100644
index 0000000000000000000000000000000000000000..33bdd9945f10bcec0adcd5e541085aa7e14e86e6
Binary files /dev/null and b/corpus/conflict-page/native-pages/b/b-merged.png differ
diff --git a/corpus/conflict-page/native-pages/b/command-0001.json b/corpus/conflict-page/native-pages/b/command-0001.json
new file mode 100644
index 0000000000000000000000000000000000000000..554ff0ef8a35478d74f3e5388f75789140b158b8
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/command-0001.json
@@ -0,0 +1 @@
+{"action": "snapshot"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/b/command-0002.json b/corpus/conflict-page/native-pages/b/command-0002.json
new file mode 100644
index 0000000000000000000000000000000000000000..dc7bcbe3a9d3ab7ff629841610b3af0459d3ffce
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/command-0002.json
@@ -0,0 +1 @@
+{"action": "move", "page": "Four", "before": null}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/b/command-0003.json b/corpus/conflict-page/native-pages/b/command-0003.json
new file mode 100644
index 0000000000000000000000000000000000000000..4b88e6612d0b555ffdaf5d84fc00c75de4937491
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/command-0003.json
@@ -0,0 +1 @@
+{"action": "move", "page": "Three", "before": "One"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/b/command-0004.json b/corpus/conflict-page/native-pages/b/command-0004.json
new file mode 100644
index 0000000000000000000000000000000000000000..eff482c021828f4924fb0845aa6043a8312745ea
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/command-0004.json
@@ -0,0 +1 @@
+{"action": "delete-page", "page": "Target"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/b/command-0005.json b/corpus/conflict-page/native-pages/b/command-0005.json
new file mode 100644
index 0000000000000000000000000000000000000000..554ff0ef8a35478d74f3e5388f75789140b158b8
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/command-0005.json
@@ -0,0 +1 @@
+{"action": "snapshot"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/b/command-0006.json b/corpus/conflict-page/native-pages/b/command-0006.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/command-0006.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/b/command-0007.json b/corpus/conflict-page/native-pages/b/command-0007.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/command-0007.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/b/command-0008.json b/corpus/conflict-page/native-pages/b/command-0008.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/command-0008.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/b/command-0009.json b/corpus/conflict-page/native-pages/b/command-0009.json
new file mode 100644
index 0000000000000000000000000000000000000000..554ff0ef8a35478d74f3e5388f75789140b158b8
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/command-0009.json
@@ -0,0 +1 @@
+{"action": "snapshot"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/b/command-0010.json b/corpus/conflict-page/native-pages/b/command-0010.json
new file mode 100644
index 0000000000000000000000000000000000000000..7adf980027ef43cc77767d4fd1b1d67ad1541ab4
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/command-0010.json
@@ -0,0 +1 @@
+{"action": "close"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/b/commands.jsonl b/corpus/conflict-page/native-pages/b/commands.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..b01eb35abba6cfb0631130c1778f33d75ea7ae3e
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/commands.jsonl
@@ -0,0 +1,12 @@
+{"command": "mkdir C:\\one-tests\\runs\\capture", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -ExecutionPolicy Bypass -File C:\\one-tests\\network.ps1 -LabMac 52:54:02:19:c7:6d", "exit": 0, "stdout": "{\"renewalResult\":0,\"releaseResult\":0,\"mac\":\"52:54:02:19:c7:6d\",\"address\":\"192.168.77.210\"}\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0001.tmp C:\\one-tests\\runs\\capture\\inbox\\0001.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0002.tmp C:\\one-tests\\runs\\capture\\inbox\\0002.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0003.tmp C:\\one-tests\\runs\\capture\\inbox\\0003.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0004.tmp C:\\one-tests\\runs\\capture\\inbox\\0004.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0005.tmp C:\\one-tests\\runs\\capture\\inbox\\0005.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0006.tmp C:\\one-tests\\runs\\capture\\inbox\\0006.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0007.tmp C:\\one-tests\\runs\\capture\\inbox\\0007.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0008.tmp C:\\one-tests\\runs\\capture\\inbox\\0008.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0009.tmp C:\\one-tests\\runs\\capture\\inbox\\0009.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0010.tmp C:\\one-tests\\runs\\capture\\inbox\\0010.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
diff --git a/corpus/conflict-page/native-pages/b/hierarchy-0001.xml b/corpus/conflict-page/native-pages/b/hierarchy-0001.xml
new file mode 100644
index 0000000000000000000000000000000000000000..109b3296412c627c6a42b9c2d44d5ce4dcdc8709
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/hierarchy-0001.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/native-pages/b/hierarchy-0005.xml b/corpus/conflict-page/native-pages/b/hierarchy-0005.xml
new file mode 100644
index 0000000000000000000000000000000000000000..633860ac88594b8ebd31efd814b5032cda6b6ff7
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/hierarchy-0005.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/native-pages/b/hierarchy-0009.xml b/corpus/conflict-page/native-pages/b/hierarchy-0009.xml
new file mode 100644
index 0000000000000000000000000000000000000000..95e5b0fbf2cd7769d2f9edc187df53611350f2f2
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/hierarchy-0009.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/native-pages/b/listed.json b/corpus/conflict-page/native-pages/b/listed.json
new file mode 100644
index 0000000000000000000000000000000000000000..67a675a4f64d48e4266aa24db7ca7731d86ad8c6
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/listed.json
@@ -0,0 +1 @@
+["Fictitious: café, 東京, مرحبا", "One", "Two", "Four", "Three", "Target"]
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/b/machine.json b/corpus/conflict-page/native-pages/b/machine.json
new file mode 100644
index 0000000000000000000000000000000000000000..4c203c09d03f32d2e43453d8cd7f643f38ad7c96
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/machine.json
@@ -0,0 +1 @@
+{"name": "m6-39da335d", "hostname": "ONE-M6-39DA335D"}
diff --git a/corpus/conflict-page/native-pages/b/teardown.json b/corpus/conflict-page/native-pages/b/teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/conflict-page/native-pages/b/teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/conflict-page/native-pages/clients.json b/corpus/conflict-page/native-pages/clients.json
new file mode 100644
index 0000000000000000000000000000000000000000..d931d1ff7f174fc34ff42841be4fca8f0991ad66
--- /dev/null
+++ b/corpus/conflict-page/native-pages/clients.json
@@ -0,0 +1,4 @@
+{
+ "a": "m6-327953db",
+ "b": "m6-39da335d"
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/initial/notebook/Open Notebook.onetoc2 b/corpus/conflict-page/native-pages/initial/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..c7beb44bf509f935090fe55e7549e4b50e0ae0e9
Binary files /dev/null and b/corpus/conflict-page/native-pages/initial/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native-pages/initial/notebook/synthetic.one b/corpus/conflict-page/native-pages/initial/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..9d00682a40e27f509b734b04bdbe10e25e7d3d56
Binary files /dev/null and b/corpus/conflict-page/native-pages/initial/notebook/synthetic.one differ
diff --git a/corpus/conflict-page/native-pages/linux-teardown.json b/corpus/conflict-page/native-pages/linux-teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/conflict-page/native-pages/linux-teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/conflict-page/native-pages/linux.json b/corpus/conflict-page/native-pages/linux.json
new file mode 100644
index 0000000000000000000000000000000000000000..f81354b6eed97724307d4606252c29ab86b6bbfa
--- /dev/null
+++ b/corpus/conflict-page/native-pages/linux.json
@@ -0,0 +1,11 @@
+{
+ "cpus": 2,
+ "disk_gb": 16,
+ "hostname": "one-conflict2",
+ "lab_address": "192.168.77.1",
+ "lab_mac": "52:54:04:00:03:8c",
+ "memory_mb": 2048,
+ "samba_port": 14450,
+ "ssh_port": 22000,
+ "wan_mac": "52:54:03:c3:23:a2"
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/merged.json b/corpus/conflict-page/native-pages/merged.json
new file mode 100644
index 0000000000000000000000000000000000000000..72ed0278337d84924b221fde7086fa5f092b024f
--- /dev/null
+++ b/corpus/conflict-page/native-pages/merged.json
@@ -0,0 +1,49 @@
+{
+ "order": [
+ "",
+ "One",
+ "Two",
+ "Four",
+ "Three",
+ "Target"
+ ],
+ "pages": [
+ [
+ "",
+ "Fictitious positioned outline.",
+ "Fictitious: café, 東京, مرحبا",
+ "Left cell",
+ "Right cell"
+ ],
+ [
+ "2:20 AM",
+ "Body One.",
+ "One",
+ "Sunday, September 27, 2026"
+ ],
+ [
+ "2:20 AM",
+ "Body Two.",
+ "Sunday, September 27, 2026",
+ "Two"
+ ],
+ [
+ "2:20 AM",
+ "Body Four.",
+ "Four",
+ "Sunday, September 27, 2026"
+ ],
+ [
+ "2:20 AM",
+ "Body Three.",
+ "Sunday, September 27, 2026",
+ "Three"
+ ],
+ [
+ "2:20 AM",
+ "Body Target edited offline.",
+ "Sunday, September 27, 2026",
+ "Target"
+ ]
+ ]
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-pages/merged/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one b/corpus/conflict-page/native-pages/merged/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one
new file mode 100644
index 0000000000000000000000000000000000000000..2b5107aef4cdfdc82bea6f5069c97e40d26506eb
Binary files /dev/null and b/corpus/conflict-page/native-pages/merged/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one differ
diff --git a/corpus/conflict-page/native-pages/merged/notebook/OneNote_RecycleBin/Open Notebook.onetoc2 b/corpus/conflict-page/native-pages/merged/notebook/OneNote_RecycleBin/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..6e4b6c9be779af4e6515e1c9facf29cf6c5b792e
Binary files /dev/null and b/corpus/conflict-page/native-pages/merged/notebook/OneNote_RecycleBin/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native-pages/merged/notebook/Open Notebook.onetoc2 b/corpus/conflict-page/native-pages/merged/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..c5257a0970c051ac5c96357e05cdd9b0f242ee38
Binary files /dev/null and b/corpus/conflict-page/native-pages/merged/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native-pages/merged/notebook/synthetic.one b/corpus/conflict-page/native-pages/merged/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..8343d60ee07dc07bdcb875b18eb5c9d07c7c293c
Binary files /dev/null and b/corpus/conflict-page/native-pages/merged/notebook/synthetic.one differ
diff --git a/corpus/conflict-page/native-pages/native_conflict.py b/corpus/conflict-page/native-pages/native_conflict.py
new file mode 100644
index 0000000000000000000000000000000000000000..ccb4026acf69f3df84fdfbb01e9b6e39461762d3
--- /dev/null
+++ b/corpus/conflict-page/native-pages/native_conflict.py
@@ -0,0 +1,362 @@
+#!/usr/bin/env python3
+"""OneNote 2010 conflict pages: how they arise, what they store and how OneNote shows them.
+
+`sync LINUX OUTPUT` runs two disposable OneNote clients on one Samba notebook: client A edits
+two paragraphs offline while client B edits one of them and a table cell online; A then
+reconnects, which makes OneNote write a conflict page. The server file is kept before and
+after, A and B are screenshotted with the information bar, the page list shown, the conflict
+page open and its menu open, and A then deletes the conflict page through that menu.
+
+`pages LINUX OUTPUT` runs the same two clients on page-list changes: client A, offline, moves
+a page that client B also moves, moves another page, and edits a page B deletes; A then
+reconnects. The server's page order and pages, with the recycle bin, are kept at each step.
+
+`restore LINUX OUTPUT` runs them on a page in the middle of the list: client A, offline, edits
+it and moves a page B leaves alone while B deletes it; A then reconnects.
+
+`cold NOTEBOOK OUTPUT [--delete]` cold-opens a notebook holding a conflict page in a fresh
+clone and screenshots the same views; `--delete` deletes the conflict page through the menu
+and captures the notebook OneNote leaves.
+"""
+import argparse
+from contextlib import ExitStack
+from concurrent.futures import ThreadPoolExecutor
+import base64
+import json
+from pathlib import Path
+import shutil
+import subprocess
+import sys
+import time
+import xml.etree.ElementTree as ET
+
+sys.path.insert(0, str(Path(__file__).resolve().parent))
+sys.path.insert(0, str(Path(__file__).resolve().parent / 'w7'))
+import native_runner as runner
+from native_runner import ROOT, clone, command, windows, vm
+import linux_vm
+
+FIXTURE = ROOT / 'corpus/native-ink/cold-ui-ink/notebook'
+SHARE = 'm6-conflict'
+ORIGINAL = 'Fictitious: café, 東京, مرحبا'
+OTHER_OUTLINE = 'Fictitious positioned outline.'
+CELL = 'Left cell'
+
+# Coordinates on OneNote 2010 maximized at 800x600, from the stage-5, m8 and sync-01 captures:
+# the information bar's text, and the first conflict page listed under its page.
+BAR = (332, 100)
+FIRST_CONFLICT = (725, 142)
+# Delete Conflict Page, first in the menu the bar opens on a conflict page.
+DELETE = (420, 113)
+
+
+def shot(name, output, label, script):
+ """Runs an AutoHotkey step on the clone, keeping its screenshot as `label.png`."""
+ (output / f'{label}.ahk').write_text(script)
+ result = windows.do_exec(script, target=name, timeout_ms=120000, shot_delay_ms=1500)
+ screenshot = result.pop('png_b64', None)
+ if screenshot:
+ (output / f'{label}.png').write_bytes(base64.b64decode(screenshot))
+ (output / f'{label}.json').write_text(json.dumps(result, indent=2))
+ if result.get('error') or result.get('exit') != 0:
+ raise RuntimeError(f'{label}: {result}')
+ return result
+
+
+def click(x, y):
+ return ('WinActivate("ahk_exe ONENOTE.EXE")\nCoordMode("Mouse", "Screen")\n'
+ f'Click({x}, {y})\nSleep(1200)\n')
+
+
+# Reports any dialog OneNote raised, so a confirmation shows in the step's record.
+DIALOG = ('if WinExist("ahk_class #32770 ahk_exe ONENOTE.EXE") {\n'
+ ' FileAppend("dialog: " WinGetTitle() " | " WinGetText() "`n", "*", "UTF-8")\n'
+ '}\n')
+
+
+def conflict_views(name, output, page, prefix, delete=False):
+ """Screenshots a page with a conflict page: the bar, the list shown, the conflict page and
+ its menu; `delete` then chooses Delete Conflict Page."""
+ shot(name, output, f'{prefix}-main', runner.navigation_script(page) + 'Sleep(2000)\n')
+ shot(name, output, f'{prefix}-shown', click(*BAR))
+ shot(name, output, f'{prefix}-conflict', click(*FIRST_CONFLICT))
+ shot(name, output, f'{prefix}-menu', click(*BAR))
+ if delete:
+ shot(name, output, f'{prefix}-delete', click(*DELETE) + DIALOG)
+ else:
+ shot(name, output, f'{prefix}-closed', 'WinActivate("ahk_exe ONENOTE.EXE")\nSend("{Esc}")\nSleep(800)\n')
+
+
+def cold(notebook, output, delete):
+ def interaction(name, output):
+ page = ET.parse(sorted((output / 'read').glob('page-*.xml'))[0]).getroot().attrib['ID']
+ conflict_views(name, output, page, 'cold', delete)
+ if delete:
+ # OneNote writes the deletion in the background; give it time before the re-read.
+ time.sleep(20)
+ runner.capture(notebook, output, collect_notebook=True, interaction=interaction)
+
+
+class Lab:
+ """Two OneNote clients on one Samba notebook: `action` runs a controller command,
+ `state` copies the share and exports its section, `settle` syncs until a check holds."""
+
+
+def page_list(model):
+ """The section's pages in order, each as its sorted texts."""
+ from document_model import ordered_pages, walk
+ return [sorted(node['kind']['text'] for _, node in walk(page, oid) if node['kind']['type'] == 'RichText')
+ for _, _, page, oid in ordered_pages(model)]
+
+
+def edits(lab):
+ from native_collaboration import reachable_page_text
+ a, b, output, action, settle = lab.a, lab.b, lab.output, lab.action, lab.settle
+ state = lambda label: reachable_page_text(lab.state(label))
+ texts = lambda state: {text for values in state.values() for text in values}
+ state('initial')
+ vm.qmp(a['name'], 'set_link', {'name': 'lab', 'up': False})
+ try:
+ action(a, 'edit', expected=ORIGINAL, text='Client A conflicting edit.')
+ action(a, 'edit', expected=OTHER_OUTLINE, text='Client A disjoint edit.')
+ action(a, 'snapshot')
+ action(b, 'edit', expected=ORIGINAL, text='Client B conflicting edit.')
+ action(b, 'edit', expected=CELL, text='Client B disjoint cell.')
+ settle(b, lambda label: {'Client B conflicting edit.', 'Client B disjoint cell.'} <= texts(state(label)), 'b-published')
+ finally:
+ vm.qmp(a['name'], 'set_link', {'name': 'lab', 'up': True})
+ settle(a, lambda label: len(found := state(label)) > 1 and 'Client A disjoint edit.' in texts(found), 'conflict')
+ (output / 'conflict.json').write_text(json.dumps(state('conflict'), indent=2, ensure_ascii=False))
+ page = action(a, 'snapshot')[0].attrib['ID']
+ conflict_views(a['name'], output / 'a', page, 'a')
+ action(b, 'sync')
+ time.sleep(5)
+ page_b = action(b, 'snapshot')[0].attrib['ID']
+ conflict_views(b['name'], output / 'b', page_b, 'b')
+ conflict_views(a['name'], output / 'a', page, 'a-again', delete=True)
+ settle(a, lambda label: len(state(label)) == 1, 'deleted')
+ action(b, 'sync')
+ time.sleep(5)
+ shot(b['name'], output / 'b', 'b-after-delete', runner.navigation_script(page_b) + 'Sleep(2000)\n')
+
+
+PAGES = ['One', 'Two', 'Three', 'Four', 'Target']
+
+
+def titled(model, titles):
+ """The section's pages in order, each named by the title among `titles` it holds."""
+ return [next((text for text in texts if text in titles), '') for texts in page_list(model)]
+
+
+def offline(lab, titles, a_actions, b_actions):
+ """Creates the titled pages; A runs its actions offline while B runs and publishes its
+ own; A then reconnects. Keeps the merged order and pages, and what each client lists."""
+ a, b, output, action = lab.a, lab.b, lab.output, lab.action
+ action(a, 'pages', titles=titles)
+ lab.settle(a, lambda label: 'Body Target.' in str(page_list(lab.state(label))), 'initial')
+ deadline = time.monotonic() + 300
+ while not any(node.get('name') == 'Target' for node in action(b, 'snapshot')):
+ action(b, 'sync')
+ if time.monotonic() > deadline: raise TimeoutError('B never listed the new pages')
+ vm.qmp(a['name'], 'set_link', {'name': 'lab', 'up': False})
+ try:
+ for kind, parameters in a_actions:
+ action(a, kind, **parameters)
+ action(a, 'snapshot')
+ for kind, parameters in b_actions:
+ action(b, kind, **parameters)
+ action(b, 'snapshot')
+ lab.settle(b, lambda label: 'Body Target.' not in str(page_list(lab.state(label))), 'b-published')
+ finally:
+ vm.qmp(a['name'], 'set_link', {'name': 'lab', 'up': True})
+ # A's changes reach the server when its sync writes; wait until the file stops changing.
+ previous, steady = None, 0
+ deadline = time.monotonic() + 300
+ while steady < 3 and time.monotonic() < deadline:
+ action(a, 'sync')
+ time.sleep(10)
+ current = page_list(lab.state('merged'))
+ steady = steady + 1 if current == previous else 0
+ previous = current
+ (output / 'merged.json').write_text(json.dumps({'order': titled(lab.state('merged'), titles), 'pages': previous}, indent=2, ensure_ascii=False))
+ for client, label in ((a, 'a'), (b, 'b')):
+ action(client, 'sync')
+ time.sleep(5)
+ listed = action(client, 'snapshot')
+ (output / label / 'listed.json').write_text(json.dumps([node.get('name') for node in listed], ensure_ascii=False))
+ if listed:
+ shot(client['name'], output / label, f'{label}-merged', runner.navigation_script(listed[0].get('ID')) + 'Sleep(2000)\n')
+
+
+def pages(lab):
+ """Competing page moves, and an offline edit of a page the other client deletes."""
+ offline(lab, PAGES, [
+ ('move', {'page': 'Four', 'before': 'One'}),
+ ('move', {'page': 'Two', 'before': None}),
+ ('edit', {'expected': 'Body Target.', 'text': 'Body Target edited offline.'}),
+ ], [
+ ('move', {'page': 'Four', 'before': None}),
+ ('move', {'page': 'Three', 'before': 'One'}),
+ ('delete-page', {'page': 'Target'}),
+ ])
+
+
+def restore(lab):
+ """An offline edit of a page in the middle of the list the other client deletes, with an
+ offline move of a page the other client leaves alone."""
+ offline(lab, ['One', 'Two', 'Target', 'Three', 'Four'], [
+ ('edit', {'expected': 'Body Target.', 'text': 'Body Target edited offline.'}),
+ ('move', {'page': 'Four', 'before': 'One'}),
+ ], [
+ ('delete-page', {'page': 'Target'}),
+ ])
+
+
+def sync(server, output, scenario):
+ from document_model import EXPORTER
+
+ if linux_vm.instance_path(server).exists():
+ raise ValueError('Choose a new Linux VM name; existing machines are not owned by this run.')
+ output = output.resolve()
+ output.mkdir(parents=True, exist_ok=False)
+ scripts = output / 'scripts'
+ scripts.mkdir()
+ for name in ('cold.ps1', 'collaborate.ps1', 'network.ps1', 'text.ps1', 'stress.ps1'):
+ shutil.copyfile(ROOT / 'tools/native' / name, scripts / name)
+ shutil.copyfile(Path(__file__), output / 'native_conflict.py')
+
+ def ssh(text, timeout=90):
+ result = linux_vm.run_ssh(server, text, timeout=timeout)
+ with (output / 'server.jsonl').open('a') as log:
+ log.write(json.dumps({'command': text, 'exit': result.returncode, 'stdout': result.stdout, 'stderr': result.stderr}) + '\n')
+ result.check_returncode()
+ return result.stdout
+
+ def server_state(label):
+ """Copies the server's notebook, recycle bin included, until its section parses,
+ keeping it as `label/notebook`; returns the section's exported model."""
+ deadline = time.monotonic() + 120
+ while True:
+ target = output / label
+ shutil.rmtree(target, ignore_errors=True)
+ (target / 'notebook').mkdir(parents=True)
+ archive = subprocess.run(linux_vm.ssh_argv(server, f"tar cf - -C '/srv/agent/{SHARE}' ."), capture_output=True, check=True, timeout=60).stdout
+ subprocess.run(['tar', 'xf', '-', '-C', target / 'notebook'], input=archive, check=True)
+ result = subprocess.run([EXPORTER, target / 'notebook/synthetic.one', target / 'model'], capture_output=True, text=True)
+ if result.returncode == 0:
+ return json.loads((target / 'model/document.json').read_text())
+ if time.monotonic() > deadline:
+ raise RuntimeError(f'{label}: {result.stderr}')
+ time.sleep(1)
+
+ linux_vm.create_instance(server)
+ try:
+ linux_vm.launch(server)
+ linux_vm.wait_instance(server, 600)
+ (output / 'linux.json').write_text(json.dumps(linux_vm.load_instance(server), indent=2))
+ ssh(f'mkdir /srv/agent/{SHARE}')
+ for name in ('synthetic.one', 'Open Notebook.onetoc2'):
+ with (FIXTURE / name).open('rb') as stream:
+ subprocess.run(linux_vm.ssh_argv(server, f"cat > '/srv/agent/{SHARE}/{name}'"), stdin=stream, check=True)
+ ssh(f'chmod u+w /srv/agent/{SHARE}/*')
+ with ExitStack() as stack:
+ labels = ['a', 'b']
+ for label in labels:
+ (output / label).mkdir()
+
+ def start(label):
+ manager = clone(output / label)
+ name = manager.__enter__()
+ return label, name, manager
+ with ThreadPoolExecutor(max_workers=2) as pool:
+ started = list(pool.map(start, labels))
+ for _, _, manager in started:
+ stack.push(manager)
+ clients = {}
+ for label, name, _ in started:
+ folder = output / label
+ for local in scripts.iterdir():
+ remote = 'cold-current.ps1' if local.name == 'cold.ps1' else local.name
+ result = windows.do_put(local, 'C:\\one-tests\\' + remote, name)
+ if result.get('error'): raise RuntimeError(result['error'])
+ command(name, 'mkdir C:\\one-tests\\runs\\capture', folder)
+ command(name, 'powershell -NoProfile -ExecutionPolicy Bypass -File C:\\one-tests\\network.ps1 -LabMac ' + vm.lab_mac(name), folder)
+ result = windows.do_spawn('cmd /c powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\collaborate.ps1 -Root C:\\one-tests\\runs\\capture -SharedPath \\\\192.168.77.1\\agent\\' + SHARE + ' -CloneHost ONE-' + name.upper() + ' > C:\\one-tests\\runs\\capture\\controller.log 2>&1', name)
+ if result.get('error'): raise RuntimeError(result['error'])
+ deadline = time.monotonic() + 180
+ while windows.do_cmd('if exist C:\\one-tests\\runs\\capture\\ready (echo ready)', target=name).get('stdout', '').find('ready') < 0:
+ if time.monotonic() > deadline: raise RuntimeError('The collaboration controller did not open the notebook')
+ time.sleep(1)
+ clients[label] = {'name': name, 'folder': folder, 'sequence': 0}
+ (output / 'clients.json').write_text(json.dumps({label: client['name'] for label, client in clients.items()}, indent=2))
+
+ def action(client, kind, **parameters):
+ client['sequence'] += 1
+ sequence = client['sequence']
+ local = client['folder'] / f'command-{sequence:04}.json'
+ local.write_text(json.dumps({'action': kind, **parameters}, ensure_ascii=False))
+ inbox = f'C:\\one-tests\\runs\\capture\\inbox\\{sequence:04}'
+ result = windows.do_put(local, inbox + '.tmp', client['name'])
+ if result.get('error'): raise RuntimeError(result['error'])
+ command(client['name'], f'move {inbox}.tmp {inbox}.json', client['folder'])
+ remote = f'C:\\one-tests\\runs\\capture\\outbox\\{sequence}'
+ deadline = time.monotonic() + 600
+ while True:
+ result = windows.do_cmd(f'if exist {remote}\\error (type {remote}\\error & exit /b 1) else (if exist {remote}\\done (echo complete))', target=client['name'])
+ if result.get('exit') != 0: raise RuntimeError(str(result))
+ if 'complete' in result.get('stdout', ''): break
+ if time.monotonic() > deadline: raise TimeoutError(f'{kind} did not complete')
+ time.sleep(.5)
+ if kind != 'snapshot':
+ return None
+ hierarchy = client['folder'] / f'hierarchy-{sequence:04}.xml'
+ windows.do_get(remote + '\\hierarchy.xml', hierarchy, client['name'])
+ pages = [node for node in ET.fromstring(hierarchy.read_text(encoding='utf-8-sig').strip()).iter() if node.tag.endswith('}Page')]
+ for i in range(len(pages)):
+ windows.do_get(remote + f'\\page-{i}.xml', client['folder'] / f'snapshot-{sequence:04}-{i}.xml', client['name'])
+ return pages
+
+ def settle(client, check, label):
+ deadline = time.monotonic() + 300
+ while True:
+ action(client, 'sync')
+ if check(label): return
+ if time.monotonic() > deadline: raise TimeoutError(label)
+ time.sleep(2)
+
+ lab = Lab()
+ lab.a, lab.b, lab.output = clients['a'], clients['b'], output
+ lab.action, lab.state, lab.settle = action, server_state, settle
+ scenario(lab)
+
+ for client in clients.values():
+ try:
+ action(client, 'close')
+ except Exception as error:
+ (client['folder'] / 'close-failure.txt').write_text(str(error))
+ server_state('final')
+ finally:
+ if linux_vm.running(server):
+ linux_vm.shutdown(server, 120)
+ linux_vm.delete_instance(server)
+ (output / 'linux-teardown.json').write_text(json.dumps({'absent': not linux_vm.instance_path(server).exists()}) + '\n')
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+ commands = parser.add_subparsers(dest='mode', required=True)
+ scenarios = {'sync': edits, 'pages': pages, 'restore': restore}
+ for mode in scenarios:
+ lab = commands.add_parser(mode)
+ lab.add_argument('linux')
+ lab.add_argument('output', type=Path)
+ reopen = commands.add_parser('cold')
+ reopen.add_argument('notebook', type=Path)
+ reopen.add_argument('output', type=Path)
+ reopen.add_argument('--delete', action='store_true')
+ args = parser.parse_args()
+ if args.mode in scenarios:
+ sync(args.linux, args.output, scenarios[args.mode])
+ else:
+ cold(args.notebook, args.output, args.delete)
+ print('Captured', args.output)
diff --git a/corpus/conflict-page/native-pages/scripts/cold.ps1 b/corpus/conflict-page/native-pages/scripts/cold.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..a0b99e7aeb86f4dc10032537800501a52ad65b4f
--- /dev/null
+++ b/corpus/conflict-page/native-pages/scripts/cold.ps1
@@ -0,0 +1,27 @@
+param([Parameter(Mandatory=$true)][string]$Root, [string]$CloneHost = '')
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
+if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
+ throw 'Choose a run directly below C:\one-tests\runs.'
+}
+if (Get-Process ONENOTE -ErrorAction SilentlyContinue) { throw 'Close OneNote before resetting its test cache.' }
+$key = 'HKCU:\Software\Microsoft\Office\14.0\OneNote'
+if ($CloneHost) {
+ if ($CloneHost -notmatch '^ONE-[A-Z0-9-]+$' -or [Environment]::MachineName -ne $CloneHost) {
+ throw 'The disposable clone hostname does not match this machine.'
+ }
+ New-Item "$key\Options\Paths" -Force | Out-Null
+ New-ItemProperty "$key\Options\Paths" -Name UnfiledNotesSection -PropertyType ExpandString -Value 'C:\one-tests\Loose.one' -Force | Out-Null
+} elseif ((Get-ItemProperty "$key\Options\Paths").UnfiledNotesSection -ne 'C:\one-tests\Loose.one' -or
+ -not (Test-Path 'C:\one-tests\profile-original-cache')) {
+ throw 'Park the personal OneNote profile before resetting the test cache.'
+}
+$cache = Join-Path $env:LOCALAPPDATA 'Microsoft\OneNote\14.0'
+$parked = Join-Path 'C:\one-tests\caches' ([IO.Path]::GetFileName($root))
+if (Test-Path $parked) { throw 'Choose a new run; its parked cache already exists.' }
+New-Item -ItemType Directory -Path 'C:\one-tests\caches' -Force | Out-Null
+if (Test-Path $cache) { Move-Item -LiteralPath $cache -Destination $parked }
+if (Test-Path "$key\OpenNotebooks") { Remove-Item "$key\OpenNotebooks" -Recurse }
+New-Item "$key\OpenNotebooks" | Out-Null
+New-ItemProperty "$key\OpenNotebooks" -Name '1' -PropertyType String -Value "$root\notebook" | Out-Null
diff --git a/corpus/conflict-page/native-pages/scripts/collaborate.ps1 b/corpus/conflict-page/native-pages/scripts/collaborate.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..6a42a3d526db486f022b032583704b0f4aee26d3
--- /dev/null
+++ b/corpus/conflict-page/native-pages/scripts/collaborate.ps1
@@ -0,0 +1,145 @@
+param([Parameter(Mandatory=$true)][string]$Root,
+ [Parameter(Mandatory=$true)][string]$SharedPath,
+ [Parameter(Mandatory=$true)][string]$CloneHost,
+ [switch]$ResumeCache,
+ [int]$StartSequence = 1)
+function Keep-Container($page, $target) {
+ $container = $target
+ while ($container.ParentNode -ne $page.DocumentElement) { $container = $container.ParentNode }
+ foreach ($sibling in @($page.DocumentElement.ChildNodes)) {
+ if ($sibling -ne $container -and $sibling.LocalName -in @('Outline', 'Title', 'Image', 'InkDrawing')) {
+ [void]$page.DocumentElement.RemoveChild($sibling)
+ }
+ }
+}
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+. "$PSScriptRoot\text.ps1"
+if ($SharedPath -notmatch '^\\\\192\.168\.77\.1\\agent\\m6-[a-z0-9-]+$') { throw 'Choose a disposable Linux lab notebook.' }
+if ($env:COMPUTERNAME -ne $CloneHost) { throw 'The collaboration controller requires its owned clone.' }
+if (-not $ResumeCache) { & "$PSScriptRoot\cold-current.ps1" -Root $Root -CloneHost $CloneHost }
+$shared = $SharedPath
+if (-not (Test-Path "$shared\synthetic.one")) { throw 'Create the shared fixture first.' }
+New-Item -ItemType Directory "$Root\inbox", "$Root\outbox" -Force | Out-Null
+$user = 'HKCU:\Software\Microsoft\Office\Common\UserInfo'
+New-Item $user -Force | Out-Null
+New-ItemProperty $user -Name UserName -Value $CloneHost -PropertyType String -Force | Out-Null
+New-ItemProperty $user -Name UserInitials -Value $CloneHost.Substring($CloneHost.Length - 3) -PropertyType String -Force | Out-Null
+$app = New-Object -ComObject OneNote.Application
+$notebook = ''
+try {
+ $app.OpenHierarchy($shared, '', [ref]$notebook, 0)
+ $section = ''
+ $app.OpenHierarchy('synthetic.one', $notebook, [ref]$section, 0)
+ [IO.File]::WriteAllText("$Root\ready", 'ready')
+ $deadline = [DateTime]::UtcNow.AddMinutes(30)
+ $sequence = $StartSequence
+ while ([DateTime]::UtcNow -lt $deadline) {
+ $name = '{0:d4}.json' -f $sequence
+ $input = "$Root\inbox\$name"
+ if (-not (Test-Path $input)) { Start-Sleep -Milliseconds 100; continue }
+ $command = Get-Content -Raw -Encoding UTF8 $input | ConvertFrom-Json
+ $output = "$Root\outbox\$sequence"
+ New-Item -ItemType Directory $output | Out-Null
+ try {
+ if ($command.action -in @('prepare-stress', 'stress')) {
+ . "$PSScriptRoot\stress.ps1"
+ Invoke-Stress $app $section $command $output $shared
+ }
+ elseif ($command.action -eq 'sync') { $app.SyncHierarchy($notebook) }
+ elseif ($command.action -in @('pages', 'move', 'delete-page')) {
+ $namespace = 'http://schemas.microsoft.com/office/onenote/2010/onenote'
+ $hierarchy = ''
+ $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
+ [xml]$tree = $hierarchy
+ $listed = @($tree.SelectNodes('//*[local-name()="Page"]'))
+ $named = { param($title) @($listed | Where-Object { $_.GetAttribute('name') -eq $title }) }
+ if ($command.action -eq 'pages') {
+ # New titled pages with one body paragraph each, appended in order.
+ foreach ($title in $command.titles) {
+ $page = ''
+ $app.CreateNewPage($section, [ref]$page, 2)
+ $body = [Security.SecurityElement]::Escape('Body ' + $title + '.')
+ $name = [Security.SecurityElement]::Escape($title)
+ $xml = '' + $name + '' + $body + ''
+ $app.UpdatePageContent($xml, [DateTime]::MinValue, 1, $false)
+ }
+ } elseif ($command.action -eq 'move') {
+ # Moves the page titled `page` before the one titled `before`, or last.
+ $moved = @(& $named $command.page)
+ if ($moved.Count -ne 1) { throw 'Expected one page to move.' }
+ $order = [Collections.ArrayList]@($listed | Where-Object { $_ -ne $moved[0] })
+ $at = $order.Count
+ if ($command.before) {
+ $anchor = @(& $named $command.before)
+ if ($anchor.Count -ne 1) { throw 'Expected one anchor page.' }
+ $at = $order.IndexOf($anchor[0])
+ }
+ $order.Insert($at, $moved[0])
+ $xml = ''
+ foreach ($node in $order) {
+ $xml += ''
+ }
+ $app.UpdateHierarchy($xml + '', 1)
+ } else {
+ $doomed = @(& $named $command.page)
+ if ($doomed.Count -ne 1) { throw 'Expected one page to delete.' }
+ $app.DeleteHierarchy($doomed[0].GetAttribute('ID'), [DateTime]::MinValue, $false)
+ }
+ }
+ elseif ($command.action -eq 'kill-process') {
+ Get-Process ONENOTE | Stop-Process -Force
+ [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
+ $app = New-Object -ComObject OneNote.Application
+ $app.OpenHierarchy($shared, '', [ref]$notebook, 0)
+ $app.OpenHierarchy('synthetic.one', $notebook, [ref]$section, 0)
+ }
+ elseif ($command.action -eq 'edit' -or $command.action -eq 'snapshot') {
+ $hierarchy = ''
+ $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
+ [IO.File]::WriteAllText("$output\hierarchy.xml", $hierarchy, [Text.Encoding]::UTF8)
+ [xml]$tree = $hierarchy
+ $matches = 0
+ $editedPage = $null
+ $i = 0
+ foreach ($node in $tree.SelectNodes('//*[local-name()="Page"]')) {
+ $content = ''
+ $app.GetPageContent($node.GetAttribute('ID'), [ref]$content, 1, 1)
+ [IO.File]::WriteAllText("$output\page-$i.xml", $content, [Text.Encoding]::UTF8)
+ [xml]$page = $content
+ if ($command.action -eq 'edit') {
+ $targets = @($page.SelectNodes('//*[local-name()="T"]') | Where-Object {
+ (Get-ParagraphText $_.InnerText) -eq $command.expected
+ })
+ if ($targets.Count -gt 1) { throw 'Expected one matching text object.' }
+ if ($targets.Count -eq 1) {
+ $matches++
+ if ($matches -gt 1) { throw 'The text occurs on multiple pages.' }
+ $targets[0].InnerText = [Security.SecurityElement]::Escape($command.text)
+ Keep-Container $page $targets[0]
+ $editedPage = $page
+ }
+ }
+ $i++
+ }
+ if ($command.action -eq 'edit' -and $matches -ne 1) { throw 'Expected one matching text object.' }
+ if ($editedPage) { $app.UpdatePageContent($editedPage.OuterXml, [DateTime]::MinValue, 1, $false) }
+ } elseif ($command.action -ne 'close') { throw 'Unknown collaboration command.' }
+ [IO.File]::WriteAllText("$output\done", 'ok')
+ } catch {
+ [IO.File]::WriteAllText("$output\error", $_.Exception.ToString())
+ }
+ if ($command.action -eq 'close') { break }
+ $sequence++
+ }
+} finally {
+ try { if ($notebook) { $app.CloseNotebook($notebook, $false) } }
+ finally {
+ [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
+ $app = $null
+ [GC]::Collect()
+ [GC]::WaitForPendingFinalizers()
+ Get-Process ONENOTE -ErrorAction SilentlyContinue | Wait-Process -Timeout 10
+ [IO.File]::WriteAllText("$Root\closed", 'closed')
+ }
+}
diff --git a/corpus/conflict-page/native-pages/scripts/network.ps1 b/corpus/conflict-page/native-pages/scripts/network.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..95fa83d5243ee6a2a653cc075128b063e2f09cf2
--- /dev/null
+++ b/corpus/conflict-page/native-pages/scripts/network.ps1
@@ -0,0 +1,20 @@
+param([Parameter(Mandatory=$true)][string]$LabMac)
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$adapter = @(Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object { $_.MACAddress -eq $LabMac })
+if ($adapter.Count -ne 1) { throw 'The clone lab adapter is ambiguous.' }
+$enabled = $adapter[0].EnableDHCP()
+if ($enabled.ReturnValue -notin @(0, 1)) {
+ throw ('Unable to enable DHCP on the clone lab adapter; result ' + $enabled.ReturnValue)
+}
+$release = $adapter[0].ReleaseDHCPLease()
+$result = $adapter[0].RenewDHCPLease()
+$deadline = [DateTime]::UtcNow.AddSeconds(60)
+do {
+ $adapter = @(Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object { $_.MACAddress -eq $LabMac })
+ $addresses = @($adapter[0].IPAddress | Where-Object { $_ -match '^192\.168\.77\.\d+$' -and $_ -ne '192.168.77.1' })
+ if ($addresses.Count -eq 1) { break }
+ Start-Sleep -Milliseconds 250
+} while ([DateTime]::UtcNow -lt $deadline)
+if ($addresses.Count -ne 1) { throw ('The clone did not receive a lab IPv4 address; renewal result ' + $result.ReturnValue + '; adapter ' + ($adapter[0] | Select-Object MACAddress,DHCPEnabled,IPEnabled,IPAddress | ConvertTo-Json -Compress)) }
+@{mac=$LabMac;address=$addresses[0];releaseResult=$release.ReturnValue;renewalResult=$result.ReturnValue} | ConvertTo-Json -Compress
diff --git a/corpus/conflict-page/native-pages/scripts/stress.ps1 b/corpus/conflict-page/native-pages/scripts/stress.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..a0ab5bc69b4dabe970373e481873dfd951a1a69a
--- /dev/null
+++ b/corpus/conflict-page/native-pages/scripts/stress.ps1
@@ -0,0 +1,83 @@
+function Invoke-Stress($app, $section, $command, $output, $shared) {
+ $hierarchy = ''
+ $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
+ [IO.File]::WriteAllText("$output\hierarchy.xml", $hierarchy, [Text.Encoding]::UTF8)
+ [xml]$tree = $hierarchy
+ $pages = @($tree.SelectNodes('//*[local-name()="Page"]'))
+ if ($pages.Count -ne 1) { throw 'Expected one stress-test page.' }
+ $pageId = $pages[0].GetAttribute('ID')
+ if ($command.action -eq 'prepare-stress') {
+ for ($i = 0; $i -lt $command.clients; $i++) {
+ $xml = ''
+ $app.UpdatePageContent($xml, [DateTime]::MinValue, 1, $false)
+ }
+ return
+ }
+ $random = New-Object Random([int]$command.seed)
+ $events = New-Object IO.StreamWriter("$output\events.jsonl", $false, [Text.Encoding]::UTF8)
+ $events.AutoFlush = $true
+ try {
+ [IO.File]::WriteAllText("$output\ready", 'ready')
+ $deadline = [DateTime]::UtcNow.AddMinutes(10)
+ while (-not (Test-Path "$shared\stress-start")) {
+ if ([DateTime]::UtcNow -gt $deadline) { throw 'Stress start timed out.' }
+ Start-Sleep -Milliseconds 20
+ }
+ for ($i = 0; $i -lt $command.operations; $i++) {
+ if (($command.PSObject.Properties.Name -contains 'maintenance') -and $command.maintenance -and $i -eq [Math]::Floor($command.operations / 2)) {
+ [IO.File]::WriteAllText("$shared\maintenance-paused-n$($command.actor)", 'paused')
+ while (-not (Test-Path "$shared\maintenance-resume")) {
+ if ([DateTime]::UtcNow -gt $deadline) { throw 'Maintenance pause timed out.' }
+ Start-Sleep -Milliseconds 100
+ }
+ }
+ Start-Sleep -Milliseconds $random.Next(10, 100)
+ $started = [DateTime]::UtcNow.Ticks
+ $content = ''
+ $app.GetPageContent($pageId, [ref]$content, 1, 1)
+ [xml]$page = $content
+ $matches = @($page.SelectNodes('//*[local-name()="T"]') | Where-Object {
+ (Get-ParagraphText $_.InnerText).StartsWith($command.prefix)
+ })
+ if ($matches.Count -ne 1) { throw 'Expected one stress-test paragraph.' }
+ $before = Get-ParagraphText $matches[0].InnerText
+ $token = ' [n' + $command.actor + ':' + $i + ']'
+ $start = $end = $before.Length
+ $replacement = $token
+ $bold = $italic = $false
+ if ($command.edit) {
+ $boundaries = New-Object 'Collections.Generic.List[int]'
+ for ($at = $command.prefix.Length; $at -le $before.Length; $at++) {
+ $boundaries.Add($at)
+ if ($at -lt $before.Length -and [char]::IsHighSurrogate($before[$at])) { $at++ }
+ }
+ $first = $boundaries[$random.Next($boundaries.Count)]
+ $second = $boundaries[$random.Next($boundaries.Count)]
+ $start = [Math]::Min($first, $second)
+ $end = [Math]::Max($first, $second)
+ $replacement = ' caf' + [char]0xe9 + ' ' + [char]::ConvertFromUtf32(0x1f980) + $token
+ $bold = $random.Next(2) -eq 1
+ $italic = $random.Next(2) -eq 1
+ }
+ $after = $before.Substring(0, $start) + $replacement + $before.Substring($end)
+ $html = [Security.SecurityElement]::Escape($after)
+ if ($command.edit) {
+ $weight = if ($bold) { 'bold' } else { 'normal' }
+ $style = if ($italic) { 'italic' } else { 'normal' }
+ $html = '' + $html + ''
+ }
+ $matches[0].InnerText = $html
+ Keep-Container $page $matches[0]
+ $updateStarted = [DateTime]::UtcNow.Ticks
+ $app.UpdatePageContent($page.OuterXml, [DateTime]::MinValue, 1, $false)
+ $updated = [DateTime]::UtcNow.Ticks
+ $events.WriteLine((@{ operation=$i; before=$before; token=$token; started_ticks=$started;
+ range=@($start, $end); replacement=$replacement; bold=$bold; italic=$italic;
+ update_started_ticks=$updateStarted; updated_ticks=$updated } | ConvertTo-Json -Compress))
+ if ($i -eq 0) { [IO.File]::WriteAllText("$output\editing", 'editing') }
+ if ($command.sync_every -gt 0 -and (($i + 1) % $command.sync_every) -eq 0) {
+ $app.SyncHierarchy($section)
+ }
+ }
+ } finally { $events.Dispose() }
+}
diff --git a/corpus/conflict-page/native-pages/scripts/text.ps1 b/corpus/conflict-page/native-pages/scripts/text.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..75e9e71207b599d16eaaf68824ca30831860610a
--- /dev/null
+++ b/corpus/conflict-page/native-pages/scripts/text.ps1
@@ -0,0 +1,13 @@
+function Get-ParagraphText([string]$html) {
+ $plain = [regex]::Replace($html, '<[^>]*>', '')
+ $plain = [regex]::Replace($plain, '(?:x([0-9a-fA-F]+)|([0-9]+));', {
+ param($match)
+ $value = if ($match.Groups[1].Success) {
+ [Convert]::ToInt32($match.Groups[1].Value, 16)
+ } else { [int]$match.Groups[2].Value }
+ # The Win7 framework leaves supplementary numeric entities undecoded.
+ if ($value -gt 0xffff -and $value -le 0x10ffff) { [char]::ConvertFromUtf32($value) }
+ else { $match.Value }
+ })
+ [Net.WebUtility]::HtmlDecode($plain)
+}
diff --git a/corpus/conflict-page/native-pages/server.jsonl b/corpus/conflict-page/native-pages/server.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..d5df993564a30ddaea1b427699648ff9b1a1417d
--- /dev/null
+++ b/corpus/conflict-page/native-pages/server.jsonl
@@ -0,0 +1,2 @@
+{"command": "mkdir /srv/agent/m6-conflict", "exit": 0, "stdout": "", "stderr": ""}
+{"command": "chmod u+w /srv/agent/m6-conflict/*", "exit": 0, "stdout": "", "stderr": ""}
diff --git a/corpus/conflict-page/native-restore/a/a-merged.json b/corpus/conflict-page/native-restore/a/a-merged.json
new file mode 100644
index 0000000000000000000000000000000000000000..2f394e7eaf1defcfe3e06e321b5af76be4f84054
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/a-merged.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Fictitious: caf\u00e9, \u6771\u4eac, \u0645\u0631\u062d\u0628\u0627 - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/a-merged.png b/corpus/conflict-page/native-restore/a/a-merged.png
new file mode 100644
index 0000000000000000000000000000000000000000..e1de0c6ef0b01ae79888f9cbead1ac135d60d788
Binary files /dev/null and b/corpus/conflict-page/native-restore/a/a-merged.png differ
diff --git a/corpus/conflict-page/native-restore/a/command-0001.json b/corpus/conflict-page/native-restore/a/command-0001.json
new file mode 100644
index 0000000000000000000000000000000000000000..e473ce8f2c61fdf632e44de1f4a834d605faaece
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0001.json
@@ -0,0 +1 @@
+{"action": "pages", "titles": ["One", "Two", "Target", "Three", "Four"]}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0002.json b/corpus/conflict-page/native-restore/a/command-0002.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0002.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0003.json b/corpus/conflict-page/native-restore/a/command-0003.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0003.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0004.json b/corpus/conflict-page/native-restore/a/command-0004.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0004.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0005.json b/corpus/conflict-page/native-restore/a/command-0005.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0005.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0006.json b/corpus/conflict-page/native-restore/a/command-0006.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0006.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0007.json b/corpus/conflict-page/native-restore/a/command-0007.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0007.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0008.json b/corpus/conflict-page/native-restore/a/command-0008.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0008.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0009.json b/corpus/conflict-page/native-restore/a/command-0009.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0009.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0010.json b/corpus/conflict-page/native-restore/a/command-0010.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0010.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0011.json b/corpus/conflict-page/native-restore/a/command-0011.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0011.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0012.json b/corpus/conflict-page/native-restore/a/command-0012.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0012.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0013.json b/corpus/conflict-page/native-restore/a/command-0013.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0013.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0014.json b/corpus/conflict-page/native-restore/a/command-0014.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0014.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0015.json b/corpus/conflict-page/native-restore/a/command-0015.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0015.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0016.json b/corpus/conflict-page/native-restore/a/command-0016.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0016.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0017.json b/corpus/conflict-page/native-restore/a/command-0017.json
new file mode 100644
index 0000000000000000000000000000000000000000..3f6f2524a6523bec79f8152f3340d2ae649729a0
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0017.json
@@ -0,0 +1 @@
+{"action": "edit", "expected": "Body Target.", "text": "Body Target edited offline."}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0018.json b/corpus/conflict-page/native-restore/a/command-0018.json
new file mode 100644
index 0000000000000000000000000000000000000000..8bd5839cfa379b0117fb5154acd9d20bc06b5cda
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0018.json
@@ -0,0 +1 @@
+{"action": "move", "page": "Four", "before": "One"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0019.json b/corpus/conflict-page/native-restore/a/command-0019.json
new file mode 100644
index 0000000000000000000000000000000000000000..554ff0ef8a35478d74f3e5388f75789140b158b8
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0019.json
@@ -0,0 +1 @@
+{"action": "snapshot"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0020.json b/corpus/conflict-page/native-restore/a/command-0020.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0020.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0021.json b/corpus/conflict-page/native-restore/a/command-0021.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0021.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0022.json b/corpus/conflict-page/native-restore/a/command-0022.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0022.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0023.json b/corpus/conflict-page/native-restore/a/command-0023.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0023.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0024.json b/corpus/conflict-page/native-restore/a/command-0024.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0024.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0025.json b/corpus/conflict-page/native-restore/a/command-0025.json
new file mode 100644
index 0000000000000000000000000000000000000000..554ff0ef8a35478d74f3e5388f75789140b158b8
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0025.json
@@ -0,0 +1 @@
+{"action": "snapshot"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/command-0026.json b/corpus/conflict-page/native-restore/a/command-0026.json
new file mode 100644
index 0000000000000000000000000000000000000000..7adf980027ef43cc77767d4fd1b1d67ad1541ab4
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/command-0026.json
@@ -0,0 +1 @@
+{"action": "close"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/commands.jsonl b/corpus/conflict-page/native-restore/a/commands.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..636ed5f31cb52c0cd0e9552fa99ebf892dc44dd1
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/commands.jsonl
@@ -0,0 +1,28 @@
+{"command": "mkdir C:\\one-tests\\runs\\capture", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -ExecutionPolicy Bypass -File C:\\one-tests\\network.ps1 -LabMac 52:54:02:b6:9a:97", "exit": 0, "stdout": "{\"renewalResult\":0,\"releaseResult\":0,\"mac\":\"52:54:02:b6:9a:97\",\"address\":\"192.168.77.119\"}\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0001.tmp C:\\one-tests\\runs\\capture\\inbox\\0001.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0002.tmp C:\\one-tests\\runs\\capture\\inbox\\0002.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0003.tmp C:\\one-tests\\runs\\capture\\inbox\\0003.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0004.tmp C:\\one-tests\\runs\\capture\\inbox\\0004.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0005.tmp C:\\one-tests\\runs\\capture\\inbox\\0005.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0006.tmp C:\\one-tests\\runs\\capture\\inbox\\0006.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0007.tmp C:\\one-tests\\runs\\capture\\inbox\\0007.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0008.tmp C:\\one-tests\\runs\\capture\\inbox\\0008.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0009.tmp C:\\one-tests\\runs\\capture\\inbox\\0009.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0010.tmp C:\\one-tests\\runs\\capture\\inbox\\0010.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0011.tmp C:\\one-tests\\runs\\capture\\inbox\\0011.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0012.tmp C:\\one-tests\\runs\\capture\\inbox\\0012.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0013.tmp C:\\one-tests\\runs\\capture\\inbox\\0013.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0014.tmp C:\\one-tests\\runs\\capture\\inbox\\0014.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0015.tmp C:\\one-tests\\runs\\capture\\inbox\\0015.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0016.tmp C:\\one-tests\\runs\\capture\\inbox\\0016.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0017.tmp C:\\one-tests\\runs\\capture\\inbox\\0017.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0018.tmp C:\\one-tests\\runs\\capture\\inbox\\0018.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0019.tmp C:\\one-tests\\runs\\capture\\inbox\\0019.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0020.tmp C:\\one-tests\\runs\\capture\\inbox\\0020.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0021.tmp C:\\one-tests\\runs\\capture\\inbox\\0021.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0022.tmp C:\\one-tests\\runs\\capture\\inbox\\0022.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0023.tmp C:\\one-tests\\runs\\capture\\inbox\\0023.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0024.tmp C:\\one-tests\\runs\\capture\\inbox\\0024.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0025.tmp C:\\one-tests\\runs\\capture\\inbox\\0025.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0026.tmp C:\\one-tests\\runs\\capture\\inbox\\0026.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
diff --git a/corpus/conflict-page/native-restore/a/hierarchy-0019.xml b/corpus/conflict-page/native-restore/a/hierarchy-0019.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9d33204b027aaa0d60fe105c9a70d960b28be1bb
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/hierarchy-0019.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/native-restore/a/hierarchy-0025.xml b/corpus/conflict-page/native-restore/a/hierarchy-0025.xml
new file mode 100644
index 0000000000000000000000000000000000000000..11e06fe95607fb8908a394804a20cbb4d93601b0
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/hierarchy-0025.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/native-restore/a/listed.json b/corpus/conflict-page/native-restore/a/listed.json
new file mode 100644
index 0000000000000000000000000000000000000000..559836710a7579ea1e2c4dd98dff086a1d6bf2b3
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/listed.json
@@ -0,0 +1 @@
+["Fictitious: café, 東京, مرحبا", "Four", "One", "Two", "Target", "Three"]
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/a/machine.json b/corpus/conflict-page/native-restore/a/machine.json
new file mode 100644
index 0000000000000000000000000000000000000000..084bcf445566e4a984e4a5465bf8a0ceabf53384
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/machine.json
@@ -0,0 +1 @@
+{"name": "m6-d281b38e", "hostname": "ONE-M6-D281B38E"}
diff --git a/corpus/conflict-page/native-restore/a/teardown.json b/corpus/conflict-page/native-restore/a/teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/conflict-page/native-restore/a/teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/conflict-page/native-restore/b-published/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one b/corpus/conflict-page/native-restore/b-published/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one
new file mode 100644
index 0000000000000000000000000000000000000000..753afa4aa521f599083936966e3e96226937d4e6
Binary files /dev/null and b/corpus/conflict-page/native-restore/b-published/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one differ
diff --git a/corpus/conflict-page/native-restore/b-published/notebook/OneNote_RecycleBin/Open Notebook.onetoc2 b/corpus/conflict-page/native-restore/b-published/notebook/OneNote_RecycleBin/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..810bfcc34d55bf600d42219614d17db63df60ef9
Binary files /dev/null and b/corpus/conflict-page/native-restore/b-published/notebook/OneNote_RecycleBin/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native-restore/b-published/notebook/Open Notebook.onetoc2 b/corpus/conflict-page/native-restore/b-published/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..b49e1ce4cfca70df24a2af81ac03d97caca48955
Binary files /dev/null and b/corpus/conflict-page/native-restore/b-published/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native-restore/b-published/notebook/synthetic.one b/corpus/conflict-page/native-restore/b-published/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..7f97f031472ec657a2563be65149f4de16f5466e
Binary files /dev/null and b/corpus/conflict-page/native-restore/b-published/notebook/synthetic.one differ
diff --git a/corpus/conflict-page/native-restore/b/b-merged.json b/corpus/conflict-page/native-restore/b/b-merged.json
new file mode 100644
index 0000000000000000000000000000000000000000..2f394e7eaf1defcfe3e06e321b5af76be4f84054
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/b-merged.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Fictitious: caf\u00e9, \u6771\u4eac, \u0645\u0631\u062d\u0628\u0627 - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/b-merged.png b/corpus/conflict-page/native-restore/b/b-merged.png
new file mode 100644
index 0000000000000000000000000000000000000000..f2c8f6396508393f4488c326a9d2d6959a14abae
Binary files /dev/null and b/corpus/conflict-page/native-restore/b/b-merged.png differ
diff --git a/corpus/conflict-page/native-restore/b/command-0001.json b/corpus/conflict-page/native-restore/b/command-0001.json
new file mode 100644
index 0000000000000000000000000000000000000000..554ff0ef8a35478d74f3e5388f75789140b158b8
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/command-0001.json
@@ -0,0 +1 @@
+{"action": "snapshot"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/command-0002.json b/corpus/conflict-page/native-restore/b/command-0002.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/command-0002.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/command-0003.json b/corpus/conflict-page/native-restore/b/command-0003.json
new file mode 100644
index 0000000000000000000000000000000000000000..554ff0ef8a35478d74f3e5388f75789140b158b8
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/command-0003.json
@@ -0,0 +1 @@
+{"action": "snapshot"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/command-0004.json b/corpus/conflict-page/native-restore/b/command-0004.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/command-0004.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/command-0005.json b/corpus/conflict-page/native-restore/b/command-0005.json
new file mode 100644
index 0000000000000000000000000000000000000000..554ff0ef8a35478d74f3e5388f75789140b158b8
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/command-0005.json
@@ -0,0 +1 @@
+{"action": "snapshot"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/command-0006.json b/corpus/conflict-page/native-restore/b/command-0006.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/command-0006.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/command-0007.json b/corpus/conflict-page/native-restore/b/command-0007.json
new file mode 100644
index 0000000000000000000000000000000000000000..554ff0ef8a35478d74f3e5388f75789140b158b8
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/command-0007.json
@@ -0,0 +1 @@
+{"action": "snapshot"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/command-0008.json b/corpus/conflict-page/native-restore/b/command-0008.json
new file mode 100644
index 0000000000000000000000000000000000000000..eff482c021828f4924fb0845aa6043a8312745ea
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/command-0008.json
@@ -0,0 +1 @@
+{"action": "delete-page", "page": "Target"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/command-0009.json b/corpus/conflict-page/native-restore/b/command-0009.json
new file mode 100644
index 0000000000000000000000000000000000000000..554ff0ef8a35478d74f3e5388f75789140b158b8
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/command-0009.json
@@ -0,0 +1 @@
+{"action": "snapshot"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/command-0010.json b/corpus/conflict-page/native-restore/b/command-0010.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/command-0010.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/command-0011.json b/corpus/conflict-page/native-restore/b/command-0011.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/command-0011.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/command-0012.json b/corpus/conflict-page/native-restore/b/command-0012.json
new file mode 100644
index 0000000000000000000000000000000000000000..2272087176d433cd1d42b77fb18efd145b9bbbf5
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/command-0012.json
@@ -0,0 +1 @@
+{"action": "sync"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/command-0013.json b/corpus/conflict-page/native-restore/b/command-0013.json
new file mode 100644
index 0000000000000000000000000000000000000000..554ff0ef8a35478d74f3e5388f75789140b158b8
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/command-0013.json
@@ -0,0 +1 @@
+{"action": "snapshot"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/command-0014.json b/corpus/conflict-page/native-restore/b/command-0014.json
new file mode 100644
index 0000000000000000000000000000000000000000..7adf980027ef43cc77767d4fd1b1d67ad1541ab4
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/command-0014.json
@@ -0,0 +1 @@
+{"action": "close"}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/commands.jsonl b/corpus/conflict-page/native-restore/b/commands.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..2335dad6840245c83e266c7e5d6601001755f2d0
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/commands.jsonl
@@ -0,0 +1,16 @@
+{"command": "mkdir C:\\one-tests\\runs\\capture", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -ExecutionPolicy Bypass -File C:\\one-tests\\network.ps1 -LabMac 52:54:02:7b:1a:4d", "exit": 0, "stdout": "{\"renewalResult\":0,\"releaseResult\":0,\"mac\":\"52:54:02:7b:1a:4d\",\"address\":\"192.168.77.40\"}\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0001.tmp C:\\one-tests\\runs\\capture\\inbox\\0001.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0002.tmp C:\\one-tests\\runs\\capture\\inbox\\0002.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0003.tmp C:\\one-tests\\runs\\capture\\inbox\\0003.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0004.tmp C:\\one-tests\\runs\\capture\\inbox\\0004.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0005.tmp C:\\one-tests\\runs\\capture\\inbox\\0005.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0006.tmp C:\\one-tests\\runs\\capture\\inbox\\0006.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0007.tmp C:\\one-tests\\runs\\capture\\inbox\\0007.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0008.tmp C:\\one-tests\\runs\\capture\\inbox\\0008.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0009.tmp C:\\one-tests\\runs\\capture\\inbox\\0009.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0010.tmp C:\\one-tests\\runs\\capture\\inbox\\0010.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0011.tmp C:\\one-tests\\runs\\capture\\inbox\\0011.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0012.tmp C:\\one-tests\\runs\\capture\\inbox\\0012.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0013.tmp C:\\one-tests\\runs\\capture\\inbox\\0013.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0014.tmp C:\\one-tests\\runs\\capture\\inbox\\0014.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
diff --git a/corpus/conflict-page/native-restore/b/hierarchy-0001.xml b/corpus/conflict-page/native-restore/b/hierarchy-0001.xml
new file mode 100644
index 0000000000000000000000000000000000000000..831d1d427c0ef26e4d79ed1c8f185ea22c942068
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/hierarchy-0001.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/native-restore/b/hierarchy-0003.xml b/corpus/conflict-page/native-restore/b/hierarchy-0003.xml
new file mode 100644
index 0000000000000000000000000000000000000000..18798800a4f9f74d5296ac80adbf8348f81d6f43
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/hierarchy-0003.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/native-restore/b/hierarchy-0005.xml b/corpus/conflict-page/native-restore/b/hierarchy-0005.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5c16c8efeebe230903ed784409d50d7c039c87fd
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/hierarchy-0005.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/native-restore/b/hierarchy-0007.xml b/corpus/conflict-page/native-restore/b/hierarchy-0007.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2a44e1e49093e9f699bfa6eaa4821e4d3c1de3f8
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/hierarchy-0007.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/native-restore/b/hierarchy-0009.xml b/corpus/conflict-page/native-restore/b/hierarchy-0009.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e5aeaf4af2852eaea5d8f51cd321eb61baf23cfb
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/hierarchy-0009.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/native-restore/b/hierarchy-0013.xml b/corpus/conflict-page/native-restore/b/hierarchy-0013.xml
new file mode 100644
index 0000000000000000000000000000000000000000..dd73cb16e07cd7840fbd0e3ed63ad9d52459e1e1
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/hierarchy-0013.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/conflict-page/native-restore/b/listed.json b/corpus/conflict-page/native-restore/b/listed.json
new file mode 100644
index 0000000000000000000000000000000000000000..559836710a7579ea1e2c4dd98dff086a1d6bf2b3
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/listed.json
@@ -0,0 +1 @@
+["Fictitious: café, 東京, مرحبا", "Four", "One", "Two", "Target", "Three"]
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/b/machine.json b/corpus/conflict-page/native-restore/b/machine.json
new file mode 100644
index 0000000000000000000000000000000000000000..54a3c6b9ac2277847928cb46fb3d062726a4070a
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/machine.json
@@ -0,0 +1 @@
+{"name": "m6-a205cc61", "hostname": "ONE-M6-A205CC61"}
diff --git a/corpus/conflict-page/native-restore/b/teardown.json b/corpus/conflict-page/native-restore/b/teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/conflict-page/native-restore/b/teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/conflict-page/native-restore/clients.json b/corpus/conflict-page/native-restore/clients.json
new file mode 100644
index 0000000000000000000000000000000000000000..0c922a99bf3f3f13b0eb03af843301a714be0c4c
--- /dev/null
+++ b/corpus/conflict-page/native-restore/clients.json
@@ -0,0 +1,4 @@
+{
+ "a": "m6-d281b38e",
+ "b": "m6-a205cc61"
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/initial/notebook/Open Notebook.onetoc2 b/corpus/conflict-page/native-restore/initial/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..c7beb44bf509f935090fe55e7549e4b50e0ae0e9
Binary files /dev/null and b/corpus/conflict-page/native-restore/initial/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native-restore/initial/notebook/synthetic.one b/corpus/conflict-page/native-restore/initial/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..058b8cd4a335818630c0305bf44d2ba394abd270
Binary files /dev/null and b/corpus/conflict-page/native-restore/initial/notebook/synthetic.one differ
diff --git a/corpus/conflict-page/native-restore/linux-teardown.json b/corpus/conflict-page/native-restore/linux-teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/conflict-page/native-restore/linux-teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/conflict-page/native-restore/linux.json b/corpus/conflict-page/native-restore/linux.json
new file mode 100644
index 0000000000000000000000000000000000000000..51be344e66ab7a2c56327e2ce949f3785fedee54
--- /dev/null
+++ b/corpus/conflict-page/native-restore/linux.json
@@ -0,0 +1,11 @@
+{
+ "cpus": 2,
+ "disk_gb": 16,
+ "hostname": "one-conflict3",
+ "lab_address": "192.168.77.1",
+ "lab_mac": "52:54:04:08:6d:03",
+ "memory_mb": 2048,
+ "samba_port": 14450,
+ "ssh_port": 22000,
+ "wan_mac": "52:54:03:bb:9c:87"
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/merged.json b/corpus/conflict-page/native-restore/merged.json
new file mode 100644
index 0000000000000000000000000000000000000000..889578b037833da0bb993bcb332e6377ca5fbb09
--- /dev/null
+++ b/corpus/conflict-page/native-restore/merged.json
@@ -0,0 +1,49 @@
+{
+ "order": [
+ "",
+ "Four",
+ "One",
+ "Two",
+ "Target",
+ "Three"
+ ],
+ "pages": [
+ [
+ "",
+ "Fictitious positioned outline.",
+ "Fictitious: café, 東京, مرحبا",
+ "Left cell",
+ "Right cell"
+ ],
+ [
+ "2:55 AM",
+ "Body Four.",
+ "Four",
+ "Sunday, September 27, 2026"
+ ],
+ [
+ "2:55 AM",
+ "Body One.",
+ "One",
+ "Sunday, September 27, 2026"
+ ],
+ [
+ "2:55 AM",
+ "Body Two.",
+ "Sunday, September 27, 2026",
+ "Two"
+ ],
+ [
+ "2:55 AM",
+ "Body Target edited offline.",
+ "Sunday, September 27, 2026",
+ "Target"
+ ],
+ [
+ "2:55 AM",
+ "Body Three.",
+ "Sunday, September 27, 2026",
+ "Three"
+ ]
+ ]
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native-restore/merged/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one b/corpus/conflict-page/native-restore/merged/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one
new file mode 100644
index 0000000000000000000000000000000000000000..d58aedc6e4ba0cee7324ec43ffa4997e89ac864a
Binary files /dev/null and b/corpus/conflict-page/native-restore/merged/notebook/OneNote_RecycleBin/OneNote_DeletedPages.one differ
diff --git a/corpus/conflict-page/native-restore/merged/notebook/OneNote_RecycleBin/Open Notebook.onetoc2 b/corpus/conflict-page/native-restore/merged/notebook/OneNote_RecycleBin/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..810bfcc34d55bf600d42219614d17db63df60ef9
Binary files /dev/null and b/corpus/conflict-page/native-restore/merged/notebook/OneNote_RecycleBin/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native-restore/merged/notebook/Open Notebook.onetoc2 b/corpus/conflict-page/native-restore/merged/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..b49e1ce4cfca70df24a2af81ac03d97caca48955
Binary files /dev/null and b/corpus/conflict-page/native-restore/merged/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native-restore/merged/notebook/synthetic.one b/corpus/conflict-page/native-restore/merged/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..82effac1ffff4519beccb2392a513f3985fa7916
Binary files /dev/null and b/corpus/conflict-page/native-restore/merged/notebook/synthetic.one differ
diff --git a/corpus/conflict-page/native-restore/native_conflict.py b/corpus/conflict-page/native-restore/native_conflict.py
new file mode 100644
index 0000000000000000000000000000000000000000..ccb4026acf69f3df84fdfbb01e9b6e39461762d3
--- /dev/null
+++ b/corpus/conflict-page/native-restore/native_conflict.py
@@ -0,0 +1,362 @@
+#!/usr/bin/env python3
+"""OneNote 2010 conflict pages: how they arise, what they store and how OneNote shows them.
+
+`sync LINUX OUTPUT` runs two disposable OneNote clients on one Samba notebook: client A edits
+two paragraphs offline while client B edits one of them and a table cell online; A then
+reconnects, which makes OneNote write a conflict page. The server file is kept before and
+after, A and B are screenshotted with the information bar, the page list shown, the conflict
+page open and its menu open, and A then deletes the conflict page through that menu.
+
+`pages LINUX OUTPUT` runs the same two clients on page-list changes: client A, offline, moves
+a page that client B also moves, moves another page, and edits a page B deletes; A then
+reconnects. The server's page order and pages, with the recycle bin, are kept at each step.
+
+`restore LINUX OUTPUT` runs them on a page in the middle of the list: client A, offline, edits
+it and moves a page B leaves alone while B deletes it; A then reconnects.
+
+`cold NOTEBOOK OUTPUT [--delete]` cold-opens a notebook holding a conflict page in a fresh
+clone and screenshots the same views; `--delete` deletes the conflict page through the menu
+and captures the notebook OneNote leaves.
+"""
+import argparse
+from contextlib import ExitStack
+from concurrent.futures import ThreadPoolExecutor
+import base64
+import json
+from pathlib import Path
+import shutil
+import subprocess
+import sys
+import time
+import xml.etree.ElementTree as ET
+
+sys.path.insert(0, str(Path(__file__).resolve().parent))
+sys.path.insert(0, str(Path(__file__).resolve().parent / 'w7'))
+import native_runner as runner
+from native_runner import ROOT, clone, command, windows, vm
+import linux_vm
+
+FIXTURE = ROOT / 'corpus/native-ink/cold-ui-ink/notebook'
+SHARE = 'm6-conflict'
+ORIGINAL = 'Fictitious: café, 東京, مرحبا'
+OTHER_OUTLINE = 'Fictitious positioned outline.'
+CELL = 'Left cell'
+
+# Coordinates on OneNote 2010 maximized at 800x600, from the stage-5, m8 and sync-01 captures:
+# the information bar's text, and the first conflict page listed under its page.
+BAR = (332, 100)
+FIRST_CONFLICT = (725, 142)
+# Delete Conflict Page, first in the menu the bar opens on a conflict page.
+DELETE = (420, 113)
+
+
+def shot(name, output, label, script):
+ """Runs an AutoHotkey step on the clone, keeping its screenshot as `label.png`."""
+ (output / f'{label}.ahk').write_text(script)
+ result = windows.do_exec(script, target=name, timeout_ms=120000, shot_delay_ms=1500)
+ screenshot = result.pop('png_b64', None)
+ if screenshot:
+ (output / f'{label}.png').write_bytes(base64.b64decode(screenshot))
+ (output / f'{label}.json').write_text(json.dumps(result, indent=2))
+ if result.get('error') or result.get('exit') != 0:
+ raise RuntimeError(f'{label}: {result}')
+ return result
+
+
+def click(x, y):
+ return ('WinActivate("ahk_exe ONENOTE.EXE")\nCoordMode("Mouse", "Screen")\n'
+ f'Click({x}, {y})\nSleep(1200)\n')
+
+
+# Reports any dialog OneNote raised, so a confirmation shows in the step's record.
+DIALOG = ('if WinExist("ahk_class #32770 ahk_exe ONENOTE.EXE") {\n'
+ ' FileAppend("dialog: " WinGetTitle() " | " WinGetText() "`n", "*", "UTF-8")\n'
+ '}\n')
+
+
+def conflict_views(name, output, page, prefix, delete=False):
+ """Screenshots a page with a conflict page: the bar, the list shown, the conflict page and
+ its menu; `delete` then chooses Delete Conflict Page."""
+ shot(name, output, f'{prefix}-main', runner.navigation_script(page) + 'Sleep(2000)\n')
+ shot(name, output, f'{prefix}-shown', click(*BAR))
+ shot(name, output, f'{prefix}-conflict', click(*FIRST_CONFLICT))
+ shot(name, output, f'{prefix}-menu', click(*BAR))
+ if delete:
+ shot(name, output, f'{prefix}-delete', click(*DELETE) + DIALOG)
+ else:
+ shot(name, output, f'{prefix}-closed', 'WinActivate("ahk_exe ONENOTE.EXE")\nSend("{Esc}")\nSleep(800)\n')
+
+
+def cold(notebook, output, delete):
+ def interaction(name, output):
+ page = ET.parse(sorted((output / 'read').glob('page-*.xml'))[0]).getroot().attrib['ID']
+ conflict_views(name, output, page, 'cold', delete)
+ if delete:
+ # OneNote writes the deletion in the background; give it time before the re-read.
+ time.sleep(20)
+ runner.capture(notebook, output, collect_notebook=True, interaction=interaction)
+
+
+class Lab:
+ """Two OneNote clients on one Samba notebook: `action` runs a controller command,
+ `state` copies the share and exports its section, `settle` syncs until a check holds."""
+
+
+def page_list(model):
+ """The section's pages in order, each as its sorted texts."""
+ from document_model import ordered_pages, walk
+ return [sorted(node['kind']['text'] for _, node in walk(page, oid) if node['kind']['type'] == 'RichText')
+ for _, _, page, oid in ordered_pages(model)]
+
+
+def edits(lab):
+ from native_collaboration import reachable_page_text
+ a, b, output, action, settle = lab.a, lab.b, lab.output, lab.action, lab.settle
+ state = lambda label: reachable_page_text(lab.state(label))
+ texts = lambda state: {text for values in state.values() for text in values}
+ state('initial')
+ vm.qmp(a['name'], 'set_link', {'name': 'lab', 'up': False})
+ try:
+ action(a, 'edit', expected=ORIGINAL, text='Client A conflicting edit.')
+ action(a, 'edit', expected=OTHER_OUTLINE, text='Client A disjoint edit.')
+ action(a, 'snapshot')
+ action(b, 'edit', expected=ORIGINAL, text='Client B conflicting edit.')
+ action(b, 'edit', expected=CELL, text='Client B disjoint cell.')
+ settle(b, lambda label: {'Client B conflicting edit.', 'Client B disjoint cell.'} <= texts(state(label)), 'b-published')
+ finally:
+ vm.qmp(a['name'], 'set_link', {'name': 'lab', 'up': True})
+ settle(a, lambda label: len(found := state(label)) > 1 and 'Client A disjoint edit.' in texts(found), 'conflict')
+ (output / 'conflict.json').write_text(json.dumps(state('conflict'), indent=2, ensure_ascii=False))
+ page = action(a, 'snapshot')[0].attrib['ID']
+ conflict_views(a['name'], output / 'a', page, 'a')
+ action(b, 'sync')
+ time.sleep(5)
+ page_b = action(b, 'snapshot')[0].attrib['ID']
+ conflict_views(b['name'], output / 'b', page_b, 'b')
+ conflict_views(a['name'], output / 'a', page, 'a-again', delete=True)
+ settle(a, lambda label: len(state(label)) == 1, 'deleted')
+ action(b, 'sync')
+ time.sleep(5)
+ shot(b['name'], output / 'b', 'b-after-delete', runner.navigation_script(page_b) + 'Sleep(2000)\n')
+
+
+PAGES = ['One', 'Two', 'Three', 'Four', 'Target']
+
+
+def titled(model, titles):
+ """The section's pages in order, each named by the title among `titles` it holds."""
+ return [next((text for text in texts if text in titles), '') for texts in page_list(model)]
+
+
+def offline(lab, titles, a_actions, b_actions):
+ """Creates the titled pages; A runs its actions offline while B runs and publishes its
+ own; A then reconnects. Keeps the merged order and pages, and what each client lists."""
+ a, b, output, action = lab.a, lab.b, lab.output, lab.action
+ action(a, 'pages', titles=titles)
+ lab.settle(a, lambda label: 'Body Target.' in str(page_list(lab.state(label))), 'initial')
+ deadline = time.monotonic() + 300
+ while not any(node.get('name') == 'Target' for node in action(b, 'snapshot')):
+ action(b, 'sync')
+ if time.monotonic() > deadline: raise TimeoutError('B never listed the new pages')
+ vm.qmp(a['name'], 'set_link', {'name': 'lab', 'up': False})
+ try:
+ for kind, parameters in a_actions:
+ action(a, kind, **parameters)
+ action(a, 'snapshot')
+ for kind, parameters in b_actions:
+ action(b, kind, **parameters)
+ action(b, 'snapshot')
+ lab.settle(b, lambda label: 'Body Target.' not in str(page_list(lab.state(label))), 'b-published')
+ finally:
+ vm.qmp(a['name'], 'set_link', {'name': 'lab', 'up': True})
+ # A's changes reach the server when its sync writes; wait until the file stops changing.
+ previous, steady = None, 0
+ deadline = time.monotonic() + 300
+ while steady < 3 and time.monotonic() < deadline:
+ action(a, 'sync')
+ time.sleep(10)
+ current = page_list(lab.state('merged'))
+ steady = steady + 1 if current == previous else 0
+ previous = current
+ (output / 'merged.json').write_text(json.dumps({'order': titled(lab.state('merged'), titles), 'pages': previous}, indent=2, ensure_ascii=False))
+ for client, label in ((a, 'a'), (b, 'b')):
+ action(client, 'sync')
+ time.sleep(5)
+ listed = action(client, 'snapshot')
+ (output / label / 'listed.json').write_text(json.dumps([node.get('name') for node in listed], ensure_ascii=False))
+ if listed:
+ shot(client['name'], output / label, f'{label}-merged', runner.navigation_script(listed[0].get('ID')) + 'Sleep(2000)\n')
+
+
+def pages(lab):
+ """Competing page moves, and an offline edit of a page the other client deletes."""
+ offline(lab, PAGES, [
+ ('move', {'page': 'Four', 'before': 'One'}),
+ ('move', {'page': 'Two', 'before': None}),
+ ('edit', {'expected': 'Body Target.', 'text': 'Body Target edited offline.'}),
+ ], [
+ ('move', {'page': 'Four', 'before': None}),
+ ('move', {'page': 'Three', 'before': 'One'}),
+ ('delete-page', {'page': 'Target'}),
+ ])
+
+
+def restore(lab):
+ """An offline edit of a page in the middle of the list the other client deletes, with an
+ offline move of a page the other client leaves alone."""
+ offline(lab, ['One', 'Two', 'Target', 'Three', 'Four'], [
+ ('edit', {'expected': 'Body Target.', 'text': 'Body Target edited offline.'}),
+ ('move', {'page': 'Four', 'before': 'One'}),
+ ], [
+ ('delete-page', {'page': 'Target'}),
+ ])
+
+
+def sync(server, output, scenario):
+ from document_model import EXPORTER
+
+ if linux_vm.instance_path(server).exists():
+ raise ValueError('Choose a new Linux VM name; existing machines are not owned by this run.')
+ output = output.resolve()
+ output.mkdir(parents=True, exist_ok=False)
+ scripts = output / 'scripts'
+ scripts.mkdir()
+ for name in ('cold.ps1', 'collaborate.ps1', 'network.ps1', 'text.ps1', 'stress.ps1'):
+ shutil.copyfile(ROOT / 'tools/native' / name, scripts / name)
+ shutil.copyfile(Path(__file__), output / 'native_conflict.py')
+
+ def ssh(text, timeout=90):
+ result = linux_vm.run_ssh(server, text, timeout=timeout)
+ with (output / 'server.jsonl').open('a') as log:
+ log.write(json.dumps({'command': text, 'exit': result.returncode, 'stdout': result.stdout, 'stderr': result.stderr}) + '\n')
+ result.check_returncode()
+ return result.stdout
+
+ def server_state(label):
+ """Copies the server's notebook, recycle bin included, until its section parses,
+ keeping it as `label/notebook`; returns the section's exported model."""
+ deadline = time.monotonic() + 120
+ while True:
+ target = output / label
+ shutil.rmtree(target, ignore_errors=True)
+ (target / 'notebook').mkdir(parents=True)
+ archive = subprocess.run(linux_vm.ssh_argv(server, f"tar cf - -C '/srv/agent/{SHARE}' ."), capture_output=True, check=True, timeout=60).stdout
+ subprocess.run(['tar', 'xf', '-', '-C', target / 'notebook'], input=archive, check=True)
+ result = subprocess.run([EXPORTER, target / 'notebook/synthetic.one', target / 'model'], capture_output=True, text=True)
+ if result.returncode == 0:
+ return json.loads((target / 'model/document.json').read_text())
+ if time.monotonic() > deadline:
+ raise RuntimeError(f'{label}: {result.stderr}')
+ time.sleep(1)
+
+ linux_vm.create_instance(server)
+ try:
+ linux_vm.launch(server)
+ linux_vm.wait_instance(server, 600)
+ (output / 'linux.json').write_text(json.dumps(linux_vm.load_instance(server), indent=2))
+ ssh(f'mkdir /srv/agent/{SHARE}')
+ for name in ('synthetic.one', 'Open Notebook.onetoc2'):
+ with (FIXTURE / name).open('rb') as stream:
+ subprocess.run(linux_vm.ssh_argv(server, f"cat > '/srv/agent/{SHARE}/{name}'"), stdin=stream, check=True)
+ ssh(f'chmod u+w /srv/agent/{SHARE}/*')
+ with ExitStack() as stack:
+ labels = ['a', 'b']
+ for label in labels:
+ (output / label).mkdir()
+
+ def start(label):
+ manager = clone(output / label)
+ name = manager.__enter__()
+ return label, name, manager
+ with ThreadPoolExecutor(max_workers=2) as pool:
+ started = list(pool.map(start, labels))
+ for _, _, manager in started:
+ stack.push(manager)
+ clients = {}
+ for label, name, _ in started:
+ folder = output / label
+ for local in scripts.iterdir():
+ remote = 'cold-current.ps1' if local.name == 'cold.ps1' else local.name
+ result = windows.do_put(local, 'C:\\one-tests\\' + remote, name)
+ if result.get('error'): raise RuntimeError(result['error'])
+ command(name, 'mkdir C:\\one-tests\\runs\\capture', folder)
+ command(name, 'powershell -NoProfile -ExecutionPolicy Bypass -File C:\\one-tests\\network.ps1 -LabMac ' + vm.lab_mac(name), folder)
+ result = windows.do_spawn('cmd /c powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\collaborate.ps1 -Root C:\\one-tests\\runs\\capture -SharedPath \\\\192.168.77.1\\agent\\' + SHARE + ' -CloneHost ONE-' + name.upper() + ' > C:\\one-tests\\runs\\capture\\controller.log 2>&1', name)
+ if result.get('error'): raise RuntimeError(result['error'])
+ deadline = time.monotonic() + 180
+ while windows.do_cmd('if exist C:\\one-tests\\runs\\capture\\ready (echo ready)', target=name).get('stdout', '').find('ready') < 0:
+ if time.monotonic() > deadline: raise RuntimeError('The collaboration controller did not open the notebook')
+ time.sleep(1)
+ clients[label] = {'name': name, 'folder': folder, 'sequence': 0}
+ (output / 'clients.json').write_text(json.dumps({label: client['name'] for label, client in clients.items()}, indent=2))
+
+ def action(client, kind, **parameters):
+ client['sequence'] += 1
+ sequence = client['sequence']
+ local = client['folder'] / f'command-{sequence:04}.json'
+ local.write_text(json.dumps({'action': kind, **parameters}, ensure_ascii=False))
+ inbox = f'C:\\one-tests\\runs\\capture\\inbox\\{sequence:04}'
+ result = windows.do_put(local, inbox + '.tmp', client['name'])
+ if result.get('error'): raise RuntimeError(result['error'])
+ command(client['name'], f'move {inbox}.tmp {inbox}.json', client['folder'])
+ remote = f'C:\\one-tests\\runs\\capture\\outbox\\{sequence}'
+ deadline = time.monotonic() + 600
+ while True:
+ result = windows.do_cmd(f'if exist {remote}\\error (type {remote}\\error & exit /b 1) else (if exist {remote}\\done (echo complete))', target=client['name'])
+ if result.get('exit') != 0: raise RuntimeError(str(result))
+ if 'complete' in result.get('stdout', ''): break
+ if time.monotonic() > deadline: raise TimeoutError(f'{kind} did not complete')
+ time.sleep(.5)
+ if kind != 'snapshot':
+ return None
+ hierarchy = client['folder'] / f'hierarchy-{sequence:04}.xml'
+ windows.do_get(remote + '\\hierarchy.xml', hierarchy, client['name'])
+ pages = [node for node in ET.fromstring(hierarchy.read_text(encoding='utf-8-sig').strip()).iter() if node.tag.endswith('}Page')]
+ for i in range(len(pages)):
+ windows.do_get(remote + f'\\page-{i}.xml', client['folder'] / f'snapshot-{sequence:04}-{i}.xml', client['name'])
+ return pages
+
+ def settle(client, check, label):
+ deadline = time.monotonic() + 300
+ while True:
+ action(client, 'sync')
+ if check(label): return
+ if time.monotonic() > deadline: raise TimeoutError(label)
+ time.sleep(2)
+
+ lab = Lab()
+ lab.a, lab.b, lab.output = clients['a'], clients['b'], output
+ lab.action, lab.state, lab.settle = action, server_state, settle
+ scenario(lab)
+
+ for client in clients.values():
+ try:
+ action(client, 'close')
+ except Exception as error:
+ (client['folder'] / 'close-failure.txt').write_text(str(error))
+ server_state('final')
+ finally:
+ if linux_vm.running(server):
+ linux_vm.shutdown(server, 120)
+ linux_vm.delete_instance(server)
+ (output / 'linux-teardown.json').write_text(json.dumps({'absent': not linux_vm.instance_path(server).exists()}) + '\n')
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+ commands = parser.add_subparsers(dest='mode', required=True)
+ scenarios = {'sync': edits, 'pages': pages, 'restore': restore}
+ for mode in scenarios:
+ lab = commands.add_parser(mode)
+ lab.add_argument('linux')
+ lab.add_argument('output', type=Path)
+ reopen = commands.add_parser('cold')
+ reopen.add_argument('notebook', type=Path)
+ reopen.add_argument('output', type=Path)
+ reopen.add_argument('--delete', action='store_true')
+ args = parser.parse_args()
+ if args.mode in scenarios:
+ sync(args.linux, args.output, scenarios[args.mode])
+ else:
+ cold(args.notebook, args.output, args.delete)
+ print('Captured', args.output)
diff --git a/corpus/conflict-page/native-restore/scripts/cold.ps1 b/corpus/conflict-page/native-restore/scripts/cold.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..a0b99e7aeb86f4dc10032537800501a52ad65b4f
--- /dev/null
+++ b/corpus/conflict-page/native-restore/scripts/cold.ps1
@@ -0,0 +1,27 @@
+param([Parameter(Mandatory=$true)][string]$Root, [string]$CloneHost = '')
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
+if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
+ throw 'Choose a run directly below C:\one-tests\runs.'
+}
+if (Get-Process ONENOTE -ErrorAction SilentlyContinue) { throw 'Close OneNote before resetting its test cache.' }
+$key = 'HKCU:\Software\Microsoft\Office\14.0\OneNote'
+if ($CloneHost) {
+ if ($CloneHost -notmatch '^ONE-[A-Z0-9-]+$' -or [Environment]::MachineName -ne $CloneHost) {
+ throw 'The disposable clone hostname does not match this machine.'
+ }
+ New-Item "$key\Options\Paths" -Force | Out-Null
+ New-ItemProperty "$key\Options\Paths" -Name UnfiledNotesSection -PropertyType ExpandString -Value 'C:\one-tests\Loose.one' -Force | Out-Null
+} elseif ((Get-ItemProperty "$key\Options\Paths").UnfiledNotesSection -ne 'C:\one-tests\Loose.one' -or
+ -not (Test-Path 'C:\one-tests\profile-original-cache')) {
+ throw 'Park the personal OneNote profile before resetting the test cache.'
+}
+$cache = Join-Path $env:LOCALAPPDATA 'Microsoft\OneNote\14.0'
+$parked = Join-Path 'C:\one-tests\caches' ([IO.Path]::GetFileName($root))
+if (Test-Path $parked) { throw 'Choose a new run; its parked cache already exists.' }
+New-Item -ItemType Directory -Path 'C:\one-tests\caches' -Force | Out-Null
+if (Test-Path $cache) { Move-Item -LiteralPath $cache -Destination $parked }
+if (Test-Path "$key\OpenNotebooks") { Remove-Item "$key\OpenNotebooks" -Recurse }
+New-Item "$key\OpenNotebooks" | Out-Null
+New-ItemProperty "$key\OpenNotebooks" -Name '1' -PropertyType String -Value "$root\notebook" | Out-Null
diff --git a/corpus/conflict-page/native-restore/scripts/collaborate.ps1 b/corpus/conflict-page/native-restore/scripts/collaborate.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..6a42a3d526db486f022b032583704b0f4aee26d3
--- /dev/null
+++ b/corpus/conflict-page/native-restore/scripts/collaborate.ps1
@@ -0,0 +1,145 @@
+param([Parameter(Mandatory=$true)][string]$Root,
+ [Parameter(Mandatory=$true)][string]$SharedPath,
+ [Parameter(Mandatory=$true)][string]$CloneHost,
+ [switch]$ResumeCache,
+ [int]$StartSequence = 1)
+function Keep-Container($page, $target) {
+ $container = $target
+ while ($container.ParentNode -ne $page.DocumentElement) { $container = $container.ParentNode }
+ foreach ($sibling in @($page.DocumentElement.ChildNodes)) {
+ if ($sibling -ne $container -and $sibling.LocalName -in @('Outline', 'Title', 'Image', 'InkDrawing')) {
+ [void]$page.DocumentElement.RemoveChild($sibling)
+ }
+ }
+}
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+. "$PSScriptRoot\text.ps1"
+if ($SharedPath -notmatch '^\\\\192\.168\.77\.1\\agent\\m6-[a-z0-9-]+$') { throw 'Choose a disposable Linux lab notebook.' }
+if ($env:COMPUTERNAME -ne $CloneHost) { throw 'The collaboration controller requires its owned clone.' }
+if (-not $ResumeCache) { & "$PSScriptRoot\cold-current.ps1" -Root $Root -CloneHost $CloneHost }
+$shared = $SharedPath
+if (-not (Test-Path "$shared\synthetic.one")) { throw 'Create the shared fixture first.' }
+New-Item -ItemType Directory "$Root\inbox", "$Root\outbox" -Force | Out-Null
+$user = 'HKCU:\Software\Microsoft\Office\Common\UserInfo'
+New-Item $user -Force | Out-Null
+New-ItemProperty $user -Name UserName -Value $CloneHost -PropertyType String -Force | Out-Null
+New-ItemProperty $user -Name UserInitials -Value $CloneHost.Substring($CloneHost.Length - 3) -PropertyType String -Force | Out-Null
+$app = New-Object -ComObject OneNote.Application
+$notebook = ''
+try {
+ $app.OpenHierarchy($shared, '', [ref]$notebook, 0)
+ $section = ''
+ $app.OpenHierarchy('synthetic.one', $notebook, [ref]$section, 0)
+ [IO.File]::WriteAllText("$Root\ready", 'ready')
+ $deadline = [DateTime]::UtcNow.AddMinutes(30)
+ $sequence = $StartSequence
+ while ([DateTime]::UtcNow -lt $deadline) {
+ $name = '{0:d4}.json' -f $sequence
+ $input = "$Root\inbox\$name"
+ if (-not (Test-Path $input)) { Start-Sleep -Milliseconds 100; continue }
+ $command = Get-Content -Raw -Encoding UTF8 $input | ConvertFrom-Json
+ $output = "$Root\outbox\$sequence"
+ New-Item -ItemType Directory $output | Out-Null
+ try {
+ if ($command.action -in @('prepare-stress', 'stress')) {
+ . "$PSScriptRoot\stress.ps1"
+ Invoke-Stress $app $section $command $output $shared
+ }
+ elseif ($command.action -eq 'sync') { $app.SyncHierarchy($notebook) }
+ elseif ($command.action -in @('pages', 'move', 'delete-page')) {
+ $namespace = 'http://schemas.microsoft.com/office/onenote/2010/onenote'
+ $hierarchy = ''
+ $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
+ [xml]$tree = $hierarchy
+ $listed = @($tree.SelectNodes('//*[local-name()="Page"]'))
+ $named = { param($title) @($listed | Where-Object { $_.GetAttribute('name') -eq $title }) }
+ if ($command.action -eq 'pages') {
+ # New titled pages with one body paragraph each, appended in order.
+ foreach ($title in $command.titles) {
+ $page = ''
+ $app.CreateNewPage($section, [ref]$page, 2)
+ $body = [Security.SecurityElement]::Escape('Body ' + $title + '.')
+ $name = [Security.SecurityElement]::Escape($title)
+ $xml = '' + $name + '' + $body + ''
+ $app.UpdatePageContent($xml, [DateTime]::MinValue, 1, $false)
+ }
+ } elseif ($command.action -eq 'move') {
+ # Moves the page titled `page` before the one titled `before`, or last.
+ $moved = @(& $named $command.page)
+ if ($moved.Count -ne 1) { throw 'Expected one page to move.' }
+ $order = [Collections.ArrayList]@($listed | Where-Object { $_ -ne $moved[0] })
+ $at = $order.Count
+ if ($command.before) {
+ $anchor = @(& $named $command.before)
+ if ($anchor.Count -ne 1) { throw 'Expected one anchor page.' }
+ $at = $order.IndexOf($anchor[0])
+ }
+ $order.Insert($at, $moved[0])
+ $xml = ''
+ foreach ($node in $order) {
+ $xml += ''
+ }
+ $app.UpdateHierarchy($xml + '', 1)
+ } else {
+ $doomed = @(& $named $command.page)
+ if ($doomed.Count -ne 1) { throw 'Expected one page to delete.' }
+ $app.DeleteHierarchy($doomed[0].GetAttribute('ID'), [DateTime]::MinValue, $false)
+ }
+ }
+ elseif ($command.action -eq 'kill-process') {
+ Get-Process ONENOTE | Stop-Process -Force
+ [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
+ $app = New-Object -ComObject OneNote.Application
+ $app.OpenHierarchy($shared, '', [ref]$notebook, 0)
+ $app.OpenHierarchy('synthetic.one', $notebook, [ref]$section, 0)
+ }
+ elseif ($command.action -eq 'edit' -or $command.action -eq 'snapshot') {
+ $hierarchy = ''
+ $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
+ [IO.File]::WriteAllText("$output\hierarchy.xml", $hierarchy, [Text.Encoding]::UTF8)
+ [xml]$tree = $hierarchy
+ $matches = 0
+ $editedPage = $null
+ $i = 0
+ foreach ($node in $tree.SelectNodes('//*[local-name()="Page"]')) {
+ $content = ''
+ $app.GetPageContent($node.GetAttribute('ID'), [ref]$content, 1, 1)
+ [IO.File]::WriteAllText("$output\page-$i.xml", $content, [Text.Encoding]::UTF8)
+ [xml]$page = $content
+ if ($command.action -eq 'edit') {
+ $targets = @($page.SelectNodes('//*[local-name()="T"]') | Where-Object {
+ (Get-ParagraphText $_.InnerText) -eq $command.expected
+ })
+ if ($targets.Count -gt 1) { throw 'Expected one matching text object.' }
+ if ($targets.Count -eq 1) {
+ $matches++
+ if ($matches -gt 1) { throw 'The text occurs on multiple pages.' }
+ $targets[0].InnerText = [Security.SecurityElement]::Escape($command.text)
+ Keep-Container $page $targets[0]
+ $editedPage = $page
+ }
+ }
+ $i++
+ }
+ if ($command.action -eq 'edit' -and $matches -ne 1) { throw 'Expected one matching text object.' }
+ if ($editedPage) { $app.UpdatePageContent($editedPage.OuterXml, [DateTime]::MinValue, 1, $false) }
+ } elseif ($command.action -ne 'close') { throw 'Unknown collaboration command.' }
+ [IO.File]::WriteAllText("$output\done", 'ok')
+ } catch {
+ [IO.File]::WriteAllText("$output\error", $_.Exception.ToString())
+ }
+ if ($command.action -eq 'close') { break }
+ $sequence++
+ }
+} finally {
+ try { if ($notebook) { $app.CloseNotebook($notebook, $false) } }
+ finally {
+ [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
+ $app = $null
+ [GC]::Collect()
+ [GC]::WaitForPendingFinalizers()
+ Get-Process ONENOTE -ErrorAction SilentlyContinue | Wait-Process -Timeout 10
+ [IO.File]::WriteAllText("$Root\closed", 'closed')
+ }
+}
diff --git a/corpus/conflict-page/native-restore/scripts/network.ps1 b/corpus/conflict-page/native-restore/scripts/network.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..95fa83d5243ee6a2a653cc075128b063e2f09cf2
--- /dev/null
+++ b/corpus/conflict-page/native-restore/scripts/network.ps1
@@ -0,0 +1,20 @@
+param([Parameter(Mandatory=$true)][string]$LabMac)
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$adapter = @(Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object { $_.MACAddress -eq $LabMac })
+if ($adapter.Count -ne 1) { throw 'The clone lab adapter is ambiguous.' }
+$enabled = $adapter[0].EnableDHCP()
+if ($enabled.ReturnValue -notin @(0, 1)) {
+ throw ('Unable to enable DHCP on the clone lab adapter; result ' + $enabled.ReturnValue)
+}
+$release = $adapter[0].ReleaseDHCPLease()
+$result = $adapter[0].RenewDHCPLease()
+$deadline = [DateTime]::UtcNow.AddSeconds(60)
+do {
+ $adapter = @(Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object { $_.MACAddress -eq $LabMac })
+ $addresses = @($adapter[0].IPAddress | Where-Object { $_ -match '^192\.168\.77\.\d+$' -and $_ -ne '192.168.77.1' })
+ if ($addresses.Count -eq 1) { break }
+ Start-Sleep -Milliseconds 250
+} while ([DateTime]::UtcNow -lt $deadline)
+if ($addresses.Count -ne 1) { throw ('The clone did not receive a lab IPv4 address; renewal result ' + $result.ReturnValue + '; adapter ' + ($adapter[0] | Select-Object MACAddress,DHCPEnabled,IPEnabled,IPAddress | ConvertTo-Json -Compress)) }
+@{mac=$LabMac;address=$addresses[0];releaseResult=$release.ReturnValue;renewalResult=$result.ReturnValue} | ConvertTo-Json -Compress
diff --git a/corpus/conflict-page/native-restore/scripts/stress.ps1 b/corpus/conflict-page/native-restore/scripts/stress.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..a0ab5bc69b4dabe970373e481873dfd951a1a69a
--- /dev/null
+++ b/corpus/conflict-page/native-restore/scripts/stress.ps1
@@ -0,0 +1,83 @@
+function Invoke-Stress($app, $section, $command, $output, $shared) {
+ $hierarchy = ''
+ $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
+ [IO.File]::WriteAllText("$output\hierarchy.xml", $hierarchy, [Text.Encoding]::UTF8)
+ [xml]$tree = $hierarchy
+ $pages = @($tree.SelectNodes('//*[local-name()="Page"]'))
+ if ($pages.Count -ne 1) { throw 'Expected one stress-test page.' }
+ $pageId = $pages[0].GetAttribute('ID')
+ if ($command.action -eq 'prepare-stress') {
+ for ($i = 0; $i -lt $command.clients; $i++) {
+ $xml = ''
+ $app.UpdatePageContent($xml, [DateTime]::MinValue, 1, $false)
+ }
+ return
+ }
+ $random = New-Object Random([int]$command.seed)
+ $events = New-Object IO.StreamWriter("$output\events.jsonl", $false, [Text.Encoding]::UTF8)
+ $events.AutoFlush = $true
+ try {
+ [IO.File]::WriteAllText("$output\ready", 'ready')
+ $deadline = [DateTime]::UtcNow.AddMinutes(10)
+ while (-not (Test-Path "$shared\stress-start")) {
+ if ([DateTime]::UtcNow -gt $deadline) { throw 'Stress start timed out.' }
+ Start-Sleep -Milliseconds 20
+ }
+ for ($i = 0; $i -lt $command.operations; $i++) {
+ if (($command.PSObject.Properties.Name -contains 'maintenance') -and $command.maintenance -and $i -eq [Math]::Floor($command.operations / 2)) {
+ [IO.File]::WriteAllText("$shared\maintenance-paused-n$($command.actor)", 'paused')
+ while (-not (Test-Path "$shared\maintenance-resume")) {
+ if ([DateTime]::UtcNow -gt $deadline) { throw 'Maintenance pause timed out.' }
+ Start-Sleep -Milliseconds 100
+ }
+ }
+ Start-Sleep -Milliseconds $random.Next(10, 100)
+ $started = [DateTime]::UtcNow.Ticks
+ $content = ''
+ $app.GetPageContent($pageId, [ref]$content, 1, 1)
+ [xml]$page = $content
+ $matches = @($page.SelectNodes('//*[local-name()="T"]') | Where-Object {
+ (Get-ParagraphText $_.InnerText).StartsWith($command.prefix)
+ })
+ if ($matches.Count -ne 1) { throw 'Expected one stress-test paragraph.' }
+ $before = Get-ParagraphText $matches[0].InnerText
+ $token = ' [n' + $command.actor + ':' + $i + ']'
+ $start = $end = $before.Length
+ $replacement = $token
+ $bold = $italic = $false
+ if ($command.edit) {
+ $boundaries = New-Object 'Collections.Generic.List[int]'
+ for ($at = $command.prefix.Length; $at -le $before.Length; $at++) {
+ $boundaries.Add($at)
+ if ($at -lt $before.Length -and [char]::IsHighSurrogate($before[$at])) { $at++ }
+ }
+ $first = $boundaries[$random.Next($boundaries.Count)]
+ $second = $boundaries[$random.Next($boundaries.Count)]
+ $start = [Math]::Min($first, $second)
+ $end = [Math]::Max($first, $second)
+ $replacement = ' caf' + [char]0xe9 + ' ' + [char]::ConvertFromUtf32(0x1f980) + $token
+ $bold = $random.Next(2) -eq 1
+ $italic = $random.Next(2) -eq 1
+ }
+ $after = $before.Substring(0, $start) + $replacement + $before.Substring($end)
+ $html = [Security.SecurityElement]::Escape($after)
+ if ($command.edit) {
+ $weight = if ($bold) { 'bold' } else { 'normal' }
+ $style = if ($italic) { 'italic' } else { 'normal' }
+ $html = '' + $html + ''
+ }
+ $matches[0].InnerText = $html
+ Keep-Container $page $matches[0]
+ $updateStarted = [DateTime]::UtcNow.Ticks
+ $app.UpdatePageContent($page.OuterXml, [DateTime]::MinValue, 1, $false)
+ $updated = [DateTime]::UtcNow.Ticks
+ $events.WriteLine((@{ operation=$i; before=$before; token=$token; started_ticks=$started;
+ range=@($start, $end); replacement=$replacement; bold=$bold; italic=$italic;
+ update_started_ticks=$updateStarted; updated_ticks=$updated } | ConvertTo-Json -Compress))
+ if ($i -eq 0) { [IO.File]::WriteAllText("$output\editing", 'editing') }
+ if ($command.sync_every -gt 0 -and (($i + 1) % $command.sync_every) -eq 0) {
+ $app.SyncHierarchy($section)
+ }
+ }
+ } finally { $events.Dispose() }
+}
diff --git a/corpus/conflict-page/native-restore/scripts/text.ps1 b/corpus/conflict-page/native-restore/scripts/text.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..75e9e71207b599d16eaaf68824ca30831860610a
--- /dev/null
+++ b/corpus/conflict-page/native-restore/scripts/text.ps1
@@ -0,0 +1,13 @@
+function Get-ParagraphText([string]$html) {
+ $plain = [regex]::Replace($html, '<[^>]*>', '')
+ $plain = [regex]::Replace($plain, '(?:x([0-9a-fA-F]+)|([0-9]+));', {
+ param($match)
+ $value = if ($match.Groups[1].Success) {
+ [Convert]::ToInt32($match.Groups[1].Value, 16)
+ } else { [int]$match.Groups[2].Value }
+ # The Win7 framework leaves supplementary numeric entities undecoded.
+ if ($value -gt 0xffff -and $value -le 0x10ffff) { [char]::ConvertFromUtf32($value) }
+ else { $match.Value }
+ })
+ [Net.WebUtility]::HtmlDecode($plain)
+}
diff --git a/corpus/conflict-page/native-restore/server.jsonl b/corpus/conflict-page/native-restore/server.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..d5df993564a30ddaea1b427699648ff9b1a1417d
--- /dev/null
+++ b/corpus/conflict-page/native-restore/server.jsonl
@@ -0,0 +1,2 @@
+{"command": "mkdir /srv/agent/m6-conflict", "exit": 0, "stdout": "", "stderr": ""}
+{"command": "chmod u+w /srv/agent/m6-conflict/*", "exit": 0, "stdout": "", "stderr": ""}
diff --git a/corpus/conflict-page/native-three/commands.jsonl b/corpus/conflict-page/native-three/commands.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..bd094dd5fa92bff9cd29a895346604fd12f198af
--- /dev/null
+++ b/corpus/conflict-page/native-three/commands.jsonl
@@ -0,0 +1,6 @@
+{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-C6F6C14F -ExpectedPages -1 -Pdf -KeepOpen", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\read -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Rename-Item C:\\one-tests\\runs\\capture\\read before-read\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-C6F6C14F -UseCurrentCache -ExpectedPages -1 -Pdf", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
diff --git a/corpus/conflict-page/native-three/conflicts.png b/corpus/conflict-page/native-three/conflicts.png
new file mode 100644
index 0000000000000000000000000000000000000000..05b2a356d3b39f7a6660730662dc6aabbbb107f0
Binary files /dev/null and b/corpus/conflict-page/native-three/conflicts.png differ
diff --git a/corpus/conflict-page/native-three/expand.png b/corpus/conflict-page/native-three/expand.png
new file mode 100644
index 0000000000000000000000000000000000000000..2996fb0cf1da7b204973c3ba4e08a9c4c6f4df53
Binary files /dev/null and b/corpus/conflict-page/native-three/expand.png differ
diff --git a/corpus/conflict-page/native-three/machine.json b/corpus/conflict-page/native-three/machine.json
new file mode 100644
index 0000000000000000000000000000000000000000..45055b0c57355c25e302d24c46f2f498763ea030
--- /dev/null
+++ b/corpus/conflict-page/native-three/machine.json
@@ -0,0 +1 @@
+{"name": "m6-c6f6c14f", "hostname": "ONE-M6-C6F6C14F"}
diff --git a/corpus/conflict-page/native-three/notebook/Open Notebook.onetoc2 b/corpus/conflict-page/native-three/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..c4a2b9c6013a262a0c6d4d71d2a50d67c98131e8
Binary files /dev/null and b/corpus/conflict-page/native-three/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native-three/notebook/synthetic.one b/corpus/conflict-page/native-three/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..1db8bd91ac5b4eb9c3623ca701e9773b66d6010e
Binary files /dev/null and b/corpus/conflict-page/native-three/notebook/synthetic.one differ
diff --git a/corpus/conflict-page/native-three/run.json b/corpus/conflict-page/native-three/run.json
new file mode 100644
index 0000000000000000000000000000000000000000..ee5c95acf2fb14729ddaece26555e5eb5face4bb
--- /dev/null
+++ b/corpus/conflict-page/native-three/run.json
@@ -0,0 +1,17 @@
+{
+ "notebook": "/Users/clo/dev/one/evidence/m8/conflict-abrupt-01/conflict-closed/notebook",
+ "expected_pages": -1,
+ "author": null,
+ "author_timeout_seconds": 600,
+ "inspect": true,
+ "base": {
+ "file": "win7-office-base.qcow2",
+ "format": "qcow2",
+ "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346",
+ "virtual_size": 68719476736
+ },
+ "scripts": {
+ "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331",
+ "read.ps1": "086789e467779add0fc071ad8c524c2b02d3d0d609aea4a41f3128cadba371e8"
+ }
+}
diff --git a/corpus/conflict-page/native-three/source.json b/corpus/conflict-page/native-three/source.json
new file mode 100644
index 0000000000000000000000000000000000000000..e6bfda307ffe01e66ae1cd42a2465e9173d9ca69
--- /dev/null
+++ b/corpus/conflict-page/native-three/source.json
@@ -0,0 +1,14 @@
+[
+ {
+ "path": "Open Notebook.onetoc2",
+ "bytes": 3780,
+ "sha256": "f21c6d4eefc25ce78aee0420fdee0b409da52fdf3119b66302ac6ca4690c55ae",
+ "mtime_ns": 1788733155312083279
+ },
+ {
+ "path": "synthetic.one",
+ "bytes": 46416,
+ "sha256": "7ac7a42932848380b90aa478800ab141fce4fb591aae2bf1e167f1e516e5edc0",
+ "mtime_ns": 1788733155311971612
+ }
+]
diff --git a/corpus/conflict-page/native-three/teardown.json b/corpus/conflict-page/native-three/teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/conflict-page/native-three/teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/conflict-page/native/a/a-conflict.json b/corpus/conflict-page/native/a/a-conflict.json
new file mode 100644
index 0000000000000000000000000000000000000000..2fd05a4a7938577ce0347213e7f3ed53dab2c0a4
--- /dev/null
+++ b/corpus/conflict-page/native/a/a-conflict.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Client A conflicting edit. - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native/a/a-conflict.png b/corpus/conflict-page/native/a/a-conflict.png
new file mode 100644
index 0000000000000000000000000000000000000000..779f4bd296c5f9cf5aa32b4c8b81d4d7704a8095
Binary files /dev/null and b/corpus/conflict-page/native/a/a-conflict.png differ
diff --git a/corpus/conflict-page/native/a/a-main.json b/corpus/conflict-page/native/a/a-main.json
new file mode 100644
index 0000000000000000000000000000000000000000..668f002c22e396d7530675ec44f659f85b6f6a86
--- /dev/null
+++ b/corpus/conflict-page/native/a/a-main.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Client B conflicting edit. - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native/a/a-main.png b/corpus/conflict-page/native/a/a-main.png
new file mode 100644
index 0000000000000000000000000000000000000000..160e31b2ff70f2b56ede40769422db0f0e81083d
Binary files /dev/null and b/corpus/conflict-page/native/a/a-main.png differ
diff --git a/corpus/conflict-page/native/a/a-menu.json b/corpus/conflict-page/native/a/a-menu.json
new file mode 100644
index 0000000000000000000000000000000000000000..f43854a7a42d5bb99c0b443513bc0c0993c2716c
--- /dev/null
+++ b/corpus/conflict-page/native/a/a-menu.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "",
+ "class": "Net UI Tool Window",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native/a/a-menu.png b/corpus/conflict-page/native/a/a-menu.png
new file mode 100644
index 0000000000000000000000000000000000000000..3dff58a0556eab58682e8cb6c2a28e3ab6aff07d
Binary files /dev/null and b/corpus/conflict-page/native/a/a-menu.png differ
diff --git a/corpus/conflict-page/native/a/a-shown.json b/corpus/conflict-page/native/a/a-shown.json
new file mode 100644
index 0000000000000000000000000000000000000000..2fd05a4a7938577ce0347213e7f3ed53dab2c0a4
--- /dev/null
+++ b/corpus/conflict-page/native/a/a-shown.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Client A conflicting edit. - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native/a/a-shown.png b/corpus/conflict-page/native/a/a-shown.png
new file mode 100644
index 0000000000000000000000000000000000000000..324161780c3b6bc1d4a76ead6a5e5c567c003dda
Binary files /dev/null and b/corpus/conflict-page/native/a/a-shown.png differ
diff --git a/corpus/conflict-page/native/a/commands.jsonl b/corpus/conflict-page/native/a/commands.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..71a20b5a21782c6d65840a920e949089a97e2520
--- /dev/null
+++ b/corpus/conflict-page/native/a/commands.jsonl
@@ -0,0 +1,124 @@
+{"command": "mkdir C:\\one-tests\\runs\\capture", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -ExecutionPolicy Bypass -File C:\\one-tests\\network.ps1 -LabMac 52:54:02:a3:5c:e3", "exit": 0, "stdout": "{\"renewalResult\":0,\"releaseResult\":0,\"mac\":\"52:54:02:a3:5c:e3\",\"address\":\"192.168.77.180\"}\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0001.tmp C:\\one-tests\\runs\\capture\\inbox\\0001.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0002.tmp C:\\one-tests\\runs\\capture\\inbox\\0002.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0003.tmp C:\\one-tests\\runs\\capture\\inbox\\0003.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0004.tmp C:\\one-tests\\runs\\capture\\inbox\\0004.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0005.tmp C:\\one-tests\\runs\\capture\\inbox\\0005.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0006.tmp C:\\one-tests\\runs\\capture\\inbox\\0006.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0007.tmp C:\\one-tests\\runs\\capture\\inbox\\0007.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0008.tmp C:\\one-tests\\runs\\capture\\inbox\\0008.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0009.tmp C:\\one-tests\\runs\\capture\\inbox\\0009.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0010.tmp C:\\one-tests\\runs\\capture\\inbox\\0010.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0011.tmp C:\\one-tests\\runs\\capture\\inbox\\0011.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0012.tmp C:\\one-tests\\runs\\capture\\inbox\\0012.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0013.tmp C:\\one-tests\\runs\\capture\\inbox\\0013.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0014.tmp C:\\one-tests\\runs\\capture\\inbox\\0014.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0015.tmp C:\\one-tests\\runs\\capture\\inbox\\0015.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0016.tmp C:\\one-tests\\runs\\capture\\inbox\\0016.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0017.tmp C:\\one-tests\\runs\\capture\\inbox\\0017.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0018.tmp C:\\one-tests\\runs\\capture\\inbox\\0018.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0019.tmp C:\\one-tests\\runs\\capture\\inbox\\0019.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0020.tmp C:\\one-tests\\runs\\capture\\inbox\\0020.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0021.tmp C:\\one-tests\\runs\\capture\\inbox\\0021.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0022.tmp C:\\one-tests\\runs\\capture\\inbox\\0022.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0023.tmp C:\\one-tests\\runs\\capture\\inbox\\0023.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0024.tmp C:\\one-tests\\runs\\capture\\inbox\\0024.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0025.tmp C:\\one-tests\\runs\\capture\\inbox\\0025.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0026.tmp C:\\one-tests\\runs\\capture\\inbox\\0026.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0027.tmp C:\\one-tests\\runs\\capture\\inbox\\0027.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0028.tmp C:\\one-tests\\runs\\capture\\inbox\\0028.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0029.tmp C:\\one-tests\\runs\\capture\\inbox\\0029.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0030.tmp C:\\one-tests\\runs\\capture\\inbox\\0030.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0031.tmp C:\\one-tests\\runs\\capture\\inbox\\0031.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0032.tmp C:\\one-tests\\runs\\capture\\inbox\\0032.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0033.tmp C:\\one-tests\\runs\\capture\\inbox\\0033.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0034.tmp C:\\one-tests\\runs\\capture\\inbox\\0034.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0035.tmp C:\\one-tests\\runs\\capture\\inbox\\0035.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0036.tmp C:\\one-tests\\runs\\capture\\inbox\\0036.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0037.tmp C:\\one-tests\\runs\\capture\\inbox\\0037.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0038.tmp C:\\one-tests\\runs\\capture\\inbox\\0038.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0039.tmp C:\\one-tests\\runs\\capture\\inbox\\0039.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0040.tmp C:\\one-tests\\runs\\capture\\inbox\\0040.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0041.tmp C:\\one-tests\\runs\\capture\\inbox\\0041.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0042.tmp C:\\one-tests\\runs\\capture\\inbox\\0042.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0043.tmp C:\\one-tests\\runs\\capture\\inbox\\0043.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0044.tmp C:\\one-tests\\runs\\capture\\inbox\\0044.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0045.tmp C:\\one-tests\\runs\\capture\\inbox\\0045.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0046.tmp C:\\one-tests\\runs\\capture\\inbox\\0046.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0047.tmp C:\\one-tests\\runs\\capture\\inbox\\0047.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0048.tmp C:\\one-tests\\runs\\capture\\inbox\\0048.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0049.tmp C:\\one-tests\\runs\\capture\\inbox\\0049.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0050.tmp C:\\one-tests\\runs\\capture\\inbox\\0050.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0051.tmp C:\\one-tests\\runs\\capture\\inbox\\0051.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0052.tmp C:\\one-tests\\runs\\capture\\inbox\\0052.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0053.tmp C:\\one-tests\\runs\\capture\\inbox\\0053.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0054.tmp C:\\one-tests\\runs\\capture\\inbox\\0054.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0055.tmp C:\\one-tests\\runs\\capture\\inbox\\0055.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0056.tmp C:\\one-tests\\runs\\capture\\inbox\\0056.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0057.tmp C:\\one-tests\\runs\\capture\\inbox\\0057.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0058.tmp C:\\one-tests\\runs\\capture\\inbox\\0058.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0059.tmp C:\\one-tests\\runs\\capture\\inbox\\0059.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0060.tmp C:\\one-tests\\runs\\capture\\inbox\\0060.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0061.tmp C:\\one-tests\\runs\\capture\\inbox\\0061.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0062.tmp C:\\one-tests\\runs\\capture\\inbox\\0062.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0063.tmp C:\\one-tests\\runs\\capture\\inbox\\0063.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0064.tmp C:\\one-tests\\runs\\capture\\inbox\\0064.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0065.tmp C:\\one-tests\\runs\\capture\\inbox\\0065.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0066.tmp C:\\one-tests\\runs\\capture\\inbox\\0066.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0067.tmp C:\\one-tests\\runs\\capture\\inbox\\0067.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0068.tmp C:\\one-tests\\runs\\capture\\inbox\\0068.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0069.tmp C:\\one-tests\\runs\\capture\\inbox\\0069.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0070.tmp C:\\one-tests\\runs\\capture\\inbox\\0070.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0071.tmp C:\\one-tests\\runs\\capture\\inbox\\0071.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0072.tmp C:\\one-tests\\runs\\capture\\inbox\\0072.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0073.tmp C:\\one-tests\\runs\\capture\\inbox\\0073.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0074.tmp C:\\one-tests\\runs\\capture\\inbox\\0074.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0075.tmp C:\\one-tests\\runs\\capture\\inbox\\0075.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0076.tmp C:\\one-tests\\runs\\capture\\inbox\\0076.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0077.tmp C:\\one-tests\\runs\\capture\\inbox\\0077.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0078.tmp C:\\one-tests\\runs\\capture\\inbox\\0078.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0079.tmp C:\\one-tests\\runs\\capture\\inbox\\0079.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0080.tmp C:\\one-tests\\runs\\capture\\inbox\\0080.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0081.tmp C:\\one-tests\\runs\\capture\\inbox\\0081.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0082.tmp C:\\one-tests\\runs\\capture\\inbox\\0082.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0083.tmp C:\\one-tests\\runs\\capture\\inbox\\0083.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0084.tmp C:\\one-tests\\runs\\capture\\inbox\\0084.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0085.tmp C:\\one-tests\\runs\\capture\\inbox\\0085.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0086.tmp C:\\one-tests\\runs\\capture\\inbox\\0086.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0087.tmp C:\\one-tests\\runs\\capture\\inbox\\0087.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0088.tmp C:\\one-tests\\runs\\capture\\inbox\\0088.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0089.tmp C:\\one-tests\\runs\\capture\\inbox\\0089.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0090.tmp C:\\one-tests\\runs\\capture\\inbox\\0090.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0091.tmp C:\\one-tests\\runs\\capture\\inbox\\0091.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0092.tmp C:\\one-tests\\runs\\capture\\inbox\\0092.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0093.tmp C:\\one-tests\\runs\\capture\\inbox\\0093.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0094.tmp C:\\one-tests\\runs\\capture\\inbox\\0094.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0095.tmp C:\\one-tests\\runs\\capture\\inbox\\0095.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0096.tmp C:\\one-tests\\runs\\capture\\inbox\\0096.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0097.tmp C:\\one-tests\\runs\\capture\\inbox\\0097.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0098.tmp C:\\one-tests\\runs\\capture\\inbox\\0098.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0099.tmp C:\\one-tests\\runs\\capture\\inbox\\0099.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0100.tmp C:\\one-tests\\runs\\capture\\inbox\\0100.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0101.tmp C:\\one-tests\\runs\\capture\\inbox\\0101.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0102.tmp C:\\one-tests\\runs\\capture\\inbox\\0102.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0103.tmp C:\\one-tests\\runs\\capture\\inbox\\0103.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0104.tmp C:\\one-tests\\runs\\capture\\inbox\\0104.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0105.tmp C:\\one-tests\\runs\\capture\\inbox\\0105.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0106.tmp C:\\one-tests\\runs\\capture\\inbox\\0106.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0107.tmp C:\\one-tests\\runs\\capture\\inbox\\0107.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0108.tmp C:\\one-tests\\runs\\capture\\inbox\\0108.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0109.tmp C:\\one-tests\\runs\\capture\\inbox\\0109.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0110.tmp C:\\one-tests\\runs\\capture\\inbox\\0110.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0111.tmp C:\\one-tests\\runs\\capture\\inbox\\0111.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0112.tmp C:\\one-tests\\runs\\capture\\inbox\\0112.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0113.tmp C:\\one-tests\\runs\\capture\\inbox\\0113.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0114.tmp C:\\one-tests\\runs\\capture\\inbox\\0114.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0115.tmp C:\\one-tests\\runs\\capture\\inbox\\0115.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0116.tmp C:\\one-tests\\runs\\capture\\inbox\\0116.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0117.tmp C:\\one-tests\\runs\\capture\\inbox\\0117.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0118.tmp C:\\one-tests\\runs\\capture\\inbox\\0118.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0119.tmp C:\\one-tests\\runs\\capture\\inbox\\0119.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0120.tmp C:\\one-tests\\runs\\capture\\inbox\\0120.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0121.tmp C:\\one-tests\\runs\\capture\\inbox\\0121.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "ZipArchiveHelper : The process cannot access the file \r\n'C:\\one-tests\\runs\\capture\\controller.log' because it is being used by another \r\nprocess.\r\nAt C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Arch\r\nive\\Microsoft.PowerShell.Archive.psm1:631 char:30\r\n+ ... sArchived = ZipArchiveHelper $sourceFilePaths $destinationPath $compr ...\r\n+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n + CategoryInfo : PermissionDenied: (C:\\one-tests\\runs\\capture\\con \r\n troller.log:String) [Write-Error], IOException\r\n + FullyQualifiedErrorId : CompressArchiveUnauthorizedAccessError,ZipArchiv \r\n eHelper\r\n \r\n", "error": null}
diff --git a/corpus/conflict-page/native/a/machine.json b/corpus/conflict-page/native/a/machine.json
new file mode 100644
index 0000000000000000000000000000000000000000..0f864b281e1b3fddd8d8f15005c6783a803b8317
--- /dev/null
+++ b/corpus/conflict-page/native/a/machine.json
@@ -0,0 +1 @@
+{"name": "m6-8a93780a", "hostname": "ONE-M6-8A93780A"}
diff --git a/corpus/conflict-page/native/a/teardown.json b/corpus/conflict-page/native/a/teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/conflict-page/native/a/teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/conflict-page/native/b-published/notebook/Open Notebook.onetoc2 b/corpus/conflict-page/native/b-published/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..c7beb44bf509f935090fe55e7549e4b50e0ae0e9
Binary files /dev/null and b/corpus/conflict-page/native/b-published/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native/b-published/notebook/synthetic.one b/corpus/conflict-page/native/b-published/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..7a6d1f09e1619db43158817a0fd6f9fab64b486f
Binary files /dev/null and b/corpus/conflict-page/native/b-published/notebook/synthetic.one differ
diff --git a/corpus/conflict-page/native/b/b-main.json b/corpus/conflict-page/native/b/b-main.json
new file mode 100644
index 0000000000000000000000000000000000000000..668f002c22e396d7530675ec44f659f85b6f6a86
--- /dev/null
+++ b/corpus/conflict-page/native/b/b-main.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Client B conflicting edit. - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native/b/b-main.png b/corpus/conflict-page/native/b/b-main.png
new file mode 100644
index 0000000000000000000000000000000000000000..6fc48cfee0af3e8e1a5eb28b9554a742fd124c56
Binary files /dev/null and b/corpus/conflict-page/native/b/b-main.png differ
diff --git a/corpus/conflict-page/native/b/b-shown.json b/corpus/conflict-page/native/b/b-shown.json
new file mode 100644
index 0000000000000000000000000000000000000000..2fd05a4a7938577ce0347213e7f3ed53dab2c0a4
--- /dev/null
+++ b/corpus/conflict-page/native/b/b-shown.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Client A conflicting edit. - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native/b/b-shown.png b/corpus/conflict-page/native/b/b-shown.png
new file mode 100644
index 0000000000000000000000000000000000000000..df913522858ab019e71ed65ae817e86cb56570ad
Binary files /dev/null and b/corpus/conflict-page/native/b/b-shown.png differ
diff --git a/corpus/conflict-page/native/b/commands.jsonl b/corpus/conflict-page/native/b/commands.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..4281870e093fd1993182f6602b819b2e68c183ff
--- /dev/null
+++ b/corpus/conflict-page/native/b/commands.jsonl
@@ -0,0 +1,21 @@
+{"command": "mkdir C:\\one-tests\\runs\\capture", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -ExecutionPolicy Bypass -File C:\\one-tests\\network.ps1 -LabMac 52:54:02:e7:d0:a7", "exit": 0, "stdout": "{\"renewalResult\":0,\"releaseResult\":0,\"mac\":\"52:54:02:e7:d0:a7\",\"address\":\"192.168.77.42\"}\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0001.tmp C:\\one-tests\\runs\\capture\\inbox\\0001.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0002.tmp C:\\one-tests\\runs\\capture\\inbox\\0002.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0003.tmp C:\\one-tests\\runs\\capture\\inbox\\0003.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0004.tmp C:\\one-tests\\runs\\capture\\inbox\\0004.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0005.tmp C:\\one-tests\\runs\\capture\\inbox\\0005.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0006.tmp C:\\one-tests\\runs\\capture\\inbox\\0006.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0007.tmp C:\\one-tests\\runs\\capture\\inbox\\0007.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0008.tmp C:\\one-tests\\runs\\capture\\inbox\\0008.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0009.tmp C:\\one-tests\\runs\\capture\\inbox\\0009.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0010.tmp C:\\one-tests\\runs\\capture\\inbox\\0010.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0011.tmp C:\\one-tests\\runs\\capture\\inbox\\0011.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0012.tmp C:\\one-tests\\runs\\capture\\inbox\\0012.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0013.tmp C:\\one-tests\\runs\\capture\\inbox\\0013.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0014.tmp C:\\one-tests\\runs\\capture\\inbox\\0014.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0015.tmp C:\\one-tests\\runs\\capture\\inbox\\0015.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0016.tmp C:\\one-tests\\runs\\capture\\inbox\\0016.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0017.tmp C:\\one-tests\\runs\\capture\\inbox\\0017.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "move C:\\one-tests\\runs\\capture\\inbox\\0018.tmp C:\\one-tests\\runs\\capture\\inbox\\0018.json", "exit": 0, "stdout": " 1 file(s) moved.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "ZipArchiveHelper : The process cannot access the file \r\n'C:\\one-tests\\runs\\capture\\controller.log' because it is being used by another \r\nprocess.\r\nAt C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Arch\r\nive\\Microsoft.PowerShell.Archive.psm1:631 char:30\r\n+ ... sArchived = ZipArchiveHelper $sourceFilePaths $destinationPath $compr ...\r\n+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n + CategoryInfo : PermissionDenied: (C:\\one-tests\\runs\\capture\\con \r\n troller.log:String) [Write-Error], IOException\r\n + FullyQualifiedErrorId : CompressArchiveUnauthorizedAccessError,ZipArchiv \r\n eHelper\r\n \r\n", "error": null}
diff --git a/corpus/conflict-page/native/b/machine.json b/corpus/conflict-page/native/b/machine.json
new file mode 100644
index 0000000000000000000000000000000000000000..844bc51f2eafc6db25c8e50620b8e329eb173289
--- /dev/null
+++ b/corpus/conflict-page/native/b/machine.json
@@ -0,0 +1 @@
+{"name": "m6-d63654a2", "hostname": "ONE-M6-D63654A2"}
diff --git a/corpus/conflict-page/native/b/teardown.json b/corpus/conflict-page/native/b/teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/conflict-page/native/b/teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/conflict-page/native/clients.json b/corpus/conflict-page/native/clients.json
new file mode 100644
index 0000000000000000000000000000000000000000..3d33f0e3408a7aaa0ca6279a8db3983cbb55d9f5
--- /dev/null
+++ b/corpus/conflict-page/native/clients.json
@@ -0,0 +1,4 @@
+{
+ "a": "m6-8a93780a",
+ "b": "m6-d63654a2"
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native/conflict.json b/corpus/conflict-page/native/conflict.json
new file mode 100644
index 0000000000000000000000000000000000000000..8a1442378c5f28dc3b321c2f15c3f17c5c7e68cb
--- /dev/null
+++ b/corpus/conflict-page/native/conflict.json
@@ -0,0 +1,15 @@
+{
+ "{64183EF4-4E19-0CBE-3D3B-88FF17D58B1C},1": [
+ "Client A disjoint edit.",
+ "Client B conflicting edit.",
+ "Client B disjoint cell.",
+ "Right cell"
+ ],
+ "{9C2E0DE1-2E23-094F-3F9F-A919195FC40D},1": [
+ "",
+ "Client A conflicting edit.",
+ "Client A disjoint edit.",
+ "Left cell",
+ "Right cell"
+ ]
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native/conflict/notebook/Open Notebook.onetoc2 b/corpus/conflict-page/native/conflict/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..c7beb44bf509f935090fe55e7549e4b50e0ae0e9
Binary files /dev/null and b/corpus/conflict-page/native/conflict/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native/conflict/notebook/synthetic.one b/corpus/conflict-page/native/conflict/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..8d01b4f2cf195178b1f688fe06874ed37eeab86c
Binary files /dev/null and b/corpus/conflict-page/native/conflict/notebook/synthetic.one differ
diff --git a/corpus/conflict-page/native/initial/notebook/Open Notebook.onetoc2 b/corpus/conflict-page/native/initial/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..c7beb44bf509f935090fe55e7549e4b50e0ae0e9
Binary files /dev/null and b/corpus/conflict-page/native/initial/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/conflict-page/native/initial/notebook/synthetic.one b/corpus/conflict-page/native/initial/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..e46d7a24cead34b8521bc6e1d117d68a9ff70f09
Binary files /dev/null and b/corpus/conflict-page/native/initial/notebook/synthetic.one differ
diff --git a/corpus/conflict-page/native/linux-teardown.json b/corpus/conflict-page/native/linux-teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/conflict-page/native/linux-teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/conflict-page/native/linux.json b/corpus/conflict-page/native/linux.json
new file mode 100644
index 0000000000000000000000000000000000000000..51c769af931eb724e1a4f34d25afa486513f27ae
--- /dev/null
+++ b/corpus/conflict-page/native/linux.json
@@ -0,0 +1,11 @@
+{
+ "cpus": 2,
+ "disk_gb": 16,
+ "hostname": "one-conflict",
+ "lab_address": "192.168.77.1",
+ "lab_mac": "52:54:04:69:39:00",
+ "memory_mb": 2048,
+ "samba_port": 14450,
+ "ssh_port": 22000,
+ "wan_mac": "52:54:03:76:a2:ae"
+}
\ No newline at end of file
diff --git a/corpus/conflict-page/native/native_conflict.py b/corpus/conflict-page/native/native_conflict.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba5057280e4efb096969c86293a53973fdb8a19d
--- /dev/null
+++ b/corpus/conflict-page/native/native_conflict.py
@@ -0,0 +1,261 @@
+#!/usr/bin/env python3
+"""OneNote 2010 conflict pages: how they arise, what they store and how OneNote shows them.
+
+`sync LINUX OUTPUT` runs two disposable OneNote clients on one Samba notebook: client A edits
+two paragraphs offline while client B edits one of them and a table cell online; A then
+reconnects, which makes OneNote write a conflict page. The server file is kept before and
+after, A and B are screenshotted with the information bar, the page list shown, the conflict
+page open and its menu open, and A then deletes the conflict page through that menu.
+
+`cold NOTEBOOK OUTPUT [--delete]` cold-opens a notebook holding a conflict page in a fresh
+clone and screenshots the same views; `--delete` deletes the conflict page through the menu
+and captures the notebook OneNote leaves.
+"""
+import argparse
+from contextlib import ExitStack
+from concurrent.futures import ThreadPoolExecutor
+import base64
+import json
+from pathlib import Path
+import shutil
+import subprocess
+import sys
+import time
+import xml.etree.ElementTree as ET
+
+sys.path.insert(0, str(Path(__file__).resolve().parent))
+sys.path.insert(0, str(Path(__file__).resolve().parent / 'w7'))
+import native_runner as runner
+from native_runner import ROOT, clone, command, windows, vm
+import linux_vm
+
+FIXTURE = ROOT / 'corpus/native-ink/cold-ui-ink/notebook'
+SHARE = 'm6-conflict'
+ORIGINAL = 'Fictitious: café, 東京, مرحبا'
+OTHER_OUTLINE = 'Fictitious positioned outline.'
+CELL = 'Left cell'
+
+# Coordinates on OneNote 2010 maximized at 800x600, from the stage-5 and m8 captures: the
+# information bar's text, and the first conflict page listed under its page.
+BAR = (332, 100)
+FIRST_CONFLICT = (725, 142)
+
+
+def shot(name, output, label, script):
+ """Runs an AutoHotkey step on the clone, keeping its screenshot as `label.png`."""
+ (output / f'{label}.ahk').write_text(script)
+ result = windows.do_exec(script, target=name, timeout_ms=120000, shot_delay_ms=1500)
+ screenshot = result.pop('png_b64', None)
+ if screenshot:
+ (output / f'{label}.png').write_bytes(base64.b64decode(screenshot))
+ (output / f'{label}.json').write_text(json.dumps(result, indent=2))
+ if result.get('error') or result.get('exit') != 0:
+ raise RuntimeError(f'{label}: {result}')
+ return result
+
+
+def click(x, y):
+ return ('WinActivate("ahk_exe ONENOTE.EXE")\nCoordMode("Mouse", "Screen")\n'
+ f'Click({x}, {y})\nSleep(1200)\n')
+
+
+# Reports any dialog OneNote raised, so a confirmation shows in the step's record.
+DIALOG = ('if WinExist("ahk_class #32770 ahk_exe ONENOTE.EXE") {\n'
+ ' FileAppend("dialog: " WinGetTitle() " | " WinGetText() "`n", "*", "UTF-8")\n'
+ '}\n')
+
+
+def conflict_views(name, output, page, prefix, delete=False):
+ """Screenshots a page with a conflict page: the bar, the list shown, the conflict page and
+ its menu; `delete` then chooses Delete Conflict Page."""
+ shot(name, output, f'{prefix}-main', runner.navigation_script(page) + 'Sleep(2000)\n')
+ shot(name, output, f'{prefix}-shown', click(*BAR))
+ shot(name, output, f'{prefix}-conflict', click(*FIRST_CONFLICT))
+ shot(name, output, f'{prefix}-menu', click(*BAR))
+ if delete:
+ shot(name, output, f'{prefix}-delete', 'WinActivate("ahk_exe ONENOTE.EXE")\nSend("d")\nSleep(1500)\n' + DIALOG)
+ else:
+ shot(name, output, f'{prefix}-closed', 'WinActivate("ahk_exe ONENOTE.EXE")\nSend("{Esc}")\nSleep(800)\n')
+
+
+def cold(notebook, output, delete):
+ def interaction(name, output):
+ page = ET.parse(sorted((output / 'read').glob('page-*.xml'))[0]).getroot().attrib['ID']
+ conflict_views(name, output, page, 'cold', delete)
+ if delete:
+ # OneNote writes the deletion in the background; give it time before the re-read.
+ time.sleep(20)
+ runner.capture(notebook, output, collect_notebook=True, interaction=interaction)
+
+
+def sync(server, output):
+ from native_collaboration import reachable_page_text
+ from document_model import EXPORTER
+
+ if linux_vm.instance_path(server).exists():
+ raise ValueError('Choose a new Linux VM name; existing machines are not owned by this run.')
+ output = output.resolve()
+ output.mkdir(parents=True, exist_ok=False)
+ scripts = output / 'scripts'
+ scripts.mkdir()
+ for name in ('cold.ps1', 'collaborate.ps1', 'network.ps1', 'text.ps1', 'stress.ps1'):
+ shutil.copyfile(ROOT / 'tools/native' / name, scripts / name)
+ shutil.copyfile(Path(__file__), output / 'native_conflict.py')
+
+ def ssh(text, timeout=90):
+ result = linux_vm.run_ssh(server, text, timeout=timeout)
+ with (output / 'server.jsonl').open('a') as log:
+ log.write(json.dumps({'command': text, 'exit': result.returncode, 'stdout': result.stdout, 'stderr': result.stderr}) + '\n')
+ result.check_returncode()
+ return result.stdout
+
+ def server_state(label):
+ """Copies the server's notebook until it parses, keeping it as `label/notebook`."""
+ deadline = time.monotonic() + 120
+ while True:
+ target = output / label
+ shutil.rmtree(target, ignore_errors=True)
+ (target / 'notebook').mkdir(parents=True)
+ for name in ('synthetic.one', 'Open Notebook.onetoc2'):
+ with (target / 'notebook' / name).open('wb') as stream:
+ subprocess.run(linux_vm.ssh_argv(server, f"cat '/srv/agent/{SHARE}/{name}'"), stdout=stream, check=True, timeout=60)
+ result = subprocess.run([EXPORTER, target / 'notebook/synthetic.one', target / 'model'], capture_output=True, text=True)
+ if result.returncode == 0:
+ model = json.loads((target / 'model/document.json').read_text())
+ return reachable_page_text(model)
+ if time.monotonic() > deadline:
+ raise RuntimeError(f'{label}: {result.stderr}')
+ time.sleep(1)
+
+ linux_vm.create_instance(server)
+ try:
+ linux_vm.launch(server)
+ linux_vm.wait_instance(server, 600)
+ (output / 'linux.json').write_text(json.dumps(linux_vm.load_instance(server), indent=2))
+ ssh(f'mkdir /srv/agent/{SHARE}')
+ for name in ('synthetic.one', 'Open Notebook.onetoc2'):
+ with (FIXTURE / name).open('rb') as stream:
+ subprocess.run(linux_vm.ssh_argv(server, f"cat > '/srv/agent/{SHARE}/{name}'"), stdin=stream, check=True)
+ ssh(f'chmod u+w /srv/agent/{SHARE}/*')
+ with ExitStack() as stack:
+ labels = ['a', 'b']
+ for label in labels:
+ (output / label).mkdir()
+
+ def start(label):
+ manager = clone(output / label)
+ name = manager.__enter__()
+ return label, name, manager
+ with ThreadPoolExecutor(max_workers=2) as pool:
+ started = list(pool.map(start, labels))
+ for _, _, manager in started:
+ stack.push(manager)
+ clients = {}
+ for label, name, _ in started:
+ folder = output / label
+ for local in scripts.iterdir():
+ remote = 'cold-current.ps1' if local.name == 'cold.ps1' else local.name
+ result = windows.do_put(local, 'C:\\one-tests\\' + remote, name)
+ if result.get('error'): raise RuntimeError(result['error'])
+ command(name, 'mkdir C:\\one-tests\\runs\\capture', folder)
+ command(name, 'powershell -NoProfile -ExecutionPolicy Bypass -File C:\\one-tests\\network.ps1 -LabMac ' + vm.lab_mac(name), folder)
+ result = windows.do_spawn('cmd /c powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\collaborate.ps1 -Root C:\\one-tests\\runs\\capture -SharedPath \\\\192.168.77.1\\agent\\' + SHARE + ' -CloneHost ONE-' + name.upper() + ' > C:\\one-tests\\runs\\capture\\controller.log 2>&1', name)
+ if result.get('error'): raise RuntimeError(result['error'])
+ deadline = time.monotonic() + 180
+ while windows.do_cmd('if exist C:\\one-tests\\runs\\capture\\ready (echo ready)', target=name).get('stdout', '').find('ready') < 0:
+ if time.monotonic() > deadline: raise RuntimeError('The collaboration controller did not open the notebook')
+ time.sleep(1)
+ clients[label] = {'name': name, 'folder': folder, 'sequence': 0}
+ (output / 'clients.json').write_text(json.dumps({label: client['name'] for label, client in clients.items()}, indent=2))
+
+ def action(client, kind, **parameters):
+ client['sequence'] += 1
+ sequence = client['sequence']
+ local = client['folder'] / f'command-{sequence:04}.json'
+ local.write_text(json.dumps({'action': kind, **parameters}, ensure_ascii=False))
+ inbox = f'C:\\one-tests\\runs\\capture\\inbox\\{sequence:04}'
+ result = windows.do_put(local, inbox + '.tmp', client['name'])
+ if result.get('error'): raise RuntimeError(result['error'])
+ command(client['name'], f'move {inbox}.tmp {inbox}.json', client['folder'])
+ remote = f'C:\\one-tests\\runs\\capture\\outbox\\{sequence}'
+ deadline = time.monotonic() + 600
+ while True:
+ result = windows.do_cmd(f'if exist {remote}\\error (type {remote}\\error & exit /b 1) else (if exist {remote}\\done (echo complete))', target=client['name'])
+ if result.get('exit') != 0: raise RuntimeError(str(result))
+ if 'complete' in result.get('stdout', ''): break
+ if time.monotonic() > deadline: raise TimeoutError(f'{kind} did not complete')
+ time.sleep(.5)
+ if kind != 'snapshot':
+ return None
+ hierarchy = client['folder'] / f'hierarchy-{sequence:04}.xml'
+ windows.do_get(remote + '\\hierarchy.xml', hierarchy, client['name'])
+ pages = [node for node in ET.fromstring(hierarchy.read_text(encoding='utf-8-sig').strip()).iter() if node.tag.endswith('}Page')]
+ for i in range(len(pages)):
+ windows.do_get(remote + f'\\page-{i}.xml', client['folder'] / f'snapshot-{sequence:04}-{i}.xml', client['name'])
+ return pages
+
+ def settle(client, check, label):
+ deadline = time.monotonic() + 300
+ while True:
+ action(client, 'sync')
+ state = server_state(label)
+ if check(state): return state
+ if time.monotonic() > deadline: raise TimeoutError(f'{label}: {state}')
+ time.sleep(2)
+
+ a, b = clients['a'], clients['b']
+ texts = lambda state: {text for values in state.values() for text in values}
+ server_state('initial')
+ vm.qmp(a['name'], 'set_link', {'name': 'lab', 'up': False})
+ try:
+ action(a, 'edit', expected=ORIGINAL, text='Client A conflicting edit.')
+ action(a, 'edit', expected=OTHER_OUTLINE, text='Client A disjoint edit.')
+ action(a, 'snapshot')
+ action(b, 'edit', expected=ORIGINAL, text='Client B conflicting edit.')
+ action(b, 'edit', expected=CELL, text='Client B disjoint cell.')
+ settle(b, lambda state: {'Client B conflicting edit.', 'Client B disjoint cell.'} <= texts(state), 'b-published')
+ finally:
+ vm.qmp(a['name'], 'set_link', {'name': 'lab', 'up': True})
+ state = settle(a, lambda state: len(state) > 1 and 'Client A disjoint edit.' in texts(state), 'conflict')
+ (output / 'conflict.json').write_text(json.dumps(state, indent=2, ensure_ascii=False))
+ page = action(a, 'snapshot')[0].attrib['ID']
+ conflict_views(a['name'], output / 'a', page, 'a')
+ action(b, 'sync')
+ time.sleep(5)
+ page_b = action(b, 'snapshot')[0].attrib['ID']
+ conflict_views(b['name'], output / 'b', page_b, 'b')
+ conflict_views(a['name'], output / 'a', page, 'a-again', delete=True)
+ state = settle(a, lambda state: len(state) == 1, 'deleted')
+ (output / 'deleted.json').write_text(json.dumps(state, indent=2, ensure_ascii=False))
+ action(b, 'sync')
+ time.sleep(5)
+ shot(b['name'], output / 'b', 'b-after-delete', runner.navigation_script(page_b) + 'Sleep(2000)\n')
+ for client in clients.values():
+ try:
+ action(client, 'close')
+ except Exception as error:
+ (client['folder'] / 'close-failure.txt').write_text(str(error))
+ server_state('final')
+ finally:
+ if linux_vm.running(server):
+ linux_vm.shutdown(server, 120)
+ linux_vm.delete_instance(server)
+ (output / 'linux-teardown.json').write_text(json.dumps({'absent': not linux_vm.instance_path(server).exists()}) + '\n')
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+ commands = parser.add_subparsers(dest='mode', required=True)
+ lab = commands.add_parser('sync')
+ lab.add_argument('linux')
+ lab.add_argument('output', type=Path)
+ reopen = commands.add_parser('cold')
+ reopen.add_argument('notebook', type=Path)
+ reopen.add_argument('output', type=Path)
+ reopen.add_argument('--delete', action='store_true')
+ args = parser.parse_args()
+ if args.mode == 'sync':
+ sync(args.linux, args.output)
+ else:
+ cold(args.notebook, args.output, args.delete)
+ print('Captured', args.output)
diff --git a/corpus/conflict-page/native/scripts/cold.ps1 b/corpus/conflict-page/native/scripts/cold.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..a0b99e7aeb86f4dc10032537800501a52ad65b4f
--- /dev/null
+++ b/corpus/conflict-page/native/scripts/cold.ps1
@@ -0,0 +1,27 @@
+param([Parameter(Mandatory=$true)][string]$Root, [string]$CloneHost = '')
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
+if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
+ throw 'Choose a run directly below C:\one-tests\runs.'
+}
+if (Get-Process ONENOTE -ErrorAction SilentlyContinue) { throw 'Close OneNote before resetting its test cache.' }
+$key = 'HKCU:\Software\Microsoft\Office\14.0\OneNote'
+if ($CloneHost) {
+ if ($CloneHost -notmatch '^ONE-[A-Z0-9-]+$' -or [Environment]::MachineName -ne $CloneHost) {
+ throw 'The disposable clone hostname does not match this machine.'
+ }
+ New-Item "$key\Options\Paths" -Force | Out-Null
+ New-ItemProperty "$key\Options\Paths" -Name UnfiledNotesSection -PropertyType ExpandString -Value 'C:\one-tests\Loose.one' -Force | Out-Null
+} elseif ((Get-ItemProperty "$key\Options\Paths").UnfiledNotesSection -ne 'C:\one-tests\Loose.one' -or
+ -not (Test-Path 'C:\one-tests\profile-original-cache')) {
+ throw 'Park the personal OneNote profile before resetting the test cache.'
+}
+$cache = Join-Path $env:LOCALAPPDATA 'Microsoft\OneNote\14.0'
+$parked = Join-Path 'C:\one-tests\caches' ([IO.Path]::GetFileName($root))
+if (Test-Path $parked) { throw 'Choose a new run; its parked cache already exists.' }
+New-Item -ItemType Directory -Path 'C:\one-tests\caches' -Force | Out-Null
+if (Test-Path $cache) { Move-Item -LiteralPath $cache -Destination $parked }
+if (Test-Path "$key\OpenNotebooks") { Remove-Item "$key\OpenNotebooks" -Recurse }
+New-Item "$key\OpenNotebooks" | Out-Null
+New-ItemProperty "$key\OpenNotebooks" -Name '1' -PropertyType String -Value "$root\notebook" | Out-Null
diff --git a/corpus/conflict-page/native/scripts/collaborate.ps1 b/corpus/conflict-page/native/scripts/collaborate.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..4a13a4320675250d49eac958bd3a020ca3b882f6
--- /dev/null
+++ b/corpus/conflict-page/native/scripts/collaborate.ps1
@@ -0,0 +1,105 @@
+param([Parameter(Mandatory=$true)][string]$Root,
+ [Parameter(Mandatory=$true)][string]$SharedPath,
+ [Parameter(Mandatory=$true)][string]$CloneHost,
+ [switch]$ResumeCache,
+ [int]$StartSequence = 1)
+function Keep-Container($page, $target) {
+ $container = $target
+ while ($container.ParentNode -ne $page.DocumentElement) { $container = $container.ParentNode }
+ foreach ($sibling in @($page.DocumentElement.ChildNodes)) {
+ if ($sibling -ne $container -and $sibling.LocalName -in @('Outline', 'Title', 'Image', 'InkDrawing')) {
+ [void]$page.DocumentElement.RemoveChild($sibling)
+ }
+ }
+}
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+. "$PSScriptRoot\text.ps1"
+if ($SharedPath -notmatch '^\\\\192\.168\.77\.1\\agent\\m6-[a-z0-9-]+$') { throw 'Choose a disposable Linux lab notebook.' }
+if ($env:COMPUTERNAME -ne $CloneHost) { throw 'The collaboration controller requires its owned clone.' }
+if (-not $ResumeCache) { & "$PSScriptRoot\cold-current.ps1" -Root $Root -CloneHost $CloneHost }
+$shared = $SharedPath
+if (-not (Test-Path "$shared\synthetic.one")) { throw 'Create the shared fixture first.' }
+New-Item -ItemType Directory "$Root\inbox", "$Root\outbox" -Force | Out-Null
+$user = 'HKCU:\Software\Microsoft\Office\Common\UserInfo'
+New-Item $user -Force | Out-Null
+New-ItemProperty $user -Name UserName -Value $CloneHost -PropertyType String -Force | Out-Null
+New-ItemProperty $user -Name UserInitials -Value $CloneHost.Substring($CloneHost.Length - 3) -PropertyType String -Force | Out-Null
+$app = New-Object -ComObject OneNote.Application
+$notebook = ''
+try {
+ $app.OpenHierarchy($shared, '', [ref]$notebook, 0)
+ $section = ''
+ $app.OpenHierarchy('synthetic.one', $notebook, [ref]$section, 0)
+ [IO.File]::WriteAllText("$Root\ready", 'ready')
+ $deadline = [DateTime]::UtcNow.AddMinutes(30)
+ $sequence = $StartSequence
+ while ([DateTime]::UtcNow -lt $deadline) {
+ $name = '{0:d4}.json' -f $sequence
+ $input = "$Root\inbox\$name"
+ if (-not (Test-Path $input)) { Start-Sleep -Milliseconds 100; continue }
+ $command = Get-Content -Raw -Encoding UTF8 $input | ConvertFrom-Json
+ $output = "$Root\outbox\$sequence"
+ New-Item -ItemType Directory $output | Out-Null
+ try {
+ if ($command.action -in @('prepare-stress', 'stress')) {
+ . "$PSScriptRoot\stress.ps1"
+ Invoke-Stress $app $section $command $output $shared
+ }
+ elseif ($command.action -eq 'sync') { $app.SyncHierarchy($notebook) }
+ elseif ($command.action -eq 'kill-process') {
+ Get-Process ONENOTE | Stop-Process -Force
+ [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
+ $app = New-Object -ComObject OneNote.Application
+ $app.OpenHierarchy($shared, '', [ref]$notebook, 0)
+ $app.OpenHierarchy('synthetic.one', $notebook, [ref]$section, 0)
+ }
+ elseif ($command.action -eq 'edit' -or $command.action -eq 'snapshot') {
+ $hierarchy = ''
+ $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
+ [IO.File]::WriteAllText("$output\hierarchy.xml", $hierarchy, [Text.Encoding]::UTF8)
+ [xml]$tree = $hierarchy
+ $matches = 0
+ $editedPage = $null
+ $i = 0
+ foreach ($node in $tree.SelectNodes('//*[local-name()="Page"]')) {
+ $content = ''
+ $app.GetPageContent($node.GetAttribute('ID'), [ref]$content, 1, 1)
+ [IO.File]::WriteAllText("$output\page-$i.xml", $content, [Text.Encoding]::UTF8)
+ [xml]$page = $content
+ if ($command.action -eq 'edit') {
+ $targets = @($page.SelectNodes('//*[local-name()="T"]') | Where-Object {
+ (Get-ParagraphText $_.InnerText) -eq $command.expected
+ })
+ if ($targets.Count -gt 1) { throw 'Expected one matching text object.' }
+ if ($targets.Count -eq 1) {
+ $matches++
+ if ($matches -gt 1) { throw 'The text occurs on multiple pages.' }
+ $targets[0].InnerText = [Security.SecurityElement]::Escape($command.text)
+ Keep-Container $page $targets[0]
+ $editedPage = $page
+ }
+ }
+ $i++
+ }
+ if ($command.action -eq 'edit' -and $matches -ne 1) { throw 'Expected one matching text object.' }
+ if ($editedPage) { $app.UpdatePageContent($editedPage.OuterXml, [DateTime]::MinValue, 1, $false) }
+ } elseif ($command.action -ne 'close') { throw 'Unknown collaboration command.' }
+ [IO.File]::WriteAllText("$output\done", 'ok')
+ } catch {
+ [IO.File]::WriteAllText("$output\error", $_.Exception.ToString())
+ }
+ if ($command.action -eq 'close') { break }
+ $sequence++
+ }
+} finally {
+ try { if ($notebook) { $app.CloseNotebook($notebook, $false) } }
+ finally {
+ [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
+ $app = $null
+ [GC]::Collect()
+ [GC]::WaitForPendingFinalizers()
+ Get-Process ONENOTE -ErrorAction SilentlyContinue | Wait-Process -Timeout 10
+ [IO.File]::WriteAllText("$Root\closed", 'closed')
+ }
+}
diff --git a/corpus/conflict-page/native/scripts/network.ps1 b/corpus/conflict-page/native/scripts/network.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..95fa83d5243ee6a2a653cc075128b063e2f09cf2
--- /dev/null
+++ b/corpus/conflict-page/native/scripts/network.ps1
@@ -0,0 +1,20 @@
+param([Parameter(Mandatory=$true)][string]$LabMac)
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$adapter = @(Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object { $_.MACAddress -eq $LabMac })
+if ($adapter.Count -ne 1) { throw 'The clone lab adapter is ambiguous.' }
+$enabled = $adapter[0].EnableDHCP()
+if ($enabled.ReturnValue -notin @(0, 1)) {
+ throw ('Unable to enable DHCP on the clone lab adapter; result ' + $enabled.ReturnValue)
+}
+$release = $adapter[0].ReleaseDHCPLease()
+$result = $adapter[0].RenewDHCPLease()
+$deadline = [DateTime]::UtcNow.AddSeconds(60)
+do {
+ $adapter = @(Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object { $_.MACAddress -eq $LabMac })
+ $addresses = @($adapter[0].IPAddress | Where-Object { $_ -match '^192\.168\.77\.\d+$' -and $_ -ne '192.168.77.1' })
+ if ($addresses.Count -eq 1) { break }
+ Start-Sleep -Milliseconds 250
+} while ([DateTime]::UtcNow -lt $deadline)
+if ($addresses.Count -ne 1) { throw ('The clone did not receive a lab IPv4 address; renewal result ' + $result.ReturnValue + '; adapter ' + ($adapter[0] | Select-Object MACAddress,DHCPEnabled,IPEnabled,IPAddress | ConvertTo-Json -Compress)) }
+@{mac=$LabMac;address=$addresses[0];releaseResult=$release.ReturnValue;renewalResult=$result.ReturnValue} | ConvertTo-Json -Compress
diff --git a/corpus/conflict-page/native/scripts/stress.ps1 b/corpus/conflict-page/native/scripts/stress.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..a0ab5bc69b4dabe970373e481873dfd951a1a69a
--- /dev/null
+++ b/corpus/conflict-page/native/scripts/stress.ps1
@@ -0,0 +1,83 @@
+function Invoke-Stress($app, $section, $command, $output, $shared) {
+ $hierarchy = ''
+ $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
+ [IO.File]::WriteAllText("$output\hierarchy.xml", $hierarchy, [Text.Encoding]::UTF8)
+ [xml]$tree = $hierarchy
+ $pages = @($tree.SelectNodes('//*[local-name()="Page"]'))
+ if ($pages.Count -ne 1) { throw 'Expected one stress-test page.' }
+ $pageId = $pages[0].GetAttribute('ID')
+ if ($command.action -eq 'prepare-stress') {
+ for ($i = 0; $i -lt $command.clients; $i++) {
+ $xml = ''
+ $app.UpdatePageContent($xml, [DateTime]::MinValue, 1, $false)
+ }
+ return
+ }
+ $random = New-Object Random([int]$command.seed)
+ $events = New-Object IO.StreamWriter("$output\events.jsonl", $false, [Text.Encoding]::UTF8)
+ $events.AutoFlush = $true
+ try {
+ [IO.File]::WriteAllText("$output\ready", 'ready')
+ $deadline = [DateTime]::UtcNow.AddMinutes(10)
+ while (-not (Test-Path "$shared\stress-start")) {
+ if ([DateTime]::UtcNow -gt $deadline) { throw 'Stress start timed out.' }
+ Start-Sleep -Milliseconds 20
+ }
+ for ($i = 0; $i -lt $command.operations; $i++) {
+ if (($command.PSObject.Properties.Name -contains 'maintenance') -and $command.maintenance -and $i -eq [Math]::Floor($command.operations / 2)) {
+ [IO.File]::WriteAllText("$shared\maintenance-paused-n$($command.actor)", 'paused')
+ while (-not (Test-Path "$shared\maintenance-resume")) {
+ if ([DateTime]::UtcNow -gt $deadline) { throw 'Maintenance pause timed out.' }
+ Start-Sleep -Milliseconds 100
+ }
+ }
+ Start-Sleep -Milliseconds $random.Next(10, 100)
+ $started = [DateTime]::UtcNow.Ticks
+ $content = ''
+ $app.GetPageContent($pageId, [ref]$content, 1, 1)
+ [xml]$page = $content
+ $matches = @($page.SelectNodes('//*[local-name()="T"]') | Where-Object {
+ (Get-ParagraphText $_.InnerText).StartsWith($command.prefix)
+ })
+ if ($matches.Count -ne 1) { throw 'Expected one stress-test paragraph.' }
+ $before = Get-ParagraphText $matches[0].InnerText
+ $token = ' [n' + $command.actor + ':' + $i + ']'
+ $start = $end = $before.Length
+ $replacement = $token
+ $bold = $italic = $false
+ if ($command.edit) {
+ $boundaries = New-Object 'Collections.Generic.List[int]'
+ for ($at = $command.prefix.Length; $at -le $before.Length; $at++) {
+ $boundaries.Add($at)
+ if ($at -lt $before.Length -and [char]::IsHighSurrogate($before[$at])) { $at++ }
+ }
+ $first = $boundaries[$random.Next($boundaries.Count)]
+ $second = $boundaries[$random.Next($boundaries.Count)]
+ $start = [Math]::Min($first, $second)
+ $end = [Math]::Max($first, $second)
+ $replacement = ' caf' + [char]0xe9 + ' ' + [char]::ConvertFromUtf32(0x1f980) + $token
+ $bold = $random.Next(2) -eq 1
+ $italic = $random.Next(2) -eq 1
+ }
+ $after = $before.Substring(0, $start) + $replacement + $before.Substring($end)
+ $html = [Security.SecurityElement]::Escape($after)
+ if ($command.edit) {
+ $weight = if ($bold) { 'bold' } else { 'normal' }
+ $style = if ($italic) { 'italic' } else { 'normal' }
+ $html = '' + $html + ''
+ }
+ $matches[0].InnerText = $html
+ Keep-Container $page $matches[0]
+ $updateStarted = [DateTime]::UtcNow.Ticks
+ $app.UpdatePageContent($page.OuterXml, [DateTime]::MinValue, 1, $false)
+ $updated = [DateTime]::UtcNow.Ticks
+ $events.WriteLine((@{ operation=$i; before=$before; token=$token; started_ticks=$started;
+ range=@($start, $end); replacement=$replacement; bold=$bold; italic=$italic;
+ update_started_ticks=$updateStarted; updated_ticks=$updated } | ConvertTo-Json -Compress))
+ if ($i -eq 0) { [IO.File]::WriteAllText("$output\editing", 'editing') }
+ if ($command.sync_every -gt 0 -and (($i + 1) % $command.sync_every) -eq 0) {
+ $app.SyncHierarchy($section)
+ }
+ }
+ } finally { $events.Dispose() }
+}
diff --git a/corpus/conflict-page/native/scripts/text.ps1 b/corpus/conflict-page/native/scripts/text.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..75e9e71207b599d16eaaf68824ca30831860610a
--- /dev/null
+++ b/corpus/conflict-page/native/scripts/text.ps1
@@ -0,0 +1,13 @@
+function Get-ParagraphText([string]$html) {
+ $plain = [regex]::Replace($html, '<[^>]*>', '')
+ $plain = [regex]::Replace($plain, '(?:x([0-9a-fA-F]+)|([0-9]+));', {
+ param($match)
+ $value = if ($match.Groups[1].Success) {
+ [Convert]::ToInt32($match.Groups[1].Value, 16)
+ } else { [int]$match.Groups[2].Value }
+ # The Win7 framework leaves supplementary numeric entities undecoded.
+ if ($value -gt 0xffff -and $value -le 0x10ffff) { [char]::ConvertFromUtf32($value) }
+ else { $match.Value }
+ })
+ [Net.WebUtility]::HtmlDecode($plain)
+}
diff --git a/corpus/conflict-page/native/server.jsonl b/corpus/conflict-page/native/server.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..d5df993564a30ddaea1b427699648ff9b1a1417d
--- /dev/null
+++ b/corpus/conflict-page/native/server.jsonl
@@ -0,0 +1,2 @@
+{"command": "mkdir /srv/agent/m6-conflict", "exit": 0, "stdout": "", "stderr": ""}
+{"command": "chmod u+w /srv/agent/m6-conflict/*", "exit": 0, "stdout": "", "stderr": ""}
diff --git a/corpus/extended-ascii/README.md b/corpus/extended-ascii/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..873405ca5918c84f20bd12c78c2bef1eb7adb4cb
--- /dev/null
+++ b/corpus/extended-ascii/README.md
@@ -0,0 +1,9 @@
+# Extended ASCII text
+
+`TextExtendedAscii` (`0x1c003498`, MS-ONE 2.2.89) names no code page. Each `candidate`
+is `m6/ansi/notebook` with the paragraph's `RichEditTextLangID` (`0x10001cfe`) set to
+0x0409 (`en`), 0x0419 (`ru`, ANSI code page 1251) or 0x0411 (`ja`, code page 932); its
+text holds the bytes 0x80–0xFF. `cold` is each one's fresh OneNote 2010 read: all three
+export U+0080–U+00FF, so OneNote reads the bytes as Latin-1 whatever the language, and
+`read/page-000.png` shows 0x80–0x9F as nothing and 0xC0 as À rather than Cyrillic А.
+OneNote left the section files unchanged.
diff --git a/corpus/extended-ascii/en/candidate/Open Notebook.onetoc2 b/corpus/extended-ascii/en/candidate/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..a33d3bfd210f844e3db618e648ac898adeee09c6
Binary files /dev/null and b/corpus/extended-ascii/en/candidate/Open Notebook.onetoc2 differ
diff --git a/corpus/extended-ascii/en/candidate/synthetic.one b/corpus/extended-ascii/en/candidate/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..d2ed44f1e9f620a51ba6e44d52867ebcb5d9ee16
Binary files /dev/null and b/corpus/extended-ascii/en/candidate/synthetic.one differ
diff --git a/corpus/extended-ascii/en/cold/commands.jsonl b/corpus/extended-ascii/en/cold/commands.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..1b14b83ce031e1dc4b11a4d3f159a39c13615d7a
--- /dev/null
+++ b/corpus/extended-ascii/en/cold/commands.jsonl
@@ -0,0 +1,3 @@
+{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-A375E105 -ExpectedPages 1 -KeepOpen", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
diff --git a/corpus/extended-ascii/en/cold/machine.json b/corpus/extended-ascii/en/cold/machine.json
new file mode 100644
index 0000000000000000000000000000000000000000..57863a786fdc66a1bf4d1f75151759a2f20903fc
--- /dev/null
+++ b/corpus/extended-ascii/en/cold/machine.json
@@ -0,0 +1 @@
+{"name": "m6-a375e105", "hostname": "ONE-M6-A375E105"}
diff --git a/corpus/extended-ascii/en/cold/notebook/Open Notebook.onetoc2 b/corpus/extended-ascii/en/cold/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..a33d3bfd210f844e3db618e648ac898adeee09c6
Binary files /dev/null and b/corpus/extended-ascii/en/cold/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/extended-ascii/en/cold/notebook/synthetic.one b/corpus/extended-ascii/en/cold/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..d2ed44f1e9f620a51ba6e44d52867ebcb5d9ee16
Binary files /dev/null and b/corpus/extended-ascii/en/cold/notebook/synthetic.one differ
diff --git a/corpus/extended-ascii/en/cold/read/environment.json b/corpus/extended-ascii/en/cold/read/environment.json
new file mode 100644
index 0000000000000000000000000000000000000000..fcb5a8538909915c2c9f4a7c9e1a70717a4d7647
--- /dev/null
+++ b/corpus/extended-ascii/en/cold/read/environment.json
@@ -0,0 +1,7 @@
+{
+ "powershell": "5.1.14409.1005",
+ "schema": "xs2010",
+ "hostname": "ONE-M6-A375E105",
+ "cold": true,
+ "onenote": "14.0.4763.1000"
+}
diff --git a/corpus/extended-ascii/en/cold/read/hierarchy.xml b/corpus/extended-ascii/en/cold/read/hierarchy.xml
new file mode 100644
index 0000000000000000000000000000000000000000..fa4d29784012bad3829a8ecd7052cd044813a56f
--- /dev/null
+++ b/corpus/extended-ascii/en/cold/read/hierarchy.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/extended-ascii/en/cold/read/page-000.navigation.json b/corpus/extended-ascii/en/cold/read/page-000.navigation.json
new file mode 100644
index 0000000000000000000000000000000000000000..f55849761016bf753db6dc26da07ab49553ce0a6
--- /dev/null
+++ b/corpus/extended-ascii/en/cold/read/page-000.navigation.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Extended bytes: \u0080 \u0081 \u0082 \u0083 \u0084 \u0085 \u0086 \u0087 \u0088 \u0089 \u008a \u008b \u008c \u008d \u008e \u008f \u0090 - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/extended-ascii/en/cold/read/page-000.png b/corpus/extended-ascii/en/cold/read/page-000.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa6b6e3797f63f2489c730ddf74e2ee8fc18f279
Binary files /dev/null and b/corpus/extended-ascii/en/cold/read/page-000.png differ
diff --git a/corpus/extended-ascii/en/cold/read/page-000.xml b/corpus/extended-ascii/en/cold/read/page-000.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4a5baca4877801b2a1b4c88c5df1fda85d1d80f1
--- /dev/null
+++ b/corpus/extended-ascii/en/cold/read/page-000.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/extended-ascii/en/cold/read/payloads.json b/corpus/extended-ascii/en/cold/read/payloads.json
new file mode 100644
index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843
--- /dev/null
+++ b/corpus/extended-ascii/en/cold/read/payloads.json
@@ -0,0 +1,3 @@
+[
+
+]
\ No newline at end of file
diff --git a/corpus/extended-ascii/en/cold/run.json b/corpus/extended-ascii/en/cold/run.json
new file mode 100644
index 0000000000000000000000000000000000000000..7d96357217a412779632df1272e8d93361ea1931
--- /dev/null
+++ b/corpus/extended-ascii/en/cold/run.json
@@ -0,0 +1,18 @@
+{
+ "notebook": "/private/tmp/ansi-lang/en/notebook",
+ "expected_pages": 1,
+ "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"
+ }
+}
diff --git a/corpus/extended-ascii/en/cold/scripts/cold.ps1 b/corpus/extended-ascii/en/cold/scripts/cold.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..a0b99e7aeb86f4dc10032537800501a52ad65b4f
--- /dev/null
+++ b/corpus/extended-ascii/en/cold/scripts/cold.ps1
@@ -0,0 +1,27 @@
+param([Parameter(Mandatory=$true)][string]$Root, [string]$CloneHost = '')
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
+if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
+ throw 'Choose a run directly below C:\one-tests\runs.'
+}
+if (Get-Process ONENOTE -ErrorAction SilentlyContinue) { throw 'Close OneNote before resetting its test cache.' }
+$key = 'HKCU:\Software\Microsoft\Office\14.0\OneNote'
+if ($CloneHost) {
+ if ($CloneHost -notmatch '^ONE-[A-Z0-9-]+$' -or [Environment]::MachineName -ne $CloneHost) {
+ throw 'The disposable clone hostname does not match this machine.'
+ }
+ New-Item "$key\Options\Paths" -Force | Out-Null
+ New-ItemProperty "$key\Options\Paths" -Name UnfiledNotesSection -PropertyType ExpandString -Value 'C:\one-tests\Loose.one' -Force | Out-Null
+} elseif ((Get-ItemProperty "$key\Options\Paths").UnfiledNotesSection -ne 'C:\one-tests\Loose.one' -or
+ -not (Test-Path 'C:\one-tests\profile-original-cache')) {
+ throw 'Park the personal OneNote profile before resetting the test cache.'
+}
+$cache = Join-Path $env:LOCALAPPDATA 'Microsoft\OneNote\14.0'
+$parked = Join-Path 'C:\one-tests\caches' ([IO.Path]::GetFileName($root))
+if (Test-Path $parked) { throw 'Choose a new run; its parked cache already exists.' }
+New-Item -ItemType Directory -Path 'C:\one-tests\caches' -Force | Out-Null
+if (Test-Path $cache) { Move-Item -LiteralPath $cache -Destination $parked }
+if (Test-Path "$key\OpenNotebooks") { Remove-Item "$key\OpenNotebooks" -Recurse }
+New-Item "$key\OpenNotebooks" | Out-Null
+New-ItemProperty "$key\OpenNotebooks" -Name '1' -PropertyType String -Value "$root\notebook" | Out-Null
diff --git a/corpus/extended-ascii/en/cold/scripts/read.ps1 b/corpus/extended-ascii/en/cold/scripts/read.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..476094ab1e0c47077988a6461db2080cf208642b
--- /dev/null
+++ b/corpus/extended-ascii/en/cold/scripts/read.ps1
@@ -0,0 +1,142 @@
+param(
+ [Parameter(Mandatory=$true)][string]$Root,
+ [int]$ExpectedPages = -1,
+ [switch]$UseCurrentCache,
+ [switch]$Pdf,
+ [switch]$KeepOpen,
+ [string]$CloneHost = ''
+)
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
+if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
+ throw 'Choose a run directly below C:\one-tests\runs.'
+}
+$notebook = Join-Path $root 'notebook'
+$output = Join-Path $root 'read'
+if (Test-Path $output) { throw 'Choose a new read destination.' }
+if ($UseCurrentCache) {
+ if ((Get-ItemProperty 'HKCU:\Software\Microsoft\Office\14.0\OneNote\Options\Paths').UnfiledNotesSection -ne 'C:\one-tests\Loose.one') {
+ throw 'Park the personal OneNote profile before reading test notebooks.'
+ }
+} else {
+ & "$PSScriptRoot\cold-current.ps1" -Root $root -CloneHost $CloneHost
+}
+New-Item -ItemType Directory -Path $output | Out-Null
+$app = New-Object -ComObject OneNote.Application
+$notebookId = ''
+$failure = $null
+try {
+ $app.OpenHierarchy($notebook, '', [ref]$notebookId, 0)
+ $process = Get-Process ONENOTE
+ @{ hostname = [Environment]::MachineName; onenote = $process.MainModule.FileVersionInfo.FileVersion;
+ powershell = $PSVersionTable.PSVersion.ToString(); schema = 'xs2010'; cold = (-not $UseCurrentCache.IsPresent) } |
+ ConvertTo-Json | Set-Content (Join-Path $output 'environment.json') -Encoding UTF8
+ $sections = @()
+ foreach ($file in @(Get-ChildItem $notebook -Recurse | Where-Object { $_.Extension -eq '.one' })) {
+ $id = ''
+ $app.OpenHierarchy($file.FullName, '', [ref]$id, 0)
+ $sections += $id
+ }
+ $deadline = [DateTime]::UtcNow.AddSeconds(300)
+ $previous = ''
+ $lastChange = ''
+ $stableSince = [DateTime]::UtcNow
+ $settled = $false
+ do {
+ $pages = @{}
+ foreach ($section in $sections) {
+ $hierarchy = ''
+ $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
+ [xml]$xml = $hierarchy
+ foreach ($node in $xml.SelectNodes('//*[@path]')) {
+ if (-not $node.GetAttribute('path').StartsWith("$notebook\", [StringComparison]::OrdinalIgnoreCase)) {
+ throw 'OneNote opened a section outside the copied notebook.'
+ }
+ }
+ foreach ($node in $xml.SelectNodes('//*[local-name()="Page"]')) {
+ $id = $node.GetAttribute('ID')
+ $content = ''
+ $app.GetPageContent($id, [ref]$content, 1, 1)
+ $pages[$id] = $content
+ }
+ }
+ $signature = [String]::Join('|', @($pages.Keys | Sort-Object | ForEach-Object { $_ + $pages[$_] }))
+ if ($signature -ne $previous) {
+ $lastChange = $previous
+ $previous = $signature
+ $stableSince = [DateTime]::UtcNow
+ }
+ if ((($ExpectedPages -ge 0 -and $pages.Count -eq $ExpectedPages) -or
+ ($ExpectedPages -lt 0 -and $pages.Count -gt 0)) -and
+ ([DateTime]::UtcNow - $stableSince).TotalSeconds -ge 2) { $settled = $true; break }
+ Start-Sleep -Milliseconds 250
+ } while ([DateTime]::UtcNow -lt $deadline)
+ if (-not $settled -or ($ExpectedPages -ge 0 -and $pages.Count -ne $ExpectedPages) -or ($ExpectedPages -lt 0 -and $pages.Count -eq 0)) {
+ [IO.File]::WriteAllText((Join-Path $output 'previous-signature.txt'), $lastChange, [Text.Encoding]::UTF8)
+ $index = 0
+ foreach ($id in @($pages.Keys | Sort-Object)) {
+ [IO.File]::WriteAllText((Join-Path $output ('unsettled-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8)
+ $index++
+ }
+ $hierarchy = ''
+ $app.GetHierarchy($notebookId, 4, [ref]$hierarchy, 1)
+ [IO.File]::WriteAllText((Join-Path $output 'unsettled-hierarchy.xml'), $hierarchy, [Text.Encoding]::UTF8)
+ throw "Expected $ExpectedPages stable pages; OneNote returned $($pages.Count), settled=$settled."
+ }
+ $index = 0
+ $payloads = @()
+ foreach ($id in @($pages.Keys | Sort-Object)) {
+ [IO.File]::WriteAllText((Join-Path $output ('page-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8)
+ if ($Pdf) {
+ $pdfPath = Join-Path $output ('page-{0:d3}.pdf' -f $index)
+ $app.NavigateTo($id, '', $false)
+ $app.Publish($id, $pdfPath, 3, '')
+ if (-not (Test-Path $pdfPath) -or (Get-Item $pdfPath).Length -eq 0) { throw 'OneNote did not publish the page PDF.' }
+ }
+ [xml]$page = $pages[$id]
+ foreach ($file in $page.SelectNodes('//*[local-name()="InsertedFile" or local-name()="MediaFile"]')) {
+ $bytes = [IO.File]::ReadAllBytes($file.GetAttribute('pathCache'))
+ $hash = [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash($bytes)).Replace('-', '').ToLowerInvariant()
+ [IO.File]::WriteAllBytes((Join-Path $output ($hash + '.attachment')), $bytes)
+ $payloads += @{ page = $id; object = $file.ParentNode.GetAttribute('objectID');
+ kind = $file.LocalName; name = $file.GetAttribute('preferredName');
+ sha256 = $hash; bytes = $bytes.Length }
+ }
+ $index++
+ }
+ [IO.File]::WriteAllText((Join-Path $output 'payloads.json'), (ConvertTo-Json -InputObject $payloads -Depth 4), [Text.Encoding]::UTF8)
+ $all = ''
+ $app.GetHierarchy($notebookId, 4, [ref]$all, 1)
+ [xml]$finalTree = $all
+ $finalIds = @($finalTree.SelectNodes('//*[local-name()="Page"]') | ForEach-Object { $_.GetAttribute('ID') } | Sort-Object -Unique)
+ if ($finalIds.Count -ne $pages.Count -or @($finalIds | Where-Object { -not $pages.ContainsKey($_) }).Count -ne 0) {
+ throw 'The notebook hierarchy changed while collecting page evidence; repeat the cold read.'
+ }
+ [IO.File]::WriteAllText((Join-Path $output 'hierarchy.xml'), $all, [Text.Encoding]::UTF8)
+ Write-Output "Read $($sections.Count) sections and $($pages.Count) pages."
+} catch {
+ $failure = $_
+ [IO.File]::WriteAllText((Join-Path $output 'failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8)
+ throw
+} finally {
+ try {
+ try {
+ if ($notebookId -and $CloneHost) { $app.SyncHierarchy($notebookId) }
+ if ($notebookId -and -not $KeepOpen -and (-not $UseCurrentCache -or $CloneHost)) {
+ $app.CloseNotebook($notebookId, $false)
+ }
+ } catch {
+ if ($null -eq $failure) { throw }
+ [IO.File]::WriteAllText((Join-Path $output 'cleanup-failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8)
+ }
+ } finally {
+ [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
+ }
+ $app = $null
+ [GC]::Collect()
+ [GC]::WaitForPendingFinalizers()
+ if (-not $UseCurrentCache -and -not $CloneHost) {
+ Get-Process ONENOTE -ErrorAction SilentlyContinue | Wait-Process -Timeout 10
+ }
+}
diff --git a/corpus/extended-ascii/en/cold/source.json b/corpus/extended-ascii/en/cold/source.json
new file mode 100644
index 0000000000000000000000000000000000000000..d3e5ffe17dfc9c590b42235a151cbd45cbf9aab2
--- /dev/null
+++ b/corpus/extended-ascii/en/cold/source.json
@@ -0,0 +1,14 @@
+[
+ {
+ "path": "Open Notebook.onetoc2",
+ "bytes": 4816,
+ "sha256": "b2f857961b76258a1582b402c048b340e89962257681426b3bfc58df8e0adbfa",
+ "mtime_ns": 1790487072902156985
+ },
+ {
+ "path": "synthetic.one",
+ "bytes": 12456,
+ "sha256": "aa6f1188773f1cc41cc5c619f213ce04b30592320c6b2e32b2b4c91639423ab4",
+ "mtime_ns": 1790487072902335569
+ }
+]
diff --git a/corpus/extended-ascii/en/cold/teardown.json b/corpus/extended-ascii/en/cold/teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/extended-ascii/en/cold/teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/extended-ascii/ja/candidate/Open Notebook.onetoc2 b/corpus/extended-ascii/ja/candidate/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..a33d3bfd210f844e3db618e648ac898adeee09c6
Binary files /dev/null and b/corpus/extended-ascii/ja/candidate/Open Notebook.onetoc2 differ
diff --git a/corpus/extended-ascii/ja/candidate/synthetic.one b/corpus/extended-ascii/ja/candidate/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..c15b622081fc5df7733206820860626a18c0521f
Binary files /dev/null and b/corpus/extended-ascii/ja/candidate/synthetic.one differ
diff --git a/corpus/extended-ascii/ja/cold/commands.jsonl b/corpus/extended-ascii/ja/cold/commands.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..09a67abb1857d332e99ed1eadf88fdf023df14c0
--- /dev/null
+++ b/corpus/extended-ascii/ja/cold/commands.jsonl
@@ -0,0 +1,3 @@
+{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-8DC7DCC7 -ExpectedPages 1 -KeepOpen", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
diff --git a/corpus/extended-ascii/ja/cold/machine.json b/corpus/extended-ascii/ja/cold/machine.json
new file mode 100644
index 0000000000000000000000000000000000000000..7a96f7cfbb857707e13a4e759dd5cb9664973b3f
--- /dev/null
+++ b/corpus/extended-ascii/ja/cold/machine.json
@@ -0,0 +1 @@
+{"name": "m6-8dc7dcc7", "hostname": "ONE-M6-8DC7DCC7"}
diff --git a/corpus/extended-ascii/ja/cold/notebook/Open Notebook.onetoc2 b/corpus/extended-ascii/ja/cold/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..a33d3bfd210f844e3db618e648ac898adeee09c6
Binary files /dev/null and b/corpus/extended-ascii/ja/cold/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/extended-ascii/ja/cold/notebook/synthetic.one b/corpus/extended-ascii/ja/cold/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..c15b622081fc5df7733206820860626a18c0521f
Binary files /dev/null and b/corpus/extended-ascii/ja/cold/notebook/synthetic.one differ
diff --git a/corpus/extended-ascii/ja/cold/read/environment.json b/corpus/extended-ascii/ja/cold/read/environment.json
new file mode 100644
index 0000000000000000000000000000000000000000..5d25d66b05678d093c31c8611399998b97e017a9
--- /dev/null
+++ b/corpus/extended-ascii/ja/cold/read/environment.json
@@ -0,0 +1,7 @@
+{
+ "powershell": "5.1.14409.1005",
+ "schema": "xs2010",
+ "hostname": "ONE-M6-8DC7DCC7",
+ "cold": true,
+ "onenote": "14.0.4763.1000"
+}
diff --git a/corpus/extended-ascii/ja/cold/read/hierarchy.xml b/corpus/extended-ascii/ja/cold/read/hierarchy.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f007d2a89ff7d4305b16385dd20d179cbe89c671
--- /dev/null
+++ b/corpus/extended-ascii/ja/cold/read/hierarchy.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/extended-ascii/ja/cold/read/page-000.navigation.json b/corpus/extended-ascii/ja/cold/read/page-000.navigation.json
new file mode 100644
index 0000000000000000000000000000000000000000..f55849761016bf753db6dc26da07ab49553ce0a6
--- /dev/null
+++ b/corpus/extended-ascii/ja/cold/read/page-000.navigation.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Extended bytes: \u0080 \u0081 \u0082 \u0083 \u0084 \u0085 \u0086 \u0087 \u0088 \u0089 \u008a \u008b \u008c \u008d \u008e \u008f \u0090 - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/extended-ascii/ja/cold/read/page-000.png b/corpus/extended-ascii/ja/cold/read/page-000.png
new file mode 100644
index 0000000000000000000000000000000000000000..32aeda3c9e3dea9b2d0a652414d228d486b0f4f2
Binary files /dev/null and b/corpus/extended-ascii/ja/cold/read/page-000.png differ
diff --git a/corpus/extended-ascii/ja/cold/read/page-000.xml b/corpus/extended-ascii/ja/cold/read/page-000.xml
new file mode 100644
index 0000000000000000000000000000000000000000..10ac23b6e39ed1116912c93e9a83d1994aa09335
--- /dev/null
+++ b/corpus/extended-ascii/ja/cold/read/page-000.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/extended-ascii/ja/cold/read/payloads.json b/corpus/extended-ascii/ja/cold/read/payloads.json
new file mode 100644
index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843
--- /dev/null
+++ b/corpus/extended-ascii/ja/cold/read/payloads.json
@@ -0,0 +1,3 @@
+[
+
+]
\ No newline at end of file
diff --git a/corpus/extended-ascii/ja/cold/run.json b/corpus/extended-ascii/ja/cold/run.json
new file mode 100644
index 0000000000000000000000000000000000000000..5e2d4b5264e879413dc43a7e9f6a66cbfdf9dd19
--- /dev/null
+++ b/corpus/extended-ascii/ja/cold/run.json
@@ -0,0 +1,18 @@
+{
+ "notebook": "/private/tmp/ansi-lang/ja/notebook",
+ "expected_pages": 1,
+ "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"
+ }
+}
diff --git a/corpus/extended-ascii/ja/cold/scripts/cold.ps1 b/corpus/extended-ascii/ja/cold/scripts/cold.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..a0b99e7aeb86f4dc10032537800501a52ad65b4f
--- /dev/null
+++ b/corpus/extended-ascii/ja/cold/scripts/cold.ps1
@@ -0,0 +1,27 @@
+param([Parameter(Mandatory=$true)][string]$Root, [string]$CloneHost = '')
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
+if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
+ throw 'Choose a run directly below C:\one-tests\runs.'
+}
+if (Get-Process ONENOTE -ErrorAction SilentlyContinue) { throw 'Close OneNote before resetting its test cache.' }
+$key = 'HKCU:\Software\Microsoft\Office\14.0\OneNote'
+if ($CloneHost) {
+ if ($CloneHost -notmatch '^ONE-[A-Z0-9-]+$' -or [Environment]::MachineName -ne $CloneHost) {
+ throw 'The disposable clone hostname does not match this machine.'
+ }
+ New-Item "$key\Options\Paths" -Force | Out-Null
+ New-ItemProperty "$key\Options\Paths" -Name UnfiledNotesSection -PropertyType ExpandString -Value 'C:\one-tests\Loose.one' -Force | Out-Null
+} elseif ((Get-ItemProperty "$key\Options\Paths").UnfiledNotesSection -ne 'C:\one-tests\Loose.one' -or
+ -not (Test-Path 'C:\one-tests\profile-original-cache')) {
+ throw 'Park the personal OneNote profile before resetting the test cache.'
+}
+$cache = Join-Path $env:LOCALAPPDATA 'Microsoft\OneNote\14.0'
+$parked = Join-Path 'C:\one-tests\caches' ([IO.Path]::GetFileName($root))
+if (Test-Path $parked) { throw 'Choose a new run; its parked cache already exists.' }
+New-Item -ItemType Directory -Path 'C:\one-tests\caches' -Force | Out-Null
+if (Test-Path $cache) { Move-Item -LiteralPath $cache -Destination $parked }
+if (Test-Path "$key\OpenNotebooks") { Remove-Item "$key\OpenNotebooks" -Recurse }
+New-Item "$key\OpenNotebooks" | Out-Null
+New-ItemProperty "$key\OpenNotebooks" -Name '1' -PropertyType String -Value "$root\notebook" | Out-Null
diff --git a/corpus/extended-ascii/ja/cold/scripts/read.ps1 b/corpus/extended-ascii/ja/cold/scripts/read.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..476094ab1e0c47077988a6461db2080cf208642b
--- /dev/null
+++ b/corpus/extended-ascii/ja/cold/scripts/read.ps1
@@ -0,0 +1,142 @@
+param(
+ [Parameter(Mandatory=$true)][string]$Root,
+ [int]$ExpectedPages = -1,
+ [switch]$UseCurrentCache,
+ [switch]$Pdf,
+ [switch]$KeepOpen,
+ [string]$CloneHost = ''
+)
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
+if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
+ throw 'Choose a run directly below C:\one-tests\runs.'
+}
+$notebook = Join-Path $root 'notebook'
+$output = Join-Path $root 'read'
+if (Test-Path $output) { throw 'Choose a new read destination.' }
+if ($UseCurrentCache) {
+ if ((Get-ItemProperty 'HKCU:\Software\Microsoft\Office\14.0\OneNote\Options\Paths').UnfiledNotesSection -ne 'C:\one-tests\Loose.one') {
+ throw 'Park the personal OneNote profile before reading test notebooks.'
+ }
+} else {
+ & "$PSScriptRoot\cold-current.ps1" -Root $root -CloneHost $CloneHost
+}
+New-Item -ItemType Directory -Path $output | Out-Null
+$app = New-Object -ComObject OneNote.Application
+$notebookId = ''
+$failure = $null
+try {
+ $app.OpenHierarchy($notebook, '', [ref]$notebookId, 0)
+ $process = Get-Process ONENOTE
+ @{ hostname = [Environment]::MachineName; onenote = $process.MainModule.FileVersionInfo.FileVersion;
+ powershell = $PSVersionTable.PSVersion.ToString(); schema = 'xs2010'; cold = (-not $UseCurrentCache.IsPresent) } |
+ ConvertTo-Json | Set-Content (Join-Path $output 'environment.json') -Encoding UTF8
+ $sections = @()
+ foreach ($file in @(Get-ChildItem $notebook -Recurse | Where-Object { $_.Extension -eq '.one' })) {
+ $id = ''
+ $app.OpenHierarchy($file.FullName, '', [ref]$id, 0)
+ $sections += $id
+ }
+ $deadline = [DateTime]::UtcNow.AddSeconds(300)
+ $previous = ''
+ $lastChange = ''
+ $stableSince = [DateTime]::UtcNow
+ $settled = $false
+ do {
+ $pages = @{}
+ foreach ($section in $sections) {
+ $hierarchy = ''
+ $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
+ [xml]$xml = $hierarchy
+ foreach ($node in $xml.SelectNodes('//*[@path]')) {
+ if (-not $node.GetAttribute('path').StartsWith("$notebook\", [StringComparison]::OrdinalIgnoreCase)) {
+ throw 'OneNote opened a section outside the copied notebook.'
+ }
+ }
+ foreach ($node in $xml.SelectNodes('//*[local-name()="Page"]')) {
+ $id = $node.GetAttribute('ID')
+ $content = ''
+ $app.GetPageContent($id, [ref]$content, 1, 1)
+ $pages[$id] = $content
+ }
+ }
+ $signature = [String]::Join('|', @($pages.Keys | Sort-Object | ForEach-Object { $_ + $pages[$_] }))
+ if ($signature -ne $previous) {
+ $lastChange = $previous
+ $previous = $signature
+ $stableSince = [DateTime]::UtcNow
+ }
+ if ((($ExpectedPages -ge 0 -and $pages.Count -eq $ExpectedPages) -or
+ ($ExpectedPages -lt 0 -and $pages.Count -gt 0)) -and
+ ([DateTime]::UtcNow - $stableSince).TotalSeconds -ge 2) { $settled = $true; break }
+ Start-Sleep -Milliseconds 250
+ } while ([DateTime]::UtcNow -lt $deadline)
+ if (-not $settled -or ($ExpectedPages -ge 0 -and $pages.Count -ne $ExpectedPages) -or ($ExpectedPages -lt 0 -and $pages.Count -eq 0)) {
+ [IO.File]::WriteAllText((Join-Path $output 'previous-signature.txt'), $lastChange, [Text.Encoding]::UTF8)
+ $index = 0
+ foreach ($id in @($pages.Keys | Sort-Object)) {
+ [IO.File]::WriteAllText((Join-Path $output ('unsettled-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8)
+ $index++
+ }
+ $hierarchy = ''
+ $app.GetHierarchy($notebookId, 4, [ref]$hierarchy, 1)
+ [IO.File]::WriteAllText((Join-Path $output 'unsettled-hierarchy.xml'), $hierarchy, [Text.Encoding]::UTF8)
+ throw "Expected $ExpectedPages stable pages; OneNote returned $($pages.Count), settled=$settled."
+ }
+ $index = 0
+ $payloads = @()
+ foreach ($id in @($pages.Keys | Sort-Object)) {
+ [IO.File]::WriteAllText((Join-Path $output ('page-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8)
+ if ($Pdf) {
+ $pdfPath = Join-Path $output ('page-{0:d3}.pdf' -f $index)
+ $app.NavigateTo($id, '', $false)
+ $app.Publish($id, $pdfPath, 3, '')
+ if (-not (Test-Path $pdfPath) -or (Get-Item $pdfPath).Length -eq 0) { throw 'OneNote did not publish the page PDF.' }
+ }
+ [xml]$page = $pages[$id]
+ foreach ($file in $page.SelectNodes('//*[local-name()="InsertedFile" or local-name()="MediaFile"]')) {
+ $bytes = [IO.File]::ReadAllBytes($file.GetAttribute('pathCache'))
+ $hash = [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash($bytes)).Replace('-', '').ToLowerInvariant()
+ [IO.File]::WriteAllBytes((Join-Path $output ($hash + '.attachment')), $bytes)
+ $payloads += @{ page = $id; object = $file.ParentNode.GetAttribute('objectID');
+ kind = $file.LocalName; name = $file.GetAttribute('preferredName');
+ sha256 = $hash; bytes = $bytes.Length }
+ }
+ $index++
+ }
+ [IO.File]::WriteAllText((Join-Path $output 'payloads.json'), (ConvertTo-Json -InputObject $payloads -Depth 4), [Text.Encoding]::UTF8)
+ $all = ''
+ $app.GetHierarchy($notebookId, 4, [ref]$all, 1)
+ [xml]$finalTree = $all
+ $finalIds = @($finalTree.SelectNodes('//*[local-name()="Page"]') | ForEach-Object { $_.GetAttribute('ID') } | Sort-Object -Unique)
+ if ($finalIds.Count -ne $pages.Count -or @($finalIds | Where-Object { -not $pages.ContainsKey($_) }).Count -ne 0) {
+ throw 'The notebook hierarchy changed while collecting page evidence; repeat the cold read.'
+ }
+ [IO.File]::WriteAllText((Join-Path $output 'hierarchy.xml'), $all, [Text.Encoding]::UTF8)
+ Write-Output "Read $($sections.Count) sections and $($pages.Count) pages."
+} catch {
+ $failure = $_
+ [IO.File]::WriteAllText((Join-Path $output 'failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8)
+ throw
+} finally {
+ try {
+ try {
+ if ($notebookId -and $CloneHost) { $app.SyncHierarchy($notebookId) }
+ if ($notebookId -and -not $KeepOpen -and (-not $UseCurrentCache -or $CloneHost)) {
+ $app.CloseNotebook($notebookId, $false)
+ }
+ } catch {
+ if ($null -eq $failure) { throw }
+ [IO.File]::WriteAllText((Join-Path $output 'cleanup-failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8)
+ }
+ } finally {
+ [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
+ }
+ $app = $null
+ [GC]::Collect()
+ [GC]::WaitForPendingFinalizers()
+ if (-not $UseCurrentCache -and -not $CloneHost) {
+ Get-Process ONENOTE -ErrorAction SilentlyContinue | Wait-Process -Timeout 10
+ }
+}
diff --git a/corpus/extended-ascii/ja/cold/source.json b/corpus/extended-ascii/ja/cold/source.json
new file mode 100644
index 0000000000000000000000000000000000000000..741eec391e4355839f257cefd01dd18b8a7df53b
--- /dev/null
+++ b/corpus/extended-ascii/ja/cold/source.json
@@ -0,0 +1,14 @@
+[
+ {
+ "path": "Open Notebook.onetoc2",
+ "bytes": 4816,
+ "sha256": "b2f857961b76258a1582b402c048b340e89962257681426b3bfc58df8e0adbfa",
+ "mtime_ns": 1790487072903313695
+ },
+ {
+ "path": "synthetic.one",
+ "bytes": 12456,
+ "sha256": "b792b045a793ef97ded2a9d6ab456070fb5d9d3f48763067ed5e24bde482358a",
+ "mtime_ns": 1790487072903438737
+ }
+]
diff --git a/corpus/extended-ascii/ja/cold/teardown.json b/corpus/extended-ascii/ja/cold/teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/extended-ascii/ja/cold/teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/extended-ascii/ru/candidate/Open Notebook.onetoc2 b/corpus/extended-ascii/ru/candidate/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..a33d3bfd210f844e3db618e648ac898adeee09c6
Binary files /dev/null and b/corpus/extended-ascii/ru/candidate/Open Notebook.onetoc2 differ
diff --git a/corpus/extended-ascii/ru/candidate/synthetic.one b/corpus/extended-ascii/ru/candidate/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..45c40a62a8fa360a8ffe1cf02ed36a71ddbf91c0
Binary files /dev/null and b/corpus/extended-ascii/ru/candidate/synthetic.one differ
diff --git a/corpus/extended-ascii/ru/cold/commands.jsonl b/corpus/extended-ascii/ru/cold/commands.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..fd5f5a24943070a800781a23db1508f1cceaf637
--- /dev/null
+++ b/corpus/extended-ascii/ru/cold/commands.jsonl
@@ -0,0 +1,3 @@
+{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-2B8202A3 -ExpectedPages 1 -KeepOpen", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
diff --git a/corpus/extended-ascii/ru/cold/machine.json b/corpus/extended-ascii/ru/cold/machine.json
new file mode 100644
index 0000000000000000000000000000000000000000..c0c1f5fced309e8a81ce576ebbe21e6841635728
--- /dev/null
+++ b/corpus/extended-ascii/ru/cold/machine.json
@@ -0,0 +1 @@
+{"name": "m6-2b8202a3", "hostname": "ONE-M6-2B8202A3"}
diff --git a/corpus/extended-ascii/ru/cold/notebook/Open Notebook.onetoc2 b/corpus/extended-ascii/ru/cold/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..a33d3bfd210f844e3db618e648ac898adeee09c6
Binary files /dev/null and b/corpus/extended-ascii/ru/cold/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/extended-ascii/ru/cold/notebook/synthetic.one b/corpus/extended-ascii/ru/cold/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..45c40a62a8fa360a8ffe1cf02ed36a71ddbf91c0
Binary files /dev/null and b/corpus/extended-ascii/ru/cold/notebook/synthetic.one differ
diff --git a/corpus/extended-ascii/ru/cold/read/environment.json b/corpus/extended-ascii/ru/cold/read/environment.json
new file mode 100644
index 0000000000000000000000000000000000000000..7bebbff19e046fca4248fbe1866abe0aded53b6e
--- /dev/null
+++ b/corpus/extended-ascii/ru/cold/read/environment.json
@@ -0,0 +1,7 @@
+{
+ "powershell": "5.1.14409.1005",
+ "schema": "xs2010",
+ "hostname": "ONE-M6-2B8202A3",
+ "cold": true,
+ "onenote": "14.0.4763.1000"
+}
diff --git a/corpus/extended-ascii/ru/cold/read/hierarchy.xml b/corpus/extended-ascii/ru/cold/read/hierarchy.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4bacc530ba83fcb77bd446d98f5f7235a277ca21
--- /dev/null
+++ b/corpus/extended-ascii/ru/cold/read/hierarchy.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/extended-ascii/ru/cold/read/page-000.navigation.json b/corpus/extended-ascii/ru/cold/read/page-000.navigation.json
new file mode 100644
index 0000000000000000000000000000000000000000..f55849761016bf753db6dc26da07ab49553ce0a6
--- /dev/null
+++ b/corpus/extended-ascii/ru/cold/read/page-000.navigation.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Extended bytes: \u0080 \u0081 \u0082 \u0083 \u0084 \u0085 \u0086 \u0087 \u0088 \u0089 \u008a \u008b \u008c \u008d \u008e \u008f \u0090 - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/extended-ascii/ru/cold/read/page-000.png b/corpus/extended-ascii/ru/cold/read/page-000.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa6b6e3797f63f2489c730ddf74e2ee8fc18f279
Binary files /dev/null and b/corpus/extended-ascii/ru/cold/read/page-000.png differ
diff --git a/corpus/extended-ascii/ru/cold/read/page-000.xml b/corpus/extended-ascii/ru/cold/read/page-000.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c4553c063a192e9509c702e94a3b772ef09903b4
--- /dev/null
+++ b/corpus/extended-ascii/ru/cold/read/page-000.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/extended-ascii/ru/cold/read/payloads.json b/corpus/extended-ascii/ru/cold/read/payloads.json
new file mode 100644
index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843
--- /dev/null
+++ b/corpus/extended-ascii/ru/cold/read/payloads.json
@@ -0,0 +1,3 @@
+[
+
+]
\ No newline at end of file
diff --git a/corpus/extended-ascii/ru/cold/run.json b/corpus/extended-ascii/ru/cold/run.json
new file mode 100644
index 0000000000000000000000000000000000000000..ad6463bfc4bfb47f16fdb81a0794cba65ee794e6
--- /dev/null
+++ b/corpus/extended-ascii/ru/cold/run.json
@@ -0,0 +1,18 @@
+{
+ "notebook": "/private/tmp/ansi-lang/ru/notebook",
+ "expected_pages": 1,
+ "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"
+ }
+}
diff --git a/corpus/extended-ascii/ru/cold/scripts/cold.ps1 b/corpus/extended-ascii/ru/cold/scripts/cold.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..a0b99e7aeb86f4dc10032537800501a52ad65b4f
--- /dev/null
+++ b/corpus/extended-ascii/ru/cold/scripts/cold.ps1
@@ -0,0 +1,27 @@
+param([Parameter(Mandatory=$true)][string]$Root, [string]$CloneHost = '')
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
+if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
+ throw 'Choose a run directly below C:\one-tests\runs.'
+}
+if (Get-Process ONENOTE -ErrorAction SilentlyContinue) { throw 'Close OneNote before resetting its test cache.' }
+$key = 'HKCU:\Software\Microsoft\Office\14.0\OneNote'
+if ($CloneHost) {
+ if ($CloneHost -notmatch '^ONE-[A-Z0-9-]+$' -or [Environment]::MachineName -ne $CloneHost) {
+ throw 'The disposable clone hostname does not match this machine.'
+ }
+ New-Item "$key\Options\Paths" -Force | Out-Null
+ New-ItemProperty "$key\Options\Paths" -Name UnfiledNotesSection -PropertyType ExpandString -Value 'C:\one-tests\Loose.one' -Force | Out-Null
+} elseif ((Get-ItemProperty "$key\Options\Paths").UnfiledNotesSection -ne 'C:\one-tests\Loose.one' -or
+ -not (Test-Path 'C:\one-tests\profile-original-cache')) {
+ throw 'Park the personal OneNote profile before resetting the test cache.'
+}
+$cache = Join-Path $env:LOCALAPPDATA 'Microsoft\OneNote\14.0'
+$parked = Join-Path 'C:\one-tests\caches' ([IO.Path]::GetFileName($root))
+if (Test-Path $parked) { throw 'Choose a new run; its parked cache already exists.' }
+New-Item -ItemType Directory -Path 'C:\one-tests\caches' -Force | Out-Null
+if (Test-Path $cache) { Move-Item -LiteralPath $cache -Destination $parked }
+if (Test-Path "$key\OpenNotebooks") { Remove-Item "$key\OpenNotebooks" -Recurse }
+New-Item "$key\OpenNotebooks" | Out-Null
+New-ItemProperty "$key\OpenNotebooks" -Name '1' -PropertyType String -Value "$root\notebook" | Out-Null
diff --git a/corpus/extended-ascii/ru/cold/scripts/read.ps1 b/corpus/extended-ascii/ru/cold/scripts/read.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..476094ab1e0c47077988a6461db2080cf208642b
--- /dev/null
+++ b/corpus/extended-ascii/ru/cold/scripts/read.ps1
@@ -0,0 +1,142 @@
+param(
+ [Parameter(Mandatory=$true)][string]$Root,
+ [int]$ExpectedPages = -1,
+ [switch]$UseCurrentCache,
+ [switch]$Pdf,
+ [switch]$KeepOpen,
+ [string]$CloneHost = ''
+)
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
+if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
+ throw 'Choose a run directly below C:\one-tests\runs.'
+}
+$notebook = Join-Path $root 'notebook'
+$output = Join-Path $root 'read'
+if (Test-Path $output) { throw 'Choose a new read destination.' }
+if ($UseCurrentCache) {
+ if ((Get-ItemProperty 'HKCU:\Software\Microsoft\Office\14.0\OneNote\Options\Paths').UnfiledNotesSection -ne 'C:\one-tests\Loose.one') {
+ throw 'Park the personal OneNote profile before reading test notebooks.'
+ }
+} else {
+ & "$PSScriptRoot\cold-current.ps1" -Root $root -CloneHost $CloneHost
+}
+New-Item -ItemType Directory -Path $output | Out-Null
+$app = New-Object -ComObject OneNote.Application
+$notebookId = ''
+$failure = $null
+try {
+ $app.OpenHierarchy($notebook, '', [ref]$notebookId, 0)
+ $process = Get-Process ONENOTE
+ @{ hostname = [Environment]::MachineName; onenote = $process.MainModule.FileVersionInfo.FileVersion;
+ powershell = $PSVersionTable.PSVersion.ToString(); schema = 'xs2010'; cold = (-not $UseCurrentCache.IsPresent) } |
+ ConvertTo-Json | Set-Content (Join-Path $output 'environment.json') -Encoding UTF8
+ $sections = @()
+ foreach ($file in @(Get-ChildItem $notebook -Recurse | Where-Object { $_.Extension -eq '.one' })) {
+ $id = ''
+ $app.OpenHierarchy($file.FullName, '', [ref]$id, 0)
+ $sections += $id
+ }
+ $deadline = [DateTime]::UtcNow.AddSeconds(300)
+ $previous = ''
+ $lastChange = ''
+ $stableSince = [DateTime]::UtcNow
+ $settled = $false
+ do {
+ $pages = @{}
+ foreach ($section in $sections) {
+ $hierarchy = ''
+ $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
+ [xml]$xml = $hierarchy
+ foreach ($node in $xml.SelectNodes('//*[@path]')) {
+ if (-not $node.GetAttribute('path').StartsWith("$notebook\", [StringComparison]::OrdinalIgnoreCase)) {
+ throw 'OneNote opened a section outside the copied notebook.'
+ }
+ }
+ foreach ($node in $xml.SelectNodes('//*[local-name()="Page"]')) {
+ $id = $node.GetAttribute('ID')
+ $content = ''
+ $app.GetPageContent($id, [ref]$content, 1, 1)
+ $pages[$id] = $content
+ }
+ }
+ $signature = [String]::Join('|', @($pages.Keys | Sort-Object | ForEach-Object { $_ + $pages[$_] }))
+ if ($signature -ne $previous) {
+ $lastChange = $previous
+ $previous = $signature
+ $stableSince = [DateTime]::UtcNow
+ }
+ if ((($ExpectedPages -ge 0 -and $pages.Count -eq $ExpectedPages) -or
+ ($ExpectedPages -lt 0 -and $pages.Count -gt 0)) -and
+ ([DateTime]::UtcNow - $stableSince).TotalSeconds -ge 2) { $settled = $true; break }
+ Start-Sleep -Milliseconds 250
+ } while ([DateTime]::UtcNow -lt $deadline)
+ if (-not $settled -or ($ExpectedPages -ge 0 -and $pages.Count -ne $ExpectedPages) -or ($ExpectedPages -lt 0 -and $pages.Count -eq 0)) {
+ [IO.File]::WriteAllText((Join-Path $output 'previous-signature.txt'), $lastChange, [Text.Encoding]::UTF8)
+ $index = 0
+ foreach ($id in @($pages.Keys | Sort-Object)) {
+ [IO.File]::WriteAllText((Join-Path $output ('unsettled-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8)
+ $index++
+ }
+ $hierarchy = ''
+ $app.GetHierarchy($notebookId, 4, [ref]$hierarchy, 1)
+ [IO.File]::WriteAllText((Join-Path $output 'unsettled-hierarchy.xml'), $hierarchy, [Text.Encoding]::UTF8)
+ throw "Expected $ExpectedPages stable pages; OneNote returned $($pages.Count), settled=$settled."
+ }
+ $index = 0
+ $payloads = @()
+ foreach ($id in @($pages.Keys | Sort-Object)) {
+ [IO.File]::WriteAllText((Join-Path $output ('page-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8)
+ if ($Pdf) {
+ $pdfPath = Join-Path $output ('page-{0:d3}.pdf' -f $index)
+ $app.NavigateTo($id, '', $false)
+ $app.Publish($id, $pdfPath, 3, '')
+ if (-not (Test-Path $pdfPath) -or (Get-Item $pdfPath).Length -eq 0) { throw 'OneNote did not publish the page PDF.' }
+ }
+ [xml]$page = $pages[$id]
+ foreach ($file in $page.SelectNodes('//*[local-name()="InsertedFile" or local-name()="MediaFile"]')) {
+ $bytes = [IO.File]::ReadAllBytes($file.GetAttribute('pathCache'))
+ $hash = [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash($bytes)).Replace('-', '').ToLowerInvariant()
+ [IO.File]::WriteAllBytes((Join-Path $output ($hash + '.attachment')), $bytes)
+ $payloads += @{ page = $id; object = $file.ParentNode.GetAttribute('objectID');
+ kind = $file.LocalName; name = $file.GetAttribute('preferredName');
+ sha256 = $hash; bytes = $bytes.Length }
+ }
+ $index++
+ }
+ [IO.File]::WriteAllText((Join-Path $output 'payloads.json'), (ConvertTo-Json -InputObject $payloads -Depth 4), [Text.Encoding]::UTF8)
+ $all = ''
+ $app.GetHierarchy($notebookId, 4, [ref]$all, 1)
+ [xml]$finalTree = $all
+ $finalIds = @($finalTree.SelectNodes('//*[local-name()="Page"]') | ForEach-Object { $_.GetAttribute('ID') } | Sort-Object -Unique)
+ if ($finalIds.Count -ne $pages.Count -or @($finalIds | Where-Object { -not $pages.ContainsKey($_) }).Count -ne 0) {
+ throw 'The notebook hierarchy changed while collecting page evidence; repeat the cold read.'
+ }
+ [IO.File]::WriteAllText((Join-Path $output 'hierarchy.xml'), $all, [Text.Encoding]::UTF8)
+ Write-Output "Read $($sections.Count) sections and $($pages.Count) pages."
+} catch {
+ $failure = $_
+ [IO.File]::WriteAllText((Join-Path $output 'failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8)
+ throw
+} finally {
+ try {
+ try {
+ if ($notebookId -and $CloneHost) { $app.SyncHierarchy($notebookId) }
+ if ($notebookId -and -not $KeepOpen -and (-not $UseCurrentCache -or $CloneHost)) {
+ $app.CloseNotebook($notebookId, $false)
+ }
+ } catch {
+ if ($null -eq $failure) { throw }
+ [IO.File]::WriteAllText((Join-Path $output 'cleanup-failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8)
+ }
+ } finally {
+ [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
+ }
+ $app = $null
+ [GC]::Collect()
+ [GC]::WaitForPendingFinalizers()
+ if (-not $UseCurrentCache -and -not $CloneHost) {
+ Get-Process ONENOTE -ErrorAction SilentlyContinue | Wait-Process -Timeout 10
+ }
+}
diff --git a/corpus/extended-ascii/ru/cold/source.json b/corpus/extended-ascii/ru/cold/source.json
new file mode 100644
index 0000000000000000000000000000000000000000..c9a2d1f495428e88c662543b6ae55467d85b3f88
--- /dev/null
+++ b/corpus/extended-ascii/ru/cold/source.json
@@ -0,0 +1,14 @@
+[
+ {
+ "path": "Open Notebook.onetoc2",
+ "bytes": 4816,
+ "sha256": "b2f857961b76258a1582b402c048b340e89962257681426b3bfc58df8e0adbfa",
+ "mtime_ns": 1790487072902780320
+ },
+ {
+ "path": "synthetic.one",
+ "bytes": 12456,
+ "sha256": "3d9562625271091ff5c85c24325ab2bf12a670e81aab2caffb6b00c724d7e19f",
+ "mtime_ns": 1790487072903016820
+ }
+]
diff --git a/corpus/extended-ascii/ru/cold/teardown.json b/corpus/extended-ascii/ru/cold/teardown.json
new file mode 100644
index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70
--- /dev/null
+++ b/corpus/extended-ascii/ru/cold/teardown.json
@@ -0,0 +1 @@
+{"absent": true}
diff --git a/corpus/m6/native-template-controls-01/ORACLE.md b/corpus/m6/native-template-controls-01/ORACLE.md
index 1398b8768f069f10b066deef297000096e66ef90..0b45ecc81398e040e4768509d5e6da38c37ab811 100644
--- a/corpus/m6/native-template-controls-01/ORACLE.md
+++ b/corpus/m6/native-template-controls-01/ORACLE.md
@@ -1,7 +1,9 @@
# Native default page template and unaligned image container
-The captured UI operations selected Hearts from the native Templates pane, selected Hearts - Decorative as the section default, and created a new page. The default background appeared automatically; `default-template.png` and `ui-operations.json` record that result. `read/` contains the three native page exports and PDFs. The clone was deleted.
+The captured UI operations selected Hearts from the native Templates pane, selected Hearts - Decorative as the section default, and created a new page. The default background appeared automatically; `default-template.png` and `ui-operations.json` record that result. The clone was deleted.
The section property 0x2c001d62 refers to a separate page object space whose role-2 metadata has JCID 0x2003e. Property 0x1c001ca5 contains the UTF-16 template name Hearts. These native template fields are not in the published MS-ONE object list; the typed model identifies them as observed native metadata. The original personal notebook uses the same relationship with the name Ivy.
-The embedded image object starts at byte 11396, four bytes past an eight-byte file boundary. Its payload is 10056 bytes; the footer begins at 21492 after four padding bytes. Native XML exports the same image bytes (SHA-256 2035407a0540e1c4f7934db08ba4add750fcb9a62863ddd9553e7871c81a99e3). MS-ONESTORE 2.6.13 pads the object length, independent of its file position. This fixture exposed the reader's incorrect absolute-offset alignment. The regression relocates the object through all eight offsets and separately corrupts its footer.
+The embedded image object starts at byte 11396, four bytes past an eight-byte file boundary. Its payload is 10056 bytes; the footer begins at 21492 after four padding bytes. MS-ONESTORE 2.6.13 pads the object length, independent of its file position. This fixture exposed the reader's incorrect absolute-offset alignment. The regression relocates the object through all eight offsets and separately corrupts its footer.
+
+The stored picture is Snowbound's rendering of Hearts (SHA-256 6a0593db927c1a34a94cf11093e5bd8700843323ed4fe8c905df0c595cb09c26), written over Microsoft's at the same length with a PNG comment as padding, so the file is otherwise native. `read/` is a cold OneNote read of that notebook, whose XML exports the same image bytes; `default-template.png` still shows Microsoft's art as OneNote drew it during the capture.
diff --git a/corpus/m6/native-template-controls-01/notebook/synthetic.one b/corpus/m6/native-template-controls-01/notebook/synthetic.one
index cebce593d432621558945ae7b5a7c38df5f0edd9..f4383ebafc3d7ba7debaeb1bd0b749220679923b 100644
Binary files a/corpus/m6/native-template-controls-01/notebook/synthetic.one and b/corpus/m6/native-template-controls-01/notebook/synthetic.one differ
diff --git a/corpus/m6/native-template-controls-01/read/environment.json b/corpus/m6/native-template-controls-01/read/environment.json
index 00b1c91c33877bb34f0dc2cf539d0a5a33ea3403..5f136cd4ef1353e4b1f1e9c219aef40a69ad2a86 100644
--- a/corpus/m6/native-template-controls-01/read/environment.json
+++ b/corpus/m6/native-template-controls-01/read/environment.json
@@ -1,7 +1,7 @@
{
"powershell": "5.1.14409.1005",
"schema": "xs2010",
- "hostname": "ONE-M6-A33C1CD2",
- "cold": false,
+ "hostname": "ONE-M6-F782B029",
+ "cold": true,
"onenote": "14.0.4763.1000"
}
diff --git a/corpus/m6/native-template-controls-01/read/hierarchy.xml b/corpus/m6/native-template-controls-01/read/hierarchy.xml
index ec464dcc8c5f9366feae4ca0516afde08819b5fc..a55855c144608eded7f667f6382bf012aa5ead4c 100644
--- a/corpus/m6/native-template-controls-01/read/hierarchy.xml
+++ b/corpus/m6/native-template-controls-01/read/hierarchy.xml
@@ -1,2 +1,2 @@
-
+
diff --git a/corpus/m6/native-template-controls-01/read/page-000.pdf b/corpus/m6/native-template-controls-01/read/page-000.pdf
index 6de9701a0cc8f7ec5a0296425c0521a3b8b0db5b..683072610667c97b348d7454745f6ddc75d03733 100644
Binary files a/corpus/m6/native-template-controls-01/read/page-000.pdf and b/corpus/m6/native-template-controls-01/read/page-000.pdf differ
diff --git a/corpus/m6/native-template-controls-01/read/page-000.xml b/corpus/m6/native-template-controls-01/read/page-000.xml
index f790c5cdcb93e85fafeaa6348f8119a9e748dfbf..36138ba823e2dbcc3096ab3ca02ed671b81ac8a7 100644
--- a/corpus/m6/native-template-controls-01/read/page-000.xml
+++ b/corpus/m6/native-template-controls-01/read/page-000.xml
@@ -1,179 +1,2 @@
-iVBORw0KGgoAAAANSUhEUgAAAKsAAAIoCAMAAAAhjet0AAADAFBMVEX////d0dncvM/S6+W+4qyw
-3t7akbqZ06ir2qrewNLB5OHcxtTr9/fp1eH9+/z9+fvcmL7tyN274avlzdu44d/cvc/jydjP6uTE
-5ub25O779/n58/bbscncw9Lbuc2f1q3Y7++03quy3t6i1qncztez3r7gxNXm9Oq14ODu3ufbpsTJ
-5+Pcz9jak7v6/f327vPcwdHcydXam7/F5uLjrcvy5u35/PrcvtDcy9bL6OPO6uq/5OT1+/vczdf7
-8ve24N/cv9D46/L06vDcxdPw4uqf1aiq0rS84uDbtsyb06jfn8Os27jw+fm64uLhpsew3KrM6dTa
-lrzaocHD5eGm2Knamb20397outTZ79/x1uXbq8fbs8vs9+/n0d7z+vTi8/Pqwdjr2eTcytXcwtK/
-5MnN6eTT7Ozd8fHansCy3aq64uC54Kvcx9S336um2bPao8Ok16n03erbrsjbqcWp2anG5s7H5uK7
-0sDJ6Oid1Kjf8eTp9fLn9fW+4+Gt26q54cPvz+HmtNDM0cyd0qvZvs/U0dLJobXQ0c+y293Y0dW/
-0cPOxtOu0rey0rr0+vnTpbbA0djYlrnd8OzT7NnRnLfLyNS12dy419u71drH0cni8u7SwtHRxNKh
-0q7Gzda20r3m9PDY7ujD0cb8/v22yK7h1t3KvrLg8vDWwNDDz9el0rG9rbLNnbb7/frexNTIw7HP
-r7TMubPV7Of7+fq909m2yLnq4uepzbHPpMPWm7i/3Ky6srHFza/d2eSxuq6dzqnn9OC+17LGyLDb
-79Du4Oju5+vKsMi8yrzIytXL6Mru+PW1tq/Gq7Ot267A47r9/P273cTewtPk0dzf1Nvh4uDfq8Xy
-7vHp2OLy6e7M6eiq1Kr6/fzFytXQ6sXz+vDr9vTVqsbZ3dm02KvS7OnY1+HD0q7B16339PbBqbPa
-7+rCxMDXm77T7Oeu3LjYydLL6ejQpLvKuc3G5snXp7za1dfx+ffgzdne0dq94rCy2dvE2t+lxqvA
-ysDRqrXNtLPU7OkRzzSdAAAAAWJLR0QAiAUdSAAAAAxjbVBQSkNtcDA3MTIAAAADSABzvAAAI95J
-REFUeF7NnXdgFMe9x/eW050k1AtCgBoCgYSECiAJ05Gw6YhmejXVgOk2YGxckzixndhOc3p7qS8v
-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
-NkH/HxtpiBH77U/NAAAAAElFTkSuQmCC
-
+
diff --git a/corpus/m6/native-template-controls-01/read/page-001.pdf b/corpus/m6/native-template-controls-01/read/page-001.pdf
index 0ac8ed808bfd97720ab024f2ebba2dee3cfb7589..11040ee567fdefb86570164433198d26730369f1 100644
Binary files a/corpus/m6/native-template-controls-01/read/page-001.pdf and b/corpus/m6/native-template-controls-01/read/page-001.pdf differ
diff --git a/corpus/m6/native-template-controls-01/read/page-001.xml b/corpus/m6/native-template-controls-01/read/page-001.xml
index ac17667115fd7991586c55deaa29fafb18625258..389fbd0fe6d35248578195aec4831642d6ccba71 100644
--- a/corpus/m6/native-template-controls-01/read/page-001.xml
+++ b/corpus/m6/native-template-controls-01/read/page-001.xml
@@ -1,179 +1,179 @@
-iVBORw0KGgoAAAANSUhEUgAAAKsAAAIoCAMAAAAhjet0AAADAFBMVEX////d0dncvM/S6+W+4qyw
-3t7akbqZ06ir2qrewNLB5OHcxtTr9/fp1eH9+/z9+fvcmL7tyN274avlzdu44d/cvc/jydjP6uTE
-5ub25O779/n58/bbscncw9Lbuc2f1q3Y7++03quy3t6i1qncztez3r7gxNXm9Oq14ODu3ufbpsTJ
-5+Pcz9jak7v6/f327vPcwdHcydXam7/F5uLjrcvy5u35/PrcvtDcy9bL6OPO6uq/5OT1+/vczdf7
-8ve24N/cv9D46/L06vDcxdPw4uqf1aiq0rS84uDbtsyb06jfn8Os27jw+fm64uLhpsew3KrM6dTa
-lrzaocHD5eGm2Knamb20397outTZ79/x1uXbq8fbs8vs9+/n0d7z+vTi8/Pqwdjr2eTcytXcwtK/
-5MnN6eTT7Ozd8fHansCy3aq64uC54Kvcx9S336um2bPao8Ok16n03erbrsjbqcWp2anG5s7H5uK7
-0sDJ6Oid1Kjf8eTp9fLn9fW+4+Gt26q54cPvz+HmtNDM0cyd0qvZvs/U0dLJobXQ0c+y293Y0dW/
-0cPOxtOu0rey0rr0+vnTpbbA0djYlrnd8OzT7NnRnLfLyNS12dy419u71drH0cni8u7SwtHRxNKh
-0q7Gzda20r3m9PDY7ujD0cb8/v22yK7h1t3KvrLg8vDWwNDDz9el0rG9rbLNnbb7/frexNTIw7HP
-r7TMubPV7Of7+fq909m2yLnq4uepzbHPpMPWm7i/3Ky6srHFza/d2eSxuq6dzqnn9OC+17LGyLDb
-79Du4Oju5+vKsMi8yrzIytXL6Mru+PW1tq/Gq7Ot267A47r9/P273cTewtPk0dzf1Nvh4uDfq8Xy
-7vHp2OLy6e7M6eiq1Kr6/fzFytXQ6sXz+vDr9vTVqsbZ3dm02KvS7OnY1+HD0q7B16339PbBqbPa
-7+rCxMDXm77T7Oeu3LjYydLL6ejQpLvKuc3G5snXp7za1dfx+ffgzdne0dq94rCy2dvE2t+lxqvA
-ysDRqrXNtLPU7OkRzzSdAAAAAWJLR0QAiAUdSAAAAAxjbVBQSkNtcDA3MTIAAAADSABzvAAAI95J
-REFUeF7NnXdgFMe9x/eW050k1AtCgBoCgYSECiAJ05Gw6YhmejXVgOk2YGxckzixndhOc3p7qS8v
-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
-NkH/HxtpiBH77U/NAAAAAElFTkSuQmCC
+iVBORw0KGgoAAAANSUhEUgAAAKsAAAIoCAMAAAAhjet0AAADAFBMVEUAAAABAQECAgIDAwMXHhna
+kbrakbrakbrakbrakbrakbrakbrakbrakbrakrvakrvblr3bmr/TnLjbnMDcn8LdosPPo7bdpsXd
+q8jdr8ncssqEtpHdt83duc7du8/evNDdvNDcvM/cvM/cvM/cvM/cvM/cvM/cvM/cvM/cvM/cvM/c
+vM/cvc/cvc/avc/TvtDTv9DTv9DUv9DYwNHbwNHcwNHcwdLewdLcwtLdw9PdxNPdxtS6yK7dyNXd
+ydXeydbgydfizNrnzd3lztzd0Nnd0Nnd0Njd0Nnd0Nnd0Nnd0Nno0d/d0dnd0dnd0dnd0dnd0dnd
+0dnd0dnd0dnd0dnd0dnd0dmY0qeh0q2Z06iZ06iZ06iZ06iZ06iZ06iZ06iZ06iZ06ia06ie06mZ
+06ig06uk1LTq1OHh1Nyc1Kib1Kie1ana1duq1sHt1+Tl1+Dr2+Ss287u3Oex3Kux3Ku03auz3auw
+3t6w3t6w3t7v3uiw3t6w3t6w3t6x3t6x3t6w3t6w3t6w3t6w3t7V3uC938i737+937m336uy396z
+39+939W9387U3+C24N/y4Ory4Oq64Ku54Ku/4La/4LS94Ni44d+84ay94ay64uC+4qy+4qy74uC8
+4uC94q694q++4qy+4qy+4qy+4qy+4qy+4qzy4uu+4ty+4qy+4q2+4qy+4qy+4+C+4+K+49+94+G/
+5OHQ5OPN6eXN6eLN6ebP6uXR6+TS6+LS6+XS6+XS6+XS6+XS6+XS6+XS6+XS6+XS6+XT7OHT7N/T
+7N/T7ODT7OLT7OLT7OTT7OLX7ujc8Ovl8/Hm9PHn9PPp9fTs9vXy9vfw9/fv9/f09/j39/j5+vr7
+/fz9/f39/f39/f78/v79/v79/v79/v79/v79/v79/v78/v79/v79/v79/v7/////////////////
+////////////////////////////////////////////////////////////////////////////
+//////////////+3cRl2AAAAGXRFWHRTbm93Ym91bmQgdGVtcGxhdGUAaGVhcnRzVrgCNgAAGoNJ
+REFUeNrt3btPI1uaAHBSR+AxWhsEInLoXd0AjMwFDNeZI0czEzkCjahtQFhNYMlkyyKkJeAO9BJc
+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
+kL0MGX+b0JsCkHuaCJ1rGWSfkLBJtIHRrM6NbW5rHAm74trSBD2HKeE2K+j5FnASGKdGWxqElVBh
+gacEq1nPJ22Cnh2zJkHo129mPc0qYB2In24UIjIgPwCyAhwZrBRCqd5bWgLWjnDDrgR+g0n1XtUD
+vvPE1rD6AmWF+B3mnf7iANBK6F1sKxmjp+f8p7jWnU0fVew+YUHw/Ni6I7BaIjQqgQp+pzRXYrbq
+2vG/YWnduupSBL6jRzyRY9mDcyZPYW1jfa1g39A7ID1qIGYlXdOjP5Y9kEnPKGWzQbfhBd+3Qbqq
+ST0tkDV1iyTdovzsetbBKy+QssZg3/FptwIf4hF9jwnxwQ067F+GeXToKCf5sA8qEH7nCikLlku0
+zWqFKh8dbG1tHcpCz8NQZEGRs2UPmH+V+LtseqTOnIvuYO/fwEp+3CyydHF8gAbEO4LI2JVSeL1i
+j/8H9g/ABkdaki4AAAlPdEVYdENvbW1lbnQAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICBmyrzgAAAAAElFTkSuQmCC
diff --git a/corpus/m6/native-template-controls-01/read/page-002.pdf b/corpus/m6/native-template-controls-01/read/page-002.pdf
index 89fb7d27fbbdb4a03794effd961c4b799aec3ba8..954a2453d2cd802238876a912ee02bc70a42ff6f 100644
Binary files a/corpus/m6/native-template-controls-01/read/page-002.pdf and b/corpus/m6/native-template-controls-01/read/page-002.pdf differ
diff --git a/corpus/m6/native-template-controls-01/read/page-002.xml b/corpus/m6/native-template-controls-01/read/page-002.xml
index e44203ad99d6e09579fd00769eadf953830dd469..21905ddc3b9598c677be8fa3e97c2a8e680b0db9 100644
--- a/corpus/m6/native-template-controls-01/read/page-002.xml
+++ b/corpus/m6/native-template-controls-01/read/page-002.xml
@@ -1,2 +1,179 @@
-
+iVBORw0KGgoAAAANSUhEUgAAAKsAAAIoCAMAAAAhjet0AAADAFBMVEUAAAABAQECAgIDAwMXHhna
+kbrakbrakbrakbrakbrakbrakbrakbrakbrakrvakrvblr3bmr/TnLjbnMDcn8LdosPPo7bdpsXd
+q8jdr8ncssqEtpHdt83duc7du8/evNDdvNDcvM/cvM/cvM/cvM/cvM/cvM/cvM/cvM/cvM/cvM/c
+vM/cvc/cvc/avc/TvtDTv9DTv9DUv9DYwNHbwNHcwNHcwdLewdLcwtLdw9PdxNPdxtS6yK7dyNXd
+ydXeydbgydfizNrnzd3lztzd0Nnd0Nnd0Njd0Nnd0Nnd0Nnd0Nno0d/d0dnd0dnd0dnd0dnd0dnd
+0dnd0dnd0dnd0dnd0dnd0dmY0qeh0q2Z06iZ06iZ06iZ06iZ06iZ06iZ06iZ06iZ06ia06ie06mZ
+06ig06uk1LTq1OHh1Nyc1Kib1Kie1ana1duq1sHt1+Tl1+Dr2+Ss287u3Oex3Kux3Ku03auz3auw
+3t6w3t6w3t7v3uiw3t6w3t6w3t6x3t6x3t6w3t6w3t6w3t6w3t7V3uC938i737+937m336uy396z
+39+939W9387U3+C24N/y4Ory4Oq64Ku54Ku/4La/4LS94Ni44d+84ay94ay64uC+4qy+4qy74uC8
+4uC94q694q++4qy+4qy+4qy+4qy+4qy+4qzy4uu+4ty+4qy+4q2+4qy+4qy+4+C+4+K+49+94+G/
+5OHQ5OPN6eXN6eLN6ebP6uXR6+TS6+LS6+XS6+XS6+XS6+XS6+XS6+XS6+XS6+XS6+XT7OHT7N/T
+7N/T7ODT7OLT7OLT7OTT7OLX7ujc8Ovl8/Hm9PHn9PPp9fTs9vXy9vfw9/fv9/f09/j39/j5+vr7
+/fz9/f39/f39/f78/v79/v79/v79/v79/v79/v79/v78/v79/v79/v79/v7/////////////////
+////////////////////////////////////////////////////////////////////////////
+//////////////+3cRl2AAAAGXRFWHRTbm93Ym91bmQgdGVtcGxhdGUAaGVhcnRzVrgCNgAAGoNJ
+REFUeNrt3btPI1uaAHBSR+AxWhsEInLoXd0AjMwFDNeZI0czEzkCjahtQFhNYMlkyyKkJeAO9BJc
+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
+kL0MGX+b0JsCkHuaCJ1rGWSfkLBJtIHRrM6NbW5rHAm74trSBD2HKeE2K+j5FnASGKdGWxqElVBh
+gacEq1nPJ22Cnh2zJkHo129mPc0qYB2In24UIjIgPwCyAhwZrBRCqd5bWgLWjnDDrgR+g0n1XtUD
+vvPE1rD6AmWF+B3mnf7iANBK6F1sKxmjp+f8p7jWnU0fVew+YUHw/Ni6I7BaIjQqgQp+pzRXYrbq
+2vG/YWnduupSBL6jRzyRY9mDcyZPYW1jfa1g39A7ID1qIGYlXdOjP5Y9kEnPKGWzQbfhBd+3Qbqq
+ST0tkDV1iyTdovzsetbBKy+QssZg3/FptwIf4hF9jwnxwQ067F+GeXToKCf5sA8qEH7nCikLlku0
+zWqFKh8dbG1tHcpCz8NQZEGRs2UPmH+V+LtseqTOnIvuYO/fwEp+3CyydHF8gAbEO4LI2JVSeL1i
+j/8H9g/ABkdaki4AAAlPdEVYdENvbW1lbnQAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICBmyrzgAAAAAElFTkSuQmCC
+
diff --git a/corpus/outline-edit/README.md b/corpus/outline-edit/README.md
index 92f9b0d0a9aba3b94b0dedcaddeeab5a1b48d6df..42b3c1c4fe77b26dd0d0d650724244199eef7875 100644
--- a/corpus/outline-edit/README.md
+++ b/corpus/outline-edit/README.md
@@ -46,7 +46,7 @@ The controller selects the target again after activating the native window and
transfers XML as UTF-8 data. Captured authoring scripts retain their exact source;
identical immutable artifacts link to one canonical corpus copy.
-`layout/candidate` applies `PreparedEdit::outline` to the five geometry and saved
+`layout/candidate` applies Rust outline edits to the five geometry and saved
collapse cases in `before`. `layout/cold` captures that Rust output through a fresh
OneNote cache. Every active identity, child/content/structure reference, child level,
and collapse default survives; text, fields, tags and explicit formatting compare
diff --git a/corpus/page-model/README.md b/corpus/page-model/README.md
index c1307cf93d91157ea6b88a82ede89a3b253f182d..6445e48b1851d4f3aa2066d2641789c74d1609ea 100644
--- a/corpus/page-model/README.md
+++ b/corpus/page-model/README.md
@@ -1,6 +1,6 @@
# Page-model edits through a cold native reopen
-`candidate` is `corpus/outline-edit/before` after eight compound `PreparedEdit::page`
+`candidate` is `corpus/outline-edit/before` after eight compound page-model
publications, one per edited page, each combining several kinds of change in a single
transaction: paragraph reorder and Unicode replacement with a bold range; a subtree
move with a new nested child and an italic/colored/resized range; a subtree deletion
diff --git a/corpus/paragraph-edit/README.md b/corpus/paragraph-edit/README.md
index 13ebe199e69cb9b6431fcd8a29d23a249168f47b..652844d8e69cde9043718ec511614cebed57fcc4 100644
--- a/corpus/paragraph-edit/README.md
+++ b/corpus/paragraph-edit/README.md
@@ -26,7 +26,7 @@ notebook before completing inspection. Cold-open the split notebook for the join
pass. Captured COM IDs belong to their originating sessions; discover fresh IDs
when regenerating the corpus.
-`rust-split` retains twelve `ParagraphSplit` outputs, their cold native captures,
+`rust-split` retains twelve Rust paragraph-split outputs, their cold native captures,
and a second native session typing into four empty boundary paragraphs. The
hyperlink case has no output: the operation rejected fields when this row was
captured, and the writer now splits before a link as OneNote does. The Rust
diff --git a/corpus/protected-edit/README.md b/corpus/protected-edit/README.md
index 21d294a63600c317ebb157826b4e0d042aeecc67..bc7dc43bc32e6c0817ecf781b64e2f6c3445e44a 100644
--- a/corpus/protected-edit/README.md
+++ b/corpus/protected-edit/README.md
@@ -1,7 +1,7 @@
# Protected page edit
`candidate/notebook` is `native-encrypted/encrypted-01` after
-`Notebook::save_unlocked` appended text to the first paragraph and added a
+a Rust edit of the unlocked page appended text to the first paragraph and added a
paragraph, exported by `an_unlocked_page_is_saved_under_the_section_key`
(`ONESTORE_PROTECTED_EXPORT`). `candidate/read` is OneNote 2010's COM read from a
fresh clone with an empty cache after unlocking the section in its UI with the
diff --git a/corpus/table-edit/README.md b/corpus/table-edit/README.md
index 24f1e88e56ac8043ec15f6b143fd8f39a5785ec6..5c413768c060e666a9906f88746c6aa4d26685f0 100644
--- a/corpus/table-edit/README.md
+++ b/corpus/table-edit/README.md
@@ -27,7 +27,7 @@ modification time, the indent array copied from a sibling cell (OneNote's
default without one), and the flags every native cell carries, rebuilds each table's row and cell order to the
model's, and writes the column count, widths, locks and border flag. New
tables start as an empty paragraph whose content the structure pass replaces.
-An emptied cell keeps a replacement paragraph, as the tree writer provides.
+An edit that ends with an empty cell is refused; its ops refill the cell themselves.
`tools/test_table_edit.py` checks all three captures without a VM. Regenerate
with `ONESTORE_TABLE_EXPORT`, `ONESTORE_TABLE_EDIT_EXPORT` and
`ONESTORE_NESTED_TABLE_EXPORT` set to new absolute directories while running
diff --git a/crates/notebook/Cargo.toml b/crates/notebook/Cargo.toml
index 798548ff3a48fc39ae90b621a5de01e91b23c191..be20d9945a195e6b3cecd1af67c01d0e0a5df09a 100644
--- a/crates/notebook/Cargo.toml
+++ b/crates/notebook/Cargo.toml
@@ -36,3 +36,7 @@ required-features = ["smb"]
[[example]]
name = "smb_concurrent_client"
required-features = ["smb"]
+
+[[example]]
+name = "smb_publish_probe"
+required-features = ["smb"]
diff --git a/crates/notebook/README.md b/crates/notebook/README.md
index aca6350886a28468c7c89088df71beb923831914..dd79930b674765da96546172bb80dfbba09d5144 100644
--- a/crates/notebook/README.md
+++ b/crates/notebook/README.md
@@ -11,7 +11,7 @@ each by hash. Local success does not acknowledge publication to a shared noteboo
editor ── Edit ──► section thread ─ Section::apply, INSERT edit ─┐
(the parsed section: base + sealed + open) │ one fsync per burst
sync thread: remote.stamp() == base ? publish(sealed batch) ──────┤ base chunks += transaction
- otherwise read once, replay the queue on it ─────────┘ conflict, or base := remote
+ otherwise read once, replay the queue on it ─────────┘ base := remote, conflict pages queued
```
## Edits
@@ -56,15 +56,11 @@ same queue after a relaunch. A section is `Send + Sync`: `apply(author, edit)`
returns at once and reports a refusal as `Event::Rejected`; `events()` drains
remote changes (`Changed(spaces)`), publication outcomes, unreachable files and
failures; `notify` runs on a background thread whenever an event waits.
-`conflict()`, `remote_page(space)` and `resolve(id, Resolution)` present and end a
-conflict; `release(id, archive, Resolution)` ends an uncertain attempt.
+`conflicts()` lists each page's conflict pages, which `page` reads and `delete_pages`
+removes; `release(id, archive, Resolution)` ends an uncertain attempt. The author an
+edit names is the host's: the app passes the account's full name.
`import_page` creates a page holding a copy of another; `delete_pages` removes pages.
-Until the application emits ops, `save`, `queue_save`, `saved`, `saving`,
-`conflicts`, `queue`, `review` and `Event::Refreshed` keep the whole-page API: a
-save lowers the page it is given against the stored page (`onestore::op::lower_page`)
-and applies the ops.
-
`Section::resume(file, replica, notify)` starts from an owned `Replica` without
consulting the remote file; with the `smb` feature, `Section::resume_smb(path,
replica, limit, connect, notify)` binds a share-relative path, `connect` running on
@@ -81,10 +77,15 @@ queue replays on it (`merge.rs`): an op whose objects the remote left alone appl
as it is; a text op shifts past the remote's changes to the same text when its
range stays clear of them; a move, deletion or property the remote already made is
done; a page the remote already holds as the local edits leave it drops their ops.
-Anything else is a `Conflict` on the batch: the queue stays on its base and the
-remote image is kept for review. `resolve(id, Mine)` rewrites the remote page to the
-local one (`lower_page`, once), `Theirs` drops the local ops on that page; either
-applies from the conflicted batch on.
+Anything else conflicts, as in OneNote 2010: the op is dropped with every later op naming
+what it named, the remote's version stays the page, and the local version becomes a
+conflict page under it (`SectionOp::Conflict`, queued as one more edit and published with
+the rest), its conflicting objects marked, named for the author of the first edit that did
+not replay. Page lists merge as OneNote 2010 merges page series: a page the remote moved
+keeps the remote's place, a page moved here goes before the next page in the local order
+the remote left in place, or last, and a page the remote removed comes back as a new copy
+of the local one, placed the same way (`corpus/conflict-page/native-pages`,
+`native-restore`). Nothing blocks the queue.
Publication attempts are recorded before network I/O. An attempt confirms only
when the remote holds its revisions, or every page it changed as it changed them,
@@ -96,7 +97,9 @@ A durable `Published` receipt survives reopening.
A schema-14 cache is converted when opened: it is exported to
`.v14-recovery`, each queued page is lowered against the page the conversion
has so far, and every converted page must equal the page in the old working image,
-or the conversion rolls back and the open fails naming the archive.
+or the conversion rolls back and the open fails naming the archive; a page it held in
+conflict becomes a conflict page. A schema-15 cache's batches lose the review conflict
+they recorded when opened.
With the optional `smb` feature, `SmbRemote::new(client, path, limit)` binds an
`notebook::smb::Client` to one share-relative file and snapshot limit. Remote identity uses the logical root
@@ -161,7 +164,7 @@ conflict policy lives in the client.
## Recovery archives
`export_recovery(new_path)` captures the base and remote images, the edit queue,
-uncertain attempts, conflicts, receipts, downloaded media and the edit-ID sequence in one
+uncertain attempts, receipts, downloaded media and the edit-ID sequence in one
SQLite snapshot. It refuses existing destinations and leaves the live queue
unchanged. Export to a local directory from a background thread: copying holds
the cache mutex while capturing the database. Failure after the final rename can
@@ -261,16 +264,16 @@ targets are `None`.
With the optional `protected` feature, `Notebook::unlock(path, password)`
reads a `Locked` section as `Unlocked { pages, .. }`, and
-`Notebook::save_unlocked(path, password, &mut unlocked, space, page, author)`
-saves an edited page under the section's key, straight to the file: nothing of
-a protected section is cached or queued, a section written since `unlocked`
-was read fails the save, and a wrong password is
+`Notebook::apply_unlocked(path, password, &mut unlocked, author, edit)`
+applies page ops and stores them under the section's key, straight to the file:
+nothing of a protected section is cached or queued, a section written since
+`unlocked` was read refuses the edit, and a wrong password is
`Error::Protected(PasswordMismatch)`.
`Section::import_page(page, author)` copies a page, usually read from another
-section, to the end of this one as a page creation and a save queued like the
+section, to the end of this one as one `SectionOp::Import` queued like the
user's own edits, under fresh identities (`Page::copy`); payloads travel with
-the model, so the copy publishes offline later like any save. Content outside
+the model, so the copy publishes offline later like any edit. Content outside
the model refuses to copy. A move is an import here followed by
`Section::delete_pages` there.
@@ -309,15 +312,20 @@ let document = onestore::document::Document::parse(&revisions)?;
```
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::page` separates preparation from I/O: inspect the immutable image
-and persist the intended revision identity before `Client::commit_transaction` publishes
-its `transaction()`. `Client::stamp` reads a file's header and length without coordination,
-for polling. `Client::confirm_snapshot` checks and flushes an observed image's stamp, 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.
+snapshot. Call from a background thread outside a Tokio runtime.
+`Client::commit_transaction` publishes a `Transaction` from `Section::seal`; its errors
+retain `onestore::CommitState`. `Client::stamp` reads a file's header and length in one
+round trip without coordination, for polling. `Client::confirm` checks and flushes a
+stamp, 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.
+
+A commit is eight round trips while its appended bytes and patches fit fifteen 64 KiB
+writes: open; the coordination locks, identity queries and the stamp check's reads as one
+compound request; the path's identity; the appended bytes and patches, the header, the
+counter (twice at a carry) and the version, each as its writes and a flush in one
+compound; close. The server
+performs a compound in order and answers its flush once the writes are durable, so the
+writes carry no write-through flag.
Readers use shared native guards while writers publish under native write-open
and byte-lock exclusion. Maintenance is excluded during each operation; pathname
diff --git a/crates/notebook/examples/cache_probe.rs b/crates/notebook/examples/cache_probe.rs
index 0e1df5bad63a10f9728981594fcb4ff3c34eeeb9..2176f33a28e75d22cf3bf794055d64bede8f1517 100644
--- a/crates/notebook/examples/cache_probe.rs
+++ b/crates/notebook/examples/cache_probe.rs
@@ -32,6 +32,17 @@ fn content(bytes: &[u8]) -> (ExGuid, ExGuid, String) {
.unwrap()
}
+/// The images the queue leaves and started from, as a recovery archive records them.
+fn images(cache: &Replica, path: &Path) -> Result<(Vec, Vec), Box> {
+ let archive = path.with_extension(format!("probe-{}", std::process::id()));
+ cache.export_recovery(&archive)?;
+ let recovery = notebook::Recovery::open(&archive)?;
+ let images = (recovery.snapshot()?, recovery.remote_snapshot()?);
+ drop(recovery);
+ std::fs::remove_file(&archive)?;
+ Ok(images)
+}
+
fn payload(operation: u64, size: usize) -> String {
format!("{operation}:🦀{}", "x".repeat(size))
}
@@ -73,8 +84,7 @@ fn main() -> Result<(), Box> {
}
let cache = Replica::open(path)?;
if mode == "read" {
- let snapshot = cache.snapshot()?;
- let base = cache.remote_snapshot()?;
+ let (snapshot, base) = images(&cache, path)?;
let (sid, target, mut expected) = content(&base);
let store = Store::parse(&snapshot)?;
assert!(store.checksum_mismatches.is_empty());
@@ -164,8 +174,7 @@ fn main() -> Result<(), Box> {
let instruction = lines.next().unwrap()?;
let (operation, acknowledgement) = instruction.split_once(' ').unwrap();
let operation: u64 = operation.parse()?;
- let source = cache.snapshot()?;
- let (sid, oid, text) = content(&source);
+ let (sid, oid, text) = content(&images(&cache, path)?.0);
let replacement = payload(operation, size);
println!("editing {operation}");
io::stdout().flush()?;
diff --git a/crates/notebook/examples/keystroke_probe.rs b/crates/notebook/examples/keystroke_probe.rs
index b7829a358d5851bbc136d47c7f89db3861f717b1..d0579cd7c47d4792a6888875e22b1a5cecafc4d5 100644
--- a/crates/notebook/examples/keystroke_probe.rs
+++ b/crates/notebook/examples/keystroke_probe.rs
@@ -66,15 +66,15 @@ impl Remote for Memory {
self.read += self.bytes.len() as u64;
Ok(self.bytes.clone())
}
- fn stamp(&mut self) -> io::Result