@vrtmrz/livesync-commonlib 0.1.0-rc.9 → 0.1.1-rc.0

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 (41) hide show
  1. package/README.md +7 -4
  2. package/dist/API/DirectFileManipulator.d.ts +1 -1
  3. package/dist/API/DirectFileManipulatorV2.d.ts +16 -31
  4. package/dist/API/DirectFileManipulatorV2.js +35 -11
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.js +3 -1
  7. package/dist/interfaces/DatabaseFileAccess.d.ts +8 -0
  8. package/dist/interfaces/FileHandler.d.ts +17 -1
  9. package/dist/interfaces/FileReflectionProvenance.d.ts +32 -0
  10. package/dist/interfaces/FileReflectionProvenance.js +25 -0
  11. package/dist/managers/ConflictManager.js +42 -9
  12. package/dist/managers/EntryManager/EntryManager.d.ts +1 -0
  13. package/dist/managers/EntryManager/EntryManager.js +5 -1
  14. package/dist/managers/EntryManager/EntryManagerImpls.d.ts +1 -0
  15. package/dist/managers/EntryManager/EntryManagerImpls.js +29 -1
  16. package/dist/managers/LiveSyncManagers.js +1 -1
  17. package/dist/pouchdb/LiveSyncLocalDB.d.ts +1 -0
  18. package/dist/pouchdb/LiveSyncLocalDB.js +3 -0
  19. package/dist/serviceFeatures/offlineScanner.js +3 -2
  20. package/dist/serviceFeatures/remoteConfig.d.ts +2 -2
  21. package/dist/serviceFeatures/remoteConfig.js +2 -2
  22. package/dist/serviceModules/ServiceDatabaseFileAccessBase.d.ts +5 -0
  23. package/dist/serviceModules/ServiceDatabaseFileAccessBase.js +69 -13
  24. package/dist/serviceModules/ServiceFileHandlerBase.d.ts +22 -1
  25. package/dist/serviceModules/ServiceFileHandlerBase.js +323 -48
  26. package/dist/services/HeadlessServices.d.ts +4 -0
  27. package/dist/services/HeadlessServices.js +7 -3
  28. package/dist/services/base/KeyValueDBService.d.ts +2 -1
  29. package/dist/services/base/KeyValueDBService.js +10 -6
  30. package/dist/services/base/PathService.d.ts +6 -0
  31. package/dist/services/base/PathService.js +4 -1
  32. package/dist/services/base/ReplicatorService.d.ts +1 -0
  33. package/dist/services/base/ReplicatorService.js +8 -6
  34. package/dist/services/implements/base/SvelteDialog.d.ts +5 -0
  35. package/dist/services/implements/headless/HeadlessAPIService.js +2 -0
  36. package/dist/worker/bgWorker.js +1 -1
  37. package/docs/conflict-resolution.md +173 -0
  38. package/docs/development.md +6 -0
  39. package/docs/proven-in-use.md +18 -6
  40. package/docs/releasing.md +10 -7
  41. package/package.json +6 -26
@@ -0,0 +1,173 @@
1
+ # Conflict resolution and file provenance
2
+
3
+ This document defines the Commonlib-owned revision-tree rules used when LiveSync detects, merges, resolves, and reflects file conflicts. A host still owns its user interface, binary-file policy, persistence, and the decision to offer an explicit newer-file mode.
4
+
5
+ ## Revision-tree model
6
+
7
+ PouchDB stores every file metadata document as a revision tree. One deterministic leaf is returned as the current winner, while the other live leaves appear in `_conflicts`. The winner is a database choice, not evidence that its content is newer, safer, or the version displayed by a host.
8
+
9
+ For example:
10
+
11
+ ```text
12
+ A1
13
+ ├── B1 ── C1 ── D1
14
+ └── B2 ── C2
15
+ ```
16
+
17
+ `D1` and `C2` are the leaves to compare. Their nearest common ancestor is `A1`, not `B1` or `B2`. Automatic three-way merge may proceed only when the same `available` revision is present in both leaf histories. A matching generation number alone does not establish ancestry.
18
+
19
+ Resolving a conflict writes the selected or merged result on one branch and deletes every losing live leaf which the resolver has observed. Deleted leaves remain part of the tree until compaction removes their bodies. A stale client can therefore receive the resolved branch and the tombstone for a branch whose old content is still present in its storage.
20
+
21
+ ## Chunk reachability during conflicts
22
+
23
+ A host which collects unreferenced chunks must include the current winner, every other live conflict leaf, their available divergent revisions, and their nearest available shared ancestor in its reachability scan. This preserves the content and merge base required to review an unresolved conflict. Chunk identifiers are shared across documents, so one reachability set must cover the whole database.
24
+
25
+ After the conflict is resolved, the deleted losing branch and no-longer-needed merge ancestry stop protecting their unique chunks. An ordinary superseded linear revision also does not protect its former chunks. A host must keep garbage collection separate from repair because collection cannot reconstruct content which is already unavailable.
26
+
27
+ ## Safety invariants
28
+
29
+ Commonlib follows these rules:
30
+
31
+ - compare file content byte-for-byte; a path, size, modification time, or revision generation is not proof of identity;
32
+ - use the nearest `available` revision shared by both leaves as the base for three-way merge;
33
+ - retain a manual conflict when the common revision or a required body is missing or compacted;
34
+ - treat content found in any available branch of the same tree, including an ancestor of a deleted leaf, as content which has already been synchronised;
35
+ - preserve storage content as a conflict when it is absent from all available branches; and
36
+ - never select the newest modification time as a package-level default.
37
+
38
+ The all-branch history check matters after a resolution has propagated. If a receiving device still displays the deleted losing revision, that exact content is known synchronised content. The resolved winner may replace it without recreating the conflict. If the device has edited that content again, the bytes no longer match the old revision, so the storage-protection guard preserves the new edit.
39
+
40
+ ## Resolution classes
41
+
42
+ | State | Safe automatic action |
43
+ | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
44
+ | Both leaves contain identical bytes | Collapse the duplicate leaf without creating merged content. |
45
+ | Text or structured data has a shared available base and non-overlapping changes | Perform a conservative three-way merge. |
46
+ | One side deletes content which the other leaves unchanged | Preserve the deletion. |
47
+ | One side deletes content which the other modifies | Retain a manual conflict. |
48
+ | A receiving file matches any available revision in the tree | Apply the propagated database result. |
49
+ | A receiving file matches no available revision | Preserve it as an unsynchronised conflict. |
50
+ | A body or common ancestor is missing or compacted | Retain a manual conflict. |
51
+ | Content is binary or otherwise not semantically mergeable | Leave the selection policy to the host; Commonlib cannot infer user intent. |
52
+
53
+ ## Stale and concurrent resolutions
54
+
55
+ A client can resolve only the leaves which it has observed. If it resolves an older pair while another client has already extended one branch, replication can reveal another live leaf and the document remains conflicted. Two clients can also resolve different leaves concurrently. Neither result is authoritative merely because it has a higher generation or modification time; after the trees meet, every remaining live leaf must be considered again.
56
+
57
+ This is expected conflict behaviour, not a replication reset. A resolver should process the current leaves repeatedly until one live result remains or user action is required.
58
+
59
+ ## More than two live versions
60
+
61
+ When a document has three or more live leaves, Commonlib compares the current PouchDB winner with one remaining leaf at a time. The remaining candidates are ordered by revision generation ascending, original leaf modification time ascending, then the complete revision ID in code-unit lexical order. A missing or non-finite modification time sorts before a finite time. This order makes the next pair reproducible; it does not make an earlier modification time authoritative.
62
+
63
+ Each duplicate collapse, conservative merge, or host-directed manual choice is committed to the ordinary revision tree before the next pair is considered. The resolver then reads the current live leaves again instead of retaining a separate accumulator. Completed stages therefore survive a process restart, while a new or externally resolved leaf is considered from the tree which actually exists at the next check.
64
+
65
+ A host must not apply a dialogue result after either compared revision has ceased to be the current pair. It should discard that stale result, refresh its warning or dialogue state, and queue the path again when conflicts remain.
66
+
67
+ ## File-reflection provenance
68
+
69
+ The compatibility implementation accepts an injected, device-local `FileReflectionProvenance` capability owned by the database-to-storage composition rather than by a filesystem adapter. Maintained hosts persist:
70
+
71
+ ```text
72
+ path -> { revision, observedStorageMtime? }
73
+ ```
74
+
75
+ `revision` is the exact database revision which most recently produced the file displayed on that device. `observedStorageMtime` is the raw modification time observed after reflection. It is neither rounded nor compared across devices, and it is diagnostic only: revision identity never comes from a timestamp, path, size, or content hash.
76
+
77
+ The record is updated only after a successful database-to-storage reflection or storage-to-database write. A plain read does not change provenance. A record remains authoritative when a user edits the displayed file to bytes which happen to equal another branch; content equality must not silently change the branch being extended.
78
+
79
+ User-directed reconciliation may select any exact current live revision, including a conflict leaf rather than the deterministic winner. `dbToStorageWithSpecificRev()` rechecks that the selected revision is still live before reflecting its content. `storeFileToDBWithBaseRevision()` performs the same live check before storing current storage content as a child of that revision. If the content already matches the selected non-deleted revision, it records that revision directly without creating a child. A host can set `createIfDifferent` to `false` when it intends only to record an exact content match; differing content then fails instead of changing the revision tree. A host may also create a logical deletion on an explicitly selected branch with `deleteRevisionFromDB()` after performing its own current-live-revision check. This leaves storage unchanged and clears device-local provenance only when that record names the deleted revision.
80
+
81
+ A successful non-deleted reconciliation records the exact selected or newly created revision as provenance. Applying a logical deletion removes the storage item and its provenance record. Deletion provenance is not retained indefinitely: an absent storage item and a current logical-deletion winner already agree and need no further reconciliation.
82
+
83
+ Hosts may construct the namespaced store handle during service composition, before its backing database is open. Store operations begin only after the host's storage lifecycle is ready. They fail rather than wait when that lifecycle contract is violated, because an implicit readiness wait could hang after failed initialisation or wait on its own initialisation handler. Database reset is a transient unavailable boundary; the host must avoid file processing during it and reconstruct derived provenance after reopening.
84
+
85
+ When a record is absent, the implementation reconstructs it only if the current storage bytes match exactly one available revision body. No match, or more than one match, leaves the branch identity unknown.
86
+
87
+ ## Operations while a conflict exists
88
+
89
+ With a proven displayed revision:
90
+
91
+ - an edit writes a child of that exact revision;
92
+ - a deletion writes a logical-deletion child of that exact revision, using the document's `deleted` marker rather than a PouchDB `_deleted` tombstone, so the operation remains a visible live branch until the conflict is resolved;
93
+ - a case-only rename writes the new path as a child of the displayed revision in the same document tree; and
94
+ - a cross-path rename stores the target first, then writes a logical-deletion child on the displayed source branch.
95
+
96
+ When edit provenance cannot be reconstructed, the new bytes are preserved as another manual-resolution branch instead of being attached silently to the deterministic winner. A deletion has no remaining file body from which to reconstruct provenance, so an unproven deletion preserves every live branch and requests conflict review. A cross-path rename with an unproven source keeps the newly stored target and preserves every source branch for review.
97
+
98
+ If chunks needed to reconstruct the winning body are missing, the document metadata and revision tree may still identify the exact winning revision and its parent. In that case, an edited storage file is preserved as a sibling branch from the shared parent without treating the unavailable body as trusted content. A generation-one revision has no parent from which a sibling can be created. If the parent or revision metadata is unavailable, Commonlib refuses the write and requests conflict review rather than inventing a base revision. A host may offer a separately confirmed exact-revision discard: logical deletion needs the revision metadata, not its chunks, and a confirmed replacement can then be stored. This is a recovery decision and must not be performed automatically.
99
+
100
+ These fallbacks favour recoverability. They may temporarily leave a duplicate target or a still-visible source conflict, but they do not discard a branch whose relationship to the user's operation cannot be proved.
101
+
102
+ ## Worked revision-tree scenarios
103
+
104
+ ### Editing the branch displayed by a host
105
+
106
+ Assume that the database has this conflict:
107
+
108
+ ```text
109
+ A1
110
+ ├── B1 ── C1 database winner
111
+ └── B2 ── C2 displayed in storage
112
+ ```
113
+
114
+ The host records `C2` when it reflects that branch into storage. If the user edits the displayed file, Commonlib writes the new revision as a child of `C2`:
115
+
116
+ ```text
117
+ A1
118
+ ├── B1 ── C1
119
+ └── B2 ── C2 ── D2 edited content
120
+ ```
121
+
122
+ It does not attach the edit to `C1`, even if PouchDB still returns `C1` as its deterministic winner. Replication carries both live leaves, `C1` and `D2`, so a resolver can compare the branches which actually produced the conflict.
123
+
124
+ ### Deleting the displayed branch
125
+
126
+ With the same starting tree and recorded `C2` provenance, deleting the storage file writes a logical-deletion child of `C2`:
127
+
128
+ ```text
129
+ A1
130
+ ├── B1 ── C1
131
+ └── B2 ── C2 ── D2 (deleted: true)
132
+ ```
133
+
134
+ `D2` remains a live metadata branch. The host can therefore ask whether to retain `C1` or the deletion. A PouchDB `_deleted` tombstone would remove that decision from the live conflict and is not used for this operation.
135
+
136
+ ### Renaming while a conflict is visible
137
+
138
+ For a case-only rename, such as `Note.md` to `note.md`, the renamed entry is written as a child of the displayed revision in the same document tree. The other live branch remains available for resolution.
139
+
140
+ For a cross-path rename, such as `draft.md` to `published.md`, Commonlib stores `published.md` first. It then writes a logical-deletion child of the recorded branch in the conflicted `draft.md` tree. Storing the target first favours recoverability: an interruption can leave a duplicate for review, but it cannot remove the only copy before the target exists.
141
+
142
+ ### Reconstructing provenance after local state is unavailable
143
+
144
+ Suppose a local-database reset removed the device-local record, but the storage file still has exactly the bytes held by `C2`. If `C2` is the only available revision with those bytes, Commonlib reconstructs `C2` as the displayed base and an edit extends it normally.
145
+
146
+ If both `C1` and `C2` contain the same bytes, or neither available body matches, content cannot identify the displayed branch. An edit is preserved as another manual-resolution branch. A deletion preserves both existing branches, and a cross-path rename preserves the source branches after storing the target. These outcomes may require user review, but they do not guess that the deterministic winner was displayed.
147
+
148
+ ### Receiving a resolution while showing the losing branch
149
+
150
+ Device A may resolve the conflict while Device B still displays the bytes from `C2`. When the resolved tree reaches Device B, the all-branch history check recognises those bytes below the deleted losing leaf. Device B can apply the resolution without recreating the conflict. If Device B edited the file after displaying `C2`, its bytes no longer match the historical revision and the overwrite guard preserves that new local edit instead.
151
+
152
+ ### Starting and resetting the provenance store
153
+
154
+ A host may create the namespaced provenance handle while composing services. It then opens the backing key-value database in its sequential settings lifecycle before enabling scans, storage watchers, or replication. If opening fails, start-up stops; a provenance operation is not held waiting for a readiness state which may never arrive.
155
+
156
+ During a local-database reset, the store is temporarily unavailable. A racing lookup fails promptly and is treated as unknown provenance, selecting the conservative behaviours above. Once the database has reopened, a later scan can reconstruct uniquely identifiable records from exact revision bodies.
157
+
158
+ ## Unsafe shortcuts
159
+
160
+ Do not:
161
+
162
+ - use the first revision whose generation is lower than the other leaf as a supposed common ancestor;
163
+ - select a winner from modification time unless the host exposes and the user selects that destructive policy;
164
+ - assume the PouchDB winner is the content currently displayed in storage;
165
+ - replace a recorded displayed revision merely because current bytes match another branch;
166
+ - recreate a conflict from a byte-identical deleted losing revision after a remote resolution;
167
+ - discard storage content because history lookup failed;
168
+ - infer revision identity from path, size, modification time, or hash without the revision identifier; or
169
+ - automatically merge overlapping text changes or unrelated binary contents.
170
+
171
+ ## Verification ownership
172
+
173
+ Commonlib unit tests build real in-memory PouchDB trees and inject provenance fakes at the file-handler boundary. They cover unequal branch lengths, nearest shared ancestry, deterministic selection from multiple live leaves, reconstruction of a later manual pair after an earlier sensible merge, content retained below a deleted losing leaf, recorded and reconstructed branch identity, ambiguous content, conflict-time editing, missing-body preservation from available parent metadata, refusal to invent a parent for a generation-one revision, exact logical deletion and replacement of an unreadable generation-one live leaf, case-only rename, cross-path rename, and safe unproven fallbacks. A maintained host should additionally verify its composition: persistent device-local provenance, real file events, replication of the resulting revision trees, and dialogue policy.
@@ -57,6 +57,12 @@ The settings lifecycle has an additional focused unit table in `src/common/model
57
57
 
58
58
  The multiple-remote profile contract has focused unit coverage in `src/serviceFeatures/remoteConfig.unit.spec.ts`. Extend it whenever profile identity, generated display names, activation, P2P selection, URI serialisation, or legacy migration changes. New hosts should import the reviewed primitives through `/remote-configurations`; `compat/serviceFeatures/remoteConfig` remains the wider migration surface used by existing clients.
59
59
 
60
+ The full offline scanner applies both the host's target-file policy and Commonlib's built-in `shouldBeIgnored` policy before constructing storage/database pairs. This keeps legacy LiveSync log files and flag files outside ordinary create, restore, and delete decisions even when a host accepts their file extension. Lower-level write guards remain a defence for direct calls; they are not the normal full-scan filter. Extend both storage-collection and database-collection tests whenever the built-in exclusions change.
61
+
62
+ The revision-tree safety rules are documented in [Conflict resolution and file provenance](conflict-resolution.md). `ConflictManager.unit.spec.ts` owns common-ancestor and conservative merge behaviour, deterministic pair selection for multiple live leaves, and reconstruction from the committed tree between stages. `ServiceDatabaseFileAccessBase.unit.spec.ts` owns all-branch content discovery and exact-revision logical deletion. `ServiceFileHandlerBase.unit.spec.ts` owns the injected provenance policy for edit, deletion, and rename operations, together with user-selected reflection, child creation, strict matching, matching-provenance cleanup after exact branch deletion, and stale-revision refusal. `LiveSyncLocalDB.unit.spec.ts` owns chunk reachability across current winners, shared chunks, live conflicts, available merge ancestry, conflict resolution, and PouchDB replication of collection and recreation. Extend the relevant sides when changing conflict resolution, compaction handling, provenance, chunk collection, or database-to-storage overwrite protection.
63
+
64
+ `openSimpleStore()` may create an inert namespace handle during composition, before the backing key-value database has opened. Maintained hosts must complete their sequential settings lifecycle before scans, watchers, or replication invoke the handle. Operations fail on a lifecycle violation rather than waiting for readiness; do not introduce an implicit wait which can hang after failed initialisation or become self-referential from an initialisation handler. Treat database reset as a transient unavailable boundary and reconstruct derived local state after reopening.
65
+
60
66
  ### P2P composition ownership
61
67
 
62
68
  `useP2PReplicatorFeature` is the sole owner of the active `LiveSyncTrysteroReplicator` and its lifecycle bindings. It creates or replaces the outer replicator when `ReplicatorService` requests one, closes the previous instance before replacement, shares an in-flight replacement between concurrent callers, and returns a stable result object whose `replicator` property resolves the current instance.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Commonlib is extracted from the implementation shared by [Self-hosted LiveSync](https://github.com/vrtmrz/obsidian-livesync) and its host applications. The package is therefore exercised by a substantial maintained consumer, but it does not yet claim a separate ecosystem of independent applications or a stable end-to-end SDK.
4
4
 
5
- The useful evidence is host- and contract-specific. Focused Commonlib tests establish reusable result semantics; Self-hosted LiveSync tests establish how those contracts compose into the Obsidian plug-in and CLI. The Webapp has focused adapter tests, and both the Webapp and WebPeer are type-checked and built, but they do not have an equally current end-to-end gate.
5
+ The useful evidence is host- and contract-specific. Focused Commonlib tests establish reusable result semantics; Self-hosted LiveSync tests establish how those contracts compose into the Obsidian plug-in, CLI, WebApp, and WebPeer. The Obsidian plug-in and CLI retain the strongest device and long-running synchronisation evidence. The browser applications add focused adapter and runtime tests, browser smoke tests, a cross-application P2P scenario ending at the production CLI, and public Pages smoke validation.
6
6
 
7
7
  The code paths below identify the maintained Self-hosted LiveSync composition points used to establish this package boundary. The exact reviewed downstream revision and compatibility imports are recorded in [`migration/downstream-imports.json`](migration/downstream-imports.json), which is regenerated by the package-boundary tooling rather than copied into prose links. The integration descriptions must still be reviewed when the maintained downstream changes.
8
8
 
@@ -12,12 +12,18 @@ The plug-in extends the neutral `ServiceContext` with Obsidian-owned capabilitie
12
12
 
13
13
  The consumer contract in `test/contracts/serviceContext.ts` checks shared result semantics and exact context identity across the composed services. The real-Obsidian suite documented in `test/e2e-obsidian/README.md` then covers plug-in loading, representative Svelte dialogue mounts, Vault reflection, CouchDB and Object Storage synchronisation, a two-device P2P Setup URI round-trip, two-Vault behaviour, and other Obsidian-owned boundaries.
14
14
 
15
+ Commonlib's real-PouchDB and injected-boundary unit tests prove that conservative merge uses the nearest revision actually shared by both conflict leaves, multiple remaining leaves are selected in a deterministic order, each completed pair is reconstructed from the committed tree, storage content from a deleted losing branch remains recognisable after resolution, and conflict-time operations retain the exact displayed branch or preserve every branch when that identity cannot be proved. They also verify that a local edit remains a readable sibling branch when the winning body is unavailable but its exact revision and parent metadata remain, that a generation-one revision without a parent is left unresolved, and that a separately confirmed exact deletion of that unreadable live leaf permits a replacement to be stored without reading the missing chunk. Exact reconciliation tests cover reflecting a selected live leaf, storing Vault content as its child, recording a matching leaf without creating a revision, clearing matching provenance after exact branch deletion, and refusing to record a stale revision or a revision whose content differs. Self-hosted LiveSync supplies the host composition, persistent device-local provenance, and the per-revision repair interface. Its optional two-Vault scopes cover post-resolution propagation and edit, logical deletion, case-only rename, and cross-path rename while conflicts remain active. The conflict-operation scope passes in real Obsidian against the exact locally packed Commonlib artefact, including replicated parent revisions and preservation of the other live branches.
16
+
17
+ Commonlib's chunk-reachability tests use real PouchDB revision trees. They verify that an ordinary superseded revision releases its unique chunks, a chunk shared by current files remains reachable, live conflict branches and their nearest available shared ancestor remain protected, and losing-branch ancestry becomes eligible after resolution. They also replicate a collected chunk deletion to another PouchDB database and verify that writing the same content can recreate and replicate the content-addressed chunk. Self-hosted LiveSync owns the CouchDB-specific Garbage Collection V3 workflow, including device checks, confirmation, deletion, push-only replication, and remote compaction.
18
+
15
19
  Most replication and storage services used by the plug-in still enter through explicit `compat/*` paths. Those imports prove that the published compatibility boundary supports the current migration; they are not examples of a finished high-level client API.
16
20
 
17
21
  The plug-in's manual onboarding and Remote Databases pane use the focused `/remote-configurations` entry to create opaque profile IDs, retain user-visible names, and select main and P2P remotes. Commonlib owns the in-memory profile result contract; Self-hosted LiveSync adds dialogue, Setup URI classification, Fetch or Rebuild scheduling, persistence, restart ordering, and real-Obsidian presentation checks.
18
22
 
19
23
  Commonlib's confirmation contract can also convey a host-neutral vertical action-layout request for long, safety-sensitive choices. Self-hosted LiveSync maps that request to Fancy Kit at its Obsidian boundary. Focused tests check the mapping, and the consumer's real-Obsidian suite checks the resulting controls and mobile touch targets; Commonlib does not own the rendering or CSS.
20
24
 
25
+ Self-hosted LiveSync's Obsidian and browser dialogue hosts now share an application-owned `SvelteDialogSession` lifecycle. They continue to consume Commonlib's dialogue types and manager base, but no maintained LiveSync path uses `SvelteDialogMixIn`. The dynamic-inheritance helper remains available through the compatibility boundary and is deprecated in favour of host-owned composition.
26
+
21
27
  ## CLI
22
28
 
23
29
  The CLI extends the same neutral context with its database path and injected `StandardIo` in `src/apps/cli/services/NodeServiceContext.ts`. Its entry point creates the Node standard-I/O adapter once and passes it to command composition, while unit tests provide stream-independent fakes for parsing, prompts, standard output, and standard error.
@@ -26,20 +32,26 @@ The CLI filesystem composition receives a configured base directory and construc
26
32
 
27
33
  The maintained CLI checks include unit tests, Deno-driven CLI E2E, Compose-based compatibility runs, and the CLI-to-real-Obsidian synchronisation scenario in `test/e2e-obsidian/scripts/cli-to-obsidian-sync.ts`. The latter is the strongest current evidence that the CLI and plug-in compose compatible Commonlib behaviour across two hosts.
28
34
 
29
- ## Webapp
35
+ ## WebApp
30
36
 
31
- The Webapp receives an already authorised `FileSystemDirectoryHandle` from its host and constructs `FileSystemAccessStorageAdapter` inside `src/apps/webapp/adapters/FSAPIFileSystemAdapter.ts`. The adapter does not call `showDirectoryPicker()`, choose a directory, or persist the handle. This is the maintained application example for the browser entry's root-ownership rule.
37
+ WebApp receives an already authorised `FileSystemDirectoryHandle` from its host and constructs `FileSystemAccessStorageAdapter` inside `src/apps/webapp/adapters/FSAPIFileSystemAdapter.ts`. The adapter does not call `showDirectoryPicker()`, choose a directory, or persist the handle. This is the maintained application example for the browser entry's root-ownership rule.
32
38
 
33
- The browser and Node storage implementations run through the same Commonlib result contract, and the Webapp adapter has focused unit coverage. The Webapp is also type-checked and built by downstream verification. Its browser E2E has not been kept at the same level as the Obsidian and CLI suites, so the documentation does not treat Webapp integration as equivalent real-runtime evidence.
39
+ The browser and Node storage implementations run through the same Commonlib result contract, and the WebApp adapter and runtime have focused unit coverage. Application-owned Deno and Chromium checks cover Root selection, runtime start-up, settings, scanning, and P2P controls. The Compose interoperability scenario transfers content from WebApp to WebPeer and then to the production CLI, while the public Pages smoke check covers the deployed Root and configuration flow. This evidence remains narrower than the real-Obsidian and CLI synchronisation suites.
34
40
 
35
41
  ## WebPeer and browser service composition
36
42
 
37
- The browser service composition in `src/apps/browser/createLiveSyncBrowserServiceHub.ts` supplies browser-owned API and UI services around a caller-supplied context. WebPeer and the browser applications exercise this compatibility surface through type checks and builds.
43
+ The browser service composition in `src/apps/browser/createLiveSyncBrowserServiceHub.ts` is owned by Self-hosted LiveSync. It combines Commonlib's injectable service contracts and data implementations with LiveSync-owned browser API, UI, settings persistence, restart policy, and Vault identity. Commonlib therefore does not provide or select a general browser host.
38
44
 
39
- The Obsidian plug-in, CLI, and Webapp compose P2P ownership through `useP2PReplicatorFeature`. Commands and Obsidian views retain its live result object and resolve the current replicator when an action occurs. WebPeer still enters through the deprecated combined compatibility function, which now delegates the same ownership and lifecycle to the focused feature instead of maintaining a second implementation. Downstream Deno and Compose P2P E2E cover headless peer discovery and synchronisation. The real-Obsidian Setup URI workflow additionally covers first-device local initialisation, one-pass additional-device Fetch, explicit peer approval, Vault reflection, and a two-way note round-trip. The Webapp and WebPeer UI surfaces themselves remain build- and type-check evidence rather than equivalent real-runtime gates.
45
+ The Obsidian plug-in, CLI, WebApp, and WebPeer compose P2P ownership through `useP2PReplicatorFeature`. Commands and views retain its live result object and resolve the current replicator when an action occurs. Downstream Deno and Compose P2P E2E cover headless peer discovery and synchronisation, including the WebApp-to-WebPeer-to-CLI journey. Application-owned Chromium checks cover the browser UI flows, and the public Pages deployment has separate post-deployment smoke validation. The real-Obsidian Setup URI workflow additionally covers first-device local initialisation, one-pass additional-device Fetch, explicit peer approval, Vault reflection, and a two-way note round-trip.
40
46
 
41
47
  This is evidence that the current package boundary can be consumed without the Obsidian host, but it is not yet a recommended standalone Commonlib client factory. The high-level lifecycle, readiness, error, and disposal contract remains future package work after Self-hosted LiveSync 1.0 rather than a gate for that release.
42
48
 
49
+ ## LiveSync Bridge
50
+
51
+ [LiveSync Bridge](https://github.com/vrtmrz/livesync-bridge) uses the root `DirectFileManipulator` entry to access CouchDB directly from Deno. The host supplies its fetch implementation, owns reachability probes, retries, and process health, and waits for the manipulator's readiness result before starting its watch. It does not compose the full LiveSync application Service Hub or use the planned file-client factory.
52
+
53
+ The downstream Deno checks and unit tests run against the installed package. Its Compose integration starts a real CouchDB instance and verifies create, read, update, and deletion between two Bridge peers. This is evidence for the existing direct-access migration path. Commonlib's focused tests separately cover host fetch injection and initialisation failure reporting. This evidence does not make `DirectFileManipulator` a stable high-level SDK contract or establish the semantics still reserved for `createLiveSyncFileClient`.
54
+
43
55
  ## Verification ownership
44
56
 
45
57
  Commonlib tests the result and lifecycle contracts of the real implementations it publishes. A maintained host tests only its own composition, persistence, presentation, restart, and policy. When a current host test needs isolation, the host may inject the narrow structural capability it consumes and provide a small fake or spy for that test; Commonlib does not publish test-runner-specific mocks or expose its private service graph.
package/docs/releasing.md CHANGED
@@ -33,7 +33,7 @@ Before publication, run the downstream workflow against an exact Self-hosted Liv
33
33
 
34
34
  Choose the version explicitly. Use a prerelease such as `0.1.0-rc.0` when registry installation must be validated before the first stable version. Package-proof versions are local artefacts and cannot be staged.
35
35
 
36
- For a stable release, prepare the version on a release branch based on `main`, review it, and merge the exact release commit into `main` before dispatch. For a pre-release which must be validated before its feature pull request is merged, prepare the version commit on that reviewed draft branch and keep the pull request in draft:
36
+ Prepare both stable releases and pre-releases on a reviewed pull request based on `main`. A pre-release may be staged from the exact reviewed pull-request branch commit so that the registry artefact can be validated in Self-hosted LiveSync before the Commonlib change is merged. A stable release must be staged only after its exact reviewed release commit is present on `main`.
37
37
 
38
38
  ```bash
39
39
  npm ci
@@ -42,7 +42,7 @@ npm run verify:package
42
42
  npm publish --dry-run .package --tag next --access public
43
43
  ```
44
44
 
45
- Review `package.json`, `package-lock.json`, the generated manifest, the tarball contents, the test results, and the downstream evidence. Commit only the source manifest and lockfile for the version change; `.package`, `.package-consumer`, and `artifacts` are generated and ignored. Push and open a release pull request only after the usual user checkpoint.
45
+ Review `package.json`, `package-lock.json`, the generated manifest, the tarball contents, the test results, and the downstream evidence. Commit only the source manifest and lockfile for the version change; `.package`, `.package-consumer`, and `artifacts` are generated and ignored. Push and open a release pull request only after the usual user checkpoint. Keep a pre-release pull request in draft while its exact registry artefact is validated in Self-hosted LiveSync. Merge and stable publication remain separate later checkpoints.
46
46
 
47
47
  ## Historical initial npm bootstrap
48
48
 
@@ -58,7 +58,7 @@ npm publish .package --tag next --access public
58
58
 
59
59
  Confirm the authenticated npm account, `@vrtmrz` scope ownership, package name, version, tarball checksum, packed contents, source commit, and target tag immediately before publication. Treat bootstrap publication as a separate user-authorised operation. npm may assign `latest` to the first published version even when `next` is requested; leave the immutable version in place and replace `latest` only after a stable release has passed consumer validation.
60
60
 
61
- Keep the release pull request in draft while the published artefact is validated in Self-hosted LiveSync. If validation succeeds, merge the exact reviewed commit. If validation fails, leave the published version immutable and prepare a new pre-release version.
61
+ Bootstrap publication was the one historical exception which used direct `npm publish`. Do not repeat that operation for routine releases. Current pre-releases use protected staged publishing and may select an exact reviewed branch commit; stable releases use an exact `main` commit. If validation fails, leave the published version immutable and prepare a new pre-release version.
62
62
 
63
63
  ## Trusted staged publishing
64
64
 
@@ -69,12 +69,15 @@ After bootstrap, configure the npm Trusted Publisher for:
69
69
  - environment: `npm`; and
70
70
  - allowed action: staged publishing only.
71
71
 
72
- Protect the GitHub `npm` environment with a required reviewer and permit only `main`. The trusted workflow definition always runs from `main`, but a pre-release may package an exact commit from a reviewed draft branch. The workflow confirms that the named source branch still points to the requested full commit SHA, permits stable versions only when the source branch is `main`, runs `verify:package`, validates the source ref, requested version, full commit SHA, and confirmation text, packs that reviewed output, records its checksum, and passes the same tarball to the protected staging job.
72
+ Protect the GitHub `npm` environment with a required reviewer and permit only `main`. The trusted workflow definition always runs from `main`. A pre-release may package an exact commit from a reviewed draft branch, while a stable release also requires the package source and workflow-triggering commit to be the exact current `main` commit. The workflow confirms that the selected source branch still points to the requested full commit SHA, checks out that SHA, runs `verify:package`, validates the requested version and confirmation text, packs the reviewed output, records its checksum, and passes the same tarball to the protected staging job.
73
73
 
74
- Dispatch a pre-release from its reviewed draft branch with the exact version and full commit SHA:
74
+ npm's automatic provenance identifies the `main` commit which triggered the trusted workflow. For a pre-release selected from a draft branch, this identifies the trusted workflow source rather than the packaged source commit. Record and verify the separate `source_ref`, exact package SHA, and tarball checksum before approving the staged version. This provenance distinction is accepted for an immutable pre-release used to validate the consumer before merge. A stable release requires the workflow and package source to be the same exact `main` commit.
75
+
76
+ Dispatch a pre-release from its exact reviewed draft branch commit:
75
77
 
76
78
  ```bash
77
79
  source_ref=separate-setting-lifecycle
80
+ git fetch origin "$source_ref"
78
81
  sha=$(git rev-parse "origin/$source_ref")
79
82
  gh workflow run publish-npm.yml \
80
83
  --ref main \
@@ -84,6 +87,6 @@ gh workflow run publish-npm.yml \
84
87
  -f confirmation="stage @vrtmrz/livesync-commonlib@<version> from $sha"
85
88
  ```
86
89
 
87
- For a stable version, use `source_ref=main`; the workflow rejects a stable version selected from any other source branch. The workflow dispatch itself still uses `--ref main` in both cases so that an unmerged branch cannot change the trusted publication job or acquire access to the `npm` environment.
90
+ For a stable release, use `source_ref=main`. The workflow rejects a stable release selected from another branch, rejects a stable release without its trusted workflow commit, and requires the workflow-triggering SHA to equal the packaged `main` SHA. The workflow dispatch itself uses `--ref main` for every release, so an unmerged branch cannot change the trusted publication job or acquire access to the `npm` environment.
88
91
 
89
- The workflow always stages to `next`. Inspect the staged package name, version, access, dist-tag, provenance, checksum, files, source branch, and source commit before approving it through npm. Approval and later promotion to `latest` are separate user-authorised operations. Keep a pre-release pull request in draft while validating the exact registry version in Self-hosted LiveSync, then merge the validated change if it succeeds. Validate a stable release in Self-hosted LiveSync before promoting it to `latest`.
92
+ The workflow always stages to `next`. Inspect the staged package name, version, access, dist-tag, provenance, checksum, files, selected source branch, and source commit before approving it through npm. Approval and later promotion to `latest` are separate user-authorised operations. Keep the Commonlib and Self-hosted LiveSync consumer pull requests in draft while validating the exact registry pre-release. If validation fails, leave that published version immutable and prepare a new pre-release. After successful consumer validation, merge Commonlib only through its separate maintainer gate. Validate a stable release in Self-hosted LiveSync before promoting it to `latest`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vrtmrz/livesync-commonlib",
3
- "version": "0.1.0-rc.9",
3
+ "version": "0.1.1-rc.0",
4
4
  "description": "Shared data, storage, and replication primitives for Self-hosted LiveSync clients.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -71,11 +71,6 @@
71
71
  "import": "./dist/API/processSetting.js",
72
72
  "default": "./dist/API/processSetting.js"
73
73
  },
74
- "./compat/bureau/bureau": {
75
- "types": "./dist/bureau/bureau.d.ts",
76
- "import": "./dist/bureau/bureau.js",
77
- "default": "./dist/bureau/bureau.js"
78
- },
79
74
  "./compat/common/ConnectionString": {
80
75
  "types": "./dist/common/ConnectionString.d.ts",
81
76
  "import": "./dist/common/ConnectionString.js",
@@ -206,6 +201,11 @@
206
201
  "import": "./dist/interfaces/FileHandler.js",
207
202
  "default": "./dist/interfaces/FileHandler.js"
208
203
  },
204
+ "./compat/interfaces/FileReflectionProvenance": {
205
+ "types": "./dist/interfaces/FileReflectionProvenance.d.ts",
206
+ "import": "./dist/interfaces/FileReflectionProvenance.js",
207
+ "default": "./dist/interfaces/FileReflectionProvenance.js"
208
+ },
209
209
  "./compat/interfaces/KeyValueDatabase": {
210
210
  "types": "./dist/interfaces/KeyValueDatabase.d.ts",
211
211
  "import": "./dist/interfaces/KeyValueDatabase.js",
@@ -326,16 +326,6 @@
326
326
  "import": "./dist/replication/trystero/P2PLogCollector.js",
327
327
  "default": "./dist/replication/trystero/P2PLogCollector.js"
328
328
  },
329
- "./compat/replication/trystero/P2PReplicatorBase": {
330
- "types": "./dist/replication/trystero/P2PReplicatorBase.d.ts",
331
- "import": "./dist/replication/trystero/P2PReplicatorBase.js",
332
- "default": "./dist/replication/trystero/P2PReplicatorBase.js"
333
- },
334
- "./compat/replication/trystero/P2PReplicatorCore": {
335
- "types": "./dist/replication/trystero/P2PReplicatorCore.d.ts",
336
- "import": "./dist/replication/trystero/P2PReplicatorCore.js",
337
- "default": "./dist/replication/trystero/P2PReplicatorCore.js"
338
- },
339
329
  "./compat/replication/trystero/P2PReplicatorPaneCommon": {
340
330
  "types": "./dist/replication/trystero/P2PReplicatorPaneCommon.d.ts",
341
331
  "import": "./dist/replication/trystero/P2PReplicatorPaneCommon.js",
@@ -431,11 +421,6 @@
431
421
  "import": "./dist/serviceModules/adapters/index.js",
432
422
  "default": "./dist/serviceModules/adapters/index.js"
433
423
  },
434
- "./compat/services/BrowserServices": {
435
- "types": "./dist/services/BrowserServices.d.ts",
436
- "import": "./dist/services/BrowserServices.js",
437
- "default": "./dist/services/BrowserServices.js"
438
- },
439
424
  "./compat/services/ServiceHub": {
440
425
  "types": "./dist/services/ServiceHub.d.ts",
441
426
  "import": "./dist/services/ServiceHub.js",
@@ -511,11 +496,6 @@
511
496
  "import": "./dist/services/implements/base/UIService.js",
512
497
  "default": "./dist/services/implements/base/UIService.js"
513
498
  },
514
- "./compat/services/implements/browser/BrowserAPIService": {
515
- "types": "./dist/services/implements/browser/BrowserAPIService.d.ts",
516
- "import": "./dist/services/implements/browser/BrowserAPIService.js",
517
- "default": "./dist/services/implements/browser/BrowserAPIService.js"
518
- },
519
499
  "./compat/services/implements/browser/ConfigServiceBrowserCompat": {
520
500
  "types": "./dist/services/implements/browser/ConfigServiceBrowserCompat.d.ts",
521
501
  "import": "./dist/services/implements/browser/ConfigServiceBrowserCompat.js",