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 e20d2a83..fbe3da8d 100644 --- a/docs/adr/2026_08_replicator_capabilities_01_core_contract.md +++ b/docs/adr/2026_08_replicator_capabilities_01_core_contract.md @@ -130,16 +130,38 @@ The selected provider and its active Replicator decide **how**, and whether, that request can be performed. A provider module must not subscribe to the application resume lifecycle merely because it can construct a transport. -Self-hosted LiveSync will have one LiveSync-owned core ServiceModule as the -replication lifecycle coordinator. It uses `AppLifecycleService`, persisted -settings, `ReplicationService`, and the active support declaration. It does -not branch on `remoteType` or use `instanceof` as a capability test. -Commonlib owns the trigger-aware replication contract; the host owns -application lifecycle wiring. +Self-hosted LiveSync will compose one LiveSync-owned serviceFeature as the +replication scheduling boundary. The serviceFeature constructs one private +scheduling controller and connects it to `AppLifecycleService`, settings +lifecycle events, and the periodic timer. The controller owns only scheduling +state and transitions: external-poller ownership, Continuous ownership of +recurring work, the daemon's satisfied initial OneShot marker, resume +coalescing, and periodic-timer reconciliation. It does not register handlers +or acquire `LiveSyncBaseCore`. + +The controller receives narrow collaborators for readiness and suspension +queries, current settings, `ReplicationService`, periodic-timer control, and +diagnostic logging. The surrounding serviceFeature owns lifecycle registration +and adapts those Services to the controller. It returns a focused control view +containing only the daemon operations to select external polling and mark the +initial OneShot as satisfied. The host may retain that view for the CLI, but +must not expose the controller's mutable state or recover it from a core-keyed +global or `WeakMap`. + +This boundary is not a ServiceModule merely because it owns state. It neither +owns a shared external resource nor supplies a general operational capability +to several unrelated consumers. If a future consumer needs a stable shared +scheduling capability beyond the focused CLI view, that ownership decision +must be reviewed explicitly rather than widening the controller implicitly. + +The scheduling controller uses persisted settings, `ReplicationService`, and +the active support declaration. It does not branch on `remoteType` or use +`instanceof` as a capability test. Commonlib owns the trigger-aware replication +contract; the host owns application lifecycle wiring. The existing `onResumed` event remains the eligible-resume boundary after initial readiness, settings application, and visibility recovery. It is not -redefined as a once-per-process event. The coordinator coalesces duplicate work +redefined as a once-per-process event. The controller coalesces duplicate work within one lifecycle generation and preserves readiness and suspension gates: - configured Continuous replication starts only through an active Continuous @@ -155,8 +177,8 @@ within one lifecycle generation and preserves readiness and suspension gates: `ReplicationService` remains responsible for readiness checks, bounded finite activity, failure processing, and replication timing. It exposes distinct user-initiated and unattended entry points, or a typed request which carries -interaction authority. The coordinator never calls a concrete Replicator's -`openReplication()` directly. +interaction authority. The scheduling controller never calls a concrete +Replicator's `openReplication()` directly. `P2P_AutoStart` remains a separate P2P room policy. It is not central Continuous replication and is not `syncOnStart`; its service lifecycle is @@ -164,14 +186,30 @@ specified in Part 2. Reopening after `EVENT_DATABASE_REBUILT` is a flow-authorised continuation requested by the Rebuilder, not evidence that AutoStart is enabled. +Correctness must not depend on the registration order of equal-priority resume +handlers. P2P AutoStart records persistent room demand, while an unattended P2P +OneShot records finite room demand. `P2PRoomSessionOwner` serialises both and +retains the room while either demand remains. Provider-specific P2P lifecycle +wiring and host replication scheduling may therefore run in either order. +Focused owner tests cover AutoStart-before-OneShot and OneShot-before-AutoStart; +the host feature-binding test must not encode their current registration order +as a scheduling prerequisite. + The CLI daemon owns its initial finite convergence before its mirror scan. Restored settings mark that convergence as satisfied for the current lifecycle generation, so `syncOnStart` does not repeat it. In `--interval` mode, the daemon poller is the sole recurring remote-poll scheduler. In changes-feed mode, -the coordinator starts one configured Continuous session when supported; +the controller starts one configured Continuous session when supported; otherwise it may enable the configured generic periodic timer. Continuous has precedence when both are configured. +The controller starts resume work synchronously far enough to reserve +Continuous ownership, then lets the lifecycle handler settle without awaiting +network completion. Concurrent resume notifications share one internal +operation. Periodic reconciliation therefore observes the reservation before +it can enable a competing timer. A failed operation is logged and releases the +coalescing slot so a later resume can retry. + ### Use a fixed current-provider definition Commonlib defines the canonical current remote kinds, provider contract, @@ -183,7 +221,7 @@ API. CouchDB is part of every current host composition. Object Storage and P2P are compile-time composition choices and may be included or omitted without -changing the generic lifecycle coordinator. Adding another current provider +changing the generic scheduling feature. Adding another current provider requires a Commonlib kind and support declaration, host composition, Setup/profile schema handling, and provider-specific tests. It does not require a runtime plug-in registry or behaviour for unknown provider kinds. 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 f176e0c7..1b8686cb 100644 --- a/docs/adr/2026_08_replicator_capabilities_03_migration_plan.md +++ b/docs/adr/2026_08_replicator_capabilities_03_migration_plan.md @@ -60,12 +60,14 @@ Object Storage handling, add a failing test in which a stopped or failed journal transfer must not produce a completed outcome; then propagate the actual journal result. -Add the LiveSync-owned lifecycle coordinator, remove the resume handler from -`ModuleReplicatorCouchDB`, and route CouchDB Continuous and OneShot Sync plus -Object Storage `syncOnStart` through `ReplicationService`. Migrate every -automatic caller to the unattended entry point in this stage, so periodic and -event calls cannot fall back to an interactive P2P role. Migrate manual commands -to the user-initiated entry point. +Add the LiveSync-owned replication scheduling serviceFeature, remove the resume +handler from `ModuleReplicatorCouchDB`, and route CouchDB Continuous and OneShot +Sync plus Object Storage `syncOnStart` through `ReplicationService`. Its private +controller owns scheduling state and transition order; the serviceFeature owns +lifecycle and settings-handler registration. Migrate every automatic caller to +the unattended entry point in this stage, so periodic and event calls cannot +fall back to an interactive P2P role. Migrate manual commands to the +user-initiated entry point. Replace the factory-registration-only responsibilities of `ModuleReplicatorCouchDB` and `ModuleReplicatorMinIO` with composed provider @@ -79,10 +81,12 @@ snapshot. This is the minimum publication fence for this stage. Waiting for in-flight adapter work and making acquisitions wait for replacement settlement remain part of the later active-construction migration. -The lifecycle coordinator coalesces its network work internally, but an +The scheduling controller coalesces its network work internally, but an `onResumed` handler settles once that work has been scheduled. It does not hold later resume consumers until a OneShot transfer or Continuous start has -settled. +settled. Compose the controller with narrow replication, settings, lifecycle, +timer, and logging collaborators. Return only the daemon-facing control view; +do not retain scheduling state in a core-keyed `WeakMap`. At this boundary, existing P2P AutoSync, AutoWatch, and incoming-request entry points receive the same non-interactive readiness and accepted-peer gate. @@ -90,6 +94,12 @@ The no-interaction authority reaches counterpart RPC authorisation and broadcast progress notifications. An unknown peer is blocked rather than prompting. +Add focused Commonlib owner tests which start an unattended finite room demand +before AutoStart demand and after AutoStart demand. Both orders retain one room +until every remaining demand has settled. The LiveSync feature-binding test +must not rely on the current registration order of equal-priority resume +handlers. + Until Stage 4 supplies target-aware unattended P2P, each host composition declares generic `P2P_SyncOnReplication` as `not-implemented`. Its automatic request settles without UI with an explicit blocked result. Existing AutoSync, @@ -97,7 +107,11 @@ AutoWatch, and accepted incoming-request paths continue with the Stage 2 gate. This is a temporary migration state, not the target matrix in Part 1. Apply and test the CLI scheduling precedence defined in Part 1, so the daemon -and lifecycle coordinator cannot schedule duplicate initial or recurring work. +and scheduling controller cannot schedule duplicate initial or recurring work. +Replace `ModuleReplicationLifecycle` and the replication-specific +`ModulePeriodicProcess` wiring only after equivalent controller and feature- +binding tests pass. Reuse the existing timer implementation behind a narrow +timer port; changing other periodic feature owners is outside this stage. ## Stage 3: make P2P transport ownership truthful