@fluidframework/container-runtime 2.102.0 → 2.110.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 (217) 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 +112 -9
  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/dataStoreContexts.d.ts.map +1 -1
  19. package/dist/dataStoreContexts.js.map +1 -1
  20. package/dist/deltaScheduler.d.ts +2 -2
  21. package/dist/deltaScheduler.d.ts.map +1 -1
  22. package/dist/deltaScheduler.js.map +1 -1
  23. package/dist/gc/garbageCollection.d.ts +2 -2
  24. package/dist/gc/garbageCollection.d.ts.map +1 -1
  25. package/dist/gc/garbageCollection.js.map +1 -1
  26. package/dist/gc/gcDefinitions.d.ts +3 -3
  27. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  28. package/dist/gc/gcDefinitions.js.map +1 -1
  29. package/dist/gc/gcTelemetry.d.ts +3 -3
  30. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  31. package/dist/gc/gcTelemetry.js.map +1 -1
  32. package/dist/inboundBatchAggregator.d.ts +2 -2
  33. package/dist/inboundBatchAggregator.d.ts.map +1 -1
  34. package/dist/inboundBatchAggregator.js.map +1 -1
  35. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  36. package/dist/opLifecycle/opCompressor.js.map +1 -1
  37. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  38. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  39. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  40. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  41. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  42. package/dist/opLifecycle/opSplitter.js.map +1 -1
  43. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  44. package/dist/opLifecycle/outbox.js.map +1 -1
  45. package/dist/packageVersion.d.ts +1 -1
  46. package/dist/packageVersion.js +1 -1
  47. package/dist/packageVersion.js.map +1 -1
  48. package/dist/pendingStateManager.d.ts +48 -1
  49. package/dist/pendingStateManager.d.ts.map +1 -1
  50. package/dist/pendingStateManager.js +54 -1
  51. package/dist/pendingStateManager.js.map +1 -1
  52. package/dist/runtimeLayerCompatState.d.ts +1 -1
  53. package/dist/signalTelemetryProcessing.d.ts +2 -2
  54. package/dist/signalTelemetryProcessing.d.ts.map +1 -1
  55. package/dist/signalTelemetryProcessing.js.map +1 -1
  56. package/dist/summary/documentSchema.d.ts +2 -2
  57. package/dist/summary/documentSchema.d.ts.map +1 -1
  58. package/dist/summary/documentSchema.js.map +1 -1
  59. package/dist/summary/orderedClientElection.d.ts +2 -2
  60. package/dist/summary/orderedClientElection.d.ts.map +1 -1
  61. package/dist/summary/orderedClientElection.js.map +1 -1
  62. package/dist/summary/summarizerClientElection.d.ts +2 -2
  63. package/dist/summary/summarizerClientElection.d.ts.map +1 -1
  64. package/dist/summary/summarizerClientElection.js.map +1 -1
  65. package/dist/summary/summarizerNode/summarizerNode.d.ts +3 -3
  66. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  67. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  68. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts +2 -2
  69. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  70. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  71. package/dist/summary/summarizerTypes.d.ts +3 -3
  72. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  73. package/dist/summary/summarizerTypes.js.map +1 -1
  74. package/dist/summary/summaryCollection.d.ts.map +1 -1
  75. package/dist/summary/summaryCollection.js.map +1 -1
  76. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.d.ts +2 -2
  77. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -1
  78. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -1
  79. package/dist/summary/summaryDelayLoadedModule/summarizer.d.ts +2 -2
  80. package/dist/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
  81. package/dist/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
  82. package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.d.ts +2 -2
  83. package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.d.ts.map +1 -1
  84. package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.js.map +1 -1
  85. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.d.ts +2 -2
  86. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.d.ts.map +1 -1
  87. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -1
  88. package/dist/summary/summaryManager.d.ts.map +1 -1
  89. package/dist/summary/summaryManager.js.map +1 -1
  90. package/lib/batchTracker.d.ts +1 -1
  91. package/lib/batchTracker.d.ts.map +1 -1
  92. package/lib/batchTracker.js +1 -1
  93. package/lib/batchTracker.js.map +1 -1
  94. package/lib/blobManager/blobManagerSnapSum.d.ts +2 -2
  95. package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  96. package/lib/blobManager/blobManagerSnapSum.js.map +1 -1
  97. package/lib/connectionTelemetry.js.map +1 -1
  98. package/lib/containerRuntime.d.ts +16 -5
  99. package/lib/containerRuntime.d.ts.map +1 -1
  100. package/lib/containerRuntime.js +112 -9
  101. package/lib/containerRuntime.js.map +1 -1
  102. package/lib/dataStore.d.ts +2 -2
  103. package/lib/dataStore.d.ts.map +1 -1
  104. package/lib/dataStore.js.map +1 -1
  105. package/lib/dataStoreContexts.d.ts.map +1 -1
  106. package/lib/dataStoreContexts.js.map +1 -1
  107. package/lib/deltaScheduler.d.ts +2 -2
  108. package/lib/deltaScheduler.d.ts.map +1 -1
  109. package/lib/deltaScheduler.js +1 -1
  110. package/lib/deltaScheduler.js.map +1 -1
  111. package/lib/gc/garbageCollection.d.ts +2 -2
  112. package/lib/gc/garbageCollection.d.ts.map +1 -1
  113. package/lib/gc/garbageCollection.js +1 -1
  114. package/lib/gc/garbageCollection.js.map +1 -1
  115. package/lib/gc/gcDefinitions.d.ts +3 -3
  116. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  117. package/lib/gc/gcDefinitions.js.map +1 -1
  118. package/lib/gc/gcTelemetry.d.ts +3 -3
  119. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  120. package/lib/gc/gcTelemetry.js.map +1 -1
  121. package/lib/inboundBatchAggregator.d.ts +2 -2
  122. package/lib/inboundBatchAggregator.d.ts.map +1 -1
  123. package/lib/inboundBatchAggregator.js.map +1 -1
  124. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  125. package/lib/opLifecycle/opCompressor.js +1 -1
  126. package/lib/opLifecycle/opCompressor.js.map +1 -1
  127. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  128. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  129. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  130. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  131. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  132. package/lib/opLifecycle/opSplitter.js +1 -1
  133. package/lib/opLifecycle/opSplitter.js.map +1 -1
  134. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  135. package/lib/opLifecycle/outbox.js +1 -1
  136. package/lib/opLifecycle/outbox.js.map +1 -1
  137. package/lib/packageVersion.d.ts +1 -1
  138. package/lib/packageVersion.js +1 -1
  139. package/lib/packageVersion.js.map +1 -1
  140. package/lib/pendingStateManager.d.ts +48 -1
  141. package/lib/pendingStateManager.d.ts.map +1 -1
  142. package/lib/pendingStateManager.js +55 -2
  143. package/lib/pendingStateManager.js.map +1 -1
  144. package/lib/runtimeLayerCompatState.d.ts +1 -1
  145. package/lib/signalTelemetryProcessing.d.ts +2 -2
  146. package/lib/signalTelemetryProcessing.d.ts.map +1 -1
  147. package/lib/signalTelemetryProcessing.js.map +1 -1
  148. package/lib/summary/documentSchema.d.ts +2 -2
  149. package/lib/summary/documentSchema.d.ts.map +1 -1
  150. package/lib/summary/documentSchema.js.map +1 -1
  151. package/lib/summary/orderedClientElection.d.ts +2 -2
  152. package/lib/summary/orderedClientElection.d.ts.map +1 -1
  153. package/lib/summary/orderedClientElection.js +1 -1
  154. package/lib/summary/orderedClientElection.js.map +1 -1
  155. package/lib/summary/summarizerClientElection.d.ts +2 -2
  156. package/lib/summary/summarizerClientElection.d.ts.map +1 -1
  157. package/lib/summary/summarizerClientElection.js.map +1 -1
  158. package/lib/summary/summarizerNode/summarizerNode.d.ts +3 -3
  159. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  160. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  161. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts +2 -2
  162. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  163. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  164. package/lib/summary/summarizerTypes.d.ts +3 -3
  165. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  166. package/lib/summary/summarizerTypes.js.map +1 -1
  167. package/lib/summary/summaryCollection.d.ts.map +1 -1
  168. package/lib/summary/summaryCollection.js.map +1 -1
  169. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.d.ts +2 -2
  170. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -1
  171. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.js +1 -1
  172. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -1
  173. package/lib/summary/summaryDelayLoadedModule/summarizer.d.ts +2 -2
  174. package/lib/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
  175. package/lib/summary/summaryDelayLoadedModule/summarizer.js +1 -1
  176. package/lib/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
  177. package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.d.ts +2 -2
  178. package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.d.ts.map +1 -1
  179. package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.js.map +1 -1
  180. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.d.ts +2 -2
  181. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.d.ts.map +1 -1
  182. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -1
  183. package/lib/summary/summaryManager.d.ts.map +1 -1
  184. package/lib/summary/summaryManager.js +1 -1
  185. package/lib/summary/summaryManager.js.map +1 -1
  186. package/package.json +19 -19
  187. package/src/batchTracker.ts +3 -3
  188. package/src/blobManager/blobManagerSnapSum.ts +2 -2
  189. package/src/connectionTelemetry.ts +5 -5
  190. package/src/containerRuntime.ts +134 -15
  191. package/src/dataStore.ts +3 -3
  192. package/src/dataStoreContexts.ts +2 -2
  193. package/src/deltaScheduler.ts +2 -5
  194. package/src/gc/garbageCollection.ts +6 -6
  195. package/src/gc/gcDefinitions.ts +3 -3
  196. package/src/gc/gcTelemetry.ts +5 -5
  197. package/src/inboundBatchAggregator.ts +2 -2
  198. package/src/opLifecycle/opCompressor.ts +3 -3
  199. package/src/opLifecycle/opDecompressor.ts +2 -2
  200. package/src/opLifecycle/opGroupingManager.ts +2 -2
  201. package/src/opLifecycle/opSplitter.ts +3 -3
  202. package/src/opLifecycle/outbox.ts +4 -4
  203. package/src/packageVersion.ts +1 -1
  204. package/src/pendingStateManager.ts +82 -4
  205. package/src/signalTelemetryProcessing.ts +2 -2
  206. package/src/summary/documentSchema.ts +2 -2
  207. package/src/summary/orderedClientElection.ts +4 -4
  208. package/src/summary/summarizerClientElection.ts +2 -2
  209. package/src/summary/summarizerNode/summarizerNode.ts +3 -3
  210. package/src/summary/summarizerNode/summarizerNodeUtils.ts +2 -2
  211. package/src/summary/summarizerTypes.ts +3 -3
  212. package/src/summary/summaryCollection.ts +2 -2
  213. package/src/summary/summaryDelayLoadedModule/runningSummarizer.ts +4 -6
  214. package/src/summary/summaryDelayLoadedModule/summarizer.ts +4 -4
  215. package/src/summary/summaryDelayLoadedModule/summarizerHeuristics.ts +2 -2
  216. package/src/summary/summaryDelayLoadedModule/summaryGenerator.ts +2 -2
  217. package/src/summary/summaryManager.ts +3 -3
@@ -632,14 +632,31 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
632
632
  this.electedSummarizerData = electedSummarizerData;
633
633
  this.runtimeOptions = runtimeOptions;
634
634
  this.containerScope = containerScope;
635
- this.baseLogger = baseLogger;
636
635
  this._storage = _storage;
637
636
  this.createIdCompressorFn = createIdCompressorFn;
638
637
  this.documentsSchemaController = documentsSchemaController;
639
638
  this.minVersionForCollab = minVersionForCollab;
640
639
  this.requestHandler = requestHandler;
641
640
  this.summaryConfiguration = summaryConfiguration;
642
- this.isReadOnly = () => this.deltaManager.readOnlyInfo.readonly === true;
641
+ /**
642
+ * Whether local op submission is currently disallowed.
643
+ *
644
+ * This is `true` in two distinct situations.
645
+ *
646
+ * First: the delta manager reports a read-only connection (host/service-imposed permission or connection state — the historical meaning of `readOnly`).
647
+ *
648
+ * Second: the `PendingStateManager` is replaying stashed ops (`isApplyingStashedOps`). During this window DDSes must not submit new local ops, as doing so would interleave fresh content ahead of the stashed pending stream and corrupt pending local state. Surfacing it through `isReadOnly()` lets DDSes that consult `readOnly` at realize time self-suppress; see the apply-lifecycle docs on `PendingStateManager` for the full rationale.
649
+ *
650
+ * Note this layers a third meaning ("transiently quiescing for stashed-op replay") onto the `readOnly` predicate, which is broader than its host/connection-permission origin.
651
+ */
652
+ this.isReadOnly = () =>
653
+ // `_deltaManager` and `pendingStateManager` are both assigned partway
654
+ // through the constructor; `baseLogger` is built earlier and stamps
655
+ // `isReadOnly` on every error event (e.g. layer-compat failures
656
+ // during construction), so this can be called before either is
657
+ // assigned. Optional chains keep that window safe.
658
+ this._deltaManager?.readOnlyInfo.readonly === true ||
659
+ this.pendingStateManager?.isApplyingStashedOps === true;
643
660
  // We accumulate Id compressor Ops while Id compressor is not loaded yet (only for "delayed" mode)
644
661
  // Once it loads, it will process all such ops and we will stop accumulating further ops - ops will be processes as they come in.
645
662
  this.pendingIdCompressorOps = [];
@@ -658,7 +675,13 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
658
675
  expiry: { policy: "absolute", durationMs: 60000 },
659
676
  });
660
677
  this.extensions = new Map();
661
- this.notifyReadOnlyState = (readonly) => this.channelCollection.notifyReadOnlyState(readonly);
678
+ // Boolean payload from the `"readonly"` delta-manager event is intentionally
679
+ // ignored — `isReadOnly()` aggregates delta-manager readonly with the PSM
680
+ // apply window, and that aggregation is the source of truth for fanout.
681
+ // `channelCollection?.` guards against future wiring changes; both callers
682
+ // today (the `"readonly"` listener and `onAfterStashedOpsApplied`) fire
683
+ // after `channelCollection` is assigned.
684
+ this.notifyReadOnlyState = (_readonly) => this.channelCollection?.notifyReadOnlyState(this.isReadOnly());
662
685
  /**
663
686
  * Enter Staging Mode, such that ops submitted to the ContainerRuntime will not be sent to the ordering service.
664
687
  * To exit Staging Mode, call either discardChanges or commitChanges on the Stage Controls returned from this method.
@@ -817,15 +840,20 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
817
840
  // In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
818
841
  this.disposeFn = disposeFn ?? closeFn;
819
842
  this.isSnapshotInstanceOfISnapshot = snapshotWithContents !== undefined;
820
- this.mc = (0, internal_8.createChildMonitoringContext)({
821
- logger: this.baseLogger,
822
- namespace: "ContainerRuntime",
843
+ this.baseLogger = (0, internal_8.createChildLogger)({
844
+ logger: baseLogger,
823
845
  properties: {
824
- all: {
825
- inStagingMode: this.inStagingMode,
846
+ error: {
847
+ inStagingMode: () => this.inStagingMode,
848
+ isApplyingStashedOps: () => this.pendingStateManager?.isApplyingStashedOps,
849
+ isReadOnly: () => this.isReadOnly(),
826
850
  },
827
851
  },
828
852
  });
853
+ this.mc = (0, internal_8.createChildMonitoringContext)({
854
+ logger: this.baseLogger,
855
+ namespace: "ContainerRuntime",
856
+ });
829
857
  // Validate that the Loader is compatible with this Runtime.
830
858
  const maybeLoaderCompatDetailsForRuntime = context;
831
859
  (0, runtimeLayerCompatState_js_1.validateLoaderCompatibility)(maybeLoaderCompatDetailsForRuntime.ILayerCompatDetails, this.disposeFn, this.mc);
@@ -948,7 +976,18 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
948
976
  reSubmitBatch: this.reSubmitBatch.bind(this),
949
977
  isActiveConnection: () => this.innerDeltaManager.active,
950
978
  isAttached: () => this.attachState !== container_definitions_1.AttachState.Detached,
951
- }, pendingRuntimeState?.pending, this.baseLogger);
979
+ }, pendingRuntimeState?.pending, this.baseLogger, {
980
+ // PSM has cleared `isApplyingStashedOps`; `isReadOnly()` now
981
+ // reflects the network-readonly state again. Fan out so DDSes
982
+ // know they can submit once more. No open hook is needed —
983
+ // the apply window opens before `channelCollection` exists,
984
+ // so a fanout there would be a no-op; data stores instead
985
+ // pick up the initial readonly state from `isReadOnly()`
986
+ // when they're first asked.
987
+ onAfterStashedOpsApplied: () => {
988
+ this.notifyReadOnlyState();
989
+ },
990
+ });
952
991
  let outerDeltaManager = this.innerDeltaManager;
953
992
  this.useDeltaManagerOpsProxy =
954
993
  this.mc.config.getBoolean("Fluid.ContainerRuntime.DeltaManagerOpsProxy") === true;
@@ -1214,6 +1253,13 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1214
1253
  telemetryDocumentId: this.telemetryDocumentId,
1215
1254
  groupedBatchingEnabled: this.groupedBatchingEnabled,
1216
1255
  initialSequenceNumber: this.deltaManager.initialSequenceNumber,
1256
+ // Number of ops since the last summary that this client is aware of (including ops still
1257
+ // queued for processing). Computed as the gap between the latest known op sequence number
1258
+ // and the sequence number of the message at which the last summary was taken (per snapshot
1259
+ // metadata). Falls back to lastKnownSeqNumber when no prior summary message is recorded
1260
+ // (e.g. new container or older snapshot without metadata).
1261
+ numUnsummarizedOps: this.deltaManager.lastKnownSeqNumber -
1262
+ (this.messageAtLastSummary?.sequenceNumber ?? 0),
1217
1263
  minVersionForCollab: this.minVersionForCollab,
1218
1264
  // logging hardware telemetry
1219
1265
  deviceSpec: { ...getDeviceSpec() },
@@ -1666,6 +1712,24 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1666
1712
  if (!this.shouldSendOps()) {
1667
1713
  return;
1668
1714
  }
1715
+ // Invariant: the canSendOps edge in `setConnectionStateCore` — the
1716
+ // only caller of this method — cannot fire while
1717
+ // `applyStashedOpsAt` is in flight, because the loader awaits the
1718
+ // apply before transitioning the runtime to a write-capable
1719
+ // connection. If this assert ever fires, that contract has changed
1720
+ // and the submit guard at `submit()` would catch a runtime-internal
1721
+ // resubmit (`Rejoin`, `GC`, `FluidDataStoreOp`) for an op type
1722
+ // outside the apply-window allowlist.
1723
+ //
1724
+ // The precondition is held by the load sequence: `loadRuntime2`
1725
+ // awaits `pendingStateManager.applyStashedOpsAt(...)` before
1726
+ // returning the runtime, and the loader gates `setLoaded` on that
1727
+ // completion before any write-capable connection edge fires. A
1728
+ // maintainer reordering either sequence (or adding a new
1729
+ // `canSendOps` edge that fires before the apply resolves) is what
1730
+ // would trip this assert.
1731
+ // @see {@link ContainerRuntime.loadRuntime2} (awaits `applyStashedOpsAt`)
1732
+ (0, internal_2.assert)(!this.pendingStateManager.isApplyingStashedOps, 0xd01 /* replayPendingStates must not be called during stashed-op apply window */);
1669
1733
  // Replaying is an internal operation and we don't want to generate noise while doing it.
1670
1734
  // So temporarily disable dirty state change events, and save the old state.
1671
1735
  // When we're done, we'll emit the event if the state changed.
@@ -3062,6 +3126,45 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
3062
3126
  }
3063
3127
  submit(containerRuntimeMessage, localOpMetadata = undefined, metadata) {
3064
3128
  this.verifyNotClosed();
3129
+ // Nothing should be submitting while we're replaying stashed ops.
3130
+ // The runtime is readonly during the apply window (see
3131
+ // `PendingStateManager._applyLifecycle`), so a compliant DDS skips
3132
+ // submits. If we land here anyway, a DDS bypassed the readonly gate
3133
+ // (e.g. a realize-time write that doesn't consult `readOnly`) and
3134
+ // produced a local op that has no counterpart in the saved-op
3135
+ // replay — we cannot reconcile the mismatch, so fail fatally. We
3136
+ // check here (rather than at flush) because outbox flushes are
3137
+ // deferred and the apply window could close before the offending op
3138
+ // reaches the pending queue.
3139
+ //
3140
+ // Allowlist: `BlobAttach` is a runtime-internal op type that may
3141
+ // legitimately fire during apply — produced by `sharePendingBlobs`,
3142
+ // which is invoked from `loadRuntime2` before `applyStashedOpsAt`
3143
+ // resolves. `IdAllocation` is not in this allowlist because the
3144
+ // assert at 0x9a5 below enforces that it never reaches `submit()`
3145
+ // at all; treating that assert as the single source of truth.
3146
+ //
3147
+ // Always surface the error event to telemetry on a bypass so we can
3148
+ // attribute incidents regardless of the on-switch state. The
3149
+ // `EnableSubmitDuringStashedApplyThrow` config opts in to the
3150
+ // throw + container close; by default we log only, so a first- or
3151
+ // third-party DDS that quietly bypasses the readonly gate in
3152
+ // production is observable without escalating to a fatal close.
3153
+ if (this.pendingStateManager.isApplyingStashedOps &&
3154
+ containerRuntimeMessage.type !== messageTypes_js_1.ContainerMessageType.BlobAttach) {
3155
+ const error = new internal_8.UsageError("Local op submitted during stashed-op apply window", {
3156
+ messageType: containerRuntimeMessage.type,
3157
+ });
3158
+ this.mc.logger.sendErrorEvent({ eventName: "SubmitDuringStashedOpApply" }, error);
3159
+ if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableSubmitDuringStashedApplyThrow") === true) {
3160
+ // Close the container before throwing so the "throw + close"
3161
+ // contract is enforced by this code path rather than by
3162
+ // whichever caller happens to wrap the throw in `.catch(closeFn)`.
3163
+ // `closeFn` is idempotent; a caller that also closes won't double-close.
3164
+ this.closeFn(error);
3165
+ throw error;
3166
+ }
3167
+ }
3065
3168
  // There should be no ops in detached container state!
3066
3169
  (0, internal_2.assert)(this.attachState !== container_definitions_1.AttachState.Detached, 0x132 /* "sending ops in detached container" */);
3067
3170
  (0, internal_2.assert)(metadata === undefined ||