@fluidframework/container-loader 2.114.0 → 2.116.0-416006

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/api-report/container-loader.legacy.alpha.api.md +10 -0
  3. package/dist/container.d.ts +1 -0
  4. package/dist/container.d.ts.map +1 -1
  5. package/dist/container.js +8 -0
  6. package/dist/container.js.map +1 -1
  7. package/dist/containerContext.d.ts +4 -2
  8. package/dist/containerContext.d.ts.map +1 -1
  9. package/dist/containerContext.js +1 -0
  10. package/dist/containerContext.js.map +1 -1
  11. package/dist/legacyAlpha.d.ts +2 -0
  12. package/dist/loadContainerToSequenceNumber.d.ts +10 -6
  13. package/dist/loadContainerToSequenceNumber.d.ts.map +1 -1
  14. package/dist/loadContainerToSequenceNumber.js +9 -5
  15. package/dist/loadContainerToSequenceNumber.js.map +1 -1
  16. package/dist/loaderLayerCompatState.d.ts +2 -2
  17. package/dist/packageVersion.d.ts +1 -1
  18. package/dist/packageVersion.d.ts.map +1 -1
  19. package/dist/packageVersion.js +1 -1
  20. package/dist/packageVersion.js.map +1 -1
  21. package/lib/container.d.ts +1 -0
  22. package/lib/container.d.ts.map +1 -1
  23. package/lib/container.js +8 -0
  24. package/lib/container.js.map +1 -1
  25. package/lib/containerContext.d.ts +4 -2
  26. package/lib/containerContext.d.ts.map +1 -1
  27. package/lib/containerContext.js +1 -0
  28. package/lib/containerContext.js.map +1 -1
  29. package/lib/legacyAlpha.d.ts +2 -0
  30. package/lib/loadContainerToSequenceNumber.d.ts +10 -6
  31. package/lib/loadContainerToSequenceNumber.d.ts.map +1 -1
  32. package/lib/loadContainerToSequenceNumber.js +9 -5
  33. package/lib/loadContainerToSequenceNumber.js.map +1 -1
  34. package/lib/loaderLayerCompatState.d.ts +2 -2
  35. package/lib/packageVersion.d.ts +1 -1
  36. package/lib/packageVersion.d.ts.map +1 -1
  37. package/lib/packageVersion.js +1 -1
  38. package/lib/packageVersion.js.map +1 -1
  39. package/package.json +11 -11
  40. package/src/container.ts +13 -0
  41. package/src/containerContext.ts +7 -0
  42. package/src/loadContainerToSequenceNumber.ts +11 -7
  43. package/src/packageVersion.ts +1 -1
  44. package/src/pointInTime/DEV.md +237 -0
@@ -0,0 +1,237 @@
1
+ # Point-in-Time Container Loading
2
+
3
+ Point-in-time loading materializes a disconnected, read-only container at an exact Fluid sequence number. The loader owns orchestration, while the driver owns finding a recoverable snapshot and supplying only the ops needed to reach the target.
4
+
5
+ This flow consumes a sequence number. It does not create or resolve version marks. The runtime version-mark design is documented in [`versionMarks/DEV.md`](../../../../runtime/container-runtime/src/versionMarks/DEV.md).
6
+
7
+ ## Public flow
8
+
9
+ The host calls:
10
+
11
+ ```ts
12
+ const historicalContainer = await loadContainerToSequenceNumber({
13
+ request,
14
+ loadToSequenceNumber,
15
+ codeLoader,
16
+ urlResolver,
17
+ documentServiceFactory: getOdspPointInTimeDocumentServiceFactory(/* credentials */),
18
+ logger,
19
+ signal,
20
+ });
21
+ ```
22
+
23
+ The result is a historical view with these invariants:
24
+
25
+ - `deltaManager.lastSequenceNumber` equals `loadToSequenceNumber`.
26
+ - The container is read-only, disconnected, and has inbound and outbound processing paused.
27
+ - It does not advance as new live ops sequence.
28
+ - It must not be connected or used as a normal collaborative container.
29
+
30
+ ## End-to-end sequence
31
+
32
+ 1. `loadContainerToSequenceNumber` validates that the target is a non-negative integer.
33
+ 2. The loader structurally checks whether the supplied `IDocumentServiceFactory` implements `createPointInTimeDocumentService`.
34
+ 3. `PointInTimeDocumentServiceFactory` adapts that capability to the normal `createDocumentService` call used by container loading, preserving the requested target sequence number.
35
+ 4. The driver creates a point-in-time document service:
36
+ - storage serves a recoverable snapshot whose sequence number is at or before the target;
37
+ - delta storage serves the live document's retained ops, bounded so replay cannot pass the target;
38
+ - the service is storage-only, preventing a live delta-stream connection.
39
+ 5. `loadContainerPaused` loads the selected snapshot with automatic op processing disabled and forces the container into read-only mode.
40
+ 6. If the snapshot is behind the target, the loader installs its op and cancellation listeners before connecting. It then replays ops until the target is processed.
41
+ 7. At the target, the loader pauses both delta-manager queues, disconnects, removes its listeners, and returns the historical container.
42
+
43
+ If the chosen snapshot is already at the target, the loader pauses immediately. A snapshot newer than the target is rejected because replay cannot move backward.
44
+
45
+ ## Delta replay batching and unpersisted ops
46
+
47
+ The load path has two independent batching concepts:
48
+
49
+ - A **runtime op batch** is an application submission unit. Grouping, compression, and chunking determine how that unit is represented and when its state can be applied atomically.
50
+ - A **delta-fetch page** is a transport range used by `requestOps` / `ParallelRequests`. ODSP defaults to pages of `hostPolicy.opsBatchSize` (5000) with `hostPolicy.concurrentOpsBatches` requests. The point-in-time wrapper converts even an unbounded caller request into the known exclusive boundary `target + 1`; `ParallelRequests` splits that bounded range into pages, buffers pages that complete out of order, and dispatches only the next contiguous sequence-number range. Partial snapshot/cache pages are continued from their first missing sequence number, oversized responses are split, and the final known range is retried until complete or failed.
51
+
52
+ For a normal connected ODSP load, each cache/storage miss also sends a `get_ops` request to PUSH. That path can supply ops that have sequenced but have not yet been flushed to durable delta storage. A point-in-time service is deliberately `storageOnly`, so it creates no delta-stream connection and its `requestFromSocket` callback has no active socket. It can therefore use bundled snapshot ops, flushed persisted-cache entries, and durable delta storage, but not PUSH-only ops.
53
+
54
+ This creates a materializability gap: a version mark can resolve from a live client's inbound map immediately after sequencing while a new point-in-time load still cannot fetch the target op from storage. `getSingleOpBatch` polls a known missing range and eventually fails after the storage retry window if the op is not persisted. Resolution of a marker and durable availability for historical loading are separate states.
55
+
56
+ ## Driver capability contract
57
+
58
+ The loader intentionally does not know how a service stores historical snapshots. A capable driver must implement:
59
+
60
+ ```ts
61
+ createPointInTimeDocumentService(
62
+ resolvedUrl: IResolvedUrl,
63
+ targetSequenceNumber: number,
64
+ logger?: ITelemetryBaseLogger,
65
+ clientIsSummarizer?: boolean,
66
+ ): Promise<IDocumentService>;
67
+ ```
68
+
69
+ That service must satisfy the following:
70
+
71
+ - `connectToStorage()` returns a snapshot at or before the target.
72
+ - `connectToDeltaStorage()` can replay every retained op after that snapshot through the target.
73
+ - Delta reads use `[from, to)` semantics; the target op is included by bounding `to` at `targetSequenceNumber + 1`.
74
+ - The service cannot become a writable live client.
75
+ - Missing bases, unavailable bridging ops, and lineage mismatches fail explicitly rather than returning an incorrect state.
76
+
77
+ The loader detects this capability structurally so callers pass the driver's factory directly. The adapter is internal and cannot create new containers.
78
+
79
+ ## Package ownership and planned extraction
80
+
81
+ The current placement in `@fluidframework/container-loader` was explicitly described as
82
+ prototype-only in the [original review discussion](https://github.com/microsoft/FluidFramework/pull/27703#discussion_r3623422928).
83
+ The follow-up package extraction is not otherwise captured in the current implementation docs.
84
+
85
+ The architectural concern is slightly narrower than "the loader is ODSP-specific":
86
+
87
+ - `loadContainerToSequenceNumber` does not import ODSP code. It is structurally driver-agnostic and
88
+ can work with any `IDocumentServiceFactory` that implements
89
+ `createPointInTimeDocumentService`.
90
+ - ODSP is currently the only driver that implements that capability. In particular, the current
91
+ recoverable-base selection relies on ODSP file-version history and epoch validation.
92
+ - Exposing this optional, currently ODSP-only workflow from the core container-loader package makes
93
+ the loader own a feature-level API even though its reusable responsibility is only loading and
94
+ pausing a container.
95
+
96
+ The proposed boundary is a dedicated point-in-time feature package (working name
97
+ `@fluidframework/point-in-time`; final naming is a package/API review decision). The package should
98
+ own the host-facing orchestration while depending on the generic loader primitive and accepting a
99
+ capable driver factory.
100
+
101
+ ### Move to the feature package
102
+
103
+ | Current location | Responsibility after extraction |
104
+ | --- | --- |
105
+ | `container-loader/src/loadContainerToSequenceNumber.ts` | Public `loadContainerToSequenceNumber` entry point, target validation, and `ILoadContainerToSequenceNumberProps`. |
106
+ | `container-loader/src/pointInTimeServices.ts` | Point-in-time factory capability contract, structural capability check, and adapter to `IDocumentServiceFactory`. |
107
+ | `container-loader/src/test/loadContainerToSequenceNumber.spec.ts` | Feature-entry-point validation and capability-boundary tests. |
108
+ | `container-loader/src/test/pointInTimeServices.spec.ts` | Capability detection and adapter tests. |
109
+ | This `pointInTime/DEV.md` | Cross-package feature flow, package boundary, host contract, and end-to-end test map. |
110
+
111
+ The move also requires removing the two host-facing exports from the container-loader entry point
112
+ and generated API surface, adding them to the new package's alpha entry point, and updating
113
+ consumers and point-in-time end-to-end tests to import from the new package. API report files must
114
+ be regenerated rather than edited by hand.
115
+
116
+ ### Keep in existing packages
117
+
118
+ | Current owner | What remains | Why |
119
+ | --- | --- | --- |
120
+ | `@fluidframework/container-loader` | `loadContainerPaused` and its general loading machinery | This is the driver-agnostic loader primitive. It predates point-in-time loading and is also used by non-ODSP callers. The feature package should compose it rather than duplicate loader internals. |
121
+ | `@fluidframework/odsp-driver` | `pointInTimeDriver/`, `odspVersionManager/`, and `getOdspPointInTimeDocumentServiceFactory` | These components depend on ODSP file-version APIs, resolved URLs, caches, storage policies, and epoch tracking. Moving them would either leak ODSP internals into the feature package or duplicate driver construction logic. |
122
+ | `@fluidframework/container-runtime` | `versionMarks/` resolver implementation and runtime hooks | Capture and locator resolution are driver-agnostic but tightly coupled to outbound batching, pending state, inbound processing, and the runtime lifecycle. They produce the sequence number consumed by the feature package; they do not perform historical loading. |
123
+
124
+ The new package should not import ODSP directly. Its contract remains capability-based so another
125
+ driver can implement point-in-time loading later. ODSP remains the only supported provider until
126
+ another driver can supply a recoverable snapshot at or before the target, all bridging ops, and an
127
+ equivalent lineage-safety guarantee.
128
+
129
+ ### Extraction dependency direction
130
+
131
+ The intended dependency flow is:
132
+
133
+ ```text
134
+ container-runtime version mark resolver
135
+ |
136
+ v
137
+ resolved sequence number
138
+ |
139
+ v
140
+ @fluidframework/point-in-time
141
+ | |
142
+ v v
143
+ container-loader capable driver factory
144
+ loadContainerPaused (currently ODSP only)
145
+ ```
146
+
147
+ This preserves the existing two-step contract: the runtime resolves an app-owned locator to a
148
+ sequence number, and the feature package materializes that sequence number. The feature package
149
+ must not make container-loader depend on ODSP or merge mark resolution into container loading.
150
+
151
+ ## ODSP implementation
152
+
153
+ ODSP resolves the closest recoverable driveItem version at or before the target. It then composes:
154
+
155
+ - storage from that file version;
156
+ - bounded delta storage from the live document;
157
+ - a shared `EpochTracker` across version discovery, snapshot reads, and live-op reads.
158
+
159
+ The shared epoch prevents replay across a disruptive file restore or other lineage change. A fresh non-persistent cache also prevents a historical snapshot from contaminating normal live-load caches.
160
+
161
+ Detailed ODSP version selection, lineage validation, and bounded replay are documented in [`odspVersionManager/DEV.md`](../../../../drivers/odsp-driver/src/odspVersionManager/DEV.md).
162
+
163
+ ## Failure and cancellation behavior
164
+
165
+ - A malformed target is rejected before URL resolution, capability inspection, or network work.
166
+ - A factory without the point-in-time capability produces a `UsageError`.
167
+ - No file version at or before the target produces a `UsageError` from the driver.
168
+ - A newer-than-target snapshot is rejected by `loadContainerPaused`.
169
+ - Missing or trimmed bridging ops fail the load rather than returning a container short of the target.
170
+ - An ODSP epoch mismatch fails non-retryably rather than combining a historical snapshot with ops from a different file lineage.
171
+ - An `AbortSignal` cancels replay, closes the partially loaded container, and rejects the load.
172
+
173
+ Like normal storage catch-up, retriable network failures may retry for an extended period. Callers that need bounded waiting should supply an `AbortSignal`.
174
+
175
+ ## Implementation map
176
+
177
+ | File | Responsibility |
178
+ | --- | --- |
179
+ | `loadContainerToSequenceNumber.ts` | Validates the target and driver capability, installs the adapter, and starts the paused load. |
180
+ | `pointInTimeServices.ts` | Defines the structural driver capability and adapts it to `IDocumentServiceFactory`. |
181
+ | `loadPaused.ts` | Loads read-only, replays to the exact target, pauses processing, disconnects, and handles cancellation. |
182
+ | `packages/drivers/odsp-driver/src/pointInTimeDriver/odspPointInTimeDocumentServiceFactory.ts` | Selects the ODSP base version and creates the historical service with shared epoch tracking. |
183
+ | `packages/drivers/odsp-driver/src/pointInTimeDriver/odspPointInTimeDocumentService.ts` | Recombines historical storage with bounded live delta storage and enforces storage-only behavior. |
184
+
185
+ ## Test map
186
+
187
+ Loader unit coverage:
188
+
189
+ - `src/test/loadContainerToSequenceNumber.spec.ts` covers target validation order and the capability error boundary.
190
+ - `src/test/pointInTimeServices.spec.ts` covers structural capability detection, target forwarding, argument forwarding, and rejecting container creation through the adapter.
191
+
192
+ ODSP unit coverage exercises base selection, no-base failures, version URL resolution, bounded delta reads, storage routing, storage-only behavior, and shared epoch/cache construction.
193
+
194
+ Real-service ODSP coverage lives under [`packages/test/test-end-to-end-tests/src/test/pointInTime/`](../../../../test/test-end-to-end-tests/src/test/pointInTime/):
195
+
196
+ - `loadToSequenceNumber.spec.ts` covers exact version boundaries, replay to a mid-stream target, and distinct historical targets.
197
+ - `loadSuccess.spec.ts` covers the earliest recoverable state, deterministic repeated loads, a frozen read-only result, and deep-history replay.
198
+ - `epochMismatch.spec.ts` and `loadFailure.spec.ts` cover lineage changes, unavailable ops, malformed targets, and cancellation during replay.
199
+ - `odspVersionApi.spec.ts` verifies the real-service version-history test setup.
200
+ - `pointInTimeTestUtils.ts` supplies the shared counter runtime, summarizer, version-snapshot helpers, and point-in-time load wrapper.
201
+
202
+ These suites validate loading after a target sequence number is already known. End-to-end creation and resolution of a version mark before loading is tracked as future work in the runtime version-mark DEV document.
203
+
204
+ ## Missing end-to-end coverage
205
+
206
+ The following loading behaviors are covered by unit or integration tests, inferred through a nearby scenario, or not covered at all, but do not yet have direct real-service end-to-end coverage:
207
+
208
+ 1. **Boundary targets:** Load sequence number `0` and the current live tip. Existing successful tests use a non-zero recoverable point and advance the document past the target before loading.
209
+ 2. **Complex runtime op representations:** Load across grouped, compressed, and chunked batches, including a large payload that genuinely uses the chunk-reassembly path. The current `SharedCounter` scenarios generate small operations. This is separate from delta-fetch page batching below.
210
+ 3. **Attachment and blob state:** Create an attachment or blob-backed handle after the base snapshot, load to a target after its attach op, and verify the historical container can read the expected content.
211
+ 4. **Cache and load isolation:** Run concurrent loads to different targets, then perform a normal live load with the same factory credentials. Verify each historical view remains pinned to its own target and no historical snapshot leaks through shared or persisted caches.
212
+ 5. **Cancellation entry and propagation:** Pass an already-aborted signal and verify no storage work begins. During replay, propagate cancellation through the delta-storage fetch rather than only rejecting the loader's wait promise, and verify retries and network reads stop promptly. The existing cancellation test aborts only after replay has begun and observes that storage retries can continue racing teardown.
213
+ 6. **Read-only enforcement:** Attempt a DDS mutation and call `connect()` on the returned historical container, then verify no op is submitted, no live connection is established, and the view does not advance. Existing coverage checks the exposed read-only and disconnected state without attempting either action.
214
+ 7. **Mid-load lineage change:** Trigger a file restore after base-version discovery but before or during live-op replay and verify the shared `EpochTracker` rejects the mixed lineage. Existing epoch tests restore before the point-in-time load starts.
215
+ 8. **Actual op-retention loss:** Target a range whose bridging ops were genuinely trimmed by the service. Existing deterministic failure coverage uses a target beyond the live tip as the available real-service approximation; it does not induce retention trimming.
216
+ 9. **Version-history pagination:** Create enough file versions to cross the ODSP `/versions` page boundary and verify base selection still finds the closest recoverable version.
217
+ 10. **Complete mark-to-load flow:** Obtain the target from `sealAndCaptureVersionMark()` and `resolve()` rather than reading `deltaManager.lastSequenceNumber`, then load and verify the marked state. The detailed marker scenarios are tracked in the runtime version-mark DEV document.
218
+ 11. **Targets inside atomic batches:** Exercise targets on the first, middle, and last sequence number of ordinary multi-op, grouped, compressed, and chunked batches. Runtime batch processing can advance `lastSequenceNumber` to the batch end atomically, so define whether a non-boundary target is rejected or normalized to the batch end; never return a container whose sequence number silently overshot the requested target. Version marks intentionally resolve to a batch's last op and should remain a safe input.
219
+ 12. **Protocol and system-op targets:** Load to sequence numbers occupied by attach, summarize/summary-ack, join/leave, and other non-runtime messages. Verify the loader still stops exactly at the requested global sequence number even when the application state does not change at that op.
220
+ 13. **Cleanup and error preservation:** Abort, close, or fail delta replay while completion is racing. Verify listeners are removed, the partial container is closed exactly once, and cleanup does not replace the original cancellation or op-availability error. Today `disconnect()` after the container has already closed can surface `"The Container is closed and cannot be disconnected"` and mask the real failure.
221
+ 14. **Numeric limits:** Reject non-safe sequence numbers, not only negative and fractional values. Driver implementations commonly compute `target + 1` for an exclusive upper bound, so `Number.MAX_SAFE_INTEGER` and nearby values need an explicit contract that cannot lose precision.
222
+ 15. **Nested routes and historical code:** Load requests with a data-store path and code hint, and load a target whose document state requires objects or schema introduced at a different code proposal. Verify request routing and code loading remain deterministic for the historical view.
223
+ 16. **Resource cleanup on construction failure:** Fail URL resolution, base selection, storage connection, code loading, and delta-storage connection after progressively more resources have been created. Verify every partially created service/container is disposed without masking the initiating error.
224
+ 17. **Sequenced but not yet persisted ops:** Start the historical load immediately after a live client observes the target sequence number, before the ordering service has flushed it to ODSP delta storage. Cover eventual persistence, persistence beyond the current 30-second missing-op window, caller cancellation while waiting, and a permanently unavailable target. Define whether the public API waits, returns a retryable availability result, or requires the host to coordinate an explicit flush.
225
+ 18. **Parallel fetch page boundaries:** Vary `opsBatchSize` and `concurrentOpsBatches`; place the target at the start, middle, and exclusive end of a page; and complete requests out of order. Cover partial and oversized responses, a final short page, and cancellation with buffered later pages. Verify an unbounded DeltaManager request is converted to the known `target + 1` boundary, only contiguous ops through the target are dispatched, and no request crosses that boundary.
226
+ 19. **Snapshot/cache/storage source transitions:** Materialize a target whose bridge begins in bundled snapshot ops, crosses one or more persisted ops-cache batches, and finishes in storage. Cover a partial dirty `OpsCache` batch that has not yet been flushed, a timer/dispose flush racing the read, a cache gap that permanently disables further cache reads, and duplicate boundary ops arriving from multiple sources.
227
+ 20. **Parallel failure isolation and backpressure:** Let later pages complete and buffer while an earlier page retries or fails. Verify no buffered page is delivered across the gap, the first terminal error reaches the stream exactly once, late request completions cannot change the outcome, and cancellation releases buffered results. Measure memory for deep-history loads with a slow consumer and multiple large pages so `results` and the output `Queue` cannot grow without an explicit bound.
228
+ 21. **Batching configuration limits:** Exercise zero, negative, fractional, and excessively large `opsBatchSize` / `concurrentOpsBatches` values. Define validation and safe upper bounds before `ParallelRequests.run()` assertions or excessive speculative work can turn host configuration into a container-closing failure or memory spike.
229
+
230
+ ## Current limitations and future work
231
+
232
+ - ODSP is currently the only driver that implements the capability.
233
+ - Loading depends on both a recoverable snapshot at or before the target and retention of every bridging op.
234
+ - The returned container is intentionally frozen and does not support normal collaborative APIs.
235
+ - Real-service tests require ODSP credentials and cannot run against the local test server because it does not emulate driveItem version history or storage epochs.
236
+ - Targets newer than the live tip currently fail only after the delta stack's retry window. A future driver availability contract could distinguish a future/not-yet-sequenced target from a permanently unavailable range and fail or wait according to explicit caller policy.
237
+ - A sequence number observed on PUSH is not necessarily available from durable delta storage yet. The current storage-only historical service cannot use PUSH's `get_ops` path, so near-head loads may wait for persistence or fail even though a live client has already processed the target.