diff --git a/docs/adr/2026_08_replicator_capabilities_01_core_contract.md b/docs/adr/2026_08_replicator_capabilities_01_core_contract.md index 74c6e91e..fb75c078 100644 --- a/docs/adr/2026_08_replicator_capabilities_01_core_contract.md +++ b/docs/adr/2026_08_replicator_capabilities_01_core_contract.md @@ -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; + openReplication( + setting: RemoteDBSettings, + keepAlive: boolean, + showResult: boolean, + ignoreCleanLock: boolean + ): Promise; + terminateSync(): void | Promise; + closeReplication(): void | Promise; +} +``` -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 = - | { 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` 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) diff --git a/docs/adr/2026_08_replicator_capabilities_03_migration_plan.md b/docs/adr/2026_08_replicator_capabilities_03_migration_plan.md index fa35f70f..072248a2 100644 --- a/docs/adr/2026_08_replicator_capabilities_03_migration_plan.md +++ b/docs/adr/2026_08_replicator_capabilities_03_migration_plan.md @@ -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)