Reconcile the Replicator capability design

This commit is contained in:
vorotamoroz
2026-08-30 09:45:19 +00:00
parent 7300db08d6
commit a5756503a2
2 changed files with 426 additions and 128 deletions
@@ -35,8 +35,9 @@ remote status, integrity inspection, and connected-device inspection.
These operations do not share one support boundary:
- CouchDB supports unattended OneShot Sync, Continuous replication, central
remote administration, and CouchDB-specific inspection and maintenance.
- CouchDB supports unattended OneShot Sync, Continuous replication,
central-remote administration, and CouchDB-specific inspection and
maintenance.
- Object Storage supports finite journal synchronisation, central reset and
lock Metadata, full upload and download, and storage-size inspection. It has
no continuous changes feed, CouchDB Chunk source, CouchDB integrity
@@ -244,12 +245,13 @@ a runtime plug-in registry or behaviour for unknown provider kinds.
Each provider definition supplies:
- canonical kind and diagnostic name;
- complete support metadata for the current catalogue;
- active Replicator construction;
- provider configuration identity and an explicit same-kind rebind-or-replace
policy;
- flow-specific probe and initial-transfer dependency factories; and
- provider-specific entry points required by existing current flows.
- a configuration predicate and private configuration identity;
- explicit user-initiated and unattended OneShot runners;
- readiness requirements, an explicit Continuous support decision, and a
transfer-stop runner;
- the exhaustive current remote-resource catalogue; and
- an optional cohesive central-remote administration runner.
The host composes CouchDB and Object Storage definitions directly. A module is
retained only when it owns separate state or behaviour; a factory-registration-
@@ -280,35 +282,62 @@ persistence/profile boundary. Capability selection then uses that canonical
kind; it never infers CouchDB by truthiness or by a negative test such as
'neither Object Storage nor P2P'.
If a provider caches effective connection settings, its declared rebind or
replacement policy keeps the active adapter current after a same-kind profile
change. Reconciliation is serialised with active work and leaves no old
credentials, Security Seed state, journal checkpoint, adapter cache, or
diagnostic connection reachable from the reconciled handle.
The private configuration identity covers every effective setting which binds
the active adapter. The active publication is retained only while both the
provider and identity are unchanged. Every changed identity follows the same
serialised replacement transition; there is no same-instance rebind branch.
### Separate exhaustive support metadata from narrow runtime roles
### Keep the active contract small and compose the differing roles
Every provider definition contains a required support record over the complete
current catalogue. An active Replicator exposes only the narrow roles marked as
supported by its definition. This gives compile-time completeness without a
giant runtime facade or a collection of Boolean flags.
The active object implements only the lifecycle and transport primitives which
are real for CouchDB, Object Storage Journal, and P2P:
The generic catalogue covers user-initiated and unattended OneShot Sync,
ordinary long-lived Continuous replication, full upload and download, central
reset and lock administration, preferred-tweak Metadata read and write,
on-demand remote Chunk reads, remote storage status, compromised-Chunk
inspection, central Security Seed, and a request to stop active transfer.
```typescript
interface ReplicatorInstance {
initializeDatabaseForReplication(): Promise<boolean>;
openReplication(
setting: RemoteDBSettings,
keepAlive: boolean,
showResult: boolean,
ignoreCleanLock: boolean
): Promise<void | boolean>;
terminateSync(): void | Promise<void>;
closeReplication(): void | Promise<void>;
}
```
Full upload/download, reset/lock, and Metadata read/write remain separate roles.
Provider initialisation remains flow-specific: CouchDB may create a database,
whereas Object Storage prepares its Security Seed and does not provision a
bucket. Provider-specific CouchDB maintenance, Object Storage journal
checkpoint maintenance, and P2P room operations are narrowed facets, not
generic feature tests. P2P facets are defined in Part 2.
Provider runners compose the differences in interaction authority, readiness,
typed settlement, explicit Continuous support or inapplicability, and P2P room
demand. Short-lived connection, preferred-tweak, Security Seed, and
synchronisation-information operations remain caller-owned resources. Central
administration remains one optional cohesive runner. None of those differences
widens `ReplicatorInstance`.
Local node identity initialisation is a database and Replicator lifecycle
concern, not a remote capability. Replication statistics remain a
`ReplicatorService` telemetry sink.
The LiveSync central-remote administration composition shares only local-identity
preparation, mutation ordering, milestone interpretation, and result
settlement. Its CouchDB and Object Storage adapters retain their own milestone
readers and connection or client ownership. The fixed provider definition has
already selected the adapter, so those readers validate only the additional
operations which they use; they do not rediscover capability support through a
concrete-class `instanceof` test.
The central OneShot adapters likewise require only the local structural
`openOneShotReplicationWithOutcome()` operation. Concrete constructors remain
at the host-composition boundary, but constructor identity is not a capability
test. A structurally incomplete active instance settles as a failed outcome
rather than falling back to the legacy `openReplication()` operation.
Directional Fetch and Rebuild, Streaming Fetch, CouchDB on-demand Chunk reads,
remote-size inspection, compromised-Chunk inspection, Garbage Collection,
compaction, and journal checkpoint maintenance are workflow or
provider-specific concerns. They do not become exhaustive provider
capabilities merely because an application flow branches by topology.
Local node identity initialisation remains at the established Replicator and
local-database initialisation boundary for this change. A later physical
database-lifetime review may move it only after establishing a concrete owner
and migration benefit. Replication statistics remain a `ReplicatorService`
telemetry sink.
### Make unattended work explicit and truthful
@@ -375,7 +404,11 @@ type ReplicationOutcome =
| typeof REPLICATION_CANCELLED
| { readonly status: "blocked"; readonly reason: ReplicationBlockReason }
| { readonly status: "partial"; readonly detail: PartialReplicationDetail }
| { readonly status: "failed"; readonly error: unknown };
| {
readonly status: "failed";
readonly error: unknown;
readonly recoveryHint?: CentralCompatibilityRecoveryHint;
};
```
Completed and cancelled values are shared singletons or literals. Blocked,
@@ -384,54 +417,108 @@ initialisation, reset, lock, unlock, and resolution settle only after their
defined remote write succeeds; a Rebuilder must not continue after an ignored
mutation failure.
CouchDB and Object Storage Journal record one immutable central-compatibility
decision inside the finite attempt which owns the connection or borrowed
client. Only a rejection from that exact attempt becomes a recovery hint. A
transport failure before assessment, or after an accepted assessment, cannot
reuse mutable mismatch or lock state from an earlier attempt. P2P produces no
central-compatibility decision. The recovery field is already specific to this
contract, so its value carries the stable rejection reason and any preferred
tweak value without a redundant kind discriminator.
`cancelled` means that the requested finite operation did not reach its normal
completion boundary. It does not promise rollback. A provider may retain
documents and checkpoints from batches which had already settled before the
cancellation signal was observed, and a later operation resumes from that
durable state.
### Distinguish observation from an identity result only when required
### Preserve uncertainty at the boundary which owns the decision
Capability availability and operation results are separate. A supported
operation may fail, while an inapplicable operation must not be called or
reported as a network attempt.
reported as a network attempt. A workflow which legitimately has no step for
its topology may complete that branch without claiming that a provider ran an
operation.
For an observation where an empty value changes a safety decision, use a tagged
result:
Keep uncertainty where it changes a safety decision. In particular, an Object
Storage read distinguishes `available`, `not-found`, and `unavailable` before a
caller decides whether creation is permitted. Only explicit `not-found` may
create Journal synchronisation parameters; an unavailable read cannot be
converted to a missing value or a new Security Seed.
```typescript
type RemoteObservation<T> =
| { readonly kind: "observed"; readonly value: T }
| { readonly kind: "unavailable"; readonly error?: unknown };
```
Compromised-Chunk inspection is the current example. `observed: 0` proves that
the supported inspection found no matching entries; `unavailable` says that it
did not complete, including while offline. A caller must handle every result
before declaring the remote clean.
Do not wrap every result. When an empty array is the safe, documented identity
for every current caller, retain it. High-frequency Chunk, document,
changes-feed, and queue paths keep arrays, iterators, and scalars unless a
measurement and a safety distinction justify a tag. No wrapper is added per
Chunk, document, changes-feed row, or queue item.
This principle does not require one generic `RemoteObservation<T>` type or an
active-read catalogue. Existing provider-specific inspection and maintenance
methods may retain their current compatibility surface until their real
consumers are migrated. When a later bounded migration needs to distinguish an
observed zero or empty result from an unavailable inspection, that consumer
owns the smallest explicit result type required by the decision.
### Give active ownership and probes explicit boundaries
`ReplicatorService` is the sole owner of the active Replicator. Replacement is
an atomic transition under its transition lock:
`ReplicatorService` is the sole owner of the active Replicator. Replacement and
disposal use one explicit quiescing transition under its transition lock:
```text
active -> retiring -> disposed -> replacement published
active -> quiescing -> closed -> replacement published
```
Acquisitions wait for the transition and receive only the replacement. A fenced
old handle cannot start work. Disposal stops Continuous activity, requests
cancellation of work which supports it, awaits work which cannot be cancelled,
and settles or reports every operation owned by the active adapter before
publication. If bounded retirement cannot settle, replacement fails visibly
and no new handle is published; the old handle is disposed when late work
settles.
The transition removes the old publication from `current`, rejects later
admission, requests the provider's supported transfer cancellation, and drains
work which was already admitted. Only then does it close the old Replicator and
publish another active context. Acquisitions ordered after the
transition receive only the replacement. A cancellation failure does not
permit the physical close boundary to be skipped. If admitted work cannot
settle, no replacement is published. If physical close fails, the quiescing
publication remains fenced so a later transition can retry that close before
constructing a replacement.
The publication object itself is the private generation identity; no separate
generation number or public lease is required. Its reservation count is not the
service-wide bounded-activity count or finite-replication count: those counts
remain status and quiescence signals and can include trials, local work, and an
outer Rebuilder flow which itself initiates a lifecycle transition. A switch
which waited for either global count could therefore wait for the operation
which is awaiting that same switch.
Production consumers cannot synchronously inspect an unreserved active
context. They must acquire it or run work inside the admitted callback boundary.
The synchronous `inspectActiveReplicatorContext()` view is protected and exists
only for lifecycle diagnostics and focused tests.
The minimum consumer surface is a callback boundary,
`runWithActiveReplicatorContext(callback)`, rather than an exposed lease or
release token. Admission is ordered with lifecycle transitions, the callback
receives one exact context, and private release runs in `finally` without
entering the transition queue.
The callback must not initiate or await settings realisation, database reset or
replacement, active Replicator retirement, or another operation which queues
the same lifecycle transition. Such recovery or reconfiguration is staged
after the reserved dispatch settles. A process-wide re-entrancy flag would
both reject unrelated asynchronous work and miss re-entry after an `await`, so
the contract is documented and tested at the owning workflows rather than
claimed as a reliable runtime detector.
Failure presentation and recovery start only after the finite reservation has
settled. A later remote mutation re-enters through the callback boundary and
requires reference equality with the failed context. It cannot apply the
decision produced by one publication to its replacement.
An edited-settings trial is different: it owns an independent Replicator and
connection, never borrows the active publication, and disposes both resources.
Application suspension is a reversible host pause, not an ownership
transition. `ReplicatorService` orders the provider's transfer-stop request but
retains the active publication, accepts later work, and does not drain or close
the Replicator. Provider-specific transport lifecycle, including P2P room and
relay handling, remains independently owned.
Plug-in unload is terminal and reuses the same quiescing retirement as disposal;
it does not add another public state or unload capability. The lifecycle handler
fences admission, requests transfer cancellation, drains admitted work, and
closes the Replicator before `ControlService` closes the local database. This
ordering matters even when disabling the plug-in leaves the JavaScript process
alive.
The generic stop role is an idempotent request to stop a provider transfer after
transport work begins. It does not promise cancellation of readiness checks,
@@ -450,25 +537,28 @@ resource with idempotent asynchronous `dispose()`. Trial settings are passed
to the probe itself and cannot silently read active settings. A probe cannot
replace the active Replicator or the P2P service.
Streaming Fetch receives an owned CouchDB HTTP configuration and
`RemoteSecuritySeed` supplier directly. The supplier is bound to the selected
configuration identity, invalidates cached seed state on reconciliation, and
is disposed by the initial-transfer flow. It does not construct a temporary
full Replicator.
Streaming Fetch receives an owned Security Seed resource bound to its settings
snapshot. The current compatibility implementation may construct an
unpublished Replicator internally, but the resource owns and disposes it and
cannot replace the active publication.
### Keep initialisation workflows explicit
- CouchDB and Object Storage first-device rebuilds reset and lock their central
remote, then perform the established convergence upload.
- A P2P first-device rebuild prepares the local database without pretending to
reset, lock, or upload to a central remote.
- CouchDB and Object Storage Fetch use their central full-download flow.
- A P2P additional-device Fetch selects a peer and performs one full download.
- CouchDB Streaming Fetch remains the separate initial-transfer service above.
- Fetch, Rebuild, overwrite, and first-device setup remain application
workflows. Their direction, reset, lock, peer selection, local-database work,
and convergence passes are not one Replicator capability.
- CouchDB and Object Storage use their established workflow-local directional
adapters and central administration where required.
- A P2P first device prepares only its local state. An additional P2P device
selects a peer and uses the real finite download path; P2P does not emulate a
central reset, lock, milestone, or upload.
- CouchDB Streaming Fetch remains a separate initial-transfer service and uses
only its owned Security Seed dependency.
The Rebuilder requests capabilities and does not cast a generic Replicator to a
concrete class. Its `EVENT_DATABASE_REBUILT` continuation is separately
authorised and does not imply `syncOnStart` or P2P AutoStart.
The `EVENT_DATABASE_REBUILT` continuation remains separately authorised and
does not imply `syncOnStart` or P2P AutoStart. Complete Rebuilder and
maintenance-facade migration is a later bounded change rather than a condition
for the active Replicator core.
## Target capability matrix for current providers
@@ -476,21 +566,47 @@ authorised and does not imply `syncOnStart` or P2P AutoStart.
Configuration and reachability are request preconditions or outcomes, not
support states.
| Generic role | CouchDB | Object Storage | P2P |
| --------------------------------------- | ------- | -------------- | --- |
| User-initiated OneShot Sync | S | S | S |
| Unattended OneShot Sync without UI | S | S | S |
| Ordinary long-lived Continuous session | S | NA | NA |
| Full upload to a central remote | S | S | NA |
| Full download | S | S | S |
| Central remote reset | S | S | NA |
| Central remote lock and resolution | S | S | NA |
| Preferred-tweak Metadata read and write | S | S | NA |
| On-demand remote Chunk source | S | NA | NA |
| Remote storage status | S | S | NA |
| Compromised-Chunk inspection | S | NA | NA |
| Central-remote Security Seed | S | S | NA |
| Request to stop active transfer | S | S | S |
| Active provider role | CouchDB | Object Storage | P2P |
| -------------------------------------- | ------- | -------------- | --- |
| User-initiated OneShot Sync | S | S | S |
| Unattended OneShot Sync without UI | S | S | S |
| Ordinary long-lived Continuous session | S | NA | NA |
| Request to stop active transfer | S | S | S |
Every provider definition records the Continuous row explicitly. CouchDB
supplies its runner, while Object Storage and P2P declare the role not
applicable; omission is not a fourth support state.
The current definition also declares the finite resources and the one optional
central facility which have real consumers:
| Provider-owned facility | CouchDB | Object Storage | P2P |
| --------------------------------------------- | ------- | -------------- | ------ |
| Connection probe | S | S | NA |
| Preferred-tweak probe | S | S | NA |
| Security Seed resource | S | S | NA |
| Synchronisation-information resource | S | NA | NA |
| Cohesive central-remote administration runner | S | S | absent |
`remoteResources` is exhaustive over its four stable machine keys, so adding a
resource requires an explicit decision from every composed provider. The
central-remote administration field is optional because there is no corresponding
P2P facility. Actions within that runner are the current central protocol, not
an exhaustive capability table imposed on every Replicator.
The public contract is named `CentralRemoteAdministration*` because every
current action, observation, failure, and postcondition belongs to that central
milestone protocol. Established CLI command names remain unchanged.
The following concerns deliberately stay outside this capability matrix:
| Concern | Current owner |
| ---------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Directional Fetch and Rebuild | Application workflow over provider-specific adapters |
| Streaming Fetch | CouchDB initial-transfer workflow plus an owned Security Seed resource |
| On-demand remote Chunks and compromised-Chunk inspection | CouchDB compatibility or maintenance consumers |
| Remote size, Garbage Collection, compaction, and device registry | Provider-specific inspection and maintenance flows |
| P2P room, relay, peer selection, admission, watch, and broadcast | Stable P2P service and room-session owner in Part 2 |
P2P unattended OneShot means a role exists which uses configured target names
without opening a dialogue. Peer-room, watch, acceptance, and broadcast roles
@@ -578,3 +694,4 @@ only when every caller proves it to be the operation's identity.
- [CouchDB Remote Connection Ownership](2026_08_couchdb_remote_connection_ownership.md)
- [Package the Common Library Behind Explicit Host Boundaries](2026_07_common_library_package_boundary.md)
- [Self-hosted LiveSync issue 1140](https://github.com/vrtmrz/obsidian-livesync/issues/1140)
- [Self-hosted LiveSync issue 1147](https://github.com/vrtmrz/obsidian-livesync/issues/1147)
@@ -19,8 +19,8 @@ another runtime contract.
Proposed. The stages below are an implementation and verification order, not
independently releasable states. Commonlib and Self-hosted LiveSync must not
publish temporary support boundaries described by an incomplete stage. A
release follows only after the target matrix, ownership boundaries, and all
production-consumer migrations in Parts 1 and 2 are complete.
release follows only after the target matrix, ownership boundaries, and the
contracted production-consumer migrations in Parts 1 and 2 are complete.
## Migration rules
@@ -215,7 +215,7 @@ sequence rather than a second description of the implemented state.
## Stage 5: separate active construction and flow-specific probes
Make active creation a private, exhaustive `ReplicatorService` operation with
an explicit same-kind rebind-or-replace policy. Migrate every non-active caller
one configuration identity and replacement policy. Migrate every non-active caller
before restricting `getNewReplicator()`: CouchDB connection and passphrase
checks, Object Storage connection and preferred-tweak trials, isolated P2P
Setup, CLI commands, and other host compositions. Prove that every probe leaves
@@ -226,32 +226,197 @@ defined in Part 1. Add replacement-fence, late-settlement,
configuration-identity, cache-invalidation, and probe-disposal tests before
making active construction private.
## Stage 6: migrate safety-sensitive and provider-specific consumers
### Implementation position after Stage 5
Migrate Setup, Rebuilder, tweak review, remote-size inspection, on-demand Chunk
retrieval, migration inspection, maintenance, and abort commands. Central
mutations settle truthfully before Rebuilder continuation. Compromised-Chunk
inspection uses `observed` or `unavailable`; an offline check is never zero.
Object Storage and P2P no longer supply dummy integrity counts, remote Chunk
results, or Security Seeds through the compatibility facade.
The provider-defined active-construction path is now private to
`ReplicatorService`. The public `getNewReplicator` handler remains as a
compatibility surface, but current Self-hosted LiveSync production code no
longer calls it. Its removal belongs to Stage 7 after any external compatibility
decision has been made.
Move local node identity initialisation out of the provider facade. Add
idempotent asynchronous disposal and settlement tests, same-kind profile
rebind-or-replace tests, and bounded activity around the host-owned garbage-
collection workflow's CouchDB OneShots. Each consumer migration removes the
corresponding generic `remoteType` or `instanceof` feature branch and adds a
focused test.
The current host composition has migrated CouchDB and Object Storage connection
checks, passphrase inspection, preferred-tweak reads, CLI remote status and
administration, isolated P2P Setup, and Streaming Fetch Security Seed access to
owned resources or focused services. Active replacement is serialised, context
acquisition waits for a queued replacement, late candidates are fenced, and
short-lived resources are disposed.
Transport-specific settings may continue to display provider kind. Provider
identity is valid for labels and provider-specific configuration; it is not a
generic feature test.
This position completes the Stage 5 construction and probe boundary. It is not
itself a release decision: the active-publication and truthful-attempt work in
Stage 6 remains required. Complete retirement of the compatibility facade is
not a prerequisite for issue 1140.
## Stage 7: retire the giant compatibility facade
## Stage 6: harden the active lifecycle and exact attempt outcome
After every current consumer has migrated, stop requiring unsupported methods
on `LiveSyncAbstractReplicator`. Retain or remove compatibility exports at the
next Commonlib compatibility boundary. Do not retain dummy methods only because
the former abstract base declared them.
Replace the active dependency on `LiveSyncAbstractReplicator` with the small
`ReplicatorInstance` contract. Retain a publication only while its provider and
private configuration identity are unchanged. Every changed identity fences
new admission, requests supported transfer cancellation, drains admitted work,
closes the old instance, and only then constructs and publishes a replacement.
Reserve the exact publication around typed finite dispatch and the currently
required workflow-local directional attempts. Release before recovery or a
dialogue. CouchDB and Journal record an immutable compatibility decision inside
the attempt which owns the connection or borrowed client, and a later mutation
must re-admit that failed context. Retry and Continuous paths reassess on each
new CouchDB connection without adding another logical connection.
Preserve Journal storage read states through `getSyncParameters()`. Only an
explicit `not-found` result permits `SyncParamsHandler` to create and upload new
synchronisation parameters. An unavailable read becomes a fetch failure and
must not regenerate the shared Security Seed. This fixes issue 1147 within the
owned-observation boundary rather than adding another Replicator capability.
### Contracted Stage 6 implementation position
The retained implementation is deliberately smaller than the earlier complete
consumer-migration proposal:
- `ReplicatorInstance` contains only initialisation, `openReplication`,
transfer termination, and close;
- provider definitions retain user-initiated and unattended OneShot runners,
an explicit Continuous support decision, readiness, transfer stop, the four
owned remote resources, and one optional cohesive central-remote administration
runner;
- every changed configuration identity replaces the active instance; there is
no same-instance rebind policy;
- typed finite dispatch reserves the exact readiness-tested publication and
releases it before failure recovery;
- CouchDB and Journal carry only a rejected attempt-local compatibility
decision into the failure outcome, so a transport failure cannot reuse old
mutable fields;
- mismatch updates, unlock, and cleaned-remote reconciliation re-admit the
failed publication before remote mutation;
- P2P uses a narrow non-owning active adapter, takes the same typed finite path,
supports its real download workflow, and exposes no central facility;
- ordinary central preparation and Streaming Fetch use owned Security Seed
resources which dispose their unpublished compatibility instances;
- CLI synchronisation diagnoses lock and clean rejection from the exact outcome
while preserving the existing success and failure return policy; and
- Journal unavailable sync-parameter reads cannot enter the create-and-upload
branch required only by explicit absence.
The active path no longer depends on the giant facade. Existing maintenance,
remote-size, on-demand Chunk, migration-inspection, and compatibility consumers
may still use focused structural checks or the legacy facade. Their complete
migration, local-node-identity redesign, generic milestone extraction, and
in-process local-database reset redesign are deferred unless a separate bounded
change proves that they are required.
Each production correction has a focused regression. The verification section
distinguishes local source and packed-consumer evidence from registry,
real-runtime, and release validation.
### Active-publication quiescing boundary
Commonlib implements the publication-scoped callback reservation defined in
Part 1 for finite provider dispatch, central-remote administration, exact
recovery mutation, and the workflow-local directional attempts which use the
active instance. Invocation queues its admission decision in the same order as
replacement and disposal. The publication object is its private generation
identity. Once admitted, release is idempotent, private, and independent of
that queue so quiescing cannot prevent the operation which allows its own drain
to settle.
Every switch follows the same order: fence admission, request supported
transfer cancellation, drain admitted callbacks, close, then publish another
context. An unchanged provider and configuration identity keeps the existing
publication.
The first implementation regressions cover:
- replacement waiting for an admitted exact-context task while ignoring an
unrelated bounded activity;
- context acquisition waiting for a queued replacement rather than returning a
stale or intermediate publication;
- rejecting central-remote administration releasing its reservation before
replacement continues;
- finite failure recovery starting only after publication release;
- directional workflow retry releasing and then re-admitting the same context;
and
- terminal unload draining admitted work before Replicator and local-database
close, while reversible suspension retains the publication.
Keep readiness, failure presentation, dialogue, independently owned trial
resources, P2P room-session demand, and Continuous replication outside this
reservation. The callback TSDoc forbids awaiting a lifecycle transition which
would wait for the same admission to settle.
## Stage 7: perform a bounded structural review
Confirm that the contracted core is minimum and robust before a commit or
release decision. The active path must stay independent of
`LiveSyncAbstractReplicator`, but complete migration of every compatibility
consumer is separate work. Remove a dummy or abstract requirement only when
its current callers have a truthful alternative; do not turn facade retirement
into a condition for issue 1140.
The retained compatibility surface includes the broad
`LiveSyncBaseCore.replicator` view and concrete central adapters required by
maintenance and migration code. It is explicitly a partial compatibility view,
not the active provider contract. P2P's active adapter does not inherit it.
CouchDB and Object Storage Journal both have a real central milestone document,
but that fact alone does not justify a generic store. Extract a focused contract
only when a second current caller demonstrates the same ownership and mutation
semantics. P2P has no central milestone document and must not receive a stub or
synthetic implementation.
The final structural review retains the following minimum boundaries:
- central provider definitions remain outside `LiveSyncBaseCore`; its thin
registration method remains as the construction-order composition boundary;
- P2P registration remains with the feature which owns the stable P2P service;
- `ReplicatorService` and `ReplicationService` remain cohesive services. Their
complex state and policy already reside in `ActiveReplicatorState`,
`TypedReplicationCoordinator`, the readiness evaluator,
`RemoteResourceResolver`, and `CentralRemoteAdministrationCoordinator`, so
another split would not improve the current test seams;
- every provider explicitly declares Continuous support or inapplicability;
- `IReplicatorService` exposes only acquired or admitted active-context access.
A protected synchronous inspector remains for focused lifecycle tests;
- central-compatibility statuses, recorders, and projection helpers remain
package-internal. Only stable rejection reason codes and public recovery
value types remain package-index exports; and
- redundant recovery-kind data, unused active-state identity matching, and the
externally visible legacy administration lookup helper are removed.
LiveSync implements its optional central-remote administration facility through one
shared protocol executor and provider-specific milestone readers. Provider
composition selects the reader; a reader validates only the structural
operations required for its CouchDB connection or Journal client before any
remote mutation. It does not impose concrete-class identity on the generic
runner contract.
The unpublished public contract, provider field, coordinator, and service
operation use the `CentralRemoteAdministration*` name because their complete
protocol is central-milestone-specific. The central OneShot adapters use a
separate local structural operation instead of concrete-class identity. Stable
capability-kind comparisons use `CAPABILITY_SUPPORT_KINDS`, and unavailable
capabilities settle once without a redundant post-narrowing check.
Provider-specific configuration identities remain explicit projections of the
settings which bind each adapter. They are not replaced with a generic identity
builder: the current URL normalisation and setting lists are clearer at the
provider boundary, and changing the shared header parser is separate work.
The review records, but does not prejudge, whether maintained Rebuild and Fetch
workflows still require an in-process local-database reset. Prefer a Flag File and restart
boundary if it can preserve user intent, CLI behaviour, failure recovery, and
the maintained test workflows without losing a supported continuation path.
Until that evidence exists, retain the current reset contract and its explicit
Replicator-retirement ordering rather than assuming that every workflow has
already moved to a restart.
It also records consumers which retain `LiveSyncLocalDB`, its physical PouchDB
handle, or its managers. If broad retention still leaks ownership after the
consumer migrations, keep the physical handle and teardown authority in
`LiveSyncLocalDB`, and expose only the smallest read-only view or
generation-bound operation contract required by each consumer. Do not cache a
detached handle snapshot across reset. Preserve the current single active
database contract, and add another abstraction only where the inventory shows
a concrete lifetime or testability benefit. These recorded questions do not
widen Stage 6 or make an anticipatory database abstraction part of this change.
## Verification
@@ -260,21 +425,29 @@ the former abstract base declared them.
Cover:
- exhaustive host-composed definitions for CouchDB, Object Storage, and P2P;
- complete support declarations and matching runtime roles;
- the four-method active `ReplicatorInstance` contract and provider-owned
runtime roles;
- user-initiated, unattended, blocked, partial, cancelled, and failed OneShot
outcomes;
- truthful Object Storage stop or transfer failure and headless P2P outcomes;
- observed and unavailable compromised-Chunk results, including offline;
- active, retiring, disposed, and replacement-published states, including
rejection of new work during retirement and late settlement;
- same-kind rebind or replacement, configuration-identity invalidation,
idempotent asynchronous disposal, and settlement ordering;
- active, quiescing, disposed, and replacement-published states, including
rejection of new work during retirement, acquisition waiting, and late
candidate settlement;
- unchanged-identity retention, changed-identity replacement, idempotent
reservation release, and close ordering;
- probes which cannot replace the active Replicator or P2P service and dispose
owned resources;
- the deliberately narrow active-transfer stop request, including work it
does not claim to cancel;
- central full-transfer and remote-administration boundaries; and
- provider-specific P2P, CouchDB, and journal facets.
- CouchDB compatibility and transfer using the same owned OneShot connection;
- Journal compatibility and transfer using one settings-bound borrowed client;
- attempt-local accepted, rejected, and not-assessed decisions, including retry
and Continuous reassessment on newly opened CouchDB connections;
- Journal synchronisation-parameter reads distinguishing explicit absence from
unavailability and never writing after the latter;
- cohesive central administration and its truthful mutation settlement; and
- the narrow non-owning P2P active adapter, including download without a
synthetic upload or central facility.
### Self-hosted LiveSync unit tests
@@ -302,6 +475,10 @@ Cover:
- counterpart RPC authorisation and broadcast progress preserving no-dialogue
authority;
- Setup and settings validation through probes;
- exact failed-context mismatch, unlock, and cleaned-remote recovery, including
rejection after active replacement;
- CLI lock diagnostics from the exact finite outcome rather than mutable fields
on a later active Replicator;
- first-device and additional-device initialisation for each current provider;
- P2P as the main remote and as an adjunct transport; and
- CLI, WebApp, and WebPeer composition against the same contracts, including
@@ -315,12 +492,15 @@ waiting for the periodic interval, ordinary CouchDB start-up, and P2P start-up
without an unexpected selection dialogue or transport replacement. P2P
validation also covers an accepted configured peer advertising after room open,
a watched peer whose remote side broadcasts, and suspension before a delayed
AutoStart callback.
AutoStart callback. Object Storage validation must also confirm that a
temporarily unavailable synchronisation-parameter read does not upload a new
Security Seed.
No temporary stage is a release candidate. Release readiness requires the
target capability matrix, production-consumer migration, focused downstream
checks with the exact packed Commonlib artefact, and the real-runtime checks
appropriate to the changed boundary.
target capability matrix, the contracted core and in-scope consumer migration,
focused downstream checks with the exact packed Commonlib artefact, and the
real-runtime checks appropriate to the changed boundary. Complete legacy-facade
retirement remains a separately reviewed compatibility change.
## References
@@ -330,3 +510,4 @@ appropriate to the changed boundary.
- [P2P Transport Compatibility Controls](2026_08_p2p_transport_compatibility.md)
- [Bounded Remote Activity](2026_07_bounded_remote_activity.md)
- [Self-hosted LiveSync issue 1140](https://github.com/vrtmrz/obsidian-livesync/issues/1140)
- [Self-hosted LiveSync issue 1147](https://github.com/vrtmrz/obsidian-livesync/issues/1147)