@vrtmrz/livesync-commonlib 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +43 -0
  2. package/dist/API/processSetting.d.ts +2 -0
  3. package/dist/common/models/setting.const.defaults.js +2 -0
  4. package/dist/common/models/setting.const.qr.js +3 -0
  5. package/dist/common/models/setting.policy.d.ts +8 -0
  6. package/dist/common/models/setting.policy.js +2 -0
  7. package/dist/common/models/setting.type.d.ts +6 -0
  8. package/dist/common/models/shared.definition.d.ts +20 -0
  9. package/dist/common/models/tweak.definition.d.ts +2 -0
  10. package/dist/common/settingConstants.d.ts +2 -0
  11. package/dist/common/settingConstants.js +10 -2
  12. package/dist/interfaces/FileHandler.d.ts +7 -1
  13. package/dist/interfaces/StorageAccess.d.ts +2 -0
  14. package/dist/interfaces/StorageEventManager.d.ts +7 -0
  15. package/dist/interfaces/StorageEventManager.js +11 -0
  16. package/dist/managers/StorageEventManager.d.ts +20 -10
  17. package/dist/managers/StorageEventManager.js +268 -189
  18. package/dist/replication/LiveSyncAbstractReplicator.d.ts +2 -0
  19. package/dist/replication/LiveSyncAbstractReplicator.js +5 -2
  20. package/dist/replication/couchdb/LiveSyncReplicator.d.ts +6 -0
  21. package/dist/replication/couchdb/LiveSyncReplicator.js +39 -2
  22. package/dist/replication/couchdb/replicationProgress.d.ts +6 -0
  23. package/dist/replication/couchdb/replicationProgress.js +105 -0
  24. package/dist/replication/index.d.ts +1 -0
  25. package/dist/replication/trystero/TrysteroReplicator.d.ts +4 -0
  26. package/dist/serviceFeatures/offlineScanner.d.ts +4 -4
  27. package/dist/serviceFeatures/offlineScanner.js +30 -13
  28. package/dist/serviceFeatures/prepareDatabaseForUse.d.ts +3 -2
  29. package/dist/serviceFeatures/prepareDatabaseForUse.js +5 -4
  30. package/dist/serviceModules/ServiceFileAccessBase.d.ts +1 -0
  31. package/dist/serviceModules/ServiceFileAccessBase.js +8 -0
  32. package/dist/serviceModules/ServiceFileHandlerBase.d.ts +9 -5
  33. package/dist/serviceModules/ServiceFileHandlerBase.js +96 -38
  34. package/dist/services/base/AppLifecycleService.js +1 -1
  35. package/dist/services/base/ControlService.js +6 -2
  36. package/dist/services/base/DatabaseEventService.d.ts +1 -1
  37. package/dist/services/base/FileProcessingService.d.ts +7 -1
  38. package/dist/services/base/FileProcessingService.js +9 -1
  39. package/dist/services/base/IService.d.ts +12 -4
  40. package/dist/services/base/ReplicatorService.d.ts +1 -0
  41. package/dist/services/base/VaultScanResult.d.ts +5 -0
  42. package/dist/services/base/VaultService.d.ts +1 -1
  43. package/dist/services/lib/HandlerUtils.d.ts +60 -0
  44. package/dist/services/lib/HandlerUtils.js +46 -0
  45. package/dist/worker/bgWorker.js +1 -1
  46. package/docs/offline-scanner.md +23 -13
  47. package/docs/settings-lifecycle.md +2 -0
  48. package/docs/storage-events-and-reflection.md +113 -10
  49. package/package.json +2 -2
@@ -1,7 +1,7 @@
1
1
  ---
2
- date: 2026-09-04
3
- commonlib-version: "0.1.21"
4
- self-hosted-livesync-version: "1.0.24"
2
+ date: 2026-10-26
3
+ commonlib-version: "0.2.1"
4
+ self-hosted-livesync-version: "1.0.40"
5
5
  status: unreleased
6
6
  ---
7
7
 
@@ -29,17 +29,25 @@ The tables below cover only paths which remain after identity validation, the ho
29
29
  6. after the first permitted invocation reaches its aggregate-result boundary, record `initialized = true`; and
30
30
  7. return the aggregate pair result under the caller's selected handling of file failures.
31
31
 
32
+ ### Startup content checking
33
+
34
+ `VaultScanOptions.strictContentCheck` is optional. `initialiseDatabase` accepts `VaultScanOptions` as its optional fifth argument, and `scanVault` accepts it as its optional fourth argument and forwards it. `performFullScan` exposes the same option through `FullScanOptions`. Omitting the option, or setting it to `true`, retains strict content comparison. Only an explicit `false` permits the scanner to pass the `canSkipUnchangedFile` hint to the file handler. Commonlib does not read a host setting or global storage; a host may select this option from a per-installation startup preference.
35
+
36
+ The hint is considered only when the normal freshness comparison reaches `EVEN`, replacing the additional body comparison at that point. It never bypasses `BASE_IS_NEW` or `TARGET_IS_NEW`; those branches retain their timestamp-directed reflection and last-seen bookkeeping. Inside `EVEN`, the hint is allowed only for a tracked file whose raw modification time exactly equals the recorded provenance mtime (not merely a value in the same platform comparison bucket), whose byte size matches the recorded value, whose tracked revision is the current live database revision, and which has no live conflicts or related queued, running, or failed file work. If tracking evidence is missing, or the host cannot establish pending-work state, the existing strict byte-comparison path remains. When the hint is accepted, the pair result is `SKIPPED`; it does not compare or claim equal bytes, write provenance, or introduce Commonlib persistent state. Incoming content, conflict handling, and logical-deletion protection retain their existing rules.
37
+
38
+ A local edit which preserves the tracked modification time and byte size may be missed by a scan using explicit `false`. Existing same-stat edit checks exercise the omitted-option strict path.
39
+
32
40
  The `initialized` marker records that a full-scan invocation has reached its aggregate-result boundary and that later invocations may restore stored storage-event operations. It is not a successful-scan marker: the first invocation records it after an aggregate `false` result, `true`, or an accepted partial result. An exception before the aggregate result is produced does not reach that write.
33
41
 
34
- Storage-event restoration is a bounded start-up recovery step. Restored events bypass their former batch delay, but `restoreState()` waits for their actual file operations to finish and preserves snapshot sentinel ordering before the file collections are compared. Saved events run first because a deletion or rename can carry operation intent which is no longer visible in the current storage listing. An individual restored operation failure is logged and does not prevent the scan from examining the resulting current state. The following full scan then makes the final reconciliation decision.
42
+ Storage-event restoration is a bounded start-up recovery step. Restored events bypass their former batch delay, but `restoreState()` waits for their actual handler outcomes and snapshot ordering before the file collections are compared. Saved events run first because a deletion or rename can carry operation intent which is no longer visible in the current storage listing. An individual restored operation failure remains in the pending snapshot and is logged; it does not prevent the scan from examining current state or independent paths. The following full scan then makes the final reconciliation decision.
35
43
 
36
- This ordering does not mean that storage unconditionally overwrites the replay result. The action matrix remains authoritative: when a replayed operation leaves an ambiguous pair, such as a storage file alongside a deleted database entry, the selected policy may deliberately return `skipped` rather than guess which side is intended. This boundary does not include events which are buffered or received later, and it must not be confused with a host's separate replication-result queue.
44
+ This ordering does not mean that storage unconditionally overwrites the replay result. The action matrix remains authoritative: when a replayed operation leaves an ambiguous pair, such as a storage file alongside a deleted database entry, the selected policy may deliberately return `skipped` rather than guess which side is intended. A failed replay does not become a completed operation; a later scan may still reconcile its current pair. This start-up recovery boundary is separate from later watcher work and a host's replication-result queue.
37
45
 
38
46
  ### Stored event interpretation
39
47
 
40
- The storage-event snapshot contains operations which the previous runtime observed but had not begun or which were still waiting for their batch boundary. It is not a file-content journal and does not discover changes made while the application was stopped. An operation which had already been removed from the queue and entered file processing is also outside the persisted boundary. The following full scan remains responsible for observing the current storage state across all of these gaps.
48
+ The storage-event snapshot retains every accepted operation while it is buffered, waiting for a batch boundary, or in a handler. It remains there until the operation has a durable local result, is proved already represented or safely covered by a successful successor, or ends in a skip explicitly allowed by policy. A `false` handler result or exception leaves the saved operation pending. A failed initial snapshot prevents the handler from starting; a later snapshot failure is reported and cannot be treated as completion. Ordered snapshot writes prevent an older asynchronous save from replacing a newer pending view. The snapshot records operation intent, not file content, and cannot recover bytes or events which the host never persisted or delivered; the full scan continues to observe current storage state.
41
49
 
42
- A restored event is an operation intent which must be revalidated against the current exact storage path before it can cause a database side effect. Its saved file stub, timestamps, and optional editor cache describe the earlier observation; they are not authoritative file content after a restart. Whenever an inclusion is still applicable, the handler reads a fresh stub and content from current storage. Whenever an ordinary deletion's expected absence is contradicted by a current storage item, that deletion is suppressed and the full scan evaluates that current item instead.
50
+ A restored event is an operation intent which must be revalidated against the current exact storage path before it can cause a database side effect. Its saved file stub, timestamps, and optional editor cache describe the earlier observation; they are not authoritative file content after a restart. Whenever an inclusion is still applicable, the handler reads a fresh stub and current content when the operation runs. Whenever an ordinary deletion's expected absence is contradicted by a current storage item, that deletion is suppressed and the full scan evaluates that current item instead.
43
51
 
44
52
  For this check, an exact path means that the canonical path returned by storage has the saved spelling after ordinary separator normalisation. A file returned only through a case-insensitive lookup does not prove the saved inclusion path. A same-ID rename remains the explicit case-change path described below.
45
53
 
@@ -58,7 +66,7 @@ The standard-file revalidation contract is:
58
66
  | Case-only or otherwise same-ID `RENAME` | A current selected file resolves to the renamed canonical path | Update that single document's path and current content without a separate deletion |
59
67
  | Case-only or otherwise same-ID `RENAME` | The current identity does not support the saved rename | Do not replay the rename; let the full scan evaluate the current path |
60
68
 
61
- An inability to inspect the current path is not proof of absence. Revalidation therefore suppresses a destructive half when storage inspection fails, records the failure, and leaves the following scan to report whether reconciliation can proceed. Target-file policy and file-size limits are evaluated from current settings rather than inherited from the saved event.
69
+ An inability to inspect the current path is not proof of absence. Revalidation suppresses a destructive half when storage inspection fails, records the failure, and leaves the operation pending. The full scan can still examine the current state and reports any selected-pair failure under its strict or explicitly partial completion policy. Target-file policy and file-size limits are evaluated from current settings rather than inherited from the saved event.
62
70
 
63
71
  The unreleased rename-exclusion guard retains `renameTarget` when a rename is converted to deletion during target filtering. Unlike an ordinary deletion, this intent can remove a file from synchronisation without removing it from storage. The guard distinguishes deliberate exclusion from transient case-collision rejection, preserves a recreated source, and permits a deliberately excluded same-ID destination without requiring that destination to disappear. Older snapshots without this optional field retain the absence-only rule. See [Storage events and database-to-storage reflection](storage-events-and-reflection.md) for the exact admission conditions and the separate ordinary watcher boundary.
64
72
 
@@ -138,11 +146,13 @@ By default, the complete scan returns `false` when any pair is `failed`, and ret
138
146
 
139
147
  Self-hosted LiveSync enables this policy only for ordinary Obsidian start-up, where one unavailable path must not prevent unaffected files from synchronising. Fast Setup, Fetch, Rebuild, direct scans, and CLI mirror or daemon paths retain the strict default.
140
148
 
141
- ## Equal-time files without a recorded revision
149
+ ## Files in the same timestamp bucket
150
+
151
+ For a `both` pair which selects `NEWER_WINS` (`sync-newer`), the normal freshness comparison can reach `EVEN` for equal raw modification times or distinct times within the platform tolerance; those timestamps alone do not establish that the storage file is unchanged. Under the omitted-option strict path, the scanner asks the file handler to record provenance for the current database revision only when current storage and one unambiguous live revision have matching content. The handler checks the storage bytes and database revision under the document lock, then revalidates the storage snapshot, current revision, and absent provenance before writing the record. This check writes neither storage content nor a database revision. With explicit `strictContentCheck: false`, the scanner may request `canSkipUnchangedFile` only in the `EVEN` branch, and only when the raw mtime exactly matches the recorded provenance mtime; a value merely in the same tolerance bucket does not qualify. The `BASE_IS_NEW` and `TARGET_IS_NEW` branches retain their existing timestamp-directed reflection and last-seen bookkeeping. The existing selection, file-size, and conflict policies still apply, and the `DB_APPLY` action remains unchanged.
142
152
 
143
- An equal modification time does not establish that the storage file is unchanged. Before completing such a pair, the scanner asks the file handler to record its current database revision if device-local provenance is absent. The handler reads the actual storage and database content under its document lock and records the revision only when both contain the same bytes, the database has no conflicting leaf, and its current revision agrees with the one observed by the scan. It rechecks the storage snapshot, database revision, and absent provenance before writing the record. This operation writes neither storage content nor a database revision.
153
+ The result of `tryRecordUntrackedFileRevision()` is not an edit predicate. Its `false` can mean that provenance already exists, content differs, history conflicts, required content or provenance is unavailable, or state changed during verification. When it returns `false`, the scanner uses the ordinary, non-forced `storeFileToDB` path with bounded storage revalidation; it does not force the Vault file into the current database winner. The file handler retains a known branch when readable provenance identifies it, applies newer database state when the storage bytes match that recorded older revision, and saves a genuine edit on its recorded branch.
144
154
 
145
- An existing provenance record keeps its branch identity even when the file bytes match another revision. An unreadable record, unavailable content, differing bytes, a competing branch, or a changing snapshot leaves provenance untouched. The scan still completes that equal-time pair; a later file event or incoming reflection retains the ordinary conservative conflict policy. If the local database has already advanced past the file's content, the equal-time repair cannot establish its earlier origin.
155
+ A valid saved content hash can prove that unchanged bytes still represent a recorded revision whose database content has been pruned. A hash cannot reconstruct ancestry for edited bytes. If the recorded base is pruned and the current bytes do not match its saved hash, the handler may preserve readable storage as an independent branch when it matches no current live leaf; it must not attach those bytes to an arbitrary database winner. This unknown-origin case is distinct from an unavailable comparison: if required storage, provenance, database content, or revalidation cannot be read, the operation remains unresolved and the pair fails for retry. Repeated storage changes are bounded and also remain failed rather than being reported as reconciled.
146
156
 
147
157
  ## Result-state verification
148
158
 
@@ -156,12 +166,12 @@ Focused two-pass coverage starts with a `DB_APPLY` reflection whose file handler
156
166
 
157
167
  Full-scan coverage also verifies that the strict default returns `false`, an ordinary-start-up scan which explicitly continues returns `completed-with-file-failures` while logging the affected path at verbose level, and a later clean scan returns `true`.
158
168
 
159
- The equal-time regression uses real in-memory PouchDB and the file handler. It checks missing and recorded provenance with unchanged files and genuine local edits, then advances the database and reflects the incoming revision. Focused handler tests cover provenance read and write failures, unreadable current content, an existing conflict, and storage or database changes during verification.
169
+ The equal-time regression uses real in-memory PouchDB and the file handler with `strictContentCheck` omitted, keeping its same-stat edit assertions on the strict default path. It checks missing and recorded provenance with unchanged files and genuine local edits at both identical raw timestamps and distinct raw timestamps within one comparison bucket. It verifies that edits are preserved during the scan, then advances the database and checks that an incoming revision retains the local branch. It also verifies that a matching saved hash remains useful after its database revision is pruned, while edited bytes with an unhashed pruned base and no matching live leaf are preserved independently. Focused scanner tests verify that a failed ordinary save returns a failed pair. Focused handler tests cover provenance read and write failures, unavailable recorded-base content, an existing conflict, and storage or database changes during verification.
160
170
 
161
171
  A second two-pass case starts with a `db-only` entry skipped by the size limit. It then removes that limit and verifies that the entry is reflected to storage rather than misclassified as an offline local deletion. Coverage for an existing storage file verifies that a failed `sync-newer` reflection retains the observed storage mtime, and full-scan coverage verifies that the aggregate failure reaches its caller after scanner initialisation completes.
162
172
 
163
173
  Identity coverage verifies early quarantine when no consistent target exists, continued processing when consistent Metadata represents the same logical path, expired-deletion retention, special-namespace routing, actual-ID-first inspection, stale and ambiguous repair rejection, target-first ordering, exact-target retry, source preservation after failure, and last-seen clearing.
164
174
 
165
- Stored-event coverage verifies that current storage content replaces saved observations, a recreated path suppresses a stale deletion, distinct-ID rename halves are admitted independently from current path state, and a same-ID rename never performs a separate deletion. It also verifies that a storage-inspection failure does not become evidence for a destructive operation and that an individual replay failure does not prevent the full scan from running.
175
+ Stored-event coverage verifies that current storage content replaces saved observations, a recreated path suppresses a stale deletion, distinct-ID rename halves are admitted independently from current path state, and a same-ID rename never performs a separate deletion. It also verifies that a storage-inspection failure does not become evidence for a destructive operation, that failed work remains pending, and that an individual replay failure does not prevent the full scan from running.
166
176
 
167
177
  Maintain that interaction boundary when changing file-handler results, last-seen persistence, Fast Setup reflection, or offline deletion detection.
@@ -68,6 +68,8 @@ if (prepared.requiresSyncReview) {
68
68
 
69
69
  Explicit stored values take precedence over every fallback. A migration therefore preserves synchronisation switches and other user choices unless a migration step documents a deliberate transformation.
70
70
 
71
+ `batchSaveInLiveSync` is a separate explicit choice for bounded local database batching in LiveSync mode. Both new and existing settings default to `false`; the legacy pair `liveSync: true, batchSave: true` does not enable it. The public setting property is optional for existing host settings shapes, and a missing value remains disabled. Other modes continue to use `batchSave`. Markdown settings and QR data preserve the explicit choice, while older imports without the property do not infer it from `batchSave`. Hosts should preserve both choices during ordinary mode changes and use the shared minimum and maximum delays.
72
+
71
73
  The sleep preferences are included in Setup URI encoding. When an older Setup URI or stored setting does not contain either key, `allowSleepDuringSynchronisation` defaults to `false`, while `allowSleepDuringSynchronisationOnDesktop` defaults to `true`. The maintained Obsidian host therefore retains best-effort screen-awake protection on mobile but allows normal automatic sleep on desktop unless the user selects otherwise.
72
74
 
73
75
  An existing settings store without an explicit `handleFilenameCaseSensitive` value is normalised to `false` and saved. This preserves the effective behaviour of earlier releases, where an absent value followed the case-insensitive branch. An explicit `true` or `false` value is preserved. New Vaults also select case-insensitive handling (`false`).
@@ -1,16 +1,18 @@
1
1
  ---
2
- date: 2026-09-05
3
- commonlib-version: "0.1.23"
4
- self-hosted-livesync-version: "1.0.25"
2
+ date: 2026-10-26
3
+ commonlib-version: "0.2.1"
4
+ self-hosted-livesync-version: "1.0.40"
5
+ status: unreleased
5
6
  ---
6
7
 
7
8
  # Storage events and database-to-storage reflection
8
9
 
9
10
  This note separates storage-to-database event handling from
10
- database-to-storage reflection. It describes the Commonlib 0.1.23 guard against
11
- a stale `DELETE` notification logically deleting Metadata while the same file
12
- is still present in storage. The version fields identify the reviewed source
13
- versions.
11
+ database-to-storage reflection. It documents the current guard against a stale
12
+ `DELETE` notification logically deleting Metadata while the same file is still
13
+ present in storage, and the accepted local completion contract for queued file
14
+ operations. The version fields identify the reviewed source versions; these
15
+ changes remain unreleased.
14
16
 
15
17
  ## Processing directions
16
18
 
@@ -46,6 +48,105 @@ and unsynchronised storage content retain their existing reflection rules.
46
48
  The watcher guard does not intercept this path or general explicit database
47
49
  deletion operations.
48
50
 
51
+ ## Startup scans and pending operations
52
+
53
+ The full scanner's optional `strictContentCheck: false` path is gated by the host's file-operation state. Any related queued, running, or failed operation blocks the `canSkipUnchangedFile` hint. The host must provide an available query before the scanner can establish that no related work exists; without that query or other required evidence, strict content comparison remains active. The hint is considered only after normal freshness comparison reaches `EVEN`, and requires the raw mtime to exactly match the recorded provenance value; a match within the platform comparison bucket is insufficient. `BASE_IS_NEW` and `TARGET_IS_NEW` retain their timestamp-directed reflection and last-seen bookkeeping. A pair returned as `SKIPPED` by this hint does not acknowledge or remove a saved event. Event work continues to follow the snapshot and handler-completion contract below. This option affects only the full-scan same-stat check; ordinary watcher `CREATE` and `CHANGED` handling, replicated writes, and logical-deletion protection retain their existing rules. See [Full offline scan](offline-scanner.md#startup-content-checking) for the full option and eligibility conditions.
54
+
55
+ ## Pending operations and local completion
56
+
57
+ Once admitted, a file operation remains pending while buffered, waiting for its
58
+ batch boundary, or running in a handler. The manager includes all three states
59
+ in its existing event snapshot. It saves snapshots in order, so an older write
60
+ cannot replace a newer view of pending work. The manager acknowledges an
61
+ ordinary file operation only after its effect is durable locally, current state proves the
62
+ effect is already represented or safely covered by a successful successor, or
63
+ the handler returns a skip explicitly allowed by policy. A handler
64
+ failure or exception leaves the saved operation pending. A failed initial
65
+ snapshot prevents the handler from starting; a later snapshot failure is
66
+ reported and cannot be treated as completion. Completion of an older operation
67
+ cannot remove a newer operation for the same path.
68
+
69
+ The event snapshot records operation intent, not authoritative current file
70
+ content. Admission does not read a file body merely to wait. When a `CREATE` or
71
+ `CHANGED` operation becomes eligible, the handler rechecks the current path and
72
+ obtains its current content for processing. Restored events are revalidated
73
+ against current storage and selection before they can affect the database. The
74
+ saved stub and any optional cache are not used as a historical file version.
75
+ The current-storage deletion checks and rename-exclusion rules below continue to
76
+ apply.
77
+
78
+ A restored deletion whose database entry is already absent or deleted completes
79
+ without another revision. A restored rename whose source deletion is already
80
+ represented reconciles the current target through the ordinary non-forced
81
+ handler. The same applies to a case-only rename when Metadata already records
82
+ the target spelling. Replaying either completed operation preserves later local
83
+ edits. If the source is still live and a target already exists, the existing
84
+ collision refusal remains in force: the snapshot does not prove that this
85
+ rename created that target, so the unresolved operation remains pending.
86
+
87
+ `commitPendingFileEvents()` requests immediate processing of the finite set of
88
+ operations accepted before the call, including work which is buffered,
89
+ waiting, or already in a handler. It waits for each actual handler result and
90
+ for the ordered snapshot update which records that result; releasing a timer is
91
+ not completion. Later events do not extend that captured boundary. A newer
92
+ coalesced operation satisfies an earlier one only when its successful result
93
+ covers the earlier intent. A failed handler or snapshot update is reported to
94
+ the caller and leaves unresolved work in the snapshot. This boundary covers
95
+ local completion under the selected handler contract only; it does not prove replication or receipt by another
96
+ Vault.
97
+
98
+ A `DELETE` or `RENAME` ordering sentinel waits for its predecessors only. It
99
+ must not wait for itself or events which follow it in the dispatch queue. Batch
100
+ timers make work eligible to run; they do not perform it. The quiet period
101
+ follows the latest eligible content update, while the maximum intentional delay
102
+ is measured from the first uncommitted update, starting when the queue call is
103
+ accepted and before asynchronous filtering or the touched-file check. Waiting
104
+ timers do not occupy a processing slot, and time spent waiting for a slot does
105
+ not restart the maximum delay. Once due, the operation acquires a slot and reads
106
+ current content for processing.
107
+
108
+ ## Optional file handlers
109
+
110
+ `INTERNAL` events and files marked `isInternal` use their owning feature's
111
+ contract. Hosts can register `processOptionalFileEventWithResult` handlers with
112
+ `firstHandled`. `HandlerOutcome<TValue = void, TVetoReason = void>` carries a
113
+ success value and a veto reason independently; a non-void payload is required
114
+ in its branch. Dispatch follows priority and registration order:
115
+
116
+ | Outcome | Dispatch and completion |
117
+ | --- | --- |
118
+ | `not-applicable` | Try the next provider. If none applies, complete the policy-approved skip. |
119
+ | `completed` | Stop and complete the event after the provider's actual save or verified no-op. |
120
+ | `veto` | Stop and retain the event with a reported refusal reason. |
121
+ | Exception or rejection | Propagate the original error and retain the event. |
122
+
123
+ A completed result stops dispatch immediately; a later provider cannot veto
124
+ it. Empty registration returns `not-applicable`. False or zero payloads remain
125
+ valid values. Providers must resolve their promise only after the operation
126
+ finishes. Snapshot acknowledgement still follows the handler result, and a
127
+ snapshot failure keeps the event recoverable.
128
+
129
+ The optional `whenFlushRequested` promise resolves when a finite completion
130
+ boundary includes this event. A provider which debounces saves uses it to
131
+ release that event's captured delay. It must wait for the corresponding save,
132
+ settle all notifications coalesced into that save, and keep later groups outside
133
+ the captured boundary. This signal is runtime state; it does not change the
134
+ persisted snapshot format.
135
+
136
+ Migrate all optional providers in a host together. Once the first typed provider
137
+ is registered, the manager uses the typed chain exclusively, even when every
138
+ provider returns `not-applicable`. A host without a registered typed provider
139
+ retains `processOptionalFileEvent` as a legacy notification contract. Its
140
+ boolean does not confirm persistence: a resolved notification completes only
141
+ that dispatch, while a rejection which reaches the manager is still retained.
142
+ The existing boolean aggregators and ordinary file contract are unchanged.
143
+
144
+ During start-up replay and settings or unload flushes, providers use actual
145
+ database readiness and initialise their required local state before saving.
146
+ They must not wait for an application-ready or resume event which occurs only
147
+ after that same flush. An unavailable prerequisite returns a veto; the manager
148
+ does not introduce an automatic retry loop for a failed operation.
149
+
49
150
  ## Event intent and current storage state
50
151
 
51
152
  | Event intent | Current storage observation | Admitted operation |
@@ -92,9 +193,11 @@ incoming logical deletion can still remove unchanged storage content.
92
193
 
93
194
  These tests do not establish the native watcher event sequence reported in
94
195
  Issue 1168. Real-runtime verification belongs to the host and must distinguish
95
- observed events from inferred causes. This change does not promise path-case
96
- convergence or full folder-rename support. It does not rewrite the queue,
97
- introduce a retry subsystem, or change the database schema.
196
+ observed events from inferred causes. The watcher guard does not promise
197
+ path-case convergence or full folder-rename support. Focused manager tests cover
198
+ finite completion boundaries, ordered snapshots, retained failures, replay, and
199
+ batch timing. This work adds no second journal, unbounded retry loop, or
200
+ database-schema change.
98
201
 
99
202
  The maintained host's real-runtime comparison is recorded in
100
203
  [Proven in maintained hosts](proven-in-use.md#obsidian-plug-in), separately
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vrtmrz/livesync-commonlib",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Shared data, storage, and replication primitives for Self-hosted LiveSync clients.",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -1010,7 +1010,7 @@
1010
1010
  "events": "^3.3.0",
1011
1011
  "fflate": "^0.8.2",
1012
1012
  "idb": "^8.0.3",
1013
- "markdown-it": "^14.3.2",
1013
+ "markdown-it": "^14.3.3",
1014
1014
  "minimatch": "^10.2.6",
1015
1015
  "octagonal-wheels": "^0.1.55",
1016
1016
  "pouchdb-adapter-http": "^9.0.0",