@fluidframework/container-runtime 2.101.1 → 2.103.0

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 (229) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/container-runtime.test-files.tar +0 -0
  3. package/dist/batchTracker.d.ts +1 -1
  4. package/dist/batchTracker.d.ts.map +1 -1
  5. package/dist/batchTracker.js +1 -1
  6. package/dist/batchTracker.js.map +1 -1
  7. package/dist/blobManager/blobManagerSnapSum.d.ts +2 -2
  8. package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  9. package/dist/blobManager/blobManagerSnapSum.js.map +1 -1
  10. package/dist/connectionTelemetry.js.map +1 -1
  11. package/dist/containerRuntime.d.ts +16 -5
  12. package/dist/containerRuntime.d.ts.map +1 -1
  13. package/dist/containerRuntime.js +160 -18
  14. package/dist/containerRuntime.js.map +1 -1
  15. package/dist/dataStore.d.ts +2 -2
  16. package/dist/dataStore.d.ts.map +1 -1
  17. package/dist/dataStore.js.map +1 -1
  18. package/dist/dataStoreContext.d.ts.map +1 -1
  19. package/dist/dataStoreContext.js +1 -4
  20. package/dist/dataStoreContext.js.map +1 -1
  21. package/dist/dataStoreContexts.d.ts.map +1 -1
  22. package/dist/dataStoreContexts.js.map +1 -1
  23. package/dist/deltaScheduler.d.ts +2 -2
  24. package/dist/deltaScheduler.d.ts.map +1 -1
  25. package/dist/deltaScheduler.js.map +1 -1
  26. package/dist/gc/garbageCollection.d.ts +2 -2
  27. package/dist/gc/garbageCollection.d.ts.map +1 -1
  28. package/dist/gc/garbageCollection.js +12 -5
  29. package/dist/gc/garbageCollection.js.map +1 -1
  30. package/dist/gc/gcDefinitions.d.ts +3 -3
  31. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  32. package/dist/gc/gcDefinitions.js.map +1 -1
  33. package/dist/gc/gcTelemetry.d.ts +3 -3
  34. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  35. package/dist/gc/gcTelemetry.js.map +1 -1
  36. package/dist/inboundBatchAggregator.d.ts +2 -2
  37. package/dist/inboundBatchAggregator.d.ts.map +1 -1
  38. package/dist/inboundBatchAggregator.js.map +1 -1
  39. package/dist/opLifecycle/duplicateBatchDetector.d.ts +39 -3
  40. package/dist/opLifecycle/duplicateBatchDetector.d.ts.map +1 -1
  41. package/dist/opLifecycle/duplicateBatchDetector.js +57 -15
  42. package/dist/opLifecycle/duplicateBatchDetector.js.map +1 -1
  43. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  44. package/dist/opLifecycle/opCompressor.js.map +1 -1
  45. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  46. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  47. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  48. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  49. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  50. package/dist/opLifecycle/opSplitter.js.map +1 -1
  51. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  52. package/dist/opLifecycle/outbox.js.map +1 -1
  53. package/dist/packageVersion.d.ts +1 -1
  54. package/dist/packageVersion.js +1 -1
  55. package/dist/packageVersion.js.map +1 -1
  56. package/dist/pendingStateManager.d.ts +48 -1
  57. package/dist/pendingStateManager.d.ts.map +1 -1
  58. package/dist/pendingStateManager.js +54 -1
  59. package/dist/pendingStateManager.js.map +1 -1
  60. package/dist/runtimeLayerCompatState.d.ts +2 -2
  61. package/dist/signalTelemetryProcessing.d.ts +2 -2
  62. package/dist/signalTelemetryProcessing.d.ts.map +1 -1
  63. package/dist/signalTelemetryProcessing.js.map +1 -1
  64. package/dist/summary/documentSchema.d.ts +2 -2
  65. package/dist/summary/documentSchema.d.ts.map +1 -1
  66. package/dist/summary/documentSchema.js +35 -3
  67. package/dist/summary/documentSchema.js.map +1 -1
  68. package/dist/summary/orderedClientElection.d.ts +2 -2
  69. package/dist/summary/orderedClientElection.d.ts.map +1 -1
  70. package/dist/summary/orderedClientElection.js.map +1 -1
  71. package/dist/summary/summarizerClientElection.d.ts +2 -2
  72. package/dist/summary/summarizerClientElection.d.ts.map +1 -1
  73. package/dist/summary/summarizerClientElection.js.map +1 -1
  74. package/dist/summary/summarizerNode/summarizerNode.d.ts +3 -3
  75. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  76. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  77. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts +2 -2
  78. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  79. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  80. package/dist/summary/summarizerTypes.d.ts +3 -3
  81. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  82. package/dist/summary/summarizerTypes.js.map +1 -1
  83. package/dist/summary/summaryCollection.d.ts.map +1 -1
  84. package/dist/summary/summaryCollection.js.map +1 -1
  85. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.d.ts +2 -2
  86. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -1
  87. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -1
  88. package/dist/summary/summaryDelayLoadedModule/summarizer.d.ts +2 -2
  89. package/dist/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
  90. package/dist/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
  91. package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.d.ts +2 -2
  92. package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.d.ts.map +1 -1
  93. package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.js.map +1 -1
  94. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.d.ts +2 -2
  95. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.d.ts.map +1 -1
  96. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -1
  97. package/dist/summary/summaryManager.d.ts.map +1 -1
  98. package/dist/summary/summaryManager.js.map +1 -1
  99. package/lib/batchTracker.d.ts +1 -1
  100. package/lib/batchTracker.d.ts.map +1 -1
  101. package/lib/batchTracker.js +1 -1
  102. package/lib/batchTracker.js.map +1 -1
  103. package/lib/blobManager/blobManagerSnapSum.d.ts +2 -2
  104. package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  105. package/lib/blobManager/blobManagerSnapSum.js.map +1 -1
  106. package/lib/connectionTelemetry.js.map +1 -1
  107. package/lib/containerRuntime.d.ts +16 -5
  108. package/lib/containerRuntime.d.ts.map +1 -1
  109. package/lib/containerRuntime.js +160 -18
  110. package/lib/containerRuntime.js.map +1 -1
  111. package/lib/dataStore.d.ts +2 -2
  112. package/lib/dataStore.d.ts.map +1 -1
  113. package/lib/dataStore.js.map +1 -1
  114. package/lib/dataStoreContext.d.ts.map +1 -1
  115. package/lib/dataStoreContext.js +2 -5
  116. package/lib/dataStoreContext.js.map +1 -1
  117. package/lib/dataStoreContexts.d.ts.map +1 -1
  118. package/lib/dataStoreContexts.js.map +1 -1
  119. package/lib/deltaScheduler.d.ts +2 -2
  120. package/lib/deltaScheduler.d.ts.map +1 -1
  121. package/lib/deltaScheduler.js +1 -1
  122. package/lib/deltaScheduler.js.map +1 -1
  123. package/lib/gc/garbageCollection.d.ts +2 -2
  124. package/lib/gc/garbageCollection.d.ts.map +1 -1
  125. package/lib/gc/garbageCollection.js +12 -5
  126. package/lib/gc/garbageCollection.js.map +1 -1
  127. package/lib/gc/gcDefinitions.d.ts +3 -3
  128. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  129. package/lib/gc/gcDefinitions.js.map +1 -1
  130. package/lib/gc/gcTelemetry.d.ts +3 -3
  131. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  132. package/lib/gc/gcTelemetry.js.map +1 -1
  133. package/lib/inboundBatchAggregator.d.ts +2 -2
  134. package/lib/inboundBatchAggregator.d.ts.map +1 -1
  135. package/lib/inboundBatchAggregator.js.map +1 -1
  136. package/lib/opLifecycle/duplicateBatchDetector.d.ts +39 -3
  137. package/lib/opLifecycle/duplicateBatchDetector.d.ts.map +1 -1
  138. package/lib/opLifecycle/duplicateBatchDetector.js +57 -15
  139. package/lib/opLifecycle/duplicateBatchDetector.js.map +1 -1
  140. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  141. package/lib/opLifecycle/opCompressor.js.map +1 -1
  142. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  143. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  144. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  145. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  146. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  147. package/lib/opLifecycle/opSplitter.js.map +1 -1
  148. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  149. package/lib/opLifecycle/outbox.js.map +1 -1
  150. package/lib/packageVersion.d.ts +1 -1
  151. package/lib/packageVersion.js +1 -1
  152. package/lib/packageVersion.js.map +1 -1
  153. package/lib/pendingStateManager.d.ts +48 -1
  154. package/lib/pendingStateManager.d.ts.map +1 -1
  155. package/lib/pendingStateManager.js +54 -1
  156. package/lib/pendingStateManager.js.map +1 -1
  157. package/lib/runtimeLayerCompatState.d.ts +2 -2
  158. package/lib/signalTelemetryProcessing.d.ts +2 -2
  159. package/lib/signalTelemetryProcessing.d.ts.map +1 -1
  160. package/lib/signalTelemetryProcessing.js.map +1 -1
  161. package/lib/summary/documentSchema.d.ts +2 -2
  162. package/lib/summary/documentSchema.d.ts.map +1 -1
  163. package/lib/summary/documentSchema.js +35 -3
  164. package/lib/summary/documentSchema.js.map +1 -1
  165. package/lib/summary/orderedClientElection.d.ts +2 -2
  166. package/lib/summary/orderedClientElection.d.ts.map +1 -1
  167. package/lib/summary/orderedClientElection.js.map +1 -1
  168. package/lib/summary/summarizerClientElection.d.ts +2 -2
  169. package/lib/summary/summarizerClientElection.d.ts.map +1 -1
  170. package/lib/summary/summarizerClientElection.js.map +1 -1
  171. package/lib/summary/summarizerNode/summarizerNode.d.ts +3 -3
  172. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  173. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  174. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts +2 -2
  175. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  176. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  177. package/lib/summary/summarizerTypes.d.ts +3 -3
  178. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  179. package/lib/summary/summarizerTypes.js.map +1 -1
  180. package/lib/summary/summaryCollection.d.ts.map +1 -1
  181. package/lib/summary/summaryCollection.js.map +1 -1
  182. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.d.ts +2 -2
  183. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -1
  184. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -1
  185. package/lib/summary/summaryDelayLoadedModule/summarizer.d.ts +2 -2
  186. package/lib/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
  187. package/lib/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
  188. package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.d.ts +2 -2
  189. package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.d.ts.map +1 -1
  190. package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.js.map +1 -1
  191. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.d.ts +2 -2
  192. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.d.ts.map +1 -1
  193. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -1
  194. package/lib/summary/summaryManager.d.ts.map +1 -1
  195. package/lib/summary/summaryManager.js.map +1 -1
  196. package/package.json +18 -18
  197. package/src/batchTracker.ts +3 -3
  198. package/src/blobManager/blobManagerSnapSum.ts +2 -2
  199. package/src/connectionTelemetry.ts +3 -3
  200. package/src/containerRuntime.ts +188 -25
  201. package/src/dataStore.ts +3 -3
  202. package/src/dataStoreContext.ts +2 -4
  203. package/src/dataStoreContexts.ts +2 -2
  204. package/src/deltaScheduler.ts +2 -5
  205. package/src/gc/garbageCollection.ts +16 -9
  206. package/src/gc/gcDefinitions.ts +3 -3
  207. package/src/gc/gcTelemetry.ts +3 -3
  208. package/src/inboundBatchAggregator.ts +2 -2
  209. package/src/opLifecycle/duplicateBatchDetector.ts +103 -23
  210. package/src/opLifecycle/opCompressor.ts +2 -2
  211. package/src/opLifecycle/opDecompressor.ts +2 -2
  212. package/src/opLifecycle/opGroupingManager.ts +2 -2
  213. package/src/opLifecycle/opSplitter.ts +2 -2
  214. package/src/opLifecycle/outbox.ts +2 -2
  215. package/src/packageVersion.ts +1 -1
  216. package/src/pendingStateManager.ts +80 -2
  217. package/src/signalTelemetryProcessing.ts +2 -2
  218. package/src/summary/documentSchema.ts +58 -5
  219. package/src/summary/orderedClientElection.ts +3 -3
  220. package/src/summary/summarizerClientElection.ts +2 -2
  221. package/src/summary/summarizerNode/summarizerNode.ts +3 -3
  222. package/src/summary/summarizerNode/summarizerNodeUtils.ts +2 -2
  223. package/src/summary/summarizerTypes.ts +3 -3
  224. package/src/summary/summaryCollection.ts +2 -2
  225. package/src/summary/summaryDelayLoadedModule/runningSummarizer.ts +2 -4
  226. package/src/summary/summaryDelayLoadedModule/summarizer.ts +3 -3
  227. package/src/summary/summaryDelayLoadedModule/summarizerHeuristics.ts +2 -2
  228. package/src/summary/summaryDelayLoadedModule/summaryGenerator.ts +2 -2
  229. package/src/summary/summaryManager.ts +2 -2
@@ -9,6 +9,41 @@ import type { ITelemetryContext } from "@fluidframework/runtime-definitions/inte
9
9
  import { getEffectiveBatchId } from "./batchManager.js";
10
10
  import type { BatchStartInfo } from "./remoteMessageProcessor.js";
11
11
 
12
+ /**
13
+ * Identifying info for a previously-recorded batch that we can include in DuplicateBatch telemetry
14
+ * to help diagnose where the duplicate came from.
15
+ *
16
+ * @remarks `batchIdExplicit` distinguishes the two main duplicate-source scenarios:
17
+ * - `true`: the batchId was stamped on the wire as explicit metadata, indicating a resubmit (PendingStateManager.replayPendingStates).
18
+ * - `false`: the batchId was derived from the wire `clientId` and `batchStartCsn`, indicating a fresh, non-resubmit batch.
19
+ */
20
+ export interface RecordedBatchInfo {
21
+ /**
22
+ * Wire clientId on the message that started the batch (NOT necessarily the `originalClientId`
23
+ * encoded in the batchId for resubmits).
24
+ */
25
+ readonly clientId: string;
26
+ /**
27
+ * Wire client sequence number at the start of the batch.
28
+ */
29
+ readonly batchStartCsn: number;
30
+ /**
31
+ * True if the batchId came from explicit metadata on the wire (i.e. a resubmit),
32
+ * false if it was derived from clientId + batchStartCsn (i.e. a fresh submit).
33
+ */
34
+ readonly batchIdExplicit: boolean;
35
+ }
36
+
37
+ interface RecordedBatch {
38
+ readonly batchId: string;
39
+ /**
40
+ * Identifying info for the batch as observed at runtime.
41
+ * `undefined` if the batch was loaded from a summary snapshot (where only the
42
+ * `[seqNum, batchId]` pair is persisted).
43
+ */
44
+ readonly info: RecordedBatchInfo | undefined;
45
+ }
46
+
12
47
  /**
13
48
  * Detects duplicate batches that can arise from the "parallel fork" scenario:
14
49
  * Container 1 is serialized, and Containers 2 and 3 are rehydrated from that state.
@@ -24,9 +59,21 @@ export class DuplicateBatchDetector {
24
59
  private readonly seqNumByBatchId = new Map<string, number>();
25
60
 
26
61
  /**
27
- * We map from sequenceNumber to batchId to find which ones we can stop tracking as MSN advances
62
+ * Map from sequenceNumber to the recorded batch info. Used to clear out old entries as MSN
63
+ * advances, and to report identifying info about the original occurrence when a duplicate
64
+ * is detected.
65
+ */
66
+ private readonly batchesBySeqNum = new Map<number, RecordedBatch>();
67
+
68
+ /**
69
+ * Number of inbound batches processed since the last summary. Reset by getRecentBatchInfoForSummary.
70
+ */
71
+ private processedBatchCount = 0;
72
+
73
+ /**
74
+ * Largest tracked-batch count observed since the last summary. Reset by getRecentBatchInfoForSummary.
28
75
  */
29
- private readonly batchIdsBySeqNum = new Map<number, string>();
76
+ private peakTrackedBatchCount = 0;
30
77
 
31
78
  /**
32
79
  * Initialize from snapshot data if provided - otherwise initialize empty
@@ -34,22 +81,31 @@ export class DuplicateBatchDetector {
34
81
  constructor(batchIdsFromSnapshot: [number, string][] | undefined) {
35
82
  if (batchIdsFromSnapshot) {
36
83
  for (const [seqNum, batchId] of batchIdsFromSnapshot) {
37
- this.batchIdsBySeqNum.set(seqNum, batchId);
84
+ // Entries loaded from a snapshot don't carry the original clientId/csn/explicit-bit;
85
+ // we record them with `info: undefined` so duplicate telemetry can indicate that.
86
+ this.batchesBySeqNum.set(seqNum, { batchId, info: undefined });
38
87
  this.seqNumByBatchId.set(batchId, seqNum);
39
88
  }
89
+ this.peakTrackedBatchCount = this.batchesBySeqNum.size;
40
90
  }
41
91
  }
42
92
 
43
93
  /**
44
94
  * Records this batch's batchId, and checks if it's a duplicate of a batch we've already seen.
45
- * If it's a duplicate, also return the sequence number of the other batch for logging.
95
+ * If it's a duplicate, also return the sequence number of the other batch (and identifying info,
96
+ * if the other batch was seen during this container session rather than loaded from snapshot) for logging.
46
97
  *
47
98
  * @remarks We also use the minimumSequenceNumber to clear out old batchIds that are no longer at risk for duplicates.
48
99
  */
49
- public processInboundBatch(
50
- batchStart: BatchStartInfo,
51
- ): { duplicate: true; otherSequenceNumber: number } | { duplicate: false } {
100
+ public processInboundBatch(batchStart: BatchStartInfo):
101
+ | {
102
+ duplicate: true;
103
+ otherSequenceNumber: number;
104
+ otherBatchInfo: RecordedBatchInfo | undefined;
105
+ }
106
+ | { duplicate: false } {
52
107
  const { sequenceNumber, minimumSequenceNumber } = batchStart.keyMessage;
108
+ this.processedBatchCount++;
53
109
 
54
110
  // Glance at this batch's MSN. Any batchIds we're tracking with a lower sequence number are now safe to forget.
55
111
  // Why? Because any other client holding the same batch locally would have seen the earlier batch and closed before submitting its duplicate.
@@ -64,22 +120,37 @@ export class DuplicateBatchDetector {
64
120
  // O(1) duplicate check + get otherSequenceNumber in one lookup
65
121
  const otherSequenceNumber = this.seqNumByBatchId.get(batchId);
66
122
  if (otherSequenceNumber !== undefined) {
123
+ const other = this.batchesBySeqNum.get(otherSequenceNumber);
67
124
  assert(
68
- this.batchIdsBySeqNum.get(otherSequenceNumber) === batchId,
125
+ other?.batchId === batchId,
69
126
  0xce0 /* batchIdToSeqNum and seqNumToBatchId should be in sync for duplicate */,
70
127
  );
71
- return { duplicate: true, otherSequenceNumber };
128
+ return {
129
+ duplicate: true,
130
+ otherSequenceNumber,
131
+ otherBatchInfo: other.info,
132
+ };
72
133
  }
73
134
 
74
135
  // Now we know it's not a duplicate, so add it to the tracked batchIds and return.
75
136
  assert(
76
- !this.batchIdsBySeqNum.has(sequenceNumber),
137
+ !this.batchesBySeqNum.has(sequenceNumber),
77
138
  0xce1 /* seqNumToBatchId and batchIdToSeqNum should be in sync */,
78
139
  );
79
140
 
80
- // Add new batch
81
- this.batchIdsBySeqNum.set(sequenceNumber, batchId);
141
+ // Add new batch. Record identifying info so we can report it if a future duplicate matches us.
142
+ const info: RecordedBatchInfo | undefined = {
143
+ clientId: batchStart.clientId,
144
+ batchStartCsn: batchStart.batchStartCsn,
145
+ // True iff the wire carried explicit batchId metadata (resubmit path).
146
+ // False indicates the batchId was derived from clientId + batchStartCsn (fresh submit).
147
+ batchIdExplicit: batchStart.batchId !== undefined,
148
+ };
149
+ this.batchesBySeqNum.set(sequenceNumber, { batchId, info });
82
150
  this.seqNumByBatchId.set(batchId, sequenceNumber);
151
+ if (this.batchesBySeqNum.size > this.peakTrackedBatchCount) {
152
+ this.peakTrackedBatchCount = this.batchesBySeqNum.size;
153
+ }
83
154
 
84
155
  return { duplicate: false };
85
156
  }
@@ -89,10 +160,10 @@ export class DuplicateBatchDetector {
89
160
  * since the batch start has been processed by all clients, and local batches are deduped and the forked client would close.
90
161
  */
91
162
  private clearOldBatchIds(msn: number): void {
92
- for (const [sequenceNumber, batchId] of this.batchIdsBySeqNum) {
163
+ for (const [sequenceNumber, recorded] of this.batchesBySeqNum) {
93
164
  if (sequenceNumber < msn) {
94
- this.batchIdsBySeqNum.delete(sequenceNumber);
95
- this.seqNumByBatchId.delete(batchId);
165
+ this.batchesBySeqNum.delete(sequenceNumber);
166
+ this.seqNumByBatchId.delete(recorded.batchId);
96
167
  } else {
97
168
  break;
98
169
  }
@@ -108,16 +179,25 @@ export class DuplicateBatchDetector {
108
179
  public getRecentBatchInfoForSummary(
109
180
  telemetryContext?: ITelemetryContext,
110
181
  ): [number, string][] | undefined {
111
- if (this.batchIdsBySeqNum.size === 0) {
112
- return undefined;
182
+ if (telemetryContext !== undefined) {
183
+ const prefix = "fluid_DuplicateBatchDetector_";
184
+ telemetryContext.set(prefix, "recentBatchCount", this.batchesBySeqNum.size);
185
+ telemetryContext.set(prefix, "peakRecentBatchCount", this.peakTrackedBatchCount);
186
+ telemetryContext.set(prefix, "processedBatchCount", this.processedBatchCount);
113
187
  }
114
188
 
115
- telemetryContext?.set(
116
- "fluid_DuplicateBatchDetector_",
117
- "recentBatchCount",
118
- this.batchIdsBySeqNum.size,
119
- );
189
+ // Reset per-window perf counters so each summary covers only the activity since the
190
+ // previous one. Peak resets to the current size (the floor for the next window).
191
+ this.processedBatchCount = 0;
192
+ this.peakTrackedBatchCount = this.batchesBySeqNum.size;
193
+
194
+ if (this.batchesBySeqNum.size === 0) {
195
+ return undefined;
196
+ }
120
197
 
121
- return [...this.batchIdsBySeqNum.entries()];
198
+ return [...this.batchesBySeqNum.entries()].map(([seqNum, recorded]) => [
199
+ seqNum,
200
+ recorded.batchId,
201
+ ]);
122
202
  }
123
203
  }
@@ -9,7 +9,7 @@ import { assert } from "@fluidframework/core-utils/internal";
9
9
  import {
10
10
  DataProcessingError,
11
11
  createChildLogger,
12
- type ITelemetryLoggerExt,
12
+ type TelemetryLoggerExt,
13
13
  } from "@fluidframework/telemetry-utils/internal";
14
14
  import { compress } from "lz4js";
15
15
 
@@ -25,7 +25,7 @@ import { estimateSocketSize } from "./outbox.js";
25
25
  * Use opGroupingManager to group a batch into a singleton batch suitable for compression.
26
26
  */
27
27
  export class OpCompressor {
28
- private readonly logger: ITelemetryLoggerExt;
28
+ private readonly logger: TelemetryLoggerExt;
29
29
 
30
30
  constructor(logger: ITelemetryBaseLogger) {
31
31
  this.logger = createChildLogger({ logger, namespace: "OpCompressor" });
@@ -9,7 +9,7 @@ import { assert } from "@fluidframework/core-utils/internal";
9
9
  import type { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
10
10
  import {
11
11
  createChildLogger,
12
- type ITelemetryLoggerExt,
12
+ type TelemetryLoggerExt,
13
13
  } from "@fluidframework/telemetry-utils/internal";
14
14
  import { decompress } from "lz4js";
15
15
 
@@ -40,7 +40,7 @@ export class OpDecompressor {
40
40
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
41
  private rootMessageContents: any | undefined;
42
42
  private processedCount = 0;
43
- private readonly logger: ITelemetryLoggerExt;
43
+ private readonly logger: TelemetryLoggerExt;
44
44
 
45
45
  constructor(logger: ITelemetryBaseLogger) {
46
46
  this.logger = createChildLogger({ logger, namespace: "OpDecompressor" });
@@ -8,7 +8,7 @@ import { assert } from "@fluidframework/core-utils/internal";
8
8
  import type { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
9
9
  import {
10
10
  createChildLogger,
11
- type ITelemetryLoggerExt,
11
+ type TelemetryLoggerExt,
12
12
  } from "@fluidframework/telemetry-utils/internal";
13
13
 
14
14
  import type {
@@ -65,7 +65,7 @@ export interface EmptyGroupedBatch {
65
65
 
66
66
  export class OpGroupingManager {
67
67
  static readonly groupedBatchOp = "groupedBatch";
68
- private readonly logger: ITelemetryLoggerExt;
68
+ private readonly logger: TelemetryLoggerExt;
69
69
 
70
70
  constructor(
71
71
  private readonly config: OpGroupingManagerConfig,
@@ -11,7 +11,7 @@ import {
11
11
  DataCorruptionError,
12
12
  createChildLogger,
13
13
  extractSafePropertiesFromMessage,
14
- type ITelemetryLoggerExt,
14
+ type TelemetryLoggerExt,
15
15
  } from "@fluidframework/telemetry-utils/internal";
16
16
 
17
17
  import {
@@ -45,7 +45,7 @@ function isChunkedContents(contents: unknown): contents is IChunkedContents {
45
45
  export class OpSplitter {
46
46
  // Local copy of incomplete received chunks.
47
47
  private readonly chunkMap: Map<string, string[]>;
48
- private readonly logger: ITelemetryLoggerExt;
48
+ private readonly logger: TelemetryLoggerExt;
49
49
 
50
50
  constructor(
51
51
  chunks: [string, string[]][],
@@ -14,7 +14,7 @@ import {
14
14
  UsageError,
15
15
  createChildLogger,
16
16
  type IFluidErrorBase,
17
- type ITelemetryLoggerExt,
17
+ type TelemetryLoggerExt,
18
18
  } from "@fluidframework/telemetry-utils/internal";
19
19
 
20
20
  import type { ICompressionRuntimeOptions } from "../compressionDefinitions.js";
@@ -194,7 +194,7 @@ export const estimateSocketSize = (batch: OutboundBatch): number => {
194
194
  * to support slight variation in semantics for each batch (e.g. support for rebasing or grouping).
195
195
  */
196
196
  export class Outbox {
197
- private readonly logger: ITelemetryLoggerExt;
197
+ private readonly logger: TelemetryLoggerExt;
198
198
  private readonly mainBatch: BatchManager;
199
199
  private readonly blobAttachBatch: BatchManager;
200
200
  private batchRebasesToReport = 5;
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.101.1";
9
+ export const pkgVersion = "2.103.0";
@@ -6,7 +6,7 @@
6
6
  import type { IDisposable, ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
7
7
  import { assert, Lazy } from "@fluidframework/core-utils/internal";
8
8
  import {
9
- type ITelemetryLoggerExt,
9
+ type TelemetryLoggerExt,
10
10
  DataProcessingError,
11
11
  LoggingError,
12
12
  extractSafePropertiesFromMessage,
@@ -142,6 +142,25 @@ export interface IRuntimeStateHandler {
142
142
  isAttached: () => boolean;
143
143
  }
144
144
 
145
+ /**
146
+ * Optional hooks invoked at the close of the stashed-op apply lifecycle.
147
+ *
148
+ * `onAfterStashedOpsApplied` fires synchronously the first time
149
+ * `initialMessages` drains during `applyStashedOpsAt`, immediately after
150
+ * `isApplyingStashedOps` flips to `false`. Fires at most once per PSM
151
+ * lifetime. If an apply throws, control never reaches the close site and
152
+ * the hook is not invoked — load is fatal in that case.
153
+ *
154
+ * No corresponding open hook is exposed. The apply window is opened eagerly
155
+ * in the PSM constructor, but at that point `ContainerRuntime` has not yet
156
+ * wired up the downstream observers (`channelCollection` is undefined), so a
157
+ * fanout fired from the constructor would be a no-op. Consumers that care
158
+ * about the open transition can read `isApplyingStashedOps` directly.
159
+ */
160
+ export interface PendingStateManagerHooks {
161
+ onAfterStashedOpsApplied?: () => void;
162
+ }
163
+
145
164
  function isEmptyBatchPendingMessage(message: IPendingMessageFromStash): boolean {
146
165
  const content = JSON.parse(message.content) as Partial<EmptyGroupedBatch>;
147
166
  return content.type === "groupedBatch" && content.contents?.length === 0;
@@ -366,17 +385,62 @@ export class PendingStateManager implements IDisposable {
366
385
  };
367
386
  }
368
387
 
369
- private readonly logger: ITelemetryLoggerExt;
388
+ private readonly logger: TelemetryLoggerExt;
389
+
390
+ /**
391
+ * One-way lifecycle of the stashed-op apply window: `ended` → `applying` → `ended`.
392
+ *
393
+ * Default is `ended` — no stashed state means there's nothing to apply, so the window is
394
+ * closed before it ever opens. `ended` → `applying` happens in the constructor when
395
+ * stashed state is present (i.e. `initialMessages` is non-empty at construction). The
396
+ * open is eager so the runtime is readonly from the moment any DDS could possibly
397
+ * observe it. `applying` → `ended` happens the first time {@link applyStashedOpsAt}
398
+ * drains `initialMessages`. After that, local edits are safe — they queue FIFO behind
399
+ * any remaining `pendingMessages`, preserving server-side ordering.
400
+ *
401
+ * The window never reopens. After the close, subsequent `applyStashedOpsAt` calls (e.g.
402
+ * from late `notifyOpReplay`s) early-return at the empty guard.
403
+ *
404
+ * `pendingMessages` state is intentionally NOT part of the close condition. Those
405
+ * entries are drained transparently by {@link replayPendingStates} on connect via
406
+ * resubmit (each pop is matched by a fresh push), so the queue size is conserved across
407
+ * resubmit and DDSes can't distinguish a resubmit-ack from a normal ack. Holding the
408
+ * window open through resubmit would force resubmits to run while the runtime is
409
+ * readonly, which is the inverse of what we want ("never resubmit during apply stashed
410
+ * ops").
411
+ *
412
+ * An apply error leaves the lifecycle at `applying` because the queue isn't drained.
413
+ * That's fine: an error here is fatal for the load, the container is unusable, and
414
+ * there's no state to restore.
415
+ */
416
+ private _applyLifecycle: "applying" | "ended" = "ended";
417
+ public get isApplyingStashedOps(): boolean {
418
+ return this._applyLifecycle === "applying";
419
+ }
420
+
421
+ private readonly hooks: PendingStateManagerHooks;
370
422
 
371
423
  constructor(
372
424
  private readonly stateHandler: IRuntimeStateHandler,
373
425
  stashedLocalState: IPendingLocalState | undefined,
374
426
  logger: ITelemetryBaseLogger,
427
+ hooks: PendingStateManagerHooks = {},
375
428
  ) {
376
429
  this.logger = createChildLogger({ logger });
430
+ this.hooks = hooks;
377
431
  if (stashedLocalState?.pendingStates) {
378
432
  this.initialMessages.push(...stashedLocalState.pendingStates);
379
433
  }
434
+ // Open the apply window eagerly if there is any stashed work. The
435
+ // runtime is readonly while `isApplyingStashedOps` is true (see
436
+ // `ContainerRuntime.isReadOnly`); compliant DDSes consult `readOnly`
437
+ // at realize time and skip submits. No fanout fires here — downstream
438
+ // observers (`channelCollection`) are not yet constructed at this
439
+ // point in the runtime constructor, and the first real readonly read
440
+ // happens after the constructor returns.
441
+ if (!this.initialMessages.isEmpty()) {
442
+ this._applyLifecycle = "applying";
443
+ }
380
444
  }
381
445
 
382
446
  public get disposed(): boolean {
@@ -451,6 +515,10 @@ export class PendingStateManager implements IDisposable {
451
515
  * @param seqNum - Sequence number at which to apply ops. Will apply all ops if seqNum is undefined.
452
516
  */
453
517
  public async applyStashedOpsAt(seqNum?: number): Promise<void> {
518
+ if (this.initialMessages.isEmpty()) {
519
+ return;
520
+ }
521
+
454
522
  // apply stashed ops at sequence number
455
523
  while (!this.initialMessages.isEmpty()) {
456
524
  if (seqNum !== undefined) {
@@ -497,6 +565,16 @@ export class PendingStateManager implements IDisposable {
497
565
  throw DataProcessingError.wrapIfUnrecognized(error, "applyStashedOp", nextMessage);
498
566
  }
499
567
  }
568
+
569
+ // The apply window was opened eagerly in the constructor when there
570
+ // was any stashed work. We close it on full successful drain only.
571
+ // If an apply throws above, control never reaches here and the
572
+ // lifecycle stays at "applying" — the load is fatal so there's no
573
+ // recoverable state.
574
+ if (this._applyLifecycle === "applying" && this.initialMessages.isEmpty()) {
575
+ this._applyLifecycle = "ended";
576
+ this.hooks.onAfterStashedOpsApplied?.();
577
+ }
500
578
  }
501
579
 
502
580
  /**
@@ -5,7 +5,7 @@
5
5
 
6
6
  import type { ISignalEnvelope } from "@fluidframework/core-interfaces/internal";
7
7
  import type {
8
- ITelemetryLoggerExt,
8
+ TelemetryLoggerExt,
9
9
  TelemetryEventPropertyTypeExt,
10
10
  } from "@fluidframework/telemetry-utils/internal";
11
11
 
@@ -101,7 +101,7 @@ export class SignalTelemetryManager {
101
101
  */
102
102
  public trackReceivedSignal(
103
103
  envelope: ISignalEnvelope,
104
- logger: ITelemetryLoggerExt,
104
+ logger: TelemetryLoggerExt,
105
105
  consecutiveReconnects: number,
106
106
  ): void {
107
107
  const {
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { assert } from "@fluidframework/core-utils/internal";
7
7
  import type { SemanticVersion } from "@fluidframework/runtime-utils/internal";
8
- import type { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
8
+ import type { TelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
9
9
  import { DataProcessingError } from "@fluidframework/telemetry-utils/internal";
10
10
  import { gt, lt, parse } from "semver-ts";
11
11
 
@@ -303,6 +303,58 @@ const documentSchemaSupportedConfigs = {
303
303
  disallowedVersions: new CheckVersions(),
304
304
  };
305
305
 
306
+ /**
307
+ * Entry in {@link retiredDocumentSchemaFeatures}.
308
+ *
309
+ * - `handler` validates/merges the persisted value (same as a "non-retired" feature).
310
+ * - `value` is the hardcoded value that this runtime will always use for the desired schema.
311
+ */
312
+ interface IRetiredFeatureEntry {
313
+ handler: IProperty;
314
+ value: DocumentSchemaValueType;
315
+ }
316
+
317
+ /**
318
+ * Retired runtime features. A retired feature is one this runtime version no longer toggles
319
+ * via {@link IDocumentSchemaFeatures}, but that older documents may still carry in their
320
+ * persisted schema. Each entry bundles the property handler with the hardcoded value for the feature.
321
+ *
322
+ * Retired features participate in the normal merge / schema-change-op flow exactly like
323
+ * non-retired features — the only difference is that their values are hardcoded.
324
+ */
325
+ const retiredDocumentSchemaFeatures = {
326
+ // Note: There are currently no retired retired features. To retire a feature, remove it from IDocumentSchemaFeatures
327
+ // and documentSchemaSupportedConfigs and add an entry here, e.g.:
328
+ // featureFoo: { handler: new TrueOrUndefined(), value: true },
329
+ } satisfies Record<string, IRetiredFeatureEntry> & {
330
+ // This ensures that retiredDocumentSchemaFeatures and IDocumentSchemaFeatures are mutually exclusive.
331
+ [K in keyof IDocumentSchemaFeatures]?: never;
332
+ };
333
+
334
+ /**
335
+ * Looks up the validator/merger for a given runtime property name across both supported and
336
+ * retired configs. Returns `undefined` if the property is unknown to this runtime.
337
+ */
338
+ function getRuntimeConfigHandler(name: string): IProperty | undefined {
339
+ return (
340
+ (documentSchemaSupportedConfigs as Record<string, IProperty>)[name] ??
341
+ (retiredDocumentSchemaFeatures as Record<string, IRetiredFeatureEntry>)[name]?.handler
342
+ );
343
+ }
344
+
345
+ /**
346
+ * Builds the `{ key: value }` for retired features (for building the desired schema).
347
+ */
348
+ function retiredFeatureValues(): Record<string, DocumentSchemaValueType> {
349
+ const result: Record<string, DocumentSchemaValueType> = {};
350
+ for (const [key, entry] of Object.entries(
351
+ retiredDocumentSchemaFeatures as Record<string, IRetiredFeatureEntry>,
352
+ )) {
353
+ result[key] = entry.value;
354
+ }
355
+ return result;
356
+ }
357
+
306
358
  /**
307
359
  * Checks if a given schema is compatible with current code, i.e. if current code can understand all the features of that schema.
308
360
  * If schema is not compatible with current code, it throws an exception.
@@ -343,7 +395,7 @@ function checkRuntimeCompatibility(
343
395
  unknownProperty = "runtime";
344
396
  } else {
345
397
  for (const [name, value] of Object.entries(documentSchema.runtime)) {
346
- const validator = documentSchemaSupportedConfigs[name] as IProperty | undefined;
398
+ const validator = getRuntimeConfigHandler(name);
347
399
  if (!(validator?.validate(value) ?? false)) {
348
400
  unknownProperty = `runtime/${name}`;
349
401
  }
@@ -377,7 +429,7 @@ function and(
377
429
  ...Object.keys(persistedSchema.runtime),
378
430
  ...Object.keys(providedSchema.runtime),
379
431
  ])) {
380
- runtime[key] = (documentSchemaSupportedConfigs[key] as IProperty).and(
432
+ runtime[key] = (getRuntimeConfigHandler(key) as IProperty).and(
381
433
  persistedSchema.runtime[key],
382
434
  providedSchema.runtime[key],
383
435
  );
@@ -405,7 +457,7 @@ function or(
405
457
  ...Object.keys(persistedSchema.runtime),
406
458
  ...Object.keys(providedSchema.runtime),
407
459
  ])) {
408
- runtime[key] = (documentSchemaSupportedConfigs[key] as IProperty).or(
460
+ runtime[key] = (getRuntimeConfigHandler(key) as IProperty).or(
409
461
  persistedSchema.runtime[key],
410
462
  providedSchema.runtime[key],
411
463
  );
@@ -581,7 +633,7 @@ export class DocumentsSchemaController {
581
633
  features: IDocumentSchemaFeatures,
582
634
  private readonly onSchemaChange: (schema: IDocumentSchemaCurrent) => void,
583
635
  info: IDocumentSchemaInfo,
584
- logger: ITelemetryLoggerExt,
636
+ logger: TelemetryLoggerExt,
585
637
  private readonly disableSchemaUpgrade: boolean,
586
638
  ) {
587
639
  // For simplicity, let's only support new schema features for explicit schema control mode
@@ -625,6 +677,7 @@ export class DocumentsSchemaController {
625
677
  opGroupingEnabled: boolToProp(features.opGroupingEnabled),
626
678
  createBlobPayloadPending: features.createBlobPayloadPending,
627
679
  disallowedVersions: arrayToProp(features.disallowedVersions),
680
+ ...retiredFeatureValues(),
628
681
  },
629
682
  };
630
683
 
@@ -19,7 +19,7 @@ import type {
19
19
  ISequencedClient,
20
20
  } from "@fluidframework/driver-definitions";
21
21
  import {
22
- type ITelemetryLoggerExt,
22
+ type TelemetryLoggerExt,
23
23
  UsageError,
24
24
  createChildLogger,
25
25
  } from "@fluidframework/telemetry-utils/internal";
@@ -135,7 +135,7 @@ export class OrderedClientCollection
135
135
  * Pointer to end of linked list, for optimized client adds.
136
136
  */
137
137
  private _youngestClient: LinkNode = this.rootNode;
138
- private readonly logger: ITelemetryLoggerExt;
138
+ private readonly logger: TelemetryLoggerExt;
139
139
 
140
140
  public get count(): number {
141
141
  return this.clientMap.size;
@@ -413,7 +413,7 @@ export class OrderedClientElection
413
413
  }
414
414
 
415
415
  constructor(
416
- private readonly logger: ITelemetryLoggerExt,
416
+ private readonly logger: TelemetryLoggerExt,
417
417
  private readonly orderedClientCollection: IOrderedClientCollection,
418
418
  /**
419
419
  * Serialized state from summary or current sequence number at time of load if new.
@@ -7,7 +7,7 @@ import { TypedEventEmitter } from "@fluid-internal/client-utils";
7
7
  import type { IEvent, IEventProvider } from "@fluidframework/core-interfaces";
8
8
  import type { IClientDetails } from "@fluidframework/driver-definitions";
9
9
  import { MessageType } from "@fluidframework/driver-definitions/internal";
10
- import type { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
10
+ import type { TelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
11
11
 
12
12
  import type {
13
13
  IOrderedClientElection,
@@ -58,7 +58,7 @@ export class SummarizerClientElection
58
58
  }
59
59
 
60
60
  constructor(
61
- private readonly logger: ITelemetryLoggerExt,
61
+ private readonly logger: TelemetryLoggerExt,
62
62
  private readonly summaryCollection: IEventProvider<ISummaryCollectionOpEvents>,
63
63
  public readonly clientElection: IOrderedClientElection,
64
64
  private readonly maxOpsSinceLastSummary: number,
@@ -31,7 +31,7 @@ import {
31
31
  } from "@fluidframework/telemetry-utils/internal";
32
32
  import type {
33
33
  ITelemetryErrorEventExt,
34
- ITelemetryLoggerExt,
34
+ TelemetryLoggerExt,
35
35
  } from "@fluidframework/telemetry-utils/internal";
36
36
 
37
37
  import type {
@@ -90,7 +90,7 @@ export class SummarizerNode implements IRootSummarizerNode {
90
90
  private wipSummarizeCalled: boolean = false;
91
91
  private wipSkipRecursion = false;
92
92
 
93
- protected readonly logger: ITelemetryLoggerExt;
93
+ protected readonly logger: TelemetryLoggerExt;
94
94
 
95
95
  /**
96
96
  * Do not call constructor directly.
@@ -664,7 +664,7 @@ export class SummarizerNode implements IRootSummarizerNode {
664
664
  * @param config - Configure behavior of summarizer node
665
665
  */
666
666
  export const createRootSummarizerNode = (
667
- logger: ITelemetryLoggerExt,
667
+ logger: TelemetryLoggerExt,
668
668
  summarizeInternalFn: SummarizeInternalFn,
669
669
  changeSequenceNumber: number,
670
670
  referenceSequenceNumber: number | undefined,
@@ -6,7 +6,7 @@
6
6
  import type { SummaryObject } from "@fluidframework/driver-definitions";
7
7
  import type { ISnapshotTree } from "@fluidframework/driver-definitions/internal";
8
8
  import type {
9
- ITelemetryLoggerExt,
9
+ TelemetryLoggerExt,
10
10
  TelemetryDataTag,
11
11
  } from "@fluidframework/telemetry-utils/internal";
12
12
 
@@ -67,7 +67,7 @@ export type ValidateSummaryResult =
67
67
  export interface ISummarizerNodeRootContract {
68
68
  startSummary(
69
69
  referenceSequenceNumber: number,
70
- summaryLogger: ITelemetryLoggerExt,
70
+ summaryLogger: TelemetryLoggerExt,
71
71
  latestSummaryRefSeqNum: number,
72
72
  ): IStartSummaryResult;
73
73
  validateSummary(): ValidateSummaryResult;
@@ -24,7 +24,7 @@ import type {
24
24
  import type { ISummaryStats } from "@fluidframework/runtime-definitions/internal";
25
25
  import type { TelemetryContext } from "@fluidframework/runtime-utils/internal";
26
26
  import type {
27
- ITelemetryLoggerExt,
27
+ TelemetryLoggerExt,
28
28
  ITelemetryLoggerPropertyBag,
29
29
  } from "@fluidframework/telemetry-utils/internal";
30
30
 
@@ -79,7 +79,7 @@ export interface IRefreshSummaryAckOptions {
79
79
  /**
80
80
  * Telemetry logger to which telemetry events will be forwarded.
81
81
  */
82
- readonly summaryLogger: ITelemetryLoggerExt;
82
+ readonly summaryLogger: TelemetryLoggerExt;
83
83
  }
84
84
 
85
85
  /**
@@ -157,7 +157,7 @@ export interface ISubmitSummaryOptions extends ISummarizeOptions {
157
157
  /**
158
158
  * Logger to use for correlated summary events
159
159
  */
160
- readonly summaryLogger: ITelemetryLoggerExt;
160
+ readonly summaryLogger: TelemetryLoggerExt;
161
161
  /**
162
162
  * Tells when summary process should be cancelled
163
163
  */