:`. The inputs are the code plus the sorted normalized original and proposed paths involved, so the key is stable across sessions and changes when the conflict changes.
+- [ ] P4 — `baseline.items` holds entries only for items that are edit or rule sources, recorded when they were staged. The full snapshot is never persisted, so plan size scales with the changes, not the scope.
+- [ ] P5 — Readers ignore unknown keys inside known objects, and writers preserve them through `extensions` or a round-trip map.
+- [ ] P6 — A missing scope root on load offers **Rebase root…**, which rewrites every path under the root to a user-chosen path. The baseline identities for those items then become invalid, and the items show SRC-001 until they are reviewed.
+
+### 7.2 Recovery journal format (`.reorgjournal`, version 1)
+
+Location: `\reorganize\journals\.reorgjournal`. The application data directory is obtained from `GetApplicationDataDirectory` (T4.14): normally `%APPDATA%\Open Salamander`, and `\appdata\Open Salamander` under the UI-test sandbox (testing.md "Journals must not leak between cases").
+
+Encoding: UTF-8, CRLF-terminated records. Each record is `TYPE|f1|f2|…|crc=XXXXXXXX`, where `crc` is the CRC-32 (`src/common/crc32.h`) of the record bytes before `|crc=`. In field values, `%`, `|`, CR, and LF are percent-encoded (`%25`, `%7C`, `%0D`, `%0A`). Key/value fields are `key=value`.
+
+| Record | Fields | Written |
+|---|---|---|
+| `REORGJOURNAL` | `1` (version), `plan=`, `apply=`, `kind=reorganize\|revert`, `planSha256=`, `compiledSha256=`, `created=`, `host=`, `plugin=`, `steps=` | First record |
+| `PLANCOPY` | `path=<…\.reorgplan>`, `sha256=` | After the header |
+| `STORE` | `volume=`, `path=` | One per volume used |
+| `STEP` | ``, `kind=createDir\|move\|copyDirTime\|removeEmptyDir`, `src=`, `dst=`, `dir=0\|1`, `cross=0\|1`, `class=exact\|withLoss\|untilFinalized\|notReversible`, `expectLoss=0x…`, `srcId=`, `deps=`, `role=edit\|tempRename\|displace\|cleanup\|merge\|store`, `node=`, `reason=` | All of them before `BEGIN-APPLY` |
+| `BEGIN-APPLY` | `utc=`, `resume=0\|1`, `pid=` | Before `ExecuteOperationSteps` is called; flushed |
+| `HOSTOP` | `operation=` | As soon as `ExecuteOperationSteps` returns TRUE; flushed |
+| `DONE` | ``, `utc=`, `flags=0x…` (`ALREADY_EXISTED`, `CROSS_VOLUME`), `losses=0x…`, `dstId=` | Per step |
+| `SKIP` | ``, `reason=dependency\|stopped` (the observer returned FALSE: a failed dependency, or the journal stop flag) | Per step |
+| `FAIL` | ``, `error=`, `detail=` | Per step; flushed immediately |
+| `VERIFY` | ``, `ok=0\|1`, `detail=` | After the apply |
+| `RECONCILE` | ``, `state=done\|notDone\|manual`, `evidence=` | When resuming |
+| `END-APPLY` | `status=completed\|partial\|stopped\|cancelled\|failed`, `done=`, `skipped=`, `failed=`, `notStarted=`, `utc=` | Final; flushed |
+| `REVERTED-BY` | `apply=`, `utc=` | Appended when a revert completes |
+| `FINALIZE` | `mode=recycle\|permanent`, `items=`, `utc=` | Appended when finalized |
+
+Identity encoding: `::::`. Use `src/common/recovery_evidence.h` (`CRecoveryObjectEvidence`, `ReadRecoveryObjectIdentity`) where it fits; extend it for the 128-bit ID instead of duplicating it.
+
+Rules:
+
+- [ ] J1 — Header, `PLANCOPY`, `STORE`, all `STEP` records, and `BEGIN-APPLY` are durable (`FlushFileBuffers`) before the first host call. Mirror `COperationJournal::FlushDurable` (`src/operation_journal.cpp`).
+- [ ] J2 — Terminal per-step records are buffered (64 KiB buffer, as in `operation_journal.cpp`) and flushed per T5.6. Recovery must therefore tolerate any suffix of `DONE` records being lost (§7.6.6).
+- [ ] J3 — Records are append-only. Nothing is rewritten in place. `REVERTED-BY` and `FINALIZE` are appended to the original journal.
+- [ ] J4 — Reader: a 64 KiB input buffer with no whole-file size cutoff; the last record is discarded when it lacks CRLF or a valid CRC (torn write); any **interior** CRC failure makes the journal *manual-only* (reported; no automatic mutation). The host's journal parser is the model (testing.md §"native journal-size cases").
+- [ ] J5 — Journal ↔ host-journal link: the `HOSTOP` `operation=` value equals the `operation=` ID in the host's `operation-journals` `PLAN|1|operation=…` header. The Recovery Journals window shows the link. If the process dies between `BEGIN-APPLY` and `HOSTOP`, the segment has no host link. Reconciliation (§7.6.6) does not need one, because it works from filesystem evidence.
+- [ ] J7 — Steps reported `NOT_STARTED` (stop-on-error or cancel) get no per-step record; they are counted in `END-APPLY` and are "not done" for Resume.
+- [ ] J6 — A journal can hold several `BEGIN-APPLY … END-APPLY` segments (resume after a stop or interruption). A journal is **incomplete** when its last segment has no `END-APPLY`. A step's state is its latest terminal record across all segments.
+
+### 7.3 Host SDK additions (version 105)
+
+#### 7.3.1 Declarations (append to `spl_gen.h`)
+
+```cpp
+// --- Reorganization step execution (SDK 105) ---------------------------------
+// Plug-ins submit an already-reviewed list of simple steps; the host executes it
+// through its native durable COperations engine so commit, metadata-loss, reparse
+// and crash-recovery contracts are identical to the Move (F6) command.
+
+#define SALOPSTEP_CREATEDIR 1 // create directory 'Target' (parent must exist)
+#define SALOPSTEP_MOVE 2 // move/rename 'Source' to 'Target' (file or directory)
+#define SALOPSTEP_REMOVEEMPTYDIR 3 // remove directory 'Source' only if it is empty
+#define SALOPSTEP_COPYDIRTIME 4 // copy last-write time of directory 'Source' to directory 'Target'
+
+#define SALOPSTEPF_SOURCE_IS_DIR 0x0001 // 'Source' is a directory
+#define SALOPSTEPF_TARGET_MUST_NOT_EXIST 0x0002 // fail with ERROR_ALREADY_EXISTS, never prompt to overwrite
+#define SALOPSTEPF_ALLOW_CROSS_VOLUME 0x0004 // otherwise fail with ERROR_NOT_SAME_DEVICE
+#define SALOPSTEPF_VERIFY_SOURCE_IDENTITY 0x0008 // compare ExpectedIdentity before acting
+#define SALOPSTEPF_METADATA_LOSS_ACCEPTED 0x0010 // losses within ExpectedMetadataLosses were reviewed
+#define SALOPSTEPF_CREATEDIR_ACCEPT_EXISTING 0x0020 // existing directory counts as success (reported)
+
+#define SALMDLOSS_LASTWRITE 0x0001
+#define SALMDLOSS_ATTRIBUTES 0x0002
+#define SALMDLOSS_SECURITY 0x0004
+#define SALMDLOSS_ADS 0x0008
+#define SALMDLOSS_COMPRESSION_EFS 0x0010
+#define SALMDLOSS_CREATION_LASTACCESS 0x0020 // always set for cross-volume moves (unsupported)
+
+// Directories are matched by VolumeSerial + FileId only (earlier steps legitimately change
+// their size and last-write time); files additionally by Size and LastWrite.
+struct CSalamanderFileIdentity
+{
+ DWORD Valid; // 0 = not supplied
+ DWORD VolumeSerial;
+ BYTE FileId[16]; // FILE_ID_128; 64-bit IDs are zero-extended
+ CQuadWord Size; // files only
+ FILETIME LastWrite; // files only
+};
+
+struct CSalamanderOperationStep
+{
+ DWORD StructSize; // sizeof(CSalamanderOperationStep); host rejects unknown sizes
+ DWORD Kind; // SALOPSTEP_xxx
+ DWORD Flags; // SALOPSTEPF_xxx
+ const WCHAR* Source; // full path; NULL for SALOPSTEP_CREATEDIR
+ const WCHAR* Target; // full path; NULL for SALOPSTEP_REMOVEEMPTYDIR
+ CSalamanderFileIdentity ExpectedIdentity;
+ DWORD ExpectedMetadataLosses; // SALMDLOSS_xxx predicted at plan time
+ DWORD_PTR UserData; // opaque, echoed to the observer
+};
+
+#define SALOPSTEP_RESULT_DONE 0
+#define SALOPSTEP_RESULT_SKIPPED 1 // only when the observer's BeforeStep returned FALSE
+#define SALOPSTEP_RESULT_FAILED 2 // error, including the user choosing Skip/Skip all in the error prompt
+#define SALOPSTEP_RESULT_CANCELLED 3 // user cancelled while this step was active
+#define SALOPSTEP_RESULT_NOT_STARTED 4 // reported in Finished summary only
+
+#define SALOPSTEP_RESULTF_ALREADY_EXISTED 0x0001 // CREATEDIR found an existing directory
+#define SALOPSTEP_RESULTF_CROSS_VOLUME 0x0002 // executed as copy + durable delete
+
+#define SALEXECF_STOP_ON_ERROR 0x0001 // after a FAILED step report all later steps NOT_STARTED
+
+struct CSalamanderOperationStepsSummary
+{
+ DWORD StructSize;
+ int Done, Skipped, Failed, Cancelled, NotStarted;
+ BOOL UserCancelled;
+};
+
+// Observer supplied by the plug-in; must stay valid until Finished() returns.
+class CSalamanderOperationStepObserverAbstract
+{
+public:
+ // Worker thread, before the host item of step 'index' (one step = one item). Return FALSE to skip it.
+ // Must not show UI, block on the main thread, or call host SDK methods.
+ virtual BOOL WINAPI BeforeStep(int index, DWORD_PTR userData) = 0;
+
+ // Worker thread, after the step finished (SALOPSTEP_RESULT_DONE/SKIPPED/FAILED/CANCELLED).
+ // 'error' is the Win32 code for FAILED; 'resultFlags' SALOPSTEP_RESULTF_xxx;
+ // 'metadataLosses' SALMDLOSS_xxx actually recorded; 'targetIdentity' is the identity
+ // of the resulting object when DONE (may be invalid when it could not be read).
+ virtual void WINAPI AfterStep(int index, DWORD_PTR userData, DWORD result, DWORD error,
+ DWORD resultFlags, DWORD metadataLosses,
+ const CSalamanderFileIdentity* targetIdentity) = 0;
+
+ // Main thread, posted exactly once after the progress dialog closed (also on cancel
+ // or startup failure after acceptance). 'operationId' matches the host journal.
+ virtual void WINAPI Finished(const CSalamanderOperationStepsSummary* summary,
+ const char* operationId) = 0;
+};
+
+typedef BOOL(WINAPI* SalEnumPathReferenceCallback)(DWORD kind, int index, const char* displayName,
+ const char* path, void* param);
+#define SALPATHREF_HOTPATH 1
+#define SALPATHREF_USERMENU_DIR 2
+#define SALPATHREF_USERMENU_ARGS 3 // free text that may contain paths
+#define SALPATHREF_PANEL 4
+
+// --- appended to CSalamanderGeneralAbstract, after GetApplicationUpdateState() ---
+
+ // Starts asynchronous execution of 'count' steps. The host copies 'steps' (the caller may
+ // free them after return). Returns FALSE when the request is invalid or the operation
+ // could not be started; the observer is then never called and GetLastError() explains why.
+ // On TRUE, 'operationIdBuf' (at least 64 chars) receives the NUL-terminated host operation
+ // correlation ID and
+ // the observer receives BeforeStep/AfterStep per step and exactly one Finished().
+ // The calling plug-in cannot be unloaded until Finished() has returned.
+ // limitation: main thread
+ virtual BOOL WINAPI ExecuteOperationSteps(HWND parent, const char* caption,
+ const CSalamanderOperationStep* steps, int count,
+ DWORD flags,
+ CSalamanderOperationStepObserverAbstract* observer,
+ char* operationIdBuf, int operationIdBufSize) = 0;
+
+ // Enumerates paths stored in the application configuration (hot paths, user menu,
+ // current panel paths) so plug-ins can warn about references a reorganization breaks.
+ // Enumeration stops when the callback returns FALSE.
+ // limitation: main thread
+ virtual void WINAPI EnumApplicationPathReferences(SalEnumPathReferenceCallback callback,
+ void* param) = 0;
+
+ // Returns the application's roaming data directory (UTF-8, no trailing backslash), the
+ // same folder that holds the host's operation journals; honours the UI-test sandbox.
+ // 'create' TRUE creates it when missing. Returns FALSE when it cannot be resolved or
+ // does not fit into 'buf'.
+ // can be called from any thread
+ virtual BOOL WINAPI GetApplicationDataDirectory(char* buf, int bufSize, BOOL create) = 0;
+```
+
+#### 7.3.2 ABI rules
+
+- [ ] A1 — The new virtuals are appended after the last existing one. Use a source-contract test (T9.2) to assert this order.
+- [ ] A2 — The structs carry `StructSize` so later versions can extend them. The host accepts `StructSize >= v105 size` and reads only the fields it knows.
+- [ ] A3 — Plug-ins that require version < 105 never see the new methods. The reorganize plug-in requires 105.
+
+#### 7.3.3 Host input validation (`ExecuteOperationSteps`)
+
+Reject with `ERROR_INVALID_PARAMETER` (and no execution) if any of the following holds:
+
+- `count` ≤ 0 or > 10,000,000;
+- an unknown `Kind`, `Flags` bit, or `StructSize`;
+- a path is NULL where it is required, is relative, is a device path (`\\.\`, `\\?\GLOBALROOT`, an NT namespace path), or has `.`/`..` segments after normalization. Extended-length `\\?\X:\…` and `\\?\UNC\…` forms are accepted;
+- `Source` equals `Target`, apart from a case-only rename;
+- a `Target` lies inside a `Source` directory of the same step;
+- a `SALOPSTEP_COPYDIRTIME` or `SALOPSTEP_REMOVEEMPTYDIR` step lacks `SALOPSTEPF_SOURCE_IS_DIR`.
+
+The host does **not** re-plan or reorder. The steps execute in the given order.
+
+#### 7.3.4 `EnumApplicationPathReferences` details
+
+- Hot paths: one callback per defined hot path. `displayName` is the hot-path name; `path` is its UTF-8 path.
+- User menu: `SALPATHREF_USERMENU_DIR` for each non-empty initial directory, and `SALPATHREF_USERMENU_ARGS` for the arguments text (the plug-in extracts candidate paths).
+- Panels: the left and right current paths (`SALPATHREF_PANEL`, index 0/1).
+
+### 7.4 Validation catalog
+
+Severity: **E** = Error (blocks apply), **W** = Warning (requires acknowledgement), **I** = Info. *Stage* says when the check runs: **edit** (on the in-memory overlay, cheap), **analysis** (on the background thread, may touch disk), **preflight** (only before apply).
+
+| Code | Sev | Stage | Condition | Suggested resolutions |
+|---|---|---|---|---|
+| **Collisions** | | | | |
+| COL-001 | E | edit | Two or more items end at the same proposed path (case-insensitive unless the target directory is case-sensitive) | Keep both · Rename… · Skip one · Unstage |
+| COL-002 | E | analysis | Proposed path is occupied by a real item that the plan does not move away | Keep both · Skip · Replace · Rename… · Merge (dir onto dir) |
+| COL-003 | W | edit | In a **case-sensitive** target directory, two names differ only by case; the filesystem allows it, but most Windows applications cannot address both | Rename… · Acknowledge |
+| COL-004 | E | edit | Move into own descendant (edit refused; appears only for loaded plans whose sources changed) | Remove edit |
+| COL-005 | I | compile | Swap/rotation detected; resolved with temporary names (`role=tempRename`) | — |
+| COL-006 | W | analysis | Directory onto existing directory with `merge` chosen; lists children that will be merged | Acknowledge · Choose another resolution |
+| COL-007 | E | analysis | Target name equals the 8.3 short name of an existing item in the target directory | Rename… |
+| COL-008 | E | edit | Target of a move is a proposed **file** where a directory is required, or the reverse | Rename… |
+| **Destinations** | | | | |
+| DST-001 | E | analysis | Destination volume or share unavailable, or path not found and not created by the plan | Rebase root · Unstage |
+| DST-002 | E | analysis | Read-only volume, or read-only target directory where creation is needed | — |
+| DST-003 | E | preflight | Access denied (write probe failed) | — |
+| DST-004 | E/W | analysis | The free space on the target volume is below the cross-volume bytes it must receive (E), or exceeds them by less than 10% (W). Same-volume renames need no space | — |
+| DST-005 | E/W | edit | Full path > 32,767 UTF-16 units or component > FS max component length (E); full path ≥ 260 (W: "Some applications may not open this file") | Rename… |
+| DST-006 | E | edit | Invalid name: reserved device names (`CON`, `PRN`, `AUX`, `NUL`, `COM0-9`, `LPT0-9`, including superscript-digit variants), trailing space or dot, characters `<>:"/\|?*` or < 0x20, plus FAT-specific restrictions on FAT targets | Rename… |
+| DST-007 | E/W | analysis | Target FS lacks a capability: file > 4 GiB on FAT32 (E); ADS/ACL/EFS/compression not supported (W, listed losses) | Choose other destination · Acknowledge |
+| DST-008 | W | analysis | Destination path passes through a junction or mount point; treated as cross-volume (`SameRootButDiffVolume` semantics) | Acknowledge |
+| DST-009 | E | edit | Destination is not a disk path (archive or plug-in FS) | — |
+| DST-010 | I | analysis | Destination is on a network share; rename semantics depend on the server | — |
+| DST-011 | E | analysis | Recovery store would not be on the same volume as the displaced item | Set explicit recovery store · Choose another resolution |
+| **Sources** | | | | |
+| SRC-001 | W | analysis/preflight | Source changed since staging (size, last-write, or attributes differ; same identity) | Acknowledge · Unstage |
+| SRC-002 | E | analysis/preflight | Source missing, or identity differs (another object now has that path) | Unstage · Re-stage |
+| SRC-003 | E | preflight (deep) | Source is locked or in use | Retry later · Skip |
+| SRC-004 | E | analysis | Cross-volume move of a reparse point, or of a directory tree containing reparse points (the host policy skips them; architecture.md §5.2.1) | Exclude link items · Move same-volume instead |
+| SRC-005 | W | analysis | Cross-volume move of a cloud placeholder (would hydrate content) | Acknowledge · Exclude |
+| SRC-006 | W | analysis | Cross-volume move of a file with more than one hard link (link set breaks) | Acknowledge |
+| SRC-007 | W | analysis | System or hidden protected items (`FILE_ATTRIBUTE_SYSTEM`, `desktop.ini`, `thumbs.db`) | Acknowledge · Exclude |
+| SRC-008 | I | analysis | Source is inside a cloud-synced folder; sync clients propagate the change to other devices | — |
+| **References** | | | | |
+| REF-001 | W | analysis | `.lnk` in a scanned folder targets a moved item | Acknowledge |
+| REF-002 | W | analysis | `.url` with a `file:` URL targets a moved item | Acknowledge |
+| REF-003 | W | analysis | A moved `.lnk` with a relative target no longer resolves | Acknowledge |
+| REF-004 | W | analysis | Text reference (relative or absolute) may break (§7.10.4); marked "heuristic" | Acknowledge |
+| REF-005 | W | analysis | Application hot path, user menu, or panel path points into a moved item | Acknowledge |
+| REF-006 | I | always | References from outside the scanned folders cannot be detected (summary only) | — |
+| **Reversibility** | | | | |
+| REV-001 | I | compile | Operation is `WithLoss` (lists predicted metadata losses) | Covered by the apply-gate acknowledgement (§2.8) |
+| REV-002 | I | compile | Operation is `UntilFinalized` (Replace, Cleanup) | Covered by the apply-gate acknowledgement |
+| REV-003 | I | compile | Operation has external effects (`NotReversible`): cloud-synced source or target | Covered by the apply-gate acknowledgement |
+| REV-004 | E | revert | Inverse impossible: recovery store finalized, or the item changed since apply | Skip item |
+| **Plan integrity** | | | | |
+| PLN-001 | E | load | Plan file invalid (parse or schema error with line and column) | — |
+| PLN-002 | W | load | Plan created on another computer or by another user | Acknowledge |
+| PLN-003 | E | apply | Compiled hash differs from the reviewed hash | Re-review |
+| PLN-004 | E | apply | Another apply of this plan is running or interrupted | Resume · Revert · Wait |
+| PLN-005 | E | compile | Internal compile error: an intent stays blocked without a cycle (§7.6.3 step 5) | Report a defect; trace diagnostics |
+| PLN-006 | E | edit | `removeEmptyFolder` used outside a `kind=revert` plan, or the folder is not empty in the proposed tree | — |
+
+Implementation notes:
+
+- [ ] V1 — Each check is a function `void Check_XXX(const CAnalysisContext&, CIssueSink&)` registered in a table, so checks can be tested in isolation.
+- [ ] V2 — Issues carry `nodes[]` (affected node keys), `params` (localized-message arguments), and `resolutions[]`.
+- [ ] V3 — Error budget: show at most 10,000 issues per code in the UI; counts are still exact.
+
+### 7.5 Reversibility classes
+
+| Class | Applies to | Inverse | Shown as |
+|---|---|---|---|
+| `Exact` | Same-volume move or rename of a file or directory (the file ID is preserved); create folder (inverse: remove if empty); keep-both rename; temporary renames | Same-volume move back; remove empty dir | "Exact" |
+| `WithLoss` | Cross-volume move (new file IDs; creation and last-access times not preserved; ACL, ADS, and EFS per the target FS from architecture.md §5.2.2; hard links broken) | Cross-volume move back (another copy) | "With loss" + list |
+| `UntilFinalized` | Replace (the displaced item goes to the recovery store); cleanup of emptied folders (the folder goes to the recovery store) | Move back from the store | "Until finalized" |
+| `NotReversible` | Changes visible to external systems: the source or target is cloud-synced (attributes `FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS`/`PINNED`/`UNPINNED`, or a reparse tag in the `IO_REPARSE_TAG_CLOUD*` family) — the local revert does not restore remote history or sharing links; Finalize itself | — | "No" + reason |
+
+- [ ] R1 — A change's class is the *worst* class of its compiled steps.
+- [ ] R2 — Merge folders is `Exact` when every child move is `Exact`.
+- [ ] R3 — The apply gate (§2.8) counts the changes per non-`Exact` class.
+
+### 7.6 Compiler
+
+#### 7.6.1 Inputs and outputs
+
+- **Input:** an overlay revision with no Errors; resolutions and options.
+- **Output:** `CCompiledPlan { steps[], hash, perStepClass[], perStepDeps[], storeRoots[] }`.
+
+#### 7.6.2 Effective moves
+
+1. For each real node *n* with proposed placement `P(n) = (parent, name)` different from its real placement:
+ - if the node's proposed parent is the proposed image of its real parent (that is, the parent also moved and *n* kept its relative position and name), *n* is **carried** and no step is emitted;
+ - otherwise *n* gets a `move` intent.
+2. Proposed new folders become `createDir` intents.
+3. A `replace` resolution creates a `displace` intent for the occupant (move to the recovery store) that must precede the incoming move.
+4. A `merge` resolution expands a directory move into per-child intents, plus a cleanup of the source directory when it ends up empty.
+5. When `cleanupEmptiedFolders` is on, every real directory that ends up empty and was emptied by the plan (it had real descendants, and all of them left) gets a `cleanup` intent (move to the recovery store, *not* delete). Cleanup runs bottom-up, after every move out of the directory.
+6. **Cross-volume directory moves are expanded by the compiler**, never by the host. The expansion is computed at scheduling time from the simulated subtree, so items moved into the directory earlier are included. The steps are:
+ 1. `createDir` for the target directory and each subdirectory, top-down;
+ 2. a cross-volume `move` per file whose **final placement** lies inside the moved directory's new subtree (excluded or separately placed descendants are not part of the expansion);
+ 3. a `copyDirTime` per directory, bottom-up, after its children;
+ 4. a `removeEmptyDir` per source directory, bottom-up, **only** for source directories that are empty in *S* at that point. A directory that still holds excluded items stays where it is.
+
+ This matches the order of `BuildScriptMain` and keeps a 1:1 relation between journal steps and host items. Reparse entries in such a subtree are SRC-004 errors before compile, so the expansion never meets one. Same-volume directory moves are **never** expanded (§8.1).
+
+#### 7.6.3 Scheduling (simulation)
+
+1. Maintain a simulated filesystem state *S*, initialized from the snapshot.
+2. A pending intent is **ready** when (a) its target parent exists in *S* — it may still be at its original location, and the intent then moves with it later — (b) the target name is free in *S* (comparing case per the directory's case sensitivity), and (c) for `cleanup`, the directory is empty in *S*.
+3. Emit ready intents as steps using the **current** simulated source paths, and update *S* after each one. Tie-break by `(depth of target parent ascending, target path ordinal, original source path ordinal)` so the output and hash are deterministic.
+4. When intents remain but none is ready, look for name-occupancy cycles. For the cycle member with the smallest original path, emit a `tempRename` to `~reorg--` in the same directory (with *k* the smallest integer giving a name free in *S* and in the snapshot), then continue.
+5. A remaining blocked intent with no cycle is an internal error (assert in Debug). The plan cannot be applied and reports PLN-005, with diagnostics in the trace output.
+6. Recovery-store directories are created first (`role=store`, with `CREATEDIR_ACCEPT_EXISTING`).
+7. Every step gets `TARGET_MUST_NOT_EXIST`, `VERIFY_SOURCE_IDENTITY` (moves), `ALLOW_CROSS_VOLUME` when the source and target volumes differ, `METADATA_LOSS_ACCEPTED`, and `ExpectedMetadataLosses` when the class is `WithLoss` and acknowledged. The exception is a source object produced by an earlier **cross-volume** step of the same apply, whose new file ID cannot be known at compile time. Such a step gets no `ExpectedIdentity` and no `VERIFY_SOURCE_IDENTITY`; its dependency on the producing step guarantees the order.
+8. Dependencies: the step that created the target parent; the step that vacated the target name; for `cleanup`, every step that moved something out of the directory; for a carried subtree, the step that moved the ancestor.
+9. Hash: SHA-256 over the canonical serialization of every step (kind, src, dst, flags, deps), joined with LF. A recovery-store path enters the hash as the placeholder `{store:}\\`. The concrete `` directory is substituted only when the apply starts, so the hash is known at review time and stays equal across applies of the same plan.
+
+#### 7.6.4 Recovery store
+
+- The root is chosen per displaced item: the nearest **destination root or scope root** that contains the item, on the same volume, without crossing a junction or mount point. The store is `\.reorg-recovery\\`. If there is no such root, or the option is explicit, use the `options.recoveryStore.paths[volumeSerial]` folder; otherwise raise DST-011.
+- Items are stored as `\\` (*k* is a 6-digit counter), so names cannot clash.
+- `README.txt` (localized) explains what the folder is and how to recover by hand. `index.tsv` holds `koriginal pathstored pathutc`, one row per displaced item. Both are written and flushed by the `Finished` handler on the main thread (T5.7), from the journal's `DONE` records, and rewritten after Resume and reconciliation. The worker-thread observer never writes them. They are informative; the journal stays authoritative.
+- The `Finished` handler also sets `FILE_ATTRIBUTE_HIDDEN` on each `.reorg-recovery` folder; a failure is reported and is not fatal. Every snapshot, rule, and scanner excludes `.reorg-recovery`, hidden or not.
+
+#### 7.6.5 Verification after apply
+
+For each `DONE` move:
+
+- the target exists;
+- same-volume moves: its identity equals the source identity (same file ID);
+- cross-volume moves: size and last-write match (allowing the FAT 2-second resolution);
+- the source no longer exists.
+
+For each `DONE` createDir, the directory exists. For each `DONE` removeEmptyDir, the directory is absent. `copyDirTime` is not verified (a later step may legitimately touch the directory). Mismatches produce `VERIFY ok=0` and appear in the report. They never trigger automatic mutation.
+
+#### 7.6.6 Reconciliation of an interrupted apply
+
+For a step without a terminal record:
+
+| Observed | Same-volume move | Cross-volume move | createDir | removeEmptyDir |
+|---|---|---|---|---|
+| Source present with expected identity, target absent | notDone | notDone (the host's temp-file recovery has already run) | — | — |
+| Source absent, target present with the expected identity (same volume) or size+mtime (cross volume) | done | done | — | — |
+| Both present | manual | notDone if the target is a host-recovered partial (otherwise manual) | — | — |
+| Neither present | manual | manual | — | — |
+| Directory exists | — | — | done | notDone |
+| Directory absent | — | — | notDone | done |
+
+A `copyDirTime` step without a terminal record is always `notDone` (re-running it is idempotent).
+
+Steps that depend on a `manual` step become `blocked`. Resume excludes `manual` and `blocked` steps and lists them in the report as needing human action.
+
+#### 7.6.7 Revert builder
+
+The revert plan is an ordinary `CPlanDocument` of `kind=revert`. Its scope roots are the original plan's scope, destination, and recovery-store roots, and its **edits** are derived from the `DONE` steps of the apply, in reverse order:
+
+- `move a→b` → edit `move` with source `b`, `destinationDir` = parent(`a`), and `newName` = leaf(`a`). The baseline identity is the `dstId` from `DONE` (or `VERIFY`). For a `role=displace` or `cleanup` step, the source is the stored item in the recovery store.
+- `createDir d` → edit `removeEmptyFolder d`, unless the step had `ALREADY_EXISTED`.
+- `removeEmptyDir d` (cross-volume expansion) → edit `createFolder d`, classified `WithLoss` (the directory's original ACL, attributes, and creation time are gone).
+- `copyDirTime` → no inverse.
+- Temporary renames collapse: the revert targets the original paths directly, and the revert compiler introduces its own temporary names if a cycle needs them.
+
+The revert plan then goes through the normal overlay, validation, compile, review, and apply pipeline, so it gets the same guarantees as a forward plan.
+
+Before preview, every inverse is checked against the current disk:
+
+- the item is missing or its identity changed → REV-004;
+- the original path is occupied now → COL-002 (normal resolution choices; `replace` is allowed and displaces into the revert apply's own store);
+- after Finalize, store-based inverses → REV-004.
+
+### 7.7 Proposed-tree overlay
+
+#### 7.7.1 Snapshot item
+
+`{ nodeKey, parentKey, name, isDir, size, lastWrite, created, attributes, reparseTag, volumeSerial, fileId? (lazy), caseSensitiveDir? (dirs) }`. Store names in a string arena, mirroring the host's `CStringArena` rationale. Target capacity: 1,000,000 items in ≤ 400 MB on x64.
+
+#### 7.7.2 Placement and listing
+
+- `P(node)` defaults to the real placement. Rules are applied first, in rule order and only to items without explicit edits. Then edits are applied in `seq` order.
+- New-folder nodes get synthetic keys `new:`. A move whose `destinationDir` does not exist creates implicit new-folder nodes with provenance `compiler: required by edit `.
+- **Residual folders.** A directory *D* can move while one of its descendants stays (an `exclude` edit, or a separate placement whose proposed path is under *D*'s original path). The overlay then creates an implicit folder node at *D*'s original path, keyed `residual:`, with provenance "kept for items that stay". The staying items are re-parented to it. The compiler then emits `move D`, `createDir ` (with `CREATEDIR_ACCEPT_EXISTING`, which covers the cross-volume case where the source directory stays), and moves for the staying items whose real location changed. This all follows from the simulation; no special case is needed.
+- Listing of a proposed directory *D* = `children[D]` from the reverse index, excluding displaced and cleanup nodes.
+- `ProposedPath(node)` is computed by walking parents, with a per-generation cache. Each edit increments the generation.
+
+#### 7.7.3 `reorg:` path syntax
+
+- The user part is an absolute disk path: `reorg:D:\a\b`, `reorg:\\server\share\a`.
+- `reorg:` or `reorg:\` alone → the virtual **Plan roots** listing. Display names are root leaf names, de-duplicated with ` (2)`; plug-in data holds the full path.
+- `IsOurPath` accepts all `reorg:` paths. `GetFullName` returns `reorg:` + proposed path. `GetRootPath` returns the drive or share root of the current path.
+
+#### 7.7.4 Columns and classification
+
+| Change value | Condition |
+|---|---|
+| `Unchanged` | Same placement, no issues |
+| `Moved here` | Parent differs, name same |
+| `Renamed` | Parent same, name differs |
+| `Moved and renamed` | Both differ |
+| `Moved with folder` | Own placement unchanged, but an ancestor moved, so the full path differs (carried node) |
+| `New folder` | Synthetic node (`new:`, `residual:`) |
+| `Kept both (renamed)` | Name changed by a `keepBoth` resolution |
+| `Replaces existing` | Incoming item with a `replace` resolution |
+| `Merged` | Directory with a `merge` resolution |
+| `Contains changes (n)` | Unchanged directory with *n* changed descendants |
+| `Applied` | Plan status Applied and node changed |
+
+- **From**: the original path. It is shown relative to the scope root when the item stays under the same root, and in full otherwise.
+- **Issues**: the highest severity and the count, as text, for example `Error (2)`.
+- **Reversible**: the class name (§7.5).
+
+Sorting by these columns uses `CompareFilesFromFS` semantics (ordinal on display text), and status is never conveyed by colour alone.
+
+### 7.8 Rule templates
+
+Tokens:
+
+| Token | Meaning |
+|---|---|
+| `{dest: