@vrtmrz/livesync-commonlib 0.1.0-rc.1 → 0.1.0-rc.10

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 (141) hide show
  1. package/README.md +90 -20
  2. package/dist/API/DirectFileManipulatorV2.d.ts +3 -0
  3. package/dist/common/configForDoc.d.ts +1 -0
  4. package/dist/common/configForDoc.js +24 -7
  5. package/dist/common/models/setting.const.defaults.d.ts +19 -0
  6. package/dist/common/models/setting.const.defaults.js +21 -2
  7. package/dist/common/models/setting.const.preferred.js +1 -2
  8. package/dist/common/models/setting.lifecycle.d.ts +33 -0
  9. package/dist/common/models/setting.lifecycle.js +121 -0
  10. package/dist/common/models/setting.type.d.ts +10 -4
  11. package/dist/common/models/shared.definition.configNames.js +3 -3
  12. package/dist/common/models/tweak.definition.d.ts +1 -1
  13. package/dist/common/models/tweak.definition.js +1 -6
  14. package/dist/common/settingConstants.d.ts +3 -2
  15. package/dist/common/settingConstants.js +6 -6
  16. package/dist/common/types.d.ts +4 -2
  17. package/dist/common/types.js +16 -1
  18. package/dist/context.d.ts +2 -1
  19. package/dist/context.js +4 -0
  20. package/dist/interfaces/Confirm.d.ts +10 -2
  21. package/dist/interfaces/DatabaseFileAccess.d.ts +8 -0
  22. package/dist/interfaces/DatabaseRebuilder.d.ts +12 -2
  23. package/dist/interfaces/FileReflectionProvenance.d.ts +32 -0
  24. package/dist/interfaces/FileReflectionProvenance.js +25 -0
  25. package/dist/managers/ConflictManager.js +16 -7
  26. package/dist/managers/EntryManager/EntryManager.d.ts +1 -0
  27. package/dist/managers/EntryManager/EntryManager.js +5 -1
  28. package/dist/managers/EntryManager/EntryManagerImpls.d.ts +1 -0
  29. package/dist/managers/EntryManager/EntryManagerImpls.js +29 -1
  30. package/dist/platform/node/storage.d.ts +10 -1
  31. package/dist/platform/node/storage.js +86 -25
  32. package/dist/pouchdb/LiveSyncLocalDB.d.ts +1 -0
  33. package/dist/pouchdb/LiveSyncLocalDB.js +3 -0
  34. package/dist/remoteConfigurations.d.ts +9 -0
  35. package/dist/remoteConfigurations.js +14 -0
  36. package/dist/replication/LiveSyncAbstractReplicator.d.ts +3 -3
  37. package/dist/replication/couchdb/LiveSyncReplicator.d.ts +0 -1
  38. package/dist/replication/couchdb/LiveSyncReplicator.js +0 -3
  39. package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +0 -1
  40. package/dist/replication/journal/LiveSyncJournalReplicator.js +0 -3
  41. package/dist/replication/trystero/LiveSyncTrysteroReplicator.d.ts +5 -3
  42. package/dist/replication/trystero/LiveSyncTrysteroReplicator.js +42 -30
  43. package/dist/replication/trystero/P2PReplicatorCore.d.ts +2 -6
  44. package/dist/replication/trystero/P2PReplicatorCore.js +16 -86
  45. package/dist/replication/trystero/TrysteroReplicator.d.ts +2 -1
  46. package/dist/replication/trystero/TrysteroReplicator.js +10 -4
  47. package/dist/replication/trystero/TrysteroReplicatorP2PServer.d.ts +4 -5
  48. package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +22 -30
  49. package/dist/replication/trystero/UseP2PReplicatorResult.d.ts +10 -2
  50. package/dist/replication/trystero/addP2PEventHandlers.d.ts +4 -2
  51. package/dist/replication/trystero/addP2PEventHandlers.js +9 -10
  52. package/dist/replication/trystero/types.d.ts +2 -0
  53. package/dist/replication/trystero/useP2PReplicatorCommands.d.ts +1 -1
  54. package/dist/replication/trystero/useP2PReplicatorCommands.js +3 -1
  55. package/dist/replication/trystero/useP2PReplicatorFeature.js +25 -15
  56. package/dist/rpc/RpcRoom.d.ts +1 -1
  57. package/dist/rpc/RpcRoom.js +22 -16
  58. package/dist/rpc/transports/TrysteroTransport.d.ts +2 -0
  59. package/dist/rpc/transports/TrysteroTransport.js +24 -17
  60. package/dist/rpc/transports/trysteroRoomEvents.d.ts +14 -0
  61. package/dist/rpc/transports/trysteroRoomEvents.js +34 -0
  62. package/dist/serviceFeatures/checkRemoteSize.d.ts +5 -3
  63. package/dist/serviceFeatures/checkRemoteSize.js +51 -11
  64. package/dist/serviceFeatures/remoteConfig.d.ts +37 -1
  65. package/dist/serviceFeatures/remoteConfig.js +90 -0
  66. package/dist/serviceModules/Rebuilder.d.ts +3 -6
  67. package/dist/serviceModules/Rebuilder.js +31 -22
  68. package/dist/serviceModules/ServiceDatabaseFileAccessBase.d.ts +5 -0
  69. package/dist/serviceModules/ServiceDatabaseFileAccessBase.js +69 -13
  70. package/dist/serviceModules/ServiceFileHandlerBase.d.ts +20 -0
  71. package/dist/serviceModules/ServiceFileHandlerBase.js +224 -40
  72. package/dist/services/BrowserServices.d.ts +2 -0
  73. package/dist/services/BrowserServices.js +4 -1
  74. package/dist/services/HeadlessServices.d.ts +2 -0
  75. package/dist/services/HeadlessServices.js +2 -1
  76. package/dist/services/base/CommonlibMessages.d.ts +318 -0
  77. package/dist/services/base/CommonlibMessages.js +313 -0
  78. package/dist/services/base/IService.d.ts +13 -6
  79. package/dist/services/base/KeyValueDBService.d.ts +1 -1
  80. package/dist/services/base/KeyValueDBService.js +3 -1
  81. package/dist/services/base/MessageTranslator.d.ts +5 -2
  82. package/dist/services/base/MessageTranslator.js +21 -0
  83. package/dist/services/base/ReplicationService.d.ts +4 -2
  84. package/dist/services/base/ReplicationService.js +11 -16
  85. package/dist/services/base/ServiceBase.d.ts +1 -1
  86. package/dist/services/base/ServiceBase.js +2 -4
  87. package/dist/services/base/SettingService.d.ts +17 -9
  88. package/dist/services/base/SettingService.js +38 -19
  89. package/dist/services/implements/headless/HeadlessAPIService.d.ts +3 -3
  90. package/dist/services/implements/headless/HeadlessAPIService.js +2 -2
  91. package/dist/services/implements/injectable/InjectableTweakValueService.d.ts +3 -3
  92. package/dist/settings.d.ts +10 -0
  93. package/dist/settings.js +32 -0
  94. package/dist/worker/bgWorker.js +1 -1
  95. package/docs/conflict-resolution.md +153 -0
  96. package/docs/development.md +106 -7
  97. package/docs/p2p-transport-lifecycle.md +62 -0
  98. package/docs/platform-storage.md +4 -0
  99. package/docs/proven-in-use.md +65 -0
  100. package/docs/releasing.md +26 -6
  101. package/docs/remote-configurations.md +63 -0
  102. package/docs/settings-lifecycle.md +85 -0
  103. package/package.json +17 -27
  104. package/dist/common/i18n.d.ts +0 -18
  105. package/dist/common/i18n.js +0 -7193
  106. package/dist/common/messages/combinedMessages.dev.d.ts +0 -7
  107. package/dist/common/messages/combinedMessages.dev.js +0 -44
  108. package/dist/common/messages/combinedMessages.prod.d.ts +0 -9264
  109. package/dist/common/messages/combinedMessages.prod.js +0 -9271
  110. package/dist/common/messages/de.d.ts +0 -297
  111. package/dist/common/messages/de.js +0 -7
  112. package/dist/common/messages/def.d.ts +0 -1119
  113. package/dist/common/messages/def.js +0 -7
  114. package/dist/common/messages/es.d.ts +0 -689
  115. package/dist/common/messages/es.js +0 -7
  116. package/dist/common/messages/fr.d.ts +0 -582
  117. package/dist/common/messages/fr.js +0 -7
  118. package/dist/common/messages/he.d.ts +0 -583
  119. package/dist/common/messages/he.js +0 -7
  120. package/dist/common/messages/ja.d.ts +0 -838
  121. package/dist/common/messages/ja.js +0 -7
  122. package/dist/common/messages/ko.d.ts +0 -800
  123. package/dist/common/messages/ko.js +0 -7
  124. package/dist/common/messages/ru.d.ts +0 -858
  125. package/dist/common/messages/ru.js +0 -7
  126. package/dist/common/messages/zh-tw.d.ts +0 -384
  127. package/dist/common/messages/zh-tw.js +0 -7
  128. package/dist/common/messages/zh.d.ts +0 -1093
  129. package/dist/common/messages/zh.js +0 -7
  130. package/dist/common/messagesJson/de.json +0 -295
  131. package/dist/common/messagesJson/en.json +0 -1117
  132. package/dist/common/messagesJson/es.json +0 -687
  133. package/dist/common/messagesJson/fr.json +0 -580
  134. package/dist/common/messagesJson/he.json +0 -581
  135. package/dist/common/messagesJson/ja.json +0 -836
  136. package/dist/common/messagesJson/ko.json +0 -798
  137. package/dist/common/messagesJson/ru.json +0 -856
  138. package/dist/common/messagesJson/zh-tw.json +0 -382
  139. package/dist/common/messagesJson/zh.json +0 -1091
  140. package/dist/common/rosetta.d.ts +0 -42
  141. package/dist/common/rosetta.js +0 -57
@@ -0,0 +1,153 @@
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
+ ## Safety invariants
22
+
23
+ Commonlib follows these rules:
24
+
25
+ - compare file content byte-for-byte; a path, size, modification time, or revision generation is not proof of identity;
26
+ - use the nearest `available` revision shared by both leaves as the base for three-way merge;
27
+ - retain a manual conflict when the common revision or a required body is missing or compacted;
28
+ - 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;
29
+ - preserve storage content as a conflict when it is absent from all available branches; and
30
+ - never select the newest modification time as a package-level default.
31
+
32
+ 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.
33
+
34
+ ## Resolution classes
35
+
36
+ | State | Safe automatic action |
37
+ | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
38
+ | Both leaves contain identical bytes | Collapse the duplicate leaf without creating merged content. |
39
+ | Text or structured data has a shared available base and non-overlapping changes | Perform a conservative three-way merge. |
40
+ | One side deletes content which the other leaves unchanged | Preserve the deletion. |
41
+ | One side deletes content which the other modifies | Retain a manual conflict. |
42
+ | A receiving file matches any available revision in the tree | Apply the propagated database result. |
43
+ | A receiving file matches no available revision | Preserve it as an unsynchronised conflict. |
44
+ | A body or common ancestor is missing or compacted | Retain a manual conflict. |
45
+ | Content is binary or otherwise not semantically mergeable | Leave the selection policy to the host; Commonlib cannot infer user intent. |
46
+
47
+ ## Stale and concurrent resolutions
48
+
49
+ 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.
50
+
51
+ 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.
52
+
53
+ ## File-reflection provenance
54
+
55
+ 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:
56
+
57
+ ```text
58
+ path -> { revision, observedStorageMtime? }
59
+ ```
60
+
61
+ `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.
62
+
63
+ 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.
64
+
65
+ 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.
66
+
67
+ 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.
68
+
69
+ ## Operations while a conflict exists
70
+
71
+ With a proven displayed revision:
72
+
73
+ - an edit writes a child of that exact revision;
74
+ - 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;
75
+ - a case-only rename writes the new path as a child of the displayed revision in the same document tree; and
76
+ - a cross-path rename stores the target first, then writes a logical-deletion child on the displayed source branch.
77
+
78
+ 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.
79
+
80
+ 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.
81
+
82
+ ## Worked revision-tree scenarios
83
+
84
+ ### Editing the branch displayed by a host
85
+
86
+ Assume that the database has this conflict:
87
+
88
+ ```text
89
+ A1
90
+ ├── B1 ── C1 database winner
91
+ └── B2 ── C2 displayed in storage
92
+ ```
93
+
94
+ 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`:
95
+
96
+ ```text
97
+ A1
98
+ ├── B1 ── C1
99
+ └── B2 ── C2 ── D2 edited content
100
+ ```
101
+
102
+ 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.
103
+
104
+ ### Deleting the displayed branch
105
+
106
+ With the same starting tree and recorded `C2` provenance, deleting the storage file writes a logical-deletion child of `C2`:
107
+
108
+ ```text
109
+ A1
110
+ ├── B1 ── C1
111
+ └── B2 ── C2 ── D2 (deleted: true)
112
+ ```
113
+
114
+ `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.
115
+
116
+ ### Renaming while a conflict is visible
117
+
118
+ 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.
119
+
120
+ 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.
121
+
122
+ ### Reconstructing provenance after local state is unavailable
123
+
124
+ 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.
125
+
126
+ 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.
127
+
128
+ ### Receiving a resolution while showing the losing branch
129
+
130
+ 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.
131
+
132
+ ### Starting and resetting the provenance store
133
+
134
+ 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.
135
+
136
+ 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.
137
+
138
+ ## Unsafe shortcuts
139
+
140
+ Do not:
141
+
142
+ - use the first revision whose generation is lower than the other leaf as a supposed common ancestor;
143
+ - select a winner from modification time unless the host exposes and the user selects that destructive policy;
144
+ - assume the PouchDB winner is the content currently displayed in storage;
145
+ - replace a recorded displayed revision merely because current bytes match another branch;
146
+ - recreate a conflict from a byte-identical deleted losing revision after a remote resolution;
147
+ - discard storage content because history lookup failed;
148
+ - infer revision identity from path, size, modification time, or hash without the revision identifier; or
149
+ - automatically merge overlapping text changes or unrelated binary contents.
150
+
151
+ ## Verification ownership
152
+
153
+ 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, content retained below a deleted losing leaf, recorded and reconstructed branch identity, ambiguous content, conflict-time editing, logical deletion, 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.
@@ -2,10 +2,40 @@
2
2
 
3
3
  This document is for Commonlib developers. Package consumers should begin with the root README and the focused platform guide.
4
4
 
5
+ ## Repository and package boundary
6
+
7
+ The source tree is larger than the supported package surface. Consumers may import only paths in the generated package export map:
8
+
9
+ - `src/index.ts`, `src/context.ts`, `src/settings.ts`, `src/remoteConfigurations.ts`, `src/platform/browser/index.ts`, and `src/platform/node/index.ts` define the focused entries;
10
+ - `src/rpc/index.ts` defines a transitional entry for the existing LiveSync P2P composition, not a stable Commonlib 1.0 contract;
11
+ - `_tools/build-package.mjs` compiles those entries and creates the publishable manifest under `.package`;
12
+ - `docs/migration/downstream-imports.json` is the reviewed inventory from which explicit `compat/*` exports are generated;
13
+ - `docs/migration/compatibility-classification.json` assigns every inventoried compatibility path to one reviewed owner and migration policy, with a boundary test preventing omissions and duplicate classifications;
14
+ - colocated `*.unit.spec.ts` and `*.unit.test.ts` files exercise implementation and result contracts; and
15
+ - colocated `*.integration.spec.ts` files, `test/integration`, and the integration runner own checks against disposable CouchDB and S3-compatible services.
16
+
17
+ Files elsewhere under `src` are implementation details unless a focused entry re-exports them or the compatibility inventory names them. Do not use the source layout, TypeScript path aliases, or the presence of generated declarations as evidence of a supported consumer import.
18
+
5
19
  ## Local validation
6
20
 
21
+ For a complete local package gate, run:
22
+
7
23
  ```bash
8
24
  npm ci
25
+ npm run verify:package
26
+ ```
27
+
28
+ `verify:package` type-checks the source, runs the unit suite and package-boundary tests, validates release selection, builds the compiled package, and checks the exact packed consumer boundary.
29
+
30
+ Run the managed integration suite as a separate gate when CouchDB streaming fetches, S3-compatible storage, or their dependencies could be affected:
31
+
32
+ ```bash
33
+ npm run test:integration:managed
34
+ ```
35
+
36
+ During focused work, the component scripts remain available independently:
37
+
38
+ ```bash
9
39
  npm run check:types
10
40
  npm test
11
41
  npm run test:contracts
@@ -17,23 +47,63 @@ npm run test:package
17
47
 
18
48
  `test:contracts` runs the host-neutral result contracts which are intended to be reusable while implementations move:
19
49
 
20
- - `test:contract:context` verifies event delivery, default instance isolation, translation results, and host extension of `ServiceContextContract`; and
50
+ - `test:contract:context` verifies event delivery, default instance isolation, translation results, and host extension of `ServiceContextContract`;
21
51
  - `test:contract:platform-storage` runs one result table against Node storage and browser File System Access storage, including missing paths, text and binary round trips, append, listing, removal, root handling, and traversal rejection; and
22
52
  - `test:contract:standard-io` verifies the injectable contract, UTF-8 decoding across chunks, text and binary output, and line-oriented prompting through injected Node streams.
23
53
 
24
54
  These scripts are deliberately available independently of CI. Add a focused result contract before moving another platform API; do not infer compatibility from matching TypeScript shapes alone. Platform-specific behaviour, such as timestamp fidelity, must remain documented outside the shared result set.
25
55
 
56
+ The settings lifecycle has an additional focused unit table in `src/common/models/setting.lifecycle.unit.spec.ts`. Extend it whenever a schema migration, a new-Vault recommendation, or downgrade handling changes. A stored setting must keep its explicit value unless the migration itself documents a deliberate transformation.
57
+
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
+
60
+ 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. `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. Extend the relevant sides when changing conflict resolution, compaction handling, provenance, or database-to-storage overwrite protection.
61
+
62
+ `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.
63
+
64
+ ### P2P composition ownership
65
+
66
+ `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.
67
+
68
+ Consumers must preserve that result object and read `result.replicator` at the point of use. Do not destructure the property into a command, event handler, view, or other long-lived closure: a database reinitialisation or remote-type transition can close and replace the captured instance, and calling `open()` on that obsolete outer object can create a second connection outside the active service state.
69
+
70
+ The similarly named compositions have narrower roles:
71
+
72
+ - `useP2PReplicatorCommands` adds host commands and resolves the current instance when a command is checked or invoked;
73
+ - host UI features add views, status presentation, and injected peer-selection callbacks without owning the replicator lifecycle; and
74
+ - the deprecated `useP2PReplicator` entry is a compatibility composition which delegates ownership to `useP2PReplicatorFeature`.
75
+
76
+ When registering P2P event handlers for a replaceable instance, pass a provider to `addP2PEventHandlers`. Passing a fixed instance remains supported only for compositions whose instance cannot change. Extend the focused feature, command, event-hub, and compatibility-wrapper tests whenever this ownership boundary changes.
77
+
78
+ The physical room, WebRTC peer, and relay-socket boundaries are documented in [P2P transport lifecycle](p2p-transport-lifecycle.md). In particular, normal shutdown leaves the Trystero room without closing raw peer connections directly.
79
+
26
80
  Self-hosted LiveSync remains the principal downstream consumer. A Commonlib change is not ready for publication until the exact packed artefact has also passed the applicable LiveSync type checks, unit and integration tests, application builds, CLI E2E, and focused real-Obsidian E2E.
27
81
 
28
- ## Translation catalogue
82
+ Commonlib owner tests exercise the real implementation behind each focused result and lifecycle contract. A consumer-owned unit test may inject the smallest structural capability it uses and provide its own fake or spy when that current test needs isolation. Do not publish Vitest-specific mocks, expose private service constructors, or refactor a production boundary merely to create possible future test infrastructure. Mock and spy helpers are not a prerequisite for the LiveSync 1.0 package-boundary work; add them only with the consumer test which requires them, or after that release work.
83
+
84
+ Prefer an object or callback injected at the host composition boundary over replacing an ESM module namespace. If a proposed focused API would require global mutation, import-order coupling, hidden construction, or private nominal types to substitute later, stop and review the production design before stabilising it. Exact-package downstream checks and focused real-runtime E2E remain responsible for proving the real package and host composition together.
85
+
86
+ Commonlib publishes the typed English messages which it owns and uses them when a host does not provide a translator. A host may inject a translator through `ServiceContext`; the full multilingual catalogue and language-selection state belong to that host and are not part of the Commonlib package.
87
+
88
+ Keep [the maintained-host evidence](proven-in-use.md) aligned with the downstream source and gates. If a host no longer consumes a focused entry, a test is retired, or a compatibility path receives a reviewed replacement, update the consumer-facing account as part of the same maintenance rather than leaving historical adoption as a current support claim.
89
+
90
+ ## Integration tests
29
91
 
30
- Human-edited translations live under `src/common/messagesYAML`. Regenerate derived JSON and TypeScript resources with:
92
+ The integration suite owns Commonlib's direct CouchDB and S3-compatible storage checks. Run it with managed, disposable CouchDB and MinIO containers:
31
93
 
32
94
  ```bash
33
- npm run i18n:bake
95
+ npm run test:integration:managed
34
96
  ```
35
97
 
36
- The generated catalogue remains an optional dependency surface. Core and context imports must not load it.
98
+ This command requires Docker Compose, creates only test credentials and data, and removes the containers and their volumes after the run. It is the same entry point used by package CI.
99
+
100
+ To test services that are already running, use `npm run test:integration`. The runner accepts `hostname`, `username`, and `password` for CouchDB, and `minioEndpoint`, `accessKey`, `secretKey`, and `bucketName` for S3-compatible storage. Without overrides, it uses the endpoints and test credentials from the managed Compose environment. The selected MinIO bucket must already exist when services are not managed by the runner.
101
+
102
+ ## Messages and translation
103
+
104
+ `src/services/base/CommonlibMessages.ts` is the canonical English definition and key type for messages requested by Commonlib. Keep a meaningful English value for every symbolic key. Hosts may translate that typed key set, but translation is optional and omission must remain safe for users.
105
+
106
+ Application language catalogues and their generation tools belong to the application repository. Do not add a process-global language selection or a complete application catalogue to Commonlib.
37
107
 
38
108
  ## Public API documentation
39
109
 
@@ -41,12 +111,41 @@ New reviewed public entry points require TSDoc describing ownership, lifecycle,
41
111
 
42
112
  Generated API reference is deferred until the compatibility export surface has been narrowed. Generating documentation for every current `compat/*` path would make migration-only implementation paths look like stable public API.
43
113
 
44
- TODO: introduce generated API reference for a reviewed allow-list of public entry points once the high-level client façade and its lifecycle contract have been accepted.
114
+ The proposed generated-reference allow-list is deliberately entry-based:
115
+
116
+ - include `/context`, `/browser`, `/node`, `/remote-configurations`, and `/settings`;
117
+ - exclude the root entry until `createLiveSyncFileClient` replaces `DirectFileManipulator` after the LiveSync 1.0 work;
118
+ - leave possible `/setup-uri` and `/database-version` entries for later review rather than creating them to clean up imports before LiveSync 1.0; and
119
+ - exclude `/rpc`, every `compat/*` path, and the metadata-only `package.json` export.
120
+
121
+ Generate reference only for this reviewed current allow-list. Future entries require their own accepted result and lifecycle contracts before joining it.
122
+
123
+ ## Open 1.0 documentation decisions
124
+
125
+ Resolve these questions before describing the corresponding behaviour as stable:
126
+
127
+ - classify compatibility paths whose owner or migration destination is still unclear, and remove obsolete paths rather than preserving the source layout; and
128
+ - introduce generated API reference only for the reviewed public allow-list, with TSDoc coverage for ownership, lifecycle, errors, platform constraints, and disposal.
129
+
130
+ The platform and RPC ownership decisions are accepted:
131
+
132
+ - `/node` and `/browser` are supported platform façades. They deliberately hide platform-specific imports, adapters, and review exceptions behind package-owned boundaries. The selected Node built-in re-exports are part of that boundary; package tests must continue to prove that browser-capable entries do not reach Node-only code.
133
+ - `/rpc` is transitional and is not part of the stable Commonlib 1.0 contract. Keep it only while the maintained LiveSync P2P composition requires the existing implementation. The RPC core, transport integration, wire protocol, and PouchDB bridge are planned for an independently owned rewrite in Fancy Kit; do not encourage new Commonlib consumers to adopt the current entry.
134
+ - The high-level composition target is a narrow asynchronous `createLiveSyncFileClient` factory, not a general Service Hub factory. It must keep the context, service graph, database implementation, protocol setup, watches, reconnect work, and shutdown order private. It resolves only after readiness and returns a small file façade with `list`, `get`, `put`, `delete`, `watch`, and `close`. Full Obsidian, CLI, and browser applications retain host-specific composition because they own UI, persistence, permissions, storage events, and process policy.
135
+ - `DirectFileManipulator` is a migration source for that file client rather than the stable target contract. Do not carry its fire-and-forget constructor initialisation, exposed service graph and database documents, incomplete enumeration, implicit watch retry, or deprecated settings options into the new public API. Define the exact result, conflict, concurrency, watch checkpoint, retry, and error semantics only after the LiveSync 1.0 work rather than making that future client a release gate.
45
136
 
46
137
  ## Compatibility exports
47
138
 
48
139
  The package build records each compatibility path explicitly. Do not add a wildcard export. A new compatibility path requires a real downstream import, a packed-consumer or downstream test, and a migration plan towards a focused public entry or the owning package.
49
140
 
50
- TODO: classify and document compatibility paths whose purpose is still unclear before stabilising the first public release. Remove obsolete paths rather than documenting accidental layout as API.
141
+ After the reviewed Self-hosted LiveSync revision changes, inspect its current compatibility imports with:
142
+
143
+ ```bash
144
+ npm run update:downstream-import-inventory -- --downstream /path/to/obsidian-livesync
145
+ ```
146
+
147
+ The command recognises both the former `@lib/*` source aliases and current `@vrtmrz/livesync-commonlib/compat/*` package imports. It ignores focused package entries. Review the resulting paths and recorded downstream commit before accepting them. Regenerate the export map only for a new package version: do not change the contents represented by an immutable published version.
148
+
149
+ The classification in `docs/migration/compatibility-classification.json` is accepted for LiveSync 1.0 planning. A destination still requires its own result and lifecycle contract before it becomes a stable public entry. Remove obsolete paths rather than documenting accidental layout as API.
51
150
 
52
151
  Release preparation and staged-publication checks are documented in [the maintainer runbook](releasing.md).
@@ -0,0 +1,62 @@
1
+ # P2P transport lifecycle
2
+
3
+ This guide is for developers composing Commonlib's Trystero P2P service. It describes resource ownership and shutdown semantics; it is not a general Trystero API reference.
4
+
5
+ ## Ownership layers
6
+
7
+ The P2P composition has three distinct owners:
8
+
9
+ - `useP2PReplicatorFeature` owns the current `LiveSyncTrysteroReplicator` exposed to a host;
10
+ - the Commonlib P2P host owns LiveSync RPC, advertisements, client proxies, diagnostics, and one Trystero room binding; and
11
+ - Trystero owns the underlying shared WebRTC peers and Nostr relay clients.
12
+
13
+ Consumers must retain the service-feature result and resolve `result.replicator` at the point of use. A settings or database lifecycle transition can replace the replicator. A command, event listener, or view which captured the former instance could otherwise reopen a transport which the current service no longer owns.
14
+
15
+ ## Normal close
16
+
17
+ `LiveSyncTrysteroReplicator.close()` serialises the close with any in-flight open operation. It stops LiveSync-owned work, closes the RPC room, calls `room.leave()`, clears instance references, pauses relay reconnection, and closes the current Nostr relay WebSockets.
18
+
19
+ Normal close deliberately does not call `close()` on the `RTCPeerConnection` values returned by `room.getPeers()`. Trystero 0.25 may share a physical peer across rooms. Leaving a room detaches its callbacks and action namespace, while Trystero may retain the idle physical peer for approximately 123 seconds for reuse. The retained peer cannot carry traffic for the departed room.
20
+
21
+ Likewise, an incoming peer-leave notification removes LiveSync advertisement and client state without directly closing the underlying peer. Trystero decides whether that peer is shared, stale, or ready for destruction.
22
+
23
+ The host command named 'Disconnect from the Signalling Server' means:
24
+
25
+ - the LiveSync P2P and RPC service stops immediately;
26
+ - room membership and room-scoped actions are removed;
27
+ - relay WebSockets close and automatic reconnection is paused; and
28
+ - an idle WebRTC object may remain under Trystero ownership until its reuse interval ends.
29
+
30
+ It is therefore a logical LiveSync disconnection and a physical signalling-server disconnection, not a synchronous destruction promise for every browser-owned WebRTC object.
31
+
32
+ ## Reconnection and replacement
33
+
34
+ An explicit open resumes Trystero relay reconnection before joining the configured room. Settings application may change the relay URLs, room ID, passphrase, or TURN configuration, so the owning service closes and discards the former LiveSync instance before constructing the replacement. Concurrent replacement requests share the same in-flight replacement and receive the same new owner. No fixed close-to-open delay is required: lifecycle operations are serialised, and peer readiness is observed through discovery.
35
+
36
+ Saving settings with P2P or automatic start disabled always requests `close()`, even when the room has not started serving. This cancels an in-flight open through the same lifecycle queue, so a connection cannot appear after the disabling setting has been applied.
37
+
38
+ Do not replace a relay socket's `onclose` handler. Trystero 0.25 shares relay clients by URL and uses its own handler to retire and recreate them. Use the exported pause and resume functions around explicit disconnection instead.
39
+
40
+ ## First-device and additional-device setup
41
+
42
+ P2P has no central remote database. A first-device rebuild initialises the local database, then returns without attempting to lock, reset, or seed a remote database.
43
+
44
+ An additional-device Fetch uses one explicit peer-selection pass. The selected peer supplies the complete finite replication from the beginning, after which the rebuild service resumes database and Vault reflection. The generic second convergence pass remains appropriate for central remote types, but must not be applied to P2P: an injected Obsidian rebuild UI would otherwise ask the user to select the same peer twice and leave reflection suspended while the second dialogue waits.
45
+
46
+ ## Unsupported forced close
47
+
48
+ Do not close the raw values returned by `room.getPeers()`. Both closing them before room departure and closing captured values after departure have prevented the same peer from being rediscovered within 60 seconds in the real-transport replacement test.
49
+
50
+ Commonlib consequently does not expose a forced physical-disconnection command. Such an operation requires a future Trystero API which removes and destroys peers through its shared-peer manager, followed by proof that immediate reconnection remains possible.
51
+
52
+ ## Verification
53
+
54
+ Maintain all five boundaries when this lifecycle changes:
55
+
56
+ - Commonlib unit tests must prove that normal close leaves the room without directly closing Trystero-owned peers, that disabling settings cancel a pending open, and that concurrent replacement requests leave one current owner;
57
+ - Commonlib rebuild tests must prove that first-device P2P initialisation does not reset a remote database and that an additional-device P2P Fetch performs one explicit peer-selection pass before resuming reflection;
58
+ - the Self-hosted LiveSync Compose P2P lifecycle test must replace a current replicator, rediscover the same real peer, perform bidirectional RPC, and verify transferred content from a separate process;
59
+ - the Self-hosted LiveSync real-Obsidian P2P Setup URI workflow must generate the second-device URI on the first device, accept both peer directions visibly, and verify a two-way note round-trip; and
60
+ - the relay-disconnect test must observe the original WebSocket reach `CLOSED`, remain closed while reconnection is paused, and be replaced after reconnection resumes.
61
+
62
+ The corresponding product decision and rejected alternatives are recorded in Self-hosted LiveSync's P2P room and transport lifecycle ADR.
@@ -39,6 +39,10 @@ await storage.write('notes/example.md', '# Example');
39
39
 
40
40
  `rootPath` may be absolute or process-relative. The host should normally resolve and validate configuration before constructing the adapter.
41
41
 
42
+ The Node adapter rejects symbolic-link components which it observes in an adapter path before an operation. File opens also use `O_NOFOLLOW` for the final component where the platform exposes that flag. These checks protect against invalid paths and symbolic links which already exist when an operation begins. Its concrete `rename(sourcePath, targetPath)` operation validates both paths before providing the atomic rename expected by Node hosts.
43
+
44
+ The host-selected `rootPath` is a trust boundary, not an operating-system filesystem sandbox. The adapter assumes that an untrusted local process cannot replace that root or its descendants concurrently with an operation. A host which grants another local actor write access to the tree must not rely on the adapter as a privilege boundary.
45
+
42
46
  ## Shared path contract
43
47
 
44
48
  Adapter paths are relative to the injected root and use forward slashes. The empty path refers to the root for root-safe operations such as `exists`, `stat`, `list`, and `mkdir`.
@@ -0,0 +1,65 @@
1
+ # Proven in maintained hosts
2
+
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
+
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.
6
+
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
+
9
+ ## Obsidian plug-in
10
+
11
+ The plug-in extends the neutral `ServiceContext` with Obsidian-owned capabilities in `src/modules/services/ObsidianServiceContext.ts`. One context instance carries the LiveSync-owned translator and event hub through the Service Hub rather than placing those dependencies in Commonlib globals. Commonlib supplies its typed English fallback when another host omits the translator.
12
+
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
+
15
+ Commonlib's real-PouchDB and injected-boundary unit tests prove that conservative merge uses the nearest revision actually shared by both conflict leaves, 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. Self-hosted LiveSync supplies the host composition and persistent device-local provenance. 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
+ 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.
18
+
19
+ 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.
20
+
21
+ 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.
22
+
23
+ ## CLI
24
+
25
+ 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
+
27
+ The CLI filesystem composition receives a configured base directory and constructs the rooted Node storage adapter in `src/apps/cli/adapters/NodeFileSystemAdapter.ts`. Commonlib's shared storage result table checks the focused root contract; the consumer adds its file cache, Vault-shaped operations, case handling, and command policy.
28
+
29
+ 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.
30
+
31
+ ## Webapp
32
+
33
+ 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.
34
+
35
+ 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.
36
+
37
+ ## WebPeer and browser service composition
38
+
39
+ 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.
40
+
41
+ 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.
42
+
43
+ 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.
44
+
45
+ ## Verification ownership
46
+
47
+ 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.
48
+
49
+ This is a boundary rule, not a requirement to build mock and spy infrastructure before LiveSync 1.0. Add a substitution seam only when a current consumer-owned test needs one. Exact-package downstream checks and focused real-runtime E2E remain the integration proof that the real Commonlib implementation and host composition work together.
50
+
51
+ ## What a new consumer should copy
52
+
53
+ A new integration should start with the focused entries, not by reproducing Self-hosted LiveSync's compatibility imports:
54
+
55
+ - create one `ServiceContext` per independently composed client, optionally inject its translator and events, and extend it only with capabilities owned by that host; an omitted translator renders Commonlib's canonical English messages;
56
+ - let the host select and authorise a storage root, then pass the root once to the Node or browser storage factory;
57
+ - inject `StandardIo` into CLI policy so that protocol output and prompts remain testable; and
58
+ - use `/remote-configurations` when a host needs to create or select saved remote profiles, while keeping naming prompts, persistence, and restart policy in the host; and
59
+ - run the relevant Commonlib result contract against another implementation before describing matching TypeScript shapes as compatible behaviour.
60
+
61
+ Use the named consumer paths as integration examples, while retaining the ownership and limitations stated in the [root README](../readme.md), [storage guide](platform-storage.md), [standard-I/O guide](platform-standard-io.md), and [remote configuration profile guide](remote-configurations.md).
62
+
63
+ ## Maintenance rule
64
+
65
+ This page records current evidence rather than historical adoption. When a host stops using an entry point, a verification path is retired, or a compatibility import moves to a focused contract, update the description and links instead of retaining a stronger claim than the maintained consumer establishes.
package/docs/releasing.md CHANGED
@@ -13,13 +13,27 @@ npm run verify:package
13
13
 
14
14
  The gate type-checks Commonlib, runs its complete unit suite, verifies the source boundary, builds the distributable package, installs its exact tarball into a clean consumer, and bundles representative browser and Node entry points. The source `package.json` remains private to prevent publishing the repository root. Only the generated `.package` directory is publishable, and its manifest defaults to public publication on the `next` dist-tag.
15
15
 
16
+ ### Lockfile reproducibility
17
+
18
+ Regenerate `package-lock.json` with the reviewed npm CLI version pinned by the `Use the reviewed npm CLI` step in `.github/workflows/publish-npm.yml`. Do this after adding, removing, or updating a dependency, even when an older local npm reports that the lockfile is already up to date. npm minor versions can differ in how they record transitive optional peer dependencies; a lockfile accepted by an older client can otherwise fail the hosted `npm ci` before any tests run.
19
+
20
+ With the currently reviewed npm version, use:
21
+
22
+ ```bash
23
+ npx --yes npm@11.18.0 install --package-lock-only --ignore-scripts --no-audit --no-fund
24
+ npx --yes npm@11.18.0 ci
25
+ npm run verify:package
26
+ ```
27
+
28
+ Inspect the lockfile diff before committing it. A dependency-only maintenance change must not alter the source package version, and lockfile normalisation must not be described as a package upgrade when resolved versions and integrity values are unchanged. When the workflow selects a newer reviewed npm CLI, update this example in the same change.
29
+
16
30
  Before publication, run the downstream workflow against an exact Self-hosted LiveSync ref which already consumes the package. The workflow installs the tarball produced from the selected Commonlib commit, then runs LiveSync type checks, unit tests, plug-in and application builds, and CLI E2E. Real Obsidian E2E remains local-only and is required when the changed boundary affects actual plug-in composition, storage, UI, or platform behaviour.
17
31
 
18
32
  ## Preparing a release
19
33
 
20
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.
21
35
 
22
- From a release branch based on `main`:
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:
23
37
 
24
38
  ```bash
25
39
  npm ci
@@ -30,7 +44,9 @@ npm publish --dry-run .package --tag next --access public
30
44
 
31
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.
32
46
 
33
- ## Initial npm bootstrap
47
+ ## Historical initial npm bootstrap
48
+
49
+ The package and its Trusted Publisher are already configured. This section records the one-time bootstrap which created the package; do not use it for routine releases. Current releases use the staged-publishing workflow below.
34
50
 
35
51
  The npm package must exist before Trusted Publishing can be configured. Bootstrap the first reviewed release candidate once from the exact reviewed commit in the draft release pull request, using an interactive npm session with 2FA:
36
52
 
@@ -53,17 +69,21 @@ After bootstrap, configure the npm Trusted Publisher for:
53
69
  - environment: `npm`; and
54
70
  - allowed action: staged publishing only.
55
71
 
56
- Protect the GitHub `npm` environment with a required reviewer and permit only the `main` branch. The workflow accepts only the exact commit currently on `main`, runs `verify:package`, validates the requested version 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`, 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.
57
73
 
58
- Dispatch it with the exact stable or prerelease version and full commit SHA:
74
+ Dispatch a pre-release from its reviewed draft branch with the exact version and full commit SHA:
59
75
 
60
76
  ```bash
61
- sha=$(git rev-parse origin/main)
77
+ source_ref=separate-setting-lifecycle
78
+ sha=$(git rev-parse "origin/$source_ref")
62
79
  gh workflow run publish-npm.yml \
63
80
  --ref main \
64
81
  -f version=<version> \
82
+ -f source_ref="$source_ref" \
65
83
  -f expected_sha="$sha" \
66
84
  -f confirmation="stage @vrtmrz/livesync-commonlib@<version> from $sha"
67
85
  ```
68
86
 
69
- The workflow always stages to `next`. Inspect the staged package name, version, access, dist-tag, provenance, checksum, files, and source commit before approving it through npm. Approval and later promotion to `latest` are separate user-authorised operations. Validate the exact registry version in Self-hosted LiveSync before promoting a stable release.
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.
88
+
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`.
@@ -0,0 +1,63 @@
1
+ # Remote configuration profiles
2
+
3
+ Commonlib represents saved remote connections as independently named profiles. The profile map is the persisted source for multiple-remote selection. Existing CouchDB, Object Storage, and P2P fields remain the runtime projection consumed by compatibility services.
4
+
5
+ Use the focused entry point for new profile-management code:
6
+
7
+ ```ts
8
+ import {
9
+ upsertRemoteConfigurationInPlace,
10
+ type RemoteConfiguration,
11
+ } from "@vrtmrz/livesync-commonlib/remote-configurations";
12
+ import { createNewVaultSettings } from "@vrtmrz/livesync-commonlib/settings";
13
+
14
+ const settings = createNewVaultSettings();
15
+ Object.assign(settings, {
16
+ couchDB_URI: "https://couch.example",
17
+ couchDB_USER: "alice",
18
+ couchDB_PASSWORD: "secret",
19
+ couchDB_DBNAME: "notes",
20
+ });
21
+
22
+ const profile: RemoteConfiguration = upsertRemoteConfigurationInPlace(settings, "couchdb", {
23
+ activate: true,
24
+ });
25
+ ```
26
+
27
+ The helper serialises the connection fields into the profile URI. When `activate` is true, it selects that profile through `activeConfigurationId` and projects the profile back onto the compatibility fields. The host still owns persistence, restart policy, credential acquisition, and user confirmation.
28
+
29
+ ## Identity and display names
30
+
31
+ Profile IDs are opaque identity. Omit `id` to allocate a new one and preserve every existing profile. Pass a known `id` only when deliberately updating that profile.
32
+
33
+ Display names are presentation, not identity. When `name` is omitted, Commonlib proposes a concise type-specific name such as `CouchDB couch.example`, `S3 notes`, or `P2P team-room`. It adds a numeric suffix when that display name is already present. A host may let users rename profiles without changing their IDs or selections.
34
+
35
+ There is no special profile named or identified as `default`. The selected main remote is represented only by `activeConfigurationId`.
36
+
37
+ ## Main and P2P selections
38
+
39
+ `activeConfigurationId` selects the main remote used by ordinary replication. `P2P_ActiveRemoteConfigurationId` independently selects the profile used by P2P features.
40
+
41
+ - Use `{ activate: true }` for a CouchDB or Object Storage profile selected as the main remote.
42
+ - Use `{ activate: true, activateForP2P: true }` when P2P is the main remote.
43
+ - Use `{ activateForP2P: true }` to select a P2P profile without changing the main remote.
44
+
45
+ Selecting a profile updates the compatibility fields immediately. Existing replication services may therefore continue to consume those fields while profile-aware hosts treat the profile map and selection IDs as authoritative persisted state.
46
+
47
+ ## Import and migration boundary
48
+
49
+ A modern Setup URI or another settings transport should preserve `remoteConfigurations`, profile IDs, display names, `activeConfigurationId`, and `P2P_ActiveRemoteConfigurationId` exactly.
50
+
51
+ An older settings payload may contain only the flat remote fields. The standard `SettingService` recognises that shape when the profile map is empty and migrates the configured connections into explicitly labelled `legacy-*` profiles. This is a compatibility path, not the recommended way to create a new profile. New setup flows should call the focused API directly.
52
+
53
+ If a settings payload already contains profiles, legacy migration does not create or replace any entry. A host which writes only flat fields in that state would therefore fail to register a new connection; profile-aware setup code must upsert the profile explicitly.
54
+
55
+ ## Persistence and encryption
56
+
57
+ The helper stores a plaintext connection URI in memory and marks the changed profile as unencrypted. The standard `SettingService` applies configured at-rest encryption when persisting settings. A host using a different persistence service must provide equivalent encryption and decryption policy itself.
58
+
59
+ ## Verification ownership
60
+
61
+ Commonlib unit tests cover preserving existing profiles, generated-name collision handling, main-remote activation, and independent P2P selection. The packed-package test imports the focused entry from a clean consumer and checks its declarations and runtime exports.
62
+
63
+ Hosts remain responsible for testing their dialogues, import classification, confirmation, Fetch or Rebuild scheduling, persistence, restart ordering, and real-runtime presentation.