@mastra/mongodb 1.23.1-alpha.0 → 1.23.1-alpha.1

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.
package/dist/index.cjs CHANGED
@@ -10,7 +10,7 @@ let _mastra_core_agent = require("@mastra/core/agent");
10
10
  let _mastra_core_evals = require("@mastra/core/evals");
11
11
  let _mastra_core_storage_domains_skills = require("@mastra/core/storage/domains/skills");
12
12
  //#region package.json
13
- var version = "1.23.1-alpha.0";
13
+ var version = "1.23.1-alpha.1";
14
14
  //#endregion
15
15
  //#region src/vector/filter.ts
16
16
  /**
@@ -1619,20 +1619,29 @@ var MongoDBConnector = class MongoDBConnector {
1619
1619
  return (await this.getConnection()).listCollections({ name: collectionName }).hasNext();
1620
1620
  }
1621
1621
  /**
1622
- * Returns true when the deployment supports multi-document transactions
1623
- * (replica set or sharded cluster). Standalone servers and custom connector
1624
- * handlers return false. Probed once and cached.
1622
+ * Whether the deployment supports multi-document transactions (replica set
1623
+ * or sharded cluster). Standalone servers and custom connector handlers
1624
+ * don't. The answer is cached once a probe completes; a probe that fails
1625
+ * rejects and is retried on the next call, so the answer never changes.
1625
1626
  */
1626
- async supportsTransactions() {
1627
+ async probeTransactions() {
1627
1628
  if (this.#supportsTransactions !== void 0) return this.#supportsTransactions;
1628
1629
  if (!this.#client) {
1629
1630
  this.#supportsTransactions = false;
1630
1631
  return false;
1631
1632
  }
1633
+ const hello = await (await this.getConnection()).admin().command({ hello: 1 });
1634
+ this.#supportsTransactions = Boolean(hello.setName) || hello.msg === "isdbgrid";
1635
+ return this.#supportsTransactions;
1636
+ }
1637
+ /**
1638
+ * Like {@link probeTransactions}, but a failed probe reads as no
1639
+ * transaction support (and is retried on the next call), so a momentary
1640
+ * outage degrades writes to non-atomic instead of failing them.
1641
+ */
1642
+ async supportsTransactions() {
1632
1643
  try {
1633
- const hello = await (await this.getConnection()).admin().command({ hello: 1 });
1634
- this.#supportsTransactions = Boolean(hello.setName) || hello.msg === "isdbgrid";
1635
- return this.#supportsTransactions;
1644
+ return await this.probeTransactions();
1636
1645
  } catch {
1637
1646
  return false;
1638
1647
  }
@@ -2279,14 +2288,19 @@ function cutoffFor(policy, encoding, now = Date.now()) {
2279
2288
  * Delete up to `limit` documents whose anchor field is older than `cutoff`.
2280
2289
  *
2281
2290
  * `deleteMany` has no limit clause, so each batch first collects `_id`s with a
2282
- * bounded `find`, then deletes exactly that set — the Mongo equivalent of the
2283
- * SQL stores' `DELETE ... WHERE rowid/ctid IN (SELECT ... LIMIT n)` pattern.
2291
+ * bounded `find`, then deletes that set — the Mongo equivalent of the SQL
2292
+ * stores' `DELETE ... WHERE rowid/ctid IN (SELECT ... LIMIT n)` pattern. The
2293
+ * delete re-checks the anchor, so a document updated after the `find` (a
2294
+ * renewed run ownership, a touched snapshot) survives.
2284
2295
  */
2285
2296
  async function pruneCollectionBatch({ connector, table, column, cutoff, limit }) {
2286
2297
  const collection = await connector.getCollection(table);
2287
2298
  const docs = await collection.find({ [column]: { $lt: cutoff } }).project({ _id: 1 }).limit(limit).toArray();
2288
2299
  if (docs.length === 0) return 0;
2289
- return (await collection.deleteMany({ _id: { $in: docs.map((doc) => doc._id) } })).deletedCount;
2300
+ return (await collection.deleteMany({
2301
+ _id: { $in: docs.map((doc) => doc._id) },
2302
+ [column]: { $lt: cutoff }
2303
+ })).deletedCount;
2290
2304
  }
2291
2305
  /**
2292
2306
  * Run the bounded/cancellable batched-delete loop for a single logical target,
@@ -6561,6 +6575,40 @@ function isAppendOnlySince(storedObservations, snapshotObservations) {
6561
6575
  return stored === snapshot || stored.startsWith(snapshot);
6562
6576
  }
6563
6577
  //#endregion
6578
+ //#region src/storage/domains/run-fencing.ts
6579
+ async function getRunClaims(connector, name) {
6580
+ return await connector.getCollection(name);
6581
+ }
6582
+ /** Duplicate-key error: an upsert lost the race to insert the document. */
6583
+ function isDuplicateKeyError(error) {
6584
+ return error?.code === 11e3;
6585
+ }
6586
+ /**
6587
+ * Runs a write, fenced when `check` is set. A fenced write runs in a
6588
+ * transaction that first updates the run's claim document, matching it on
6589
+ * the fence. A fence that is no longer current matches nothing, and the write
6590
+ * throws `RunFenceConflictError` before anything is written. A claim that
6591
+ * changes while the transaction runs makes the update conflict, so the
6592
+ * transaction retries and sees the new claim; a claim attempted after the
6593
+ * update waits until the transaction commits. Updating the claim document,
6594
+ * rather than reading it, is what makes concurrent claims conflict.
6595
+ *
6596
+ * Fencing is declared only on deployments that support transactions.
6597
+ */
6598
+ async function withRunFence(connector, check, write) {
6599
+ if (!check) return write();
6600
+ const { claims, fence, operation } = check;
6601
+ const collection = await getRunClaims(connector, claims);
6602
+ return connector.withTransaction(async (session) => {
6603
+ if ((await collection.updateOne({
6604
+ _id: fence.runId,
6605
+ generation: fence.generation,
6606
+ ownerId: fence.ownerId
6607
+ }, { $inc: { fencedWrites: 1 } }, { session })).matchedCount === 0) throw new _mastra_core_storage.RunFenceConflictError(fence, operation);
6608
+ return write(session);
6609
+ });
6610
+ }
6611
+ //#endregion
6564
6612
  //#region src/storage/domains/memory/index.ts
6565
6613
  /**
6566
6614
  * Local constant for the observational memory table name.
@@ -6630,9 +6678,6 @@ function buildSuccessorDocument(old, pending) {
6630
6678
  function matchStoredFields(doc, fields) {
6631
6679
  return Object.fromEntries(fields.map((f) => [f, doc[f] === void 0 ? { $exists: false } : doc[f]]));
6632
6680
  }
6633
- function isDuplicateKeyError(error) {
6634
- return error?.code === 11e3;
6635
- }
6636
6681
  function omNotFound(operation, id) {
6637
6682
  return new _mastra_core_error.MastraError({
6638
6683
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", operation, "NOT_FOUND"),
@@ -6672,11 +6717,14 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
6672
6717
  _mastra_core_storage.TABLE_THREADS,
6673
6718
  _mastra_core_storage.TABLE_MESSAGES,
6674
6719
  _mastra_core_storage.TABLE_RESOURCES,
6675
- OM_TABLE
6720
+ OM_TABLE,
6721
+ _mastra_core_storage.TABLE_MEMORY_RUN_FENCES
6676
6722
  ];
6677
6723
  /**
6678
6724
  * Retention-eligible collections. The observational-memory collection is
6679
6725
  * excluded: it has no timestamp anchor to age on. All anchors are BSON dates.
6726
+ * A run fence's `retiredAt` stays null until the execution holding it
6727
+ * settles, and null is never pruned.
6680
6728
  */
6681
6729
  static retentionTables = {
6682
6730
  messages: {
@@ -6693,6 +6741,11 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
6693
6741
  table: _mastra_core_storage.TABLE_THREADS,
6694
6742
  column: "createdAt",
6695
6743
  indexed: true
6744
+ },
6745
+ runFences: {
6746
+ table: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES,
6747
+ column: "retiredAt",
6748
+ indexed: true
6696
6749
  }
6697
6750
  };
6698
6751
  constructor(config) {
@@ -6711,11 +6764,76 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
6711
6764
  await this.#maintainSupersededBy();
6712
6765
  }
6713
6766
  /**
6767
+ * Fenced writes need multi-document transactions, so only replica sets and
6768
+ * sharded clusters fence. Rejects while the deployment can't be probed.
6769
+ */
6770
+ supportsRunFencing() {
6771
+ return this.#connector.probeTransactions();
6772
+ }
6773
+ async raiseRunFence(fence) {
6774
+ try {
6775
+ const fences = await getRunClaims(this.#connector, _mastra_core_storage.TABLE_MEMORY_RUN_FENCES);
6776
+ for (;;) {
6777
+ try {
6778
+ await fences.updateOne({
6779
+ _id: fence.runId,
6780
+ $or: [{ generation: { $lt: fence.generation } }, {
6781
+ generation: fence.generation,
6782
+ ownerId: fence.ownerId
6783
+ }]
6784
+ }, { $set: {
6785
+ generation: fence.generation,
6786
+ ownerId: fence.ownerId,
6787
+ retiredAt: null
6788
+ } }, { upsert: true });
6789
+ return true;
6790
+ } catch (error) {
6791
+ if (!isDuplicateKeyError(error)) throw error;
6792
+ }
6793
+ const current = await fences.findOne({ _id: fence.runId });
6794
+ if (current && current.generation >= fence.generation) return current.generation === fence.generation && current.ownerId === fence.ownerId;
6795
+ }
6796
+ } catch (error) {
6797
+ throw new _mastra_core_error.MastraError({
6798
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "RAISE_RUN_FENCE", "FAILED"),
6799
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
6800
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
6801
+ details: { runId: fence.runId }
6802
+ }, error);
6803
+ }
6804
+ }
6805
+ async retireRunFence(fence) {
6806
+ try {
6807
+ return (await (await getRunClaims(this.#connector, _mastra_core_storage.TABLE_MEMORY_RUN_FENCES)).updateOne({
6808
+ _id: fence.runId,
6809
+ generation: fence.generation,
6810
+ ownerId: fence.ownerId
6811
+ }, [{ $set: { retiredAt: "$$NOW" } }])).matchedCount > 0;
6812
+ } catch (error) {
6813
+ throw new _mastra_core_error.MastraError({
6814
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "RETIRE_RUN_FENCE", "FAILED"),
6815
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
6816
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
6817
+ details: { runId: fence.runId }
6818
+ }, error);
6819
+ }
6820
+ }
6821
+ /** The check a write must pass: its own fence, otherwise the one in scope. */
6822
+ #runFenceCheck(fence, operation) {
6823
+ const resolved = (0, _mastra_core_storage.resolveRunFence)(this, fence);
6824
+ return resolved && {
6825
+ claims: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES,
6826
+ fence: resolved,
6827
+ operation
6828
+ };
6829
+ }
6830
+ /**
6714
6831
  * Delete memory rows older than each table's `maxAge`, batched. Order is
6715
6832
  * messages → resources → threads so child rows never outlive the delete of
6716
- * their thread. Like `deleteThread()`, this does not sweep vector-store
6717
- * embeddings — semantic-recall vectors live in a separate vector store the
6718
- * memory domain cannot reach; cleaning those up is the operator's concern.
6833
+ * their thread; run fences are independent and go last. Like
6834
+ * `deleteThread()`, this does not sweep vector-store embeddings —
6835
+ * semantic-recall vectors live in a separate vector store the memory domain
6836
+ * cannot reach; cleaning those up is the operator's concern.
6719
6837
  */
6720
6838
  async prune(policies, options) {
6721
6839
  const targets = resolveTargets({
@@ -6724,7 +6842,8 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
6724
6842
  order: [
6725
6843
  "messages",
6726
6844
  "resources",
6727
- "threads"
6845
+ "threads",
6846
+ "runFences"
6728
6847
  ]
6729
6848
  });
6730
6849
  return runPrune({
@@ -6830,17 +6949,19 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
6830
6949
  }
6831
6950
  }
6832
6951
  async dangerouslyClearAll() {
6833
- const [threadsCollection, messagesCollection, resourcesCollection, omCollection] = await Promise.all([
6952
+ const [threadsCollection, messagesCollection, resourcesCollection, omCollection, fencesCollection] = await Promise.all([
6834
6953
  this.getCollection(_mastra_core_storage.TABLE_THREADS),
6835
6954
  this.getCollection(_mastra_core_storage.TABLE_MESSAGES),
6836
6955
  this.getCollection(_mastra_core_storage.TABLE_RESOURCES),
6837
- this.getCollection(OM_TABLE)
6956
+ this.getCollection(OM_TABLE),
6957
+ this.getCollection(_mastra_core_storage.TABLE_MEMORY_RUN_FENCES)
6838
6958
  ]);
6839
6959
  await Promise.all([
6840
6960
  threadsCollection.deleteMany({}),
6841
6961
  messagesCollection.deleteMany({}),
6842
6962
  resourcesCollection.deleteMany({}),
6843
- omCollection.deleteMany({})
6963
+ omCollection.deleteMany({}),
6964
+ fencesCollection.deleteMany({})
6844
6965
  ]);
6845
6966
  }
6846
6967
  parseRow(row) {
@@ -7196,7 +7317,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7196
7317
  throw mastraError;
7197
7318
  }
7198
7319
  }
7199
- async saveMessages({ messages }) {
7320
+ async saveMessages({ messages, fence }) {
7200
7321
  if (messages.length === 0) return { messages: [] };
7201
7322
  try {
7202
7323
  if (!messages[0]?.threadId) throw new Error("Thread ID is required");
@@ -7224,12 +7345,16 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7224
7345
  });
7225
7346
  const allThreadIds = new Set(messages.map((m) => m.threadId));
7226
7347
  const now = /* @__PURE__ */ new Date();
7227
- await this.#connector.withTransaction(async (session) => {
7348
+ const writeMessages = async (session) => {
7228
7349
  await collection.bulkWrite(messagesToInsert, { session });
7229
7350
  for (const tid of allThreadIds) await threadsCollection.updateOne({ id: tid }, { $set: { updatedAt: now } }, { session });
7230
- });
7351
+ };
7352
+ const check = this.#runFenceCheck(fence, "saveMessages");
7353
+ if (check) await withRunFence(this.#connector, check, writeMessages);
7354
+ else await this.#connector.withTransaction(writeMessages);
7231
7355
  return { messages: new _mastra_core_agent.MessageList().add(messages, "memory").get.all.db() };
7232
7356
  } catch (error) {
7357
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
7233
7358
  throw new _mastra_core_error.MastraError({
7234
7359
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SAVE_MESSAGES", "FAILED"),
7235
7360
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -7237,49 +7362,51 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7237
7362
  }, error);
7238
7363
  }
7239
7364
  }
7240
- async updateMessages({ messages }) {
7365
+ async updateMessages({ messages, fence }) {
7241
7366
  if (messages.length === 0) return [];
7242
7367
  const messageIds = messages.map((m) => m.id);
7243
7368
  const collection = await this.getCollection(_mastra_core_storage.TABLE_MESSAGES);
7244
- const existingMessagesParsed = (await collection.find({ id: { $in: messageIds } }).toArray()).map((msg) => this.parseRow(msg));
7245
- if (existingMessagesParsed.length === 0) return [];
7246
- const threadIdsToUpdate = /* @__PURE__ */ new Set();
7247
- const bulkOps = [];
7248
- for (const existingMessage of existingMessagesParsed) {
7249
- const updatePayload = messages.find((m) => m.id === existingMessage.id);
7250
- if (!updatePayload) continue;
7251
- const { id, ...fieldsToUpdate } = updatePayload;
7252
- if (Object.keys(fieldsToUpdate).length === 0) continue;
7253
- threadIdsToUpdate.add(existingMessage.threadId);
7254
- if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) threadIdsToUpdate.add(updatePayload.threadId);
7255
- const updateDoc = {};
7256
- const updatableFields = { ...fieldsToUpdate };
7257
- if (updatableFields.content) {
7258
- const newContent = {
7259
- ...existingMessage.content,
7260
- ...updatableFields.content,
7261
- ...existingMessage.content?.metadata && updatableFields.content.metadata ? { metadata: {
7262
- ...existingMessage.content.metadata,
7263
- ...updatableFields.content.metadata
7264
- } } : {}
7265
- };
7266
- updateDoc.content = JSON.stringify(newContent);
7267
- delete updatableFields.content;
7268
- }
7269
- for (const key in updatableFields) if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
7270
- const dbKey = key === "threadId" ? "thread_id" : key;
7271
- let value = updatableFields[key];
7272
- if (typeof value === "object" && value !== null) value = JSON.stringify(value);
7273
- updateDoc[dbKey] = value;
7369
+ return withRunFence(this.#connector, this.#runFenceCheck(fence, "updateMessages"), async (session) => {
7370
+ const existingMessagesParsed = (await collection.find({ id: { $in: messageIds } }, { session }).toArray()).map((msg) => this.parseRow(msg));
7371
+ if (existingMessagesParsed.length === 0) return [];
7372
+ const threadIdsToUpdate = /* @__PURE__ */ new Set();
7373
+ const bulkOps = [];
7374
+ for (const existingMessage of existingMessagesParsed) {
7375
+ const updatePayload = messages.find((m) => m.id === existingMessage.id);
7376
+ if (!updatePayload) continue;
7377
+ const { id, ...fieldsToUpdate } = updatePayload;
7378
+ if (Object.keys(fieldsToUpdate).length === 0) continue;
7379
+ threadIdsToUpdate.add(existingMessage.threadId);
7380
+ if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) threadIdsToUpdate.add(updatePayload.threadId);
7381
+ const updateDoc = {};
7382
+ const updatableFields = { ...fieldsToUpdate };
7383
+ if (updatableFields.content) {
7384
+ const newContent = {
7385
+ ...existingMessage.content,
7386
+ ...updatableFields.content,
7387
+ ...existingMessage.content?.metadata && updatableFields.content.metadata ? { metadata: {
7388
+ ...existingMessage.content.metadata,
7389
+ ...updatableFields.content.metadata
7390
+ } } : {}
7391
+ };
7392
+ updateDoc.content = JSON.stringify(newContent);
7393
+ delete updatableFields.content;
7394
+ }
7395
+ for (const key in updatableFields) if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
7396
+ const dbKey = key === "threadId" ? "thread_id" : key;
7397
+ let value = updatableFields[key];
7398
+ if (typeof value === "object" && value !== null) value = JSON.stringify(value);
7399
+ updateDoc[dbKey] = value;
7400
+ }
7401
+ if (Object.keys(updateDoc).length > 0) bulkOps.push({ updateOne: {
7402
+ filter: { id },
7403
+ update: { $set: updateDoc }
7404
+ } });
7274
7405
  }
7275
- if (Object.keys(updateDoc).length > 0) bulkOps.push({ updateOne: {
7276
- filter: { id },
7277
- update: { $set: updateDoc }
7278
- } });
7279
- }
7280
- if (bulkOps.length > 0) await collection.bulkWrite(bulkOps);
7281
- if (threadIdsToUpdate.size > 0) await (await this.getCollection(_mastra_core_storage.TABLE_THREADS)).updateMany({ id: { $in: Array.from(threadIdsToUpdate) } }, { $set: { updatedAt: /* @__PURE__ */ new Date() } });
7282
- return (await collection.find({ id: { $in: messageIds } }).toArray()).map((row) => this.parseRow(row));
7406
+ if (bulkOps.length > 0) await collection.bulkWrite(bulkOps, { session });
7407
+ if (threadIdsToUpdate.size > 0) await (await this.getCollection(_mastra_core_storage.TABLE_THREADS)).updateMany({ id: { $in: Array.from(threadIdsToUpdate) } }, { $set: { updatedAt: /* @__PURE__ */ new Date() } }, { session });
7408
+ return (await collection.find({ id: { $in: messageIds } }, { session }).toArray()).map((row) => this.parseRow(row));
7409
+ });
7283
7410
  }
7284
7411
  async getResourceById({ resourceId }) {
7285
7412
  try {
@@ -7314,9 +7441,11 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7314
7441
  }, error);
7315
7442
  }
7316
7443
  }
7317
- async updateResource({ resourceId, workingMemory, metadata }) {
7444
+ async updateResource({ resourceId, workingMemory, metadata, fence }) {
7318
7445
  try {
7446
+ const check = this.#runFenceCheck(fence, "updateResource");
7319
7447
  const existingResource = await this.getResourceById({ resourceId });
7448
+ const collection = await this.getCollection(_mastra_core_storage.TABLE_RESOURCES);
7320
7449
  if (!existingResource) {
7321
7450
  const newResource = {
7322
7451
  id: resourceId,
@@ -7325,7 +7454,11 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7325
7454
  createdAt: /* @__PURE__ */ new Date(),
7326
7455
  updatedAt: /* @__PURE__ */ new Date()
7327
7456
  };
7328
- return this.saveResource({ resource: newResource });
7457
+ await withRunFence(this.#connector, check, (session) => collection.updateOne({ id: resourceId }, { $set: { ...newResource } }, {
7458
+ upsert: true,
7459
+ session
7460
+ }));
7461
+ return newResource;
7329
7462
  }
7330
7463
  const updatedResource = {
7331
7464
  ...existingResource,
@@ -7336,13 +7469,13 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7336
7469
  } : existingResource.metadata,
7337
7470
  updatedAt: /* @__PURE__ */ new Date()
7338
7471
  };
7339
- const collection = await this.getCollection(_mastra_core_storage.TABLE_RESOURCES);
7340
7472
  const updateDoc = { updatedAt: updatedResource.updatedAt };
7341
7473
  if (workingMemory !== void 0) updateDoc.workingMemory = workingMemory;
7342
7474
  if (metadata) updateDoc.metadata = updatedResource.metadata;
7343
- await collection.updateOne({ id: resourceId }, { $set: updateDoc });
7475
+ await withRunFence(this.#connector, check, (session) => collection.updateOne({ id: resourceId }, { $set: updateDoc }, { session }));
7344
7476
  return updatedResource;
7345
7477
  } catch (error) {
7478
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
7346
7479
  throw new _mastra_core_error.MastraError({
7347
7480
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_RESOURCE", "FAILED"),
7348
7481
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -7442,14 +7575,19 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7442
7575
  throw mastraError;
7443
7576
  }
7444
7577
  }
7445
- async saveThread({ thread }) {
7578
+ async saveThread({ thread, fence }) {
7446
7579
  try {
7447
- await (await this.getCollection(_mastra_core_storage.TABLE_THREADS)).updateOne({ id: thread.id }, { $set: {
7580
+ const collection = await this.getCollection(_mastra_core_storage.TABLE_THREADS);
7581
+ await withRunFence(this.#connector, this.#runFenceCheck(fence, "saveThread"), (session) => collection.updateOne({ id: thread.id }, { $set: {
7448
7582
  ...thread,
7449
7583
  metadata: thread.metadata
7450
- } }, { upsert: true });
7584
+ } }, {
7585
+ upsert: true,
7586
+ session
7587
+ }));
7451
7588
  return thread;
7452
7589
  } catch (error) {
7590
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
7453
7591
  throw new _mastra_core_error.MastraError({
7454
7592
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SAVE_THREAD", "FAILED"),
7455
7593
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -7458,7 +7596,8 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7458
7596
  }, error);
7459
7597
  }
7460
7598
  }
7461
- async updateThread({ id, title, metadata }) {
7599
+ async updateThread({ id, title, metadata, fence }) {
7600
+ const check = this.#runFenceCheck(fence, "updateThread");
7462
7601
  const thread = await this.getThreadById({ threadId: id });
7463
7602
  if (!thread) throw new _mastra_core_error.MastraError({
7464
7603
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_THREAD", "NOT_FOUND"),
@@ -7481,12 +7620,14 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7481
7620
  updatedAt: now
7482
7621
  };
7483
7622
  try {
7484
- await (await this.getCollection(_mastra_core_storage.TABLE_THREADS)).updateOne({ id }, { $set: {
7623
+ const collection = await this.getCollection(_mastra_core_storage.TABLE_THREADS);
7624
+ await withRunFence(this.#connector, check, (session) => collection.updateOne({ id }, { $set: {
7485
7625
  title: updatedThread.title,
7486
7626
  metadata: updatedThread.metadata,
7487
7627
  updatedAt: now
7488
- } });
7628
+ } }, { session }));
7489
7629
  } catch (error) {
7630
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
7490
7631
  throw new _mastra_core_error.MastraError({
7491
7632
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_THREAD", "FAILED"),
7492
7633
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -7509,16 +7650,19 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7509
7650
  }, error);
7510
7651
  }
7511
7652
  }
7512
- async deleteMessages(messageIds) {
7653
+ async deleteMessages(messageIds, options) {
7513
7654
  if (messageIds.length === 0) return;
7514
7655
  try {
7515
7656
  const messagesCollection = await this.getCollection(_mastra_core_storage.TABLE_MESSAGES);
7516
7657
  const threadsCollection = await this.getCollection(_mastra_core_storage.TABLE_THREADS);
7517
- const messagesToDelete = await messagesCollection.find({ id: { $in: messageIds } }).toArray();
7518
- const threadIds = [...new Set(messagesToDelete.map((m) => m.thread_id))];
7519
- await messagesCollection.deleteMany({ id: { $in: messageIds } });
7520
- if (threadIds.length > 0) await threadsCollection.updateMany({ id: { $in: threadIds } }, { $set: { updatedAt: /* @__PURE__ */ new Date() } });
7658
+ await withRunFence(this.#connector, this.#runFenceCheck(options?.fence, "deleteMessages"), async (session) => {
7659
+ const messagesToDelete = await messagesCollection.find({ id: { $in: messageIds } }, { session }).toArray();
7660
+ const threadIds = [...new Set(messagesToDelete.map((m) => m.thread_id))];
7661
+ await messagesCollection.deleteMany({ id: { $in: messageIds } }, { session });
7662
+ if (threadIds.length > 0) await threadsCollection.updateMany({ id: { $in: threadIds } }, { $set: { updatedAt: /* @__PURE__ */ new Date() } }, { session });
7663
+ });
7521
7664
  } catch (error) {
7665
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
7522
7666
  throw new _mastra_core_error.MastraError({
7523
7667
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "DELETE_MESSAGES", "FAILED"),
7524
7668
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -7885,7 +8029,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7885
8029
  applied: false,
7886
8030
  reason: "conflict"
7887
8031
  };
7888
- if ((await collection.updateOne({
8032
+ if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "updateActiveObservations"), (session) => collection.updateOne({
7889
8033
  id: input.id,
7890
8034
  supersededBy: null,
7891
8035
  ...matchStoredFields(doc, ["activeObservations", "lastObservedAt"])
@@ -7899,7 +8043,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7899
8043
  updatedAt: /* @__PURE__ */ new Date()
7900
8044
  },
7901
8045
  $inc: { totalTokensObserved: safeTokenCount }
7902
- })).matchedCount === 1) return { applied: true };
8046
+ }, { session }))).matchedCount === 1) return { applied: true };
7903
8047
  }
7904
8048
  throw omConflict("UPDATE_ACTIVE_OBSERVATIONS", input.id);
7905
8049
  } catch (error) {
@@ -7915,7 +8059,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7915
8059
  async createReflectionGeneration(input) {
7916
8060
  try {
7917
8061
  const { currentRecord } = input;
7918
- return await this.#rollOver(currentRecord, (doc) => {
8062
+ return await this.#rollOver(currentRecord, "createReflectionGeneration", (doc) => {
7919
8063
  const stored = this.parseOMDocument(doc);
7920
8064
  const plan = planReflectionGenerationText({
7921
8065
  storedObservations: stored.activeObservations,
@@ -8152,7 +8296,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8152
8296
  let hops = 0;
8153
8297
  let retries = 0;
8154
8298
  for (;;) {
8155
- if ((await collection.updateOne({
8299
+ if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "updateBufferedObservations"), (session) => collection.updateOne({
8156
8300
  id: targetId,
8157
8301
  supersededBy: null,
8158
8302
  "bufferedObservationChunks.cycleId": { $ne: input.chunk.cycleId },
@@ -8166,7 +8310,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8166
8310
  bufferedObservationChunks: { $concatArrays: [{ $ifNull: ["$bufferedObservationChunks", []] }, { $literal: [newChunk] }] },
8167
8311
  ...lastBufferedAtTime ? { lastBufferedAtTime: { $max: ["$lastBufferedAtTime", { $literal: lastBufferedAtTime }] } } : {},
8168
8312
  updatedAt: { $literal: /* @__PURE__ */ new Date() }
8169
- } }])).matchedCount === 1) return {
8313
+ } }], { session }))).matchedCount === 1) return {
8170
8314
  persisted: true,
8171
8315
  recordId: targetId
8172
8316
  };
@@ -8189,7 +8333,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8189
8333
  recordId: targetId
8190
8334
  };
8191
8335
  if (stored !== void 0 && stored !== null && !Array.isArray(stored)) {
8192
- if ((await collection.updateOne({
8336
+ if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "updateBufferedObservations"), (session) => collection.updateOne({
8193
8337
  id: targetId,
8194
8338
  supersededBy: null,
8195
8339
  bufferedObservationChunks: stored,
@@ -8198,7 +8342,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8198
8342
  bufferedObservationChunks: [newChunk],
8199
8343
  ...lastBufferedAtTime ? { lastBufferedAtTime: maxObservationCursor(doc.lastBufferedAtTime, lastBufferedAtTime) } : {},
8200
8344
  updatedAt: /* @__PURE__ */ new Date()
8201
- } })).matchedCount === 1) return {
8345
+ } }, { session }))).matchedCount === 1) return {
8202
8346
  persisted: true,
8203
8347
  recordId: targetId
8204
8348
  };
@@ -8288,7 +8432,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8288
8432
  const newTokenCount = existingTokenCount + activatedTokens;
8289
8433
  const existingPending = Number(doc.pendingMessageTokens || 0);
8290
8434
  const newPending = Math.max(0, existingPending - activatedMessageTokens);
8291
- if ((await collection.updateOne({
8435
+ if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "swapBufferedToActive"), (session) => collection.updateOne({
8292
8436
  id: input.id,
8293
8437
  supersededBy: null,
8294
8438
  ...matchStoredFields(doc, [
@@ -8305,7 +8449,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8305
8449
  bufferedObservationChunks: remainingChunks,
8306
8450
  lastObservedAt: maxObservationCursor(doc.lastObservedAt, lastObservedAt),
8307
8451
  updatedAt: /* @__PURE__ */ new Date()
8308
- } })).matchedCount === 0) continue;
8452
+ } }, { session }))).matchedCount === 0) continue;
8309
8453
  const latestChunkHints = activatedChunks[activatedChunks.length - 1];
8310
8454
  return {
8311
8455
  chunksActivated: activatedChunks.length,
@@ -8354,13 +8498,13 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8354
8498
  const newContent = existingContent ? `${existingContent}\n\n${input.reflection}` : input.reflection;
8355
8499
  const newTokens = existingTokens + input.tokenCount;
8356
8500
  const newInputTokens = existingInputTokens + input.inputTokenCount;
8357
- if ((await collection.updateOne({ id: input.id }, { $set: {
8501
+ if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "updateBufferedReflection"), (session) => collection.updateOne({ id: input.id }, { $set: {
8358
8502
  bufferedReflection: newContent,
8359
8503
  bufferedReflectionTokens: newTokens,
8360
8504
  bufferedReflectionInputTokens: newInputTokens,
8361
8505
  reflectedObservationLineCount: input.reflectedObservationLineCount,
8362
8506
  updatedAt: /* @__PURE__ */ new Date()
8363
- } })).matchedCount === 0) throw new _mastra_core_error.MastraError({
8507
+ } }, { session }))).matchedCount === 0) throw new _mastra_core_error.MastraError({
8364
8508
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_BUFFERED_REFLECTION", "NOT_FOUND"),
8365
8509
  text: `Observational memory record not found: ${input.id}`,
8366
8510
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -8380,7 +8524,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8380
8524
  async swapBufferedReflectionToActive(input) {
8381
8525
  try {
8382
8526
  const { currentRecord } = input;
8383
- return await this.#rollOver(currentRecord, (doc) => {
8527
+ return await this.#rollOver(currentRecord, "swapBufferedReflectionToActive", (doc) => {
8384
8528
  const stored = this.parseOMDocument(doc);
8385
8529
  const bufferedReflection = stored.bufferedReflection || "";
8386
8530
  if (!bufferedReflection) throw new _mastra_core_error.MastraError({
@@ -8466,7 +8610,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8466
8610
  *
8467
8611
  * A crash between 2 and 3 is finished by the next head read (`#getHeadDoc`) or startup.
8468
8612
  */
8469
- async #rollOver(currentRecord, decide) {
8613
+ async #rollOver(currentRecord, operation, decide) {
8470
8614
  const collection = await this.getCollection(OM_TABLE);
8471
8615
  for (let attempt = 1; attempt <= OM_MAX_CONDITIONAL_ATTEMPTS; attempt++) {
8472
8616
  const doc = await collection.findOne({ id: currentRecord.id });
@@ -8477,7 +8621,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8477
8621
  }
8478
8622
  const pending = decide(doc);
8479
8623
  if (!pending) return this.parseOMDocument(doc);
8480
- const frozen = await collection.findOneAndUpdate({
8624
+ const frozen = await withRunFence(this.#connector, this.#runFenceCheck(void 0, operation), (session) => collection.findOneAndUpdate({
8481
8625
  id: doc.id,
8482
8626
  supersededBy: null,
8483
8627
  ...matchStoredFields(doc, [
@@ -8489,7 +8633,10 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8489
8633
  }, { $set: {
8490
8634
  supersededBy: pending.newId,
8491
8635
  pendingSuccessor: pending
8492
- } }, { returnDocument: "before" });
8636
+ } }, {
8637
+ returnDocument: "before",
8638
+ session
8639
+ }));
8493
8640
  if (!frozen) continue;
8494
8641
  return this.parseOMDocument(await this.#completeRollover(collection, frozen, pending));
8495
8642
  }
@@ -11804,33 +11951,58 @@ var MongoDBWorkflowDefinitionsStore = class MongoDBWorkflowDefinitionsStore exte
11804
11951
  };
11805
11952
  //#endregion
11806
11953
  //#region src/storage/domains/workflows/index.ts
11954
+ const RUN_OWNER_PROJECTION = {
11955
+ generation: 1,
11956
+ ownerId: 1,
11957
+ leaseExpiresAt: 1,
11958
+ live: { $gt: ["$leaseExpiresAt", "$$NOW"] }
11959
+ };
11960
+ const NOT_LIVE = { $or: [{ leaseExpiresAt: null }, { $expr: { $lte: ["$leaseExpiresAt", "$$NOW"] } }] };
11961
+ function toRunOwnershipRecord(doc) {
11962
+ return {
11963
+ runId: doc._id,
11964
+ generation: Number(doc.generation),
11965
+ ownerId: doc.ownerId,
11966
+ leaseExpiresAt: doc.leaseExpiresAt ?? null,
11967
+ live: Boolean(doc.live)
11968
+ };
11969
+ }
11807
11970
  var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core_storage.WorkflowsStorage {
11808
11971
  #connector;
11809
11972
  #skipDefaultIndexes;
11810
11973
  #indexes;
11811
11974
  /** Collections managed by this domain */
11812
- static MANAGED_COLLECTIONS = [_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT];
11975
+ static MANAGED_COLLECTIONS = [_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS];
11813
11976
  /**
11814
- * Anchor is `updatedAt` (BSON date), so the policy reads as inactivity:
11815
- * a run is pruned only after its snapshot has not been touched for `maxAge`.
11977
+ * Both anchors are `updatedAt` (BSON date), so the policies read as
11978
+ * inactivity: a run is pruned only after its snapshot has not been touched
11979
+ * for `maxAge`, and an ownership document only after no claim, renewal or
11980
+ * release has touched it for `maxAge`.
11816
11981
  */
11817
- static retentionTables = { workflowSnapshot: {
11818
- table: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
11819
- column: "updatedAt",
11820
- indexed: true
11821
- } };
11982
+ static retentionTables = {
11983
+ workflowSnapshot: {
11984
+ table: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
11985
+ column: "updatedAt",
11986
+ indexed: true
11987
+ },
11988
+ runOwnership: {
11989
+ table: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
11990
+ column: "updatedAt",
11991
+ indexed: true
11992
+ }
11993
+ };
11822
11994
  constructor(config) {
11823
11995
  super();
11824
11996
  this.#connector = resolveMongoDBConfig(config);
11825
11997
  this.#skipDefaultIndexes = config.skipDefaultIndexes;
11826
11998
  this.#indexes = config.indexes?.filter((idx) => WorkflowsStorageMongoDB.MANAGED_COLLECTIONS.includes(idx.collection));
11827
11999
  }
11828
- /** Delete workflow snapshots idle for longer than the policy's `maxAge`, batched. */
12000
+ /** Delete workflow snapshots and ownership documents idle for longer than each policy's `maxAge`, batched. */
11829
12001
  async prune(policies, options) {
11830
12002
  const targets = resolveTargets({
11831
12003
  policies,
11832
12004
  descriptor: WorkflowsStorageMongoDB.retentionTables,
11833
- order: ["workflowSnapshot"]
12005
+ order: ["workflowSnapshot", "runOwnership"]
11834
12006
  });
11835
12007
  return runPrune({
11836
12008
  connector: this.#connector,
@@ -11843,6 +12015,13 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11843
12015
  supportsConcurrentUpdates() {
11844
12016
  return true;
11845
12017
  }
12018
+ /**
12019
+ * Fenced writes need multi-document transactions, so only replica sets and
12020
+ * sharded clusters fence. Rejects while the deployment can't be probed.
12021
+ */
12022
+ supportsRunFencing() {
12023
+ return this.#connector.probeTransactions();
12024
+ }
11846
12025
  async getCollection(name) {
11847
12026
  return this.#connector.getCollection(name);
11848
12027
  }
@@ -11850,6 +12029,124 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11850
12029
  await this.createDefaultIndexes();
11851
12030
  await this.createCustomIndexes();
11852
12031
  }
12032
+ #runFenceCheck(fence, runId, operation) {
12033
+ const resolved = (0, _mastra_core_storage.resolveRunFence)(this, fence, runId);
12034
+ return resolved && {
12035
+ claims: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
12036
+ fence: resolved,
12037
+ operation
12038
+ };
12039
+ }
12040
+ async #readRunOwner(runId) {
12041
+ const doc = await (await getRunClaims(this.#connector, _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS)).findOne({ _id: runId }, { projection: RUN_OWNER_PROJECTION });
12042
+ return doc ? toRunOwnershipRecord(doc) : null;
12043
+ }
12044
+ #ownershipError(operation, runId, error) {
12045
+ return new _mastra_core_error.MastraError({
12046
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", operation, "FAILED"),
12047
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
12048
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
12049
+ details: { runId }
12050
+ }, error);
12051
+ }
12052
+ async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
12053
+ try {
12054
+ const owners = await getRunClaims(this.#connector, _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS);
12055
+ const claim = (generation) => [{ $set: {
12056
+ generation,
12057
+ ownerId: { $literal: ownerId },
12058
+ leaseExpiresAt: { $add: ["$$NOW", leaseMs] },
12059
+ updatedAt: "$$NOW"
12060
+ } }];
12061
+ if (!expectedGeneration) {
12062
+ try {
12063
+ const created = await owners.findOneAndUpdate({
12064
+ _id: runId,
12065
+ generation: { $exists: false }
12066
+ }, claim({ $max: [1, { $toLong: "$$NOW" }] }), {
12067
+ upsert: true,
12068
+ returnDocument: "after",
12069
+ projection: RUN_OWNER_PROJECTION
12070
+ });
12071
+ if (created) return {
12072
+ acquired: true,
12073
+ record: toRunOwnershipRecord(created)
12074
+ };
12075
+ } catch (error) {
12076
+ if (!isDuplicateKeyError(error)) throw error;
12077
+ }
12078
+ if (expectedGeneration === 0) return {
12079
+ acquired: false,
12080
+ record: await this.#readRunOwner(runId)
12081
+ };
12082
+ }
12083
+ const advanced = await owners.findOneAndUpdate({
12084
+ _id: runId,
12085
+ ...expectedGeneration !== void 0 ? { generation: expectedGeneration } : {},
12086
+ ...force ? {} : NOT_LIVE
12087
+ }, claim({ $add: ["$generation", 1] }), {
12088
+ returnDocument: "after",
12089
+ projection: RUN_OWNER_PROJECTION
12090
+ });
12091
+ if (advanced) return {
12092
+ acquired: true,
12093
+ record: toRunOwnershipRecord(advanced)
12094
+ };
12095
+ return {
12096
+ acquired: false,
12097
+ record: await this.#readRunOwner(runId)
12098
+ };
12099
+ } catch (error) {
12100
+ throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", runId, error);
12101
+ }
12102
+ }
12103
+ async renewRunOwnership({ leaseMs, ...fence }) {
12104
+ try {
12105
+ const renewed = await (await getRunClaims(this.#connector, _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS)).findOneAndUpdate({
12106
+ _id: fence.runId,
12107
+ generation: fence.generation,
12108
+ ownerId: fence.ownerId,
12109
+ leaseExpiresAt: { $ne: null }
12110
+ }, [{ $set: {
12111
+ leaseExpiresAt: { $add: ["$$NOW", leaseMs] },
12112
+ updatedAt: "$$NOW"
12113
+ } }], {
12114
+ returnDocument: "after",
12115
+ projection: RUN_OWNER_PROJECTION
12116
+ });
12117
+ if (renewed) return {
12118
+ renewed: true,
12119
+ record: toRunOwnershipRecord(renewed)
12120
+ };
12121
+ return {
12122
+ renewed: false,
12123
+ record: await this.#readRunOwner(fence.runId)
12124
+ };
12125
+ } catch (error) {
12126
+ throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, error);
12127
+ }
12128
+ }
12129
+ async releaseRunOwnership(fence) {
12130
+ try {
12131
+ return (await (await getRunClaims(this.#connector, _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS)).updateOne({
12132
+ _id: fence.runId,
12133
+ generation: fence.generation,
12134
+ ownerId: fence.ownerId
12135
+ }, [{ $set: {
12136
+ leaseExpiresAt: null,
12137
+ updatedAt: "$$NOW"
12138
+ } }])).matchedCount > 0;
12139
+ } catch (error) {
12140
+ throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
12141
+ }
12142
+ }
12143
+ async getRunOwnership({ runId }) {
12144
+ try {
12145
+ return await this.#readRunOwner(runId);
12146
+ } catch (error) {
12147
+ throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
12148
+ }
12149
+ }
11853
12150
  /**
11854
12151
  * Returns default index definitions for the workflows domain collections.
11855
12152
  */
@@ -11909,8 +12206,10 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11909
12206
  }
11910
12207
  async dangerouslyClearAll() {
11911
12208
  await (await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT)).deleteMany({});
12209
+ await (await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS)).deleteMany({});
11912
12210
  }
11913
- async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state }) {
12211
+ async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence }) {
12212
+ const check = this.#runFenceCheck(fence, runId, "updateWorkflowResults");
11914
12213
  try {
11915
12214
  const collection = await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
11916
12215
  const now = /* @__PURE__ */ new Date();
@@ -11930,7 +12229,7 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11930
12229
  runId,
11931
12230
  requestContext: {}
11932
12231
  };
11933
- const updatedDoc = await collection.findOneAndUpdate({
12232
+ const updatedDoc = await withRunFence(this.#connector, check, (session) => collection.findOneAndUpdate({
11934
12233
  workflow_name: workflowName,
11935
12234
  run_id: runId
11936
12235
  }, [{ $set: {
@@ -11945,10 +12244,12 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11945
12244
  createdAt: { $ifNull: ["$createdAt", now] }
11946
12245
  } }], {
11947
12246
  upsert: true,
11948
- returnDocument: "after"
11949
- });
12247
+ returnDocument: "after",
12248
+ session
12249
+ }));
11950
12250
  return (typeof updatedDoc?.snapshot === "string" ? JSON.parse(updatedDoc.snapshot) : updatedDoc?.snapshot)?.context || {};
11951
12251
  } catch (error) {
12252
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
11952
12253
  throw new _mastra_core_error.MastraError({
11953
12254
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
11954
12255
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -11961,7 +12262,8 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11961
12262
  }, error);
11962
12263
  }
11963
12264
  }
11964
- async updateWorkflowState({ workflowName, runId, opts }) {
12265
+ async updateWorkflowState({ workflowName, runId, opts, fence }) {
12266
+ const check = this.#runFenceCheck(fence, runId, "updateWorkflowState");
11965
12267
  try {
11966
12268
  const collection = await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
11967
12269
  const { expectedStatus, ...state } = opts;
@@ -11970,10 +12272,13 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11970
12272
  run_id: runId
11971
12273
  };
11972
12274
  if (expectedStatus !== void 0) filter["snapshot.status"] = { $in: Array.isArray(expectedStatus) ? expectedStatus : [expectedStatus] };
11973
- const updatedDoc = await collection.findOneAndUpdate(filter, [{ $set: {
12275
+ const updatedDoc = await withRunFence(this.#connector, check, (session) => collection.findOneAndUpdate(filter, [{ $set: {
11974
12276
  snapshot: { $mergeObjects: ["$snapshot", state] },
11975
12277
  updatedAt: /* @__PURE__ */ new Date()
11976
- } }], { returnDocument: "after" });
12278
+ } }], {
12279
+ returnDocument: "after",
12280
+ session
12281
+ }));
11977
12282
  if (!updatedDoc) return;
11978
12283
  const snapshot = typeof updatedDoc.snapshot === "string" ? JSON.parse(updatedDoc.snapshot) : updatedDoc.snapshot;
11979
12284
  if (!snapshot?.context) throw new _mastra_core_error.MastraError({
@@ -11988,7 +12293,7 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11988
12293
  });
11989
12294
  return snapshot;
11990
12295
  } catch (error) {
11991
- if (error instanceof _mastra_core_error.MastraError) throw error;
12296
+ if (error instanceof _mastra_core_error.MastraError || (0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
11992
12297
  throw new _mastra_core_error.MastraError({
11993
12298
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_WORKFLOW_STATE", "FAILED"),
11994
12299
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -12000,10 +12305,12 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
12000
12305
  }, error);
12001
12306
  }
12002
12307
  }
12003
- async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
12308
+ async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
12309
+ const check = this.#runFenceCheck(fence, runId, "persistWorkflowSnapshot");
12004
12310
  try {
12005
12311
  const now = /* @__PURE__ */ new Date();
12006
- await (await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT)).updateOne({
12312
+ const collection = await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
12313
+ await withRunFence(this.#connector, check, (session) => collection.updateOne({
12007
12314
  workflow_name: workflowName,
12008
12315
  run_id: runId
12009
12316
  }, {
@@ -12015,8 +12322,12 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
12015
12322
  updatedAt: updatedAt ?? now
12016
12323
  },
12017
12324
  $setOnInsert: { createdAt: createdAt ?? now }
12018
- }, { upsert: true });
12325
+ }, {
12326
+ upsert: true,
12327
+ session
12328
+ }));
12019
12329
  } catch (error) {
12330
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
12020
12331
  throw new _mastra_core_error.MastraError({
12021
12332
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
12022
12333
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -12109,13 +12420,16 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
12109
12420
  }, error);
12110
12421
  }
12111
12422
  }
12112
- async deleteWorkflowRunById({ runId, workflowName }) {
12423
+ async deleteWorkflowRunById({ runId, workflowName, fence }) {
12424
+ const check = this.#runFenceCheck(fence, runId, "deleteWorkflowRunById");
12113
12425
  try {
12114
- await (await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT)).deleteOne({
12426
+ const collection = await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
12427
+ await withRunFence(this.#connector, check, (session) => collection.deleteOne({
12115
12428
  workflow_name: workflowName,
12116
12429
  run_id: runId
12117
- });
12430
+ }, { session }));
12118
12431
  } catch (error) {
12432
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
12119
12433
  throw new _mastra_core_error.MastraError({
12120
12434
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
12121
12435
  domain: _mastra_core_error.ErrorDomain.STORAGE,