From 8e5b058eefc837b0c583e96cdbe527246a46d1e4 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 27 Aug 2026 13:18:17 +0000 Subject: [PATCH] Define P2P session cancellation ownership --- ...eplicator_capabilities_01_core_contract.md | 27 ++++++--- ...r_capabilities_02_p2p_service_lifecycle.md | 59 ++++++++++++++----- ...plicator_capabilities_03_migration_plan.md | 26 ++++++-- 3 files changed, 84 insertions(+), 28 deletions(-) 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 1bf66f34..e20d2a83 100644 --- a/docs/adr/2026_08_replicator_capabilities_01_core_contract.md +++ b/docs/adr/2026_08_replicator_capabilities_01_core_contract.md @@ -331,6 +331,12 @@ initialisation, reset, lock, unlock, and resolution settle only after their defined remote write succeeds; a Rebuilder must not continue after an ignored mutation failure. +`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 Capability availability and operation results are separate. A supported @@ -367,17 +373,22 @@ active -> retiring -> disposed -> replacement published ``` Acquisitions wait for the transition and receive only the replacement. A fenced -old handle cannot start work. Disposal stops Continuous activity, 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. +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 generic stop role is an idempotent request to stop a provider transfer after transport work begins. It does not promise cancellation of readiness checks, Security Seed acquisition, or external storage calls which do not consume a -cancellation signal. P2P declares this role `not-implemented` until its lower -level transfer and RPC work have a real stop path. +cancellation signal. P2P implements this role through its room-session owner: +the request aborts the current finite-operation scopes without closing the room +or disabling later transfers. Its RPC request, incoming `reqSync`, and +replication batch loop consume the same effective signal. An already-started +atomic database operation may settle before cancellation completes, but no new +batch is started afterwards. `getNewReplicator()` is not a general temporary-instance API. Setup and settings flows request narrow probes, such as connection, preferred-tweak, or isolated @@ -426,7 +437,7 @@ support states. | 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 | NI | +| Request to stop active transfer | S | S | S | P2P unattended OneShot means a role exists which uses configured target names without opening a dialogue. Peer-room, watch, acceptance, and broadcast roles diff --git a/docs/adr/2026_08_replicator_capabilities_02_p2p_service_lifecycle.md b/docs/adr/2026_08_replicator_capabilities_02_p2p_service_lifecycle.md index 26d900b5..735e9343 100644 --- a/docs/adr/2026_08_replicator_capabilities_02_p2p_service_lifecycle.md +++ b/docs/adr/2026_08_replicator_capabilities_02_p2p_service_lifecycle.md @@ -65,7 +65,8 @@ A **P2P room session** is one active room membership and every resource whose validity depends on that membership: - the Trystero room, RPC actions, and `RpcRoom`; -- its internal session epoch and cancellation signal; +- its internal session epoch, session controller, and finite-operation + registry; - advertisement state and temporary peer decisions; - peer-bound RPC clients and remote database proxies; - connection, diagnostic, and platform-event subscriptions; and @@ -147,11 +148,31 @@ dialogue. Persisted or automatic acceptance may authorise an unattended path; it cannot create local interaction authority implicitly. Every finite operation which needs a room obtains its own internal, -session-epoch-bound demand. Acquiring another demand does not open another -session. Releasing it never closes a session still required by AutoStart, -another finite operation, or another host consumer. Demand bookkeeping is -internal to the service and is not a general consumer contract; it cannot turn -a finite transfer into persistent transport policy. +session-epoch-bound demand and operation controller. The operation consumes an +effective signal composed from the room session, its operation controller, and +any narrower caller or incoming-RPC signal. The room-session owner, rather than +the adapter or UI consumer, owns every controller and operation settlement. + +Acquiring another demand does not open another session. Releasing it never +closes a session still required by AutoStart, another finite operation, or +another host consumer. A request to stop active transfer aborts the registered +finite-operation controllers but does not abort the room-session controller; +the room remains usable and later transfers obtain fresh operation controllers. +Retiring the room session aborts its session controller, which cancels every +remaining child operation. Demand and controller bookkeeping is internal to the +service and is not a general consumer contract; it cannot turn a finite transfer +into persistent transport policy. + +Cancellation is cooperative and does not roll back durable work. Pull, +requested push, and bidirectional synchronisation propagate the effective +signal through the initiating RPC, incoming `reqSync`, the reverse database RPC +calls, and the replication batch loop. An atomic PouchDB read or write which has +already begun may settle. If a batch write has begun, the operation processes +its successful writes and records the batch checkpoint only after every +required revision has settled successfully. Cancellation before the write does +not advance that checkpoint. The operation then reports a cancelled result and +does not start another batch. Session retirement awaits that settlement before +releasing RPC and room resources. ### Reconcile session settings atomically @@ -173,19 +194,18 @@ client, or policy demand remains reachable after replacement. Reconciliation is serialised with room lifecycle operations: 1. fence new session work; -2. allow already-started finite transfers to settle, because the current lower - level transfer may not yet have a real cancellation contract; +2. abort the old session's finite-operation scopes and await their cooperative + settlement; 3. close and leave the old room in transport-owned order; 4. open and validate the candidate session; and 5. publish the replacement only after it has opened successfully. -The service never exposes a partly initialised candidate or silently interrupts -a transfer without a cancellation contract. If bounded settlement or candidate -opening fails, reconciliation reports the failure and publishes no mixed old -and new session. A candidate-open failure leaves one observable disconnected -state with policy demands unsatisfied; it does not revive the fenced session or -start an unbounded retry loop. A later lifecycle trigger or explicit connect may -retry. +The service never exposes a partly initialised candidate or treats a cancellation +request as rollback. If bounded settlement or candidate opening fails, +reconciliation reports the failure and publishes no mixed old and new session. +A candidate-open failure leaves one observable disconnected state with policy +demands unsatisfied; it does not revive the fenced session or start an unbounded +retry loop. A later lifecycle trigger or explicit connect may retry. ### Order local database replacement across both owners @@ -258,6 +278,15 @@ host must reject concurrent probing while the active service uses those sockets. It must not pause or close active relay sockets as a side effect of a short-lived check. +The probe owns the same kind of lifetime controller as a room session, and +`dispose()` aborts and settles its work before releasing its own room. This +makes probe retirement bounded, but it does not turn process-global Trystero +relay state into isolated state. Runtime acquisition therefore reuses or +observes a compatible active session where the probe requires no second room, +allows a separately owned logical room only when the effective relay binding is +compatible, and otherwise returns a blocked result. It does not silently retire +the active service to make an incompatible probe possible. + ### Keep provider composition explicit The stable P2P service can be composed even when P2P is not the selected main 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 bc5eb129..0712741b 100644 --- a/docs/adr/2026_08_replicator_capabilities_03_migration_plan.md +++ b/docs/adr/2026_08_replicator_capabilities_03_migration_plan.md @@ -116,10 +116,14 @@ Preserve RTC diagnostics through `P2PDiagnostics`, rather than retaining `rawHost`. The compatibility facade may delegate during this stage, but new consumers cannot receive it. -Separate active-adapter release, room-session leave, and the stop request. Keep -the P2P stop role `not-implemented` until a lower-level operation exists. Add -internal session-demand ownership for finite operations and policy-held -AutoStart without exposing that bookkeeping as a general consumer API. +Separate active-adapter release, room-session leave, and the stop request. +Implement the lower-level cooperative cancellation path before declaring the +P2P stop role supported: caller abort through RPC request cancellation, +incoming-handler signal propagation, signal-bound reverse database RPC calls, +and safe batch-boundary termination in `replicateShim`. Add room-session and +operation controllers beside internal session-demand ownership for finite +operations and policy-held AutoStart, without exposing that bookkeeping as a +general consumer API. Add ownership regressions immediately before implementation: @@ -132,7 +136,19 @@ Add ownership regressions immediately before implementation: advertisements do not; - local database replacement retires database-bound feeds and publication; - repeated replacement does not retain platform-event subscriptions; -- policy-only change waits for an already-started finite transfer to settle; +- an active-transfer stop aborts finite operations without closing the room, + while a later operation can use the same room; +- room retirement aborts both locally initiated and incoming `reqSync` work, + waits for an already-started atomic database operation to settle, and starts + no later batch; +- RPC cancellation, timeout, peer departure, and room close abort a + cancellation-aware handler rather than only discarding its eventual result; +- the inbound request context exists before request admission begins, so a + cancellation received while admission waits cannot be lost; +- cancellation retains already-settled documents and checkpoints and reports + `cancelled`, rather than claiming rollback or completion; +- a per-document batch-write failure does not advance the replication + checkpoint past the failed revision; - explicit disconnect suppresses AutoStart and relay reconnection until explicit connect; and - database replacement fences both active-provider and P2P work before