@vrtmrz/livesync-commonlib 0.1.0-rc.10 → 0.1.0-rc.11
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.
|
@@ -15,6 +15,21 @@ import {
|
|
|
15
15
|
isObjectMargeApplicable
|
|
16
16
|
} from "../common/utils.js";
|
|
17
17
|
import { isErrorOfMissingDoc } from "../pouchdb/utils_couchdb.js";
|
|
18
|
+
function revisionGeneration(revision) {
|
|
19
|
+
const generation = Number(revision.split("-", 1)[0]);
|
|
20
|
+
return Number.isFinite(generation) ? generation : Number.MAX_SAFE_INTEGER;
|
|
21
|
+
}
|
|
22
|
+
function compareConflictCandidates(left, right) {
|
|
23
|
+
const generationDifference = revisionGeneration(left.revision) - revisionGeneration(right.revision);
|
|
24
|
+
if (generationDifference !== 0) return generationDifference;
|
|
25
|
+
const leftMtime = left.leaf === false || !Number.isFinite(left.leaf.mtime) ? Number.NEGATIVE_INFINITY : left.leaf.mtime;
|
|
26
|
+
const rightMtime = right.leaf === false || !Number.isFinite(right.leaf.mtime) ? Number.NEGATIVE_INFINITY : right.leaf.mtime;
|
|
27
|
+
const mtimeDifference = leftMtime - rightMtime;
|
|
28
|
+
if (mtimeDifference !== 0) return mtimeDifference;
|
|
29
|
+
if (left.revision < right.revision) return -1;
|
|
30
|
+
if (left.revision > right.revision) return 1;
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
18
33
|
class ConflictManager {
|
|
19
34
|
options;
|
|
20
35
|
constructor(options) {
|
|
@@ -311,9 +326,18 @@ class ConflictManager {
|
|
|
311
326
|
if (test == null) return { ok: MISSING_OR_ERROR };
|
|
312
327
|
if (!test._conflicts) return { ok: NOT_CONFLICTED };
|
|
313
328
|
if (test._conflicts.length == 0) return { ok: NOT_CONFLICTED };
|
|
314
|
-
const
|
|
329
|
+
const conflictCandidates = await Promise.all(
|
|
330
|
+
test._conflicts.map(
|
|
331
|
+
async (revision) => ({
|
|
332
|
+
revision,
|
|
333
|
+
leaf: await this.getConflictedDoc(path, revision)
|
|
334
|
+
})
|
|
335
|
+
)
|
|
336
|
+
);
|
|
337
|
+
conflictCandidates.sort(compareConflictCandidates);
|
|
338
|
+
const conflicts = conflictCandidates.map(({ revision }) => revision);
|
|
315
339
|
const leftLeaf = await this.getConflictedDoc(path, test._rev);
|
|
316
|
-
const rightLeaf =
|
|
340
|
+
const rightLeaf = conflictCandidates[0].leaf;
|
|
317
341
|
if (leftLeaf !== false && rightLeaf !== false && leftLeaf.data == rightLeaf.data && leftLeaf.deleted == rightLeaf.deleted) {
|
|
318
342
|
return { leftRev: test._rev, rightRev: conflicts[0], leftLeaf, rightLeaf };
|
|
319
343
|
}
|
|
@@ -50,6 +50,14 @@ A client can resolve only the leaves which it has observed. If it resolves an ol
|
|
|
50
50
|
|
|
51
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
52
|
|
|
53
|
+
## More than two live versions
|
|
54
|
+
|
|
55
|
+
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.
|
|
56
|
+
|
|
57
|
+
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.
|
|
58
|
+
|
|
59
|
+
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.
|
|
60
|
+
|
|
53
61
|
## File-reflection provenance
|
|
54
62
|
|
|
55
63
|
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:
|
|
@@ -150,4 +158,4 @@ Do not:
|
|
|
150
158
|
|
|
151
159
|
## Verification ownership
|
|
152
160
|
|
|
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.
|
|
161
|
+
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, 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.
|
package/docs/development.md
CHANGED
|
@@ -57,7 +57,7 @@ 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 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.
|
|
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, 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. Extend the relevant sides when changing conflict resolution, compaction handling, provenance, or database-to-storage overwrite protection.
|
|
61
61
|
|
|
62
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
63
|
|
package/docs/proven-in-use.md
CHANGED
|
@@ -12,7 +12,7 @@ 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, 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.
|
|
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. 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
16
|
|
|
17
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
18
|
|
package/package.json
CHANGED