Compose replication scheduling as a service feature

This commit is contained in:
vorotamoroz
2026-08-28 06:05:29 +00:00
parent fc160ee060
commit f7206b1a6e
15 changed files with 850 additions and 525 deletions
@@ -131,38 +131,41 @@ 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 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`.
replication scheduling boundary. The serviceFeature creates one private
scheduling context and passes it to module-level transition functions. The
context contains only scheduling state and narrow collaborators; the functions
implement external-poller ownership, Continuous ownership of recurring work,
the daemon's satisfied initial OneShot marker, resume coalescing, and
periodic-timer reconciliation. They do 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`.
The context receives narrow collaborators for readiness and suspension queries,
current settings, `ReplicationService`, periodic-timer control, and diagnostic
logging. The surrounding serviceFeature owns the context lifetime, lifecycle
registration, and adaptation from those Services. It returns a focused control
view containing only the daemon operations to select external polling and mark
the initial OneShot as satisfied. Core construction passes a frozen bundle of
built-in feature views to host composition, without retaining those views as
public `LiveSyncBaseCore` properties. The CLI injects the scheduling view into
its command context; other hosts may ignore it. No host may expose the context'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.
must be reviewed explicitly rather than widening the returned view implicitly.
The scheduling controller uses persisted settings, `ReplicationService`, and
the active support declaration. It does not branch on `remoteType` or use
The scheduling functions use persisted settings, `ReplicationService`, and
the active support declaration. They do 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 controller coalesces duplicate work
within one lifecycle generation and preserves readiness and suspension gates:
redefined as a once-per-process event. The context-backed functions coalesce
duplicate work within one lifecycle generation and preserve readiness and
suspension gates:
- configured Continuous replication starts only through an active Continuous
role;
@@ -177,7 +180,7 @@ 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 scheduling controller never calls a concrete
interaction authority. The scheduling functions never call a concrete
Replicator's `openReplication()` directly.
`P2P_AutoStart` remains a separate P2P room policy. It is not central
@@ -199,17 +202,29 @@ 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 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 scheduling functions start one configured Continuous session when
supported; otherwise they may enable the configured generic periodic timer.
Continuous has precedence when both are configured.
The controller starts resume work synchronously far enough to reserve
The resume function starts 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.
Coalescing applies only within one observed lifecycle generation. If the
application suspends and resumes while an earlier operation is still settling,
the context retains the newer generation and runs it after the earlier
operation releases the slot. A result from the obsolete generation cannot
change recurring-work ownership or initiate a OneShot fallback for the newer
generation.
Disabling an interval does not retract a callback which the runtime has already
queued. Each Periodic callback therefore rechecks lifecycle eligibility,
readiness, suspension, configuration, external-poller ownership, and
Continuous ownership immediately before it requests replication.
### Use a fixed current-provider definition
Commonlib defines the canonical current remote kinds, provider contract,
@@ -63,8 +63,9 @@ actual journal result.
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
context owns scheduling state, module-level functions implement transitions,
and 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.
@@ -81,12 +82,14 @@ 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 scheduling controller coalesces its network work internally, but an
The scheduling context 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. 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`.
settled. Pass one private context with narrow replication, settings, lifecycle,
timer, and logging collaborators to module-level functions. Return only the
daemon-facing control view, pass it to host composition, and inject it into the
CLI command context. Do not retain the view as a public `LiveSyncBaseCore`
property or 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.
@@ -107,9 +110,9 @@ 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 scheduling controller cannot schedule duplicate initial or recurring work.
and scheduling context cannot schedule duplicate initial or recurring work.
Replace `ModuleReplicationLifecycle` and the replication-specific
`ModulePeriodicProcess` wiring only after equivalent controller and feature-
`ModulePeriodicProcess` wiring only after equivalent context and feature-
binding tests pass. Reuse the existing timer implementation behind a narrow
timer port; changing other periodic feature owners is outside this stage.
@@ -280,6 +283,12 @@ Cover:
- Object Storage `syncOnStart` through resume, including a migrated profile
which retains `liveSync: true`;
- existing CouchDB Continuous and OneShot paths;
- same-generation resume coalescing, a fresh attempt after a later lifecycle
generation, and rejection of an obsolete generation's OneShot fallback;
- a queued Periodic callback rechecking lifecycle and recurring-work ownership
after its interval has been disabled;
- the daemon's satisfied initial OneShot marker being consumed even when a
Continuous start throws, so a later resume may retry normally;
- periodic, database-save, editor-save, file-open, merge, and daemon triggers
remaining free of dialogues;
- manual P2P and configured peer-targeted flows remaining available;