@mastra/pg 1.31.0-alpha.2 → 1.31.0-alpha.4

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 (32) hide show
  1. package/dist/_types/@internal_core/dist/knowledge-compat/index.d.ts +594 -0
  2. package/dist/_types/@internal_core/package.json +1 -0
  3. package/dist/docs/SKILL.md +1 -1
  4. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  5. package/dist/docs/references/reference-storage-retention.md +21 -18
  6. package/dist/index.cjs +1749 -310
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.js +1752 -313
  9. package/dist/index.js.map +1 -1
  10. package/dist/shared/config.d.ts +6 -0
  11. package/dist/shared/config.d.ts.map +1 -1
  12. package/dist/storage/db/index.d.ts +15 -9
  13. package/dist/storage/db/index.d.ts.map +1 -1
  14. package/dist/storage/db/run-fencing.d.ts +23 -0
  15. package/dist/storage/db/run-fencing.d.ts.map +1 -0
  16. package/dist/storage/domains/agents/index.d.ts.map +1 -1
  17. package/dist/storage/domains/knowledge/index.d.ts +38 -9
  18. package/dist/storage/domains/knowledge/index.d.ts.map +1 -1
  19. package/dist/storage/domains/mcp-clients/index.d.ts.map +1 -1
  20. package/dist/storage/domains/mcp-servers/index.d.ts.map +1 -1
  21. package/dist/storage/domains/memory/index.d.ts +21 -7
  22. package/dist/storage/domains/memory/index.d.ts.map +1 -1
  23. package/dist/storage/domains/prompt-blocks/index.d.ts.map +1 -1
  24. package/dist/storage/domains/scorer-definitions/index.d.ts.map +1 -1
  25. package/dist/storage/domains/skills/index.d.ts.map +1 -1
  26. package/dist/storage/domains/workflows/index.d.ts +21 -6
  27. package/dist/storage/domains/workflows/index.d.ts.map +1 -1
  28. package/dist/storage/domains/workspaces/index.d.ts.map +1 -1
  29. package/dist/storage/index.d.ts.map +1 -1
  30. package/dist/storage/retention.d.ts +3 -3
  31. package/dist/storage/retention.d.ts.map +1 -1
  32. package/package.json +5 -4
package/dist/index.js CHANGED
@@ -1,18 +1,18 @@
1
1
  import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
2
2
  import * as coreStorage from "@mastra/core/storage";
3
- import { AgentsStorage, BRANCH_SPAN_TYPES, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, EntityType, ExperimentsStorage, FactoryStorage, FavoritesStorage, KNOWLEDGE_ACTIVITY_SCHEMA, KNOWLEDGE_CURSORS_SCHEMA, KNOWLEDGE_MENTIONS_SCHEMA, KNOWLEDGE_NODES_SCHEMA, KNOWLEDGE_RECORDS_SCHEMA, KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PromptBlocksStorage, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_KNOWLEDGE_ACTIVITY, TABLE_KNOWLEDGE_CURSORS, TABLE_KNOWLEDGE_MENTIONS, TABLE_KNOWLEDGE_NODES, TABLE_KNOWLEDGE_RECORDS, TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, assertKnowledgeCeilingRaised, assertKnowledgeScopeWithinCeiling, calculatePagination, canonicalizeKnowledgeScope, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getDefaultValue, getSqlType, hasErrorCode, isKnowledgeScopeVisible, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, matchesExpectedWorkflowStatus, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, parseDuration, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveListOrderBy, resolveRetentionTargets, retentionCutoffMs, runRetentionBatches, safelyParseJSON, storageMessageMatchesMetadataFilter, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
3
+ import { AgentsStorage, BRANCH_SPAN_TYPES, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, EntityType, ExperimentsStorage, FactoryStorage, FavoritesStorage, KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PromptBlocksStorage, RUN_FENCING_TABLE_SCHEMAS, RunFenceConflictError, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_KNOWLEDGE_ACTIVITY, TABLE_KNOWLEDGE_MENTIONS, TABLE_KNOWLEDGE_NODES, TABLE_KNOWLEDGE_RECORDS, TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MEMORY_RUN_FENCES, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, calculatePagination, canonicalizeKnowledgeScope, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getDefaultValue, getSqlType, hasErrorCode, isKnowledgeScopeVisible, isRunFenceConflictError, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, matchesExpectedWorkflowStatus, matchesRunFence, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, parseDuration, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveListOrderBy, resolveRetentionTargets, resolveRunFence, retentionCutoffMs, runRetentionBatches, safelyParseJSON, storageMessageMatchesMetadataFilter, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
4
4
  import { parseFieldKey, parseSqlIdentifier } from "@mastra/core/utils";
5
5
  import { MastraVector, validateTopK, validateUpsertInput } from "@mastra/core/vector";
6
6
  import { Mutex } from "async-mutex";
7
7
  import * as pg$1 from "pg";
8
- import pg, { Pool } from "pg";
8
+ import pg, { Pool, defaults } from "pg";
9
9
  import xxhash from "xxhash-wasm";
10
10
  import { parse } from "pg-connection-string";
11
11
  import { BaseFilterTranslator } from "@mastra/core/vector/filter";
12
12
  import { MastraBase } from "@mastra/core/base";
13
- import { createHash } from "crypto";
14
- import { MessageList } from "@mastra/core/agent";
13
+ import { createHash, randomUUID } from "crypto";
15
14
  import { coreFeatures } from "@mastra/core/features";
15
+ import { MessageList } from "@mastra/core/agent";
16
16
  import { saveScorePayloadSchema } from "@mastra/core/evals";
17
17
  import { skillSnapshotFieldValuesEqual } from "@mastra/core/storage/domains/skills";
18
18
  import { isDeepStrictEqual } from "util";
@@ -3189,9 +3189,10 @@ var PgDB = class extends MastraBase {
3189
3189
  }
3190
3190
  await flush();
3191
3191
  }
3192
- async insert({ tableName, record }) {
3192
+ /** `client` lets a caller run the insert inside its own transaction. */
3193
+ async insert({ tableName, record }, client = this.client) {
3193
3194
  try {
3194
- await this.executeInsert(this.client, {
3195
+ await this.executeInsert(client, {
3195
3196
  tableName,
3196
3197
  record
3197
3198
  });
@@ -4025,13 +4026,15 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
4025
4026
  indexName: tableName,
4026
4027
  schemaName: getSchemaName$6(this.schemaName)
4027
4028
  });
4029
+ const parsedColumn = `"${parseSqlIdentifier(column, "column name")}"`;
4028
4030
  const sql = `
4029
4031
  DELETE FROM ${fullTableName}
4030
4032
  WHERE ctid IN (
4031
4033
  SELECT ctid FROM ${fullTableName}
4032
- WHERE ${`"${parseSqlIdentifier(column, "column name")}"`} < $1
4034
+ WHERE ${parsedColumn} < $1
4033
4035
  LIMIT $2
4034
4036
  )
4037
+ AND ${parsedColumn} < $1
4035
4038
  `;
4036
4039
  return (await this.client.query(sql, [cutoff, limit])).rowCount ?? 0;
4037
4040
  }
@@ -4494,6 +4497,7 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
4494
4497
  }
4495
4498
  async create(input) {
4496
4499
  const { agent } = input;
4500
+ let draftInserted = false;
4497
4501
  try {
4498
4502
  const agentsTable = getTableName$5({
4499
4503
  indexName: TABLE_AGENTS,
@@ -4520,6 +4524,7 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
4520
4524
  nowIso,
4521
4525
  nowIso
4522
4526
  ]);
4527
+ draftInserted = true;
4523
4528
  const { id: _id, authorId: _authorId, visibility: _visibility, metadata: _metadata, ...snapshotConfig } = agent;
4524
4529
  const versionId = crypto.randomUUID();
4525
4530
  await this.createVersion({
@@ -4542,14 +4547,14 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
4542
4547
  updatedAt: now
4543
4548
  };
4544
4549
  } catch (error) {
4545
- if (error instanceof MastraError) throw error;
4546
- try {
4550
+ if (draftInserted) try {
4547
4551
  const agentsTable = getTableName$5({
4548
4552
  indexName: TABLE_AGENTS,
4549
4553
  schemaName: getSchemaName$5(this.#schema)
4550
4554
  });
4551
4555
  await this.#db.client.none(`DELETE FROM ${agentsTable} WHERE id = $1 AND status = 'draft' AND "activeVersionId" IS NULL`, [agent.id]);
4552
4556
  } catch {}
4557
+ if (error instanceof MastraError) throw error;
4553
4558
  throw new MastraError({
4554
4559
  id: createStorageErrorId("PG", "CREATE_AGENT", "FAILED"),
4555
4560
  domain: ErrorDomain.STORAGE,
@@ -8277,18 +8282,650 @@ var FavoritesPG = class FavoritesPG extends FavoritesStorage {
8277
8282
  }
8278
8283
  };
8279
8284
  //#endregion
8285
+ //#region ../../packages/_internal-core/dist/knowledge-compat/index.js
8286
+ const KNOWLEDGE_V2_CORE_FEATURE = "knowledge-v2";
8287
+ /** Hard cap on scope nodes returned by one `listScopeNodes` read. */
8288
+ const MAX_KNOWLEDGE_SCOPE_NODES = 1e3;
8289
+ function knowledgeScopeNodeFilterKey(input) {
8290
+ return JSON.stringify([
8291
+ input.withinAddress ?? null,
8292
+ input.addresses ? [...input.addresses].sort() : null,
8293
+ input.ids ? [...input.ids].sort() : null
8294
+ ]);
8295
+ }
8296
+ function createKnowledgeScopeNodeCursor(scope, input) {
8297
+ return encodeURIComponent(JSON.stringify({
8298
+ version: 1,
8299
+ type: "scope",
8300
+ name: scope.name,
8301
+ id: scope.id,
8302
+ filter: knowledgeScopeNodeFilterKey(input)
8303
+ }));
8304
+ }
8305
+ /** Validates the page size and cursor of a `listScopeNodes` query; throws on a cursor from a different query. */
8306
+ function parseListKnowledgeScopeNodesInput(input = {}) {
8307
+ const requested = Number.isFinite(input.limit) ? Math.trunc(input.limit) : MAX_KNOWLEDGE_SCOPE_NODES;
8308
+ const limit = Math.min(Math.max(requested, 1), MAX_KNOWLEDGE_SCOPE_NODES);
8309
+ if (!input.cursor) return {
8310
+ limit,
8311
+ after: null
8312
+ };
8313
+ let value;
8314
+ try {
8315
+ value = JSON.parse(decodeURIComponent(input.cursor));
8316
+ } catch {
8317
+ throw new Error("Invalid Knowledge scope node cursor.");
8318
+ }
8319
+ const parsed = value && typeof value === "object" ? value : {};
8320
+ if (parsed.version !== 1 || parsed.type !== "scope" || typeof parsed.name !== "string" || typeof parsed.id !== "string" || parsed.filter !== knowledgeScopeNodeFilterKey(input)) throw new Error("Knowledge scope node cursor does not match this query.");
8321
+ return {
8322
+ limit,
8323
+ after: {
8324
+ name: parsed.name,
8325
+ id: parsed.id
8326
+ }
8327
+ };
8328
+ }
8329
+ /** Builds a page from up to `limit + 1` name/id-ordered scope nodes. */
8330
+ function pageKnowledgeScopeNodes(rows, limit, input) {
8331
+ const scopes = rows.slice(0, limit);
8332
+ const last = scopes.at(-1);
8333
+ return {
8334
+ scopes,
8335
+ nextCursor: rows.length > limit && last ? createKnowledgeScopeNodeCursor(last, input) : null
8336
+ };
8337
+ }
8338
+ /** Clamps a requested member limit to 1–{@link MAX_KNOWLEDGE_SCOPE_MEMBERS_LIMIT}. */
8339
+ function knowledgeScopeMembersLimit(limit) {
8340
+ if (limit === void 0 || !Number.isFinite(limit)) return 500;
8341
+ return Math.min(Math.max(Math.trunc(limit), 1), 500);
8342
+ }
8343
+ /** Validates the page size and cursor of a `listScopeMembers` query; throws on a cursor from another scope. */
8344
+ function parseListKnowledgeScopeMembersInput(input) {
8345
+ const limit = knowledgeScopeMembersLimit(input.limit);
8346
+ if (!input.cursor) return {
8347
+ limit,
8348
+ after: null
8349
+ };
8350
+ let value;
8351
+ try {
8352
+ value = JSON.parse(decodeURIComponent(input.cursor));
8353
+ } catch {
8354
+ throw new Error("Invalid Knowledge scope member cursor.");
8355
+ }
8356
+ const parsed = value && typeof value === "object" ? value : {};
8357
+ const updatedAt = typeof parsed.updatedAt === "string" ? new Date(parsed.updatedAt) : /* @__PURE__ */ new Date(NaN);
8358
+ if (parsed.version !== 1 || parsed.type !== "scope-member" || parsed.scopeNodeId !== input.scopeNodeId || typeof parsed.name !== "string" || typeof parsed.id !== "string" || Number.isNaN(updatedAt.getTime())) throw new Error("Knowledge scope member cursor does not match this query.");
8359
+ return {
8360
+ limit,
8361
+ after: {
8362
+ updatedAt,
8363
+ name: parsed.name,
8364
+ id: parsed.id
8365
+ }
8366
+ };
8367
+ }
8368
+ /** Builds a page from up to `limit + 1` members in `listScopeMembers` order. */
8369
+ function pageKnowledgeScopeMembers(rows, limit, input) {
8370
+ const members = rows.slice(0, limit);
8371
+ const last = members.at(-1);
8372
+ const hasMore = rows.length > limit;
8373
+ return {
8374
+ members,
8375
+ hasMore,
8376
+ nextCursor: hasMore && last ? encodeURIComponent(JSON.stringify({
8377
+ version: 1,
8378
+ type: "scope-member",
8379
+ scopeNodeId: input.scopeNodeId,
8380
+ updatedAt: last.updatedAt.toISOString(),
8381
+ name: last.name,
8382
+ id: last.id
8383
+ })) : null
8384
+ };
8385
+ }
8386
+ const TABLE_KNOWLEDGE_NODES$1 = "mastra_knowledge_nodes";
8387
+ const TABLE_KNOWLEDGE_RECORDS$1 = "mastra_knowledge_records";
8388
+ const TABLE_KNOWLEDGE_MENTIONS$1 = "mastra_knowledge_mentions";
8389
+ const TABLE_KNOWLEDGE_ACTIVITY$1 = "mastra_knowledge_activity";
8390
+ const TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1 = "mastra_knowledge_semantic_outbox";
8391
+ const TABLE_KNOWLEDGE_NODE_SCOPES = "mastra_knowledge_node_scopes";
8392
+ const TABLE_KNOWLEDGE_RECORD_SCOPES = "mastra_knowledge_record_scopes";
8393
+ const TABLE_KNOWLEDGE_SCOPE_GRANTS = "mastra_knowledge_scope_grants";
8394
+ const TABLE_KNOWLEDGE_ACCESS_STATE = "mastra_knowledge_access_state";
8395
+ const TABLE_KNOWLEDGE_SCOPE_ADDRESSES = "mastra_knowledge_scope_addresses";
8396
+ const TABLE_KNOWLEDGE_NODE_ADDRESSES = "mastra_knowledge_node_addresses";
8397
+ const TABLE_KNOWLEDGE_IMPORT_STATE = "mastra_knowledge_import_state";
8398
+ const TABLE_KNOWLEDGE_IMPORT_RUNS = "mastra_knowledge_import_runs";
8399
+ const TABLE_KNOWLEDGE_PROPOSALS = "mastra_knowledge_proposals";
8400
+ const KNOWLEDGE_TABLE_NAMES = [
8401
+ TABLE_KNOWLEDGE_NODES$1,
8402
+ TABLE_KNOWLEDGE_RECORDS$1,
8403
+ TABLE_KNOWLEDGE_MENTIONS$1,
8404
+ TABLE_KNOWLEDGE_ACTIVITY$1,
8405
+ TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1,
8406
+ TABLE_KNOWLEDGE_NODE_SCOPES,
8407
+ TABLE_KNOWLEDGE_RECORD_SCOPES,
8408
+ TABLE_KNOWLEDGE_SCOPE_GRANTS,
8409
+ TABLE_KNOWLEDGE_ACCESS_STATE,
8410
+ TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
8411
+ TABLE_KNOWLEDGE_NODE_ADDRESSES,
8412
+ TABLE_KNOWLEDGE_IMPORT_STATE,
8413
+ TABLE_KNOWLEDGE_IMPORT_RUNS,
8414
+ TABLE_KNOWLEDGE_PROPOSALS
8415
+ ];
8416
+ /** Knowledge v1 tables with no v2 equivalent; an explicit schema reset drops them. */
8417
+ const RETIRED_KNOWLEDGE_TABLE_NAMES = ["mastra_knowledge_cursors"];
8418
+ /** Tables and indexes published v1 adapters created for every app, whether or not it used Knowledge. */
8419
+ const PUBLISHED_KNOWLEDGE_V1_TABLE_NAMES = /* @__PURE__ */ new Set([
8420
+ TABLE_KNOWLEDGE_NODES$1,
8421
+ TABLE_KNOWLEDGE_RECORDS$1,
8422
+ TABLE_KNOWLEDGE_MENTIONS$1,
8423
+ TABLE_KNOWLEDGE_ACTIVITY$1,
8424
+ TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1,
8425
+ ...RETIRED_KNOWLEDGE_TABLE_NAMES
8426
+ ]);
8427
+ const PUBLISHED_KNOWLEDGE_V1_INDEX_NAMES = /* @__PURE__ */ new Set([
8428
+ "idx_knowledge_nodes_identity",
8429
+ "idx_knowledge_nodes_scope",
8430
+ "idx_knowledge_records_node_latest",
8431
+ "idx_knowledge_records_thread_latest",
8432
+ "idx_knowledge_mentions_record",
8433
+ "idx_knowledge_activity_latest",
8434
+ "idx_knowledge_outbox_idempotency",
8435
+ "idx_knowledge_outbox_claim"
8436
+ ]);
8437
+ const KNOWLEDGE_V2_NODES_SCHEMA = {
8438
+ id: {
8439
+ type: "text",
8440
+ nullable: false,
8441
+ primaryKey: true
8442
+ },
8443
+ name: {
8444
+ type: "text",
8445
+ nullable: false
8446
+ },
8447
+ kind: {
8448
+ type: "text",
8449
+ nullable: true
8450
+ },
8451
+ isScope: {
8452
+ type: "boolean",
8453
+ nullable: false
8454
+ },
8455
+ metadata: {
8456
+ type: "jsonb",
8457
+ nullable: true
8458
+ },
8459
+ version: {
8460
+ type: "integer",
8461
+ nullable: false
8462
+ },
8463
+ createdAt: {
8464
+ type: "timestamp",
8465
+ nullable: false
8466
+ },
8467
+ updatedAt: {
8468
+ type: "timestamp",
8469
+ nullable: false
8470
+ },
8471
+ deletedAt: {
8472
+ type: "timestamp",
8473
+ nullable: true
8474
+ },
8475
+ deletedBy: {
8476
+ type: "text",
8477
+ nullable: true
8478
+ },
8479
+ type: {
8480
+ type: "text",
8481
+ nullable: true
8482
+ },
8483
+ canonicalName: {
8484
+ type: "text",
8485
+ nullable: true
8486
+ },
8487
+ content: {
8488
+ type: "text",
8489
+ nullable: true
8490
+ },
8491
+ description: {
8492
+ type: "text",
8493
+ nullable: true
8494
+ },
8495
+ scope: {
8496
+ type: "jsonb",
8497
+ nullable: true
8498
+ },
8499
+ scopeKey: {
8500
+ type: "text",
8501
+ nullable: true
8502
+ },
8503
+ mergedInto: {
8504
+ type: "text",
8505
+ nullable: true
8506
+ }
8507
+ };
8508
+ const KNOWLEDGE_V2_RECORDS_SCHEMA = {
8509
+ id: {
8510
+ type: "text",
8511
+ nullable: false,
8512
+ primaryKey: true
8513
+ },
8514
+ node: {
8515
+ type: "text",
8516
+ nullable: false,
8517
+ references: {
8518
+ table: TABLE_KNOWLEDGE_NODES$1,
8519
+ column: "id"
8520
+ }
8521
+ },
8522
+ text: {
8523
+ type: "text",
8524
+ nullable: false
8525
+ },
8526
+ metadata: {
8527
+ type: "jsonb",
8528
+ nullable: true
8529
+ },
8530
+ version: {
8531
+ type: "integer",
8532
+ nullable: false
8533
+ },
8534
+ capturedAt: {
8535
+ type: "timestamp",
8536
+ nullable: false
8537
+ },
8538
+ updatedAt: {
8539
+ type: "timestamp",
8540
+ nullable: false
8541
+ },
8542
+ deletedAt: {
8543
+ type: "timestamp",
8544
+ nullable: true
8545
+ },
8546
+ deletedBy: {
8547
+ type: "text",
8548
+ nullable: true
8549
+ },
8550
+ scope: {
8551
+ type: "jsonb",
8552
+ nullable: true
8553
+ },
8554
+ scopeKey: {
8555
+ type: "text",
8556
+ nullable: true
8557
+ },
8558
+ sourceThreadId: {
8559
+ type: "text",
8560
+ nullable: true
8561
+ },
8562
+ when: {
8563
+ type: "timestamp",
8564
+ nullable: true
8565
+ }
8566
+ };
8567
+ const KNOWLEDGE_V2_MENTIONS_SCHEMA = {
8568
+ recordId: {
8569
+ type: "text",
8570
+ nullable: false,
8571
+ references: {
8572
+ table: TABLE_KNOWLEDGE_RECORDS$1,
8573
+ column: "id"
8574
+ }
8575
+ },
8576
+ targetNodeId: {
8577
+ type: "text",
8578
+ nullable: true,
8579
+ references: {
8580
+ table: TABLE_KNOWLEDGE_NODES$1,
8581
+ column: "id"
8582
+ }
8583
+ },
8584
+ sourceType: {
8585
+ type: "text",
8586
+ nullable: true
8587
+ },
8588
+ sourceId: {
8589
+ type: "text",
8590
+ nullable: true
8591
+ }
8592
+ };
8593
+ const KNOWLEDGE_NODE_SCOPES_SCHEMA = {
8594
+ nodeId: {
8595
+ type: "text",
8596
+ nullable: false,
8597
+ references: {
8598
+ table: TABLE_KNOWLEDGE_NODES$1,
8599
+ column: "id"
8600
+ }
8601
+ },
8602
+ scopeNodeId: {
8603
+ type: "text",
8604
+ nullable: false,
8605
+ references: {
8606
+ table: TABLE_KNOWLEDGE_NODES$1,
8607
+ column: "id"
8608
+ }
8609
+ },
8610
+ addedAt: {
8611
+ type: "timestamp",
8612
+ nullable: false
8613
+ }
8614
+ };
8615
+ const KNOWLEDGE_RECORD_SCOPES_SCHEMA = {
8616
+ recordId: {
8617
+ type: "text",
8618
+ nullable: false,
8619
+ references: {
8620
+ table: TABLE_KNOWLEDGE_RECORDS$1,
8621
+ column: "id"
8622
+ }
8623
+ },
8624
+ scopeNodeId: {
8625
+ type: "text",
8626
+ nullable: false,
8627
+ references: {
8628
+ table: TABLE_KNOWLEDGE_NODES$1,
8629
+ column: "id"
8630
+ }
8631
+ },
8632
+ addedAt: {
8633
+ type: "timestamp",
8634
+ nullable: false
8635
+ }
8636
+ };
8637
+ const KNOWLEDGE_SCOPE_GRANTS_SCHEMA = {
8638
+ scopeNodeId: {
8639
+ type: "text",
8640
+ nullable: false,
8641
+ references: {
8642
+ table: TABLE_KNOWLEDGE_NODES$1,
8643
+ column: "id"
8644
+ }
8645
+ },
8646
+ scopeRefId: {
8647
+ type: "text",
8648
+ nullable: false,
8649
+ references: {
8650
+ table: TABLE_KNOWLEDGE_NODES$1,
8651
+ column: "id"
8652
+ }
8653
+ },
8654
+ role: {
8655
+ type: "text",
8656
+ nullable: false
8657
+ },
8658
+ canSuggest: {
8659
+ type: "boolean",
8660
+ nullable: true
8661
+ }
8662
+ };
8663
+ const KNOWLEDGE_ACCESS_STATE_SCHEMA = {
8664
+ id: {
8665
+ type: "text",
8666
+ nullable: false,
8667
+ primaryKey: true
8668
+ },
8669
+ epoch: {
8670
+ type: "integer",
8671
+ nullable: false
8672
+ },
8673
+ schemaVersion: {
8674
+ type: "integer",
8675
+ nullable: false
8676
+ }
8677
+ };
8678
+ const KNOWLEDGE_SCOPE_ADDRESSES_SCHEMA = {
8679
+ address: {
8680
+ type: "text",
8681
+ nullable: false,
8682
+ primaryKey: true
8683
+ },
8684
+ scopeNodeId: {
8685
+ type: "text",
8686
+ nullable: false,
8687
+ references: {
8688
+ table: TABLE_KNOWLEDGE_NODES$1,
8689
+ column: "id"
8690
+ }
8691
+ }
8692
+ };
8693
+ const KNOWLEDGE_NODE_ADDRESSES_SCHEMA = {
8694
+ source: {
8695
+ type: "text",
8696
+ nullable: false
8697
+ },
8698
+ address: {
8699
+ type: "text",
8700
+ nullable: false
8701
+ },
8702
+ nodeId: {
8703
+ type: "text",
8704
+ nullable: false,
8705
+ references: {
8706
+ table: TABLE_KNOWLEDGE_NODES$1,
8707
+ column: "id"
8708
+ }
8709
+ }
8710
+ };
8711
+ const KNOWLEDGE_IMPORT_STATE_SCHEMA = {
8712
+ importerId: {
8713
+ type: "text",
8714
+ nullable: false
8715
+ },
8716
+ binding: {
8717
+ type: "text",
8718
+ nullable: false
8719
+ },
8720
+ key: {
8721
+ type: "text",
8722
+ nullable: false
8723
+ },
8724
+ value: {
8725
+ type: "text",
8726
+ nullable: false
8727
+ }
8728
+ };
8729
+ const KNOWLEDGE_IMPORT_RUNS_SCHEMA = {
8730
+ id: {
8731
+ type: "text",
8732
+ nullable: false,
8733
+ primaryKey: true
8734
+ },
8735
+ importerId: {
8736
+ type: "text",
8737
+ nullable: false
8738
+ },
8739
+ binding: {
8740
+ type: "text",
8741
+ nullable: false
8742
+ },
8743
+ importKind: {
8744
+ type: "text",
8745
+ nullable: false
8746
+ },
8747
+ triggerKind: {
8748
+ type: "text",
8749
+ nullable: false
8750
+ },
8751
+ status: {
8752
+ type: "text",
8753
+ nullable: false
8754
+ },
8755
+ error: {
8756
+ type: "text",
8757
+ nullable: true
8758
+ },
8759
+ transcriptThreadId: {
8760
+ type: "text",
8761
+ nullable: true
8762
+ },
8763
+ traceId: {
8764
+ type: "text",
8765
+ nullable: true
8766
+ },
8767
+ queuedAt: {
8768
+ type: "timestamp",
8769
+ nullable: false
8770
+ },
8771
+ startedAt: {
8772
+ type: "timestamp",
8773
+ nullable: true
8774
+ },
8775
+ completedAt: {
8776
+ type: "timestamp",
8777
+ nullable: true
8778
+ }
8779
+ };
8780
+ const KNOWLEDGE_V2_ACTIVITY_SCHEMA = {
8781
+ id: {
8782
+ type: "text",
8783
+ nullable: false,
8784
+ primaryKey: true
8785
+ },
8786
+ action: {
8787
+ type: "text",
8788
+ nullable: false
8789
+ },
8790
+ targetType: {
8791
+ type: "text",
8792
+ nullable: true
8793
+ },
8794
+ targetId: {
8795
+ type: "text",
8796
+ nullable: true
8797
+ },
8798
+ contextScopeId: {
8799
+ type: "text",
8800
+ nullable: true,
8801
+ references: {
8802
+ table: TABLE_KNOWLEDGE_NODES$1,
8803
+ column: "id"
8804
+ }
8805
+ },
8806
+ importRunId: {
8807
+ type: "text",
8808
+ nullable: true,
8809
+ references: {
8810
+ table: TABLE_KNOWLEDGE_IMPORT_RUNS,
8811
+ column: "id"
8812
+ }
8813
+ },
8814
+ details: {
8815
+ type: "jsonb",
8816
+ nullable: true
8817
+ },
8818
+ createdAt: {
8819
+ type: "timestamp",
8820
+ nullable: false
8821
+ },
8822
+ recordType: {
8823
+ type: "text",
8824
+ nullable: true
8825
+ },
8826
+ recordId: {
8827
+ type: "text",
8828
+ nullable: true
8829
+ },
8830
+ scope: {
8831
+ type: "jsonb",
8832
+ nullable: true
8833
+ },
8834
+ scopeKey: {
8835
+ type: "text",
8836
+ nullable: true
8837
+ },
8838
+ sourceThreadId: {
8839
+ type: "text",
8840
+ nullable: true
8841
+ }
8842
+ };
8843
+ const KNOWLEDGE_PROPOSALS_SCHEMA = {
8844
+ id: {
8845
+ type: "text",
8846
+ nullable: false,
8847
+ primaryKey: true
8848
+ },
8849
+ targetType: {
8850
+ type: "text",
8851
+ nullable: false
8852
+ },
8853
+ targetId: {
8854
+ type: "text",
8855
+ nullable: false
8856
+ },
8857
+ action: {
8858
+ type: "text",
8859
+ nullable: false
8860
+ },
8861
+ changes: {
8862
+ type: "jsonb",
8863
+ nullable: false
8864
+ },
8865
+ reason: {
8866
+ type: "text",
8867
+ nullable: true
8868
+ },
8869
+ proposerContextScopeId: {
8870
+ type: "text",
8871
+ nullable: false
8872
+ },
8873
+ expectedVersion: {
8874
+ type: "integer",
8875
+ nullable: false
8876
+ },
8877
+ status: {
8878
+ type: "text",
8879
+ nullable: false
8880
+ },
8881
+ reviewerContextScopeId: {
8882
+ type: "text",
8883
+ nullable: true
8884
+ },
8885
+ reviewedAt: {
8886
+ type: "timestamp",
8887
+ nullable: true
8888
+ },
8889
+ createdAt: {
8890
+ type: "timestamp",
8891
+ nullable: false
8892
+ }
8893
+ };
8894
+ function assertKnowledgeV2CoreSupport(features) {
8895
+ if (!features.has("knowledge-v2")) throw new Error(`Knowledge v2 requires a @mastra/core release with the "${KNOWLEDGE_V2_CORE_FEATURE}" feature. Upgrade @mastra/core to use Knowledge; other storage domains keep working on this version.`);
8896
+ }
8897
+ function resolveKnowledgeV2Core(storageModule) {
8898
+ if (typeof storageModule !== "object" || storageModule === null) throw new Error("@mastra/core advertises Knowledge v2 without the required storage API");
8899
+ const { assertKnowledgeDescriptionWithinBound: assertDescription, assertKnowledgeRecordTextWithinBound: assertRecordText, assertKnowledgeSchemaCompatible: assertCompatible, inspectKnowledgeSchema: inspectSchema } = storageModule;
8900
+ if (typeof assertDescription !== "function" || typeof assertRecordText !== "function" || typeof assertCompatible !== "function" || typeof inspectSchema !== "function") throw new Error("@mastra/core advertises Knowledge v2 without the required storage API");
8901
+ return {
8902
+ assertKnowledgeDescriptionWithinBound: (description) => assertDescription(description),
8903
+ assertKnowledgeRecordTextWithinBound: (text) => assertRecordText(text),
8904
+ assertKnowledgeSchemaCompatible: (inspection) => assertCompatible(inspection),
8905
+ inspectKnowledgeSchema: (snapshot) => inspectSchema(snapshot)
8906
+ };
8907
+ }
8908
+ function createKnowledgeV2CoreLoader(features, loadStorageModule) {
8909
+ let knowledgeV2Core;
8910
+ return async () => {
8911
+ assertKnowledgeV2CoreSupport(features);
8912
+ knowledgeV2Core ??= loadStorageModule().then(resolveKnowledgeV2Core).catch((error) => {
8913
+ knowledgeV2Core = void 0;
8914
+ throw error;
8915
+ });
8916
+ return knowledgeV2Core;
8917
+ };
8918
+ }
8919
+ //#endregion
8280
8920
  //#region src/storage/domains/knowledge/index.ts
8281
- let assertDescriptionWithinBound;
8921
+ const loadKnowledgeV2Core = createKnowledgeV2CoreLoader(coreFeatures, () => import("@mastra/core/storage"));
8282
8922
  async function assertKnowledgeDescriptionWithinBoundCompat(description) {
8283
- let assertWithinBound = assertDescriptionWithinBound;
8284
- if (!assertWithinBound) {
8285
- const resolvedAssert = (await import("@mastra/core/storage")).assertKnowledgeDescriptionWithinBound ?? ((value) => {
8286
- if (value !== void 0 && value.length > 400) throw new Error("Knowledge node description exceeds the 400 UTF-16 code unit limit");
8287
- });
8288
- assertDescriptionWithinBound = resolvedAssert;
8289
- assertWithinBound = resolvedAssert;
8290
- }
8291
- assertWithinBound(description);
8923
+ const { assertKnowledgeDescriptionWithinBound } = await loadKnowledgeV2Core();
8924
+ assertKnowledgeDescriptionWithinBound(description);
8925
+ }
8926
+ async function assertKnowledgeRecordTextWithinBoundCompat(text) {
8927
+ const { assertKnowledgeRecordTextWithinBound } = await loadKnowledgeV2Core();
8928
+ assertKnowledgeRecordTextWithinBound(text);
8292
8929
  }
8293
8930
  const camelCaseColumns = [
8294
8931
  "canonicalName",
@@ -8299,7 +8936,6 @@ const camelCaseColumns = [
8299
8936
  "node",
8300
8937
  "sourceThreadId",
8301
8938
  "capturedAt",
8302
- "maxScope",
8303
8939
  "deletedAt",
8304
8940
  "deletedBy",
8305
8941
  "sourceType",
@@ -8313,7 +8949,30 @@ const camelCaseColumns = [
8313
8949
  "availableAt",
8314
8950
  "claimedAt",
8315
8951
  "claimedBy",
8316
- "completedAt"
8952
+ "completedAt",
8953
+ "isScope",
8954
+ "nodeId",
8955
+ "targetNodeId",
8956
+ "scopeNodeId",
8957
+ "scopeRefId",
8958
+ "canSuggest",
8959
+ "schemaVersion",
8960
+ "importerId",
8961
+ "importKind",
8962
+ "triggerKind",
8963
+ "transcriptThreadId",
8964
+ "traceId",
8965
+ "queuedAt",
8966
+ "startedAt",
8967
+ "targetType",
8968
+ "targetId",
8969
+ "contextScopeId",
8970
+ "importRunId",
8971
+ "proposerContextScopeId",
8972
+ "expectedVersion",
8973
+ "reviewerContextScopeId",
8974
+ "reviewedAt",
8975
+ "addedAt"
8317
8976
  ];
8318
8977
  function transformSqlCode(sql, transform) {
8319
8978
  return sql.split(/('(?:''|[^'])*')/g).map((part, index) => index % 2 === 0 ? transform(part) : part).join("");
@@ -8328,14 +8987,7 @@ function postgresSql(sql, schemaName) {
8328
8987
  const quotedSchema = `"${parseSchemaName(schemaName)}"`;
8329
8988
  normalized = transformSqlCode(normalized, (code) => {
8330
8989
  let transformed = code;
8331
- for (const table of [
8332
- TABLE_KNOWLEDGE_NODES,
8333
- TABLE_KNOWLEDGE_RECORDS,
8334
- TABLE_KNOWLEDGE_MENTIONS,
8335
- TABLE_KNOWLEDGE_CURSORS,
8336
- TABLE_KNOWLEDGE_ACTIVITY,
8337
- TABLE_KNOWLEDGE_SEMANTIC_OUTBOX
8338
- ]) transformed = transformed.replaceAll(`"${table}"`, `${quotedSchema}."${table}"`);
8990
+ for (const table of [...KNOWLEDGE_TABLE_NAMES, ...RETIRED_KNOWLEDGE_TABLE_NAMES]) transformed = transformed.replaceAll(`"${table}"`, `${quotedSchema}."${table}"`);
8339
8991
  return transformed;
8340
8992
  });
8341
8993
  }
@@ -8343,17 +8995,35 @@ function postgresSql(sql, schemaName) {
8343
8995
  return transformSqlCode(normalized, (code) => code.replace(/\?/g, () => `$${++index}`));
8344
8996
  }
8345
8997
  function createExecutor(client, schemaName) {
8346
- return { async execute(statement) {
8998
+ const execute = async (statement, qualifyWithSchema) => {
8347
8999
  const sql = typeof statement === "string" ? statement : statement.sql;
8348
9000
  const args = typeof statement === "string" ? [] : statement.args ?? [];
8349
- const result = await client.query(postgresSql(sql, schemaName), args);
9001
+ const result = await client.query(postgresSql(sql, qualifyWithSchema ? schemaName : void 0), args);
8350
9002
  return {
8351
9003
  rows: result.rows,
8352
9004
  rowsAffected: result.rowCount ?? 0
8353
9005
  };
8354
- } };
9006
+ };
9007
+ return {
9008
+ execute: (statement) => execute(statement, true),
9009
+ executeRaw: (statement) => execute(statement, false)
9010
+ };
8355
9011
  }
8356
- const visibleSql = `(scopeKey = ? OR LEFT(?, LENGTH(scopeKey) + 1) = scopeKey || chr(31))`;
9012
+ /**
9013
+ * Scope keys visible to a caller holding `scope`: a row is visible when its scope is a subset of the
9014
+ * caller's (companion entries such as `resource:x:uncurated` make this more than a prefix match).
9015
+ * Matching rows by `scopeKey` keeps visibility filters on an index; a caller scope has only a few
9016
+ * entries, so the list stays small.
9017
+ */
9018
+ function visibleScopeKeys(scope) {
9019
+ const canonical = canonicalizeKnowledgeScope(scope);
9020
+ const keys = /* @__PURE__ */ new Set();
9021
+ for (let mask = 1; mask < 1 << canonical.length; mask++) try {
9022
+ keys.add(knowledgeScopeKey(canonical.filter((_, index) => mask & 1 << index)));
9023
+ } catch {}
9024
+ return [...keys];
9025
+ }
9026
+ const visibleSql = (keys, scopeKeyColumn = "scopeKey") => `${scopeKeyColumn} IN (${keys.map(() => "?").join(",")})`;
8357
9027
  function parseJson$1(value) {
8358
9028
  if (typeof value === "string") return JSON.parse(value);
8359
9029
  if (value instanceof Uint8Array) return JSON.parse(new TextDecoder().decode(value));
@@ -8393,9 +9063,10 @@ function parseNode(row) {
8393
9063
  id: String(row.id),
8394
9064
  type: "node",
8395
9065
  name: String(row.name),
8396
- kind: String(row.kind),
9066
+ kind: row.kind == null ? "" : String(row.kind),
8397
9067
  content: row.content == null ? void 0 : String(row.content),
8398
9068
  description: row.description == null ? void 0 : String(row.description),
9069
+ isScope: row.isScope === true || row.isScope === 1 ? true : void 0,
8399
9070
  scope: parseJson$1(row.scopeJson ?? row.scope),
8400
9071
  version: Number(row.version),
8401
9072
  mergedInto: row.mergedInto == null ? void 0 : String(row.mergedInto),
@@ -8412,7 +9083,6 @@ function parseKnowledge(row) {
8412
9083
  sourceThreadId: String(row.sourceThreadId),
8413
9084
  capturedAt: toDate$1(row.capturedAt),
8414
9085
  when: optionalDate(row.when),
8415
- maxScope: row.maxScope == null ? void 0 : String(row.maxScope),
8416
9086
  metadata: row.metadata == null ? void 0 : parseJson$1(row.metadata),
8417
9087
  deletedAt: optionalDate(row.deletedAt),
8418
9088
  deletedBy: row.deletedBy == null ? void 0 : String(row.deletedBy)
@@ -8449,6 +9119,10 @@ function knowledgeIndexes(schemaName) {
8449
9119
  name: "idx_knowledge_nodes_scope",
8450
9120
  sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_nodes_scope ON ${table(TABLE_KNOWLEDGE_NODES)} ("scopeKey", "type");`
8451
9121
  },
9122
+ {
9123
+ name: "idx_knowledge_nodes_name",
9124
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_nodes_name ON ${table(TABLE_KNOWLEDGE_NODES)} ("type", "canonicalName");`
9125
+ },
8452
9126
  {
8453
9127
  name: "idx_knowledge_records_node_latest",
8454
9128
  sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_records_node_latest ON ${table(TABLE_KNOWLEDGE_RECORDS)} ("node", "id" DESC);`
@@ -8465,6 +9139,14 @@ function knowledgeIndexes(schemaName) {
8465
9139
  name: "idx_knowledge_activity_latest",
8466
9140
  sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_activity_latest ON ${table(TABLE_KNOWLEDGE_ACTIVITY)} ("id" DESC);`
8467
9141
  },
9142
+ {
9143
+ name: "idx_knowledge_records_scope",
9144
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_records_scope ON ${table(TABLE_KNOWLEDGE_RECORDS)} ("scopeKey", "id");`
9145
+ },
9146
+ {
9147
+ name: "idx_knowledge_activity_scope",
9148
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_activity_scope ON ${table(TABLE_KNOWLEDGE_ACTIVITY)} ("scopeKey", "id" DESC);`
9149
+ },
8468
9150
  {
8469
9151
  name: "idx_knowledge_outbox_idempotency",
8470
9152
  sql: `CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_outbox_idempotency ON ${table(TABLE_KNOWLEDGE_SEMANTIC_OUTBOX)} ("idempotencyKey");`
@@ -8472,67 +9154,152 @@ function knowledgeIndexes(schemaName) {
8472
9154
  {
8473
9155
  name: "idx_knowledge_outbox_claim",
8474
9156
  sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_outbox_claim ON ${table(TABLE_KNOWLEDGE_SEMANTIC_OUTBOX)} ("status", "availableAt", "createdAt");`
9157
+ },
9158
+ {
9159
+ name: "idx_knowledge_node_scopes_scope",
9160
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_node_scopes_scope ON ${table(TABLE_KNOWLEDGE_NODE_SCOPES)} ("scopeNodeId", "nodeId");`
9161
+ },
9162
+ {
9163
+ name: "idx_knowledge_record_scopes_scope",
9164
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_record_scopes_scope ON ${table(TABLE_KNOWLEDGE_RECORD_SCOPES)} ("scopeNodeId", "recordId");`
9165
+ },
9166
+ {
9167
+ name: "idx_knowledge_scope_grants_ref",
9168
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_scope_grants_ref ON ${table(TABLE_KNOWLEDGE_SCOPE_GRANTS)} ("scopeRefId", "scopeNodeId");`
9169
+ },
9170
+ {
9171
+ name: "idx_knowledge_node_addresses_node",
9172
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_node_addresses_node ON ${table(TABLE_KNOWLEDGE_NODE_ADDRESSES)} ("nodeId");`
9173
+ },
9174
+ {
9175
+ name: "idx_knowledge_import_runs_lookup",
9176
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_import_runs_lookup ON ${table(TABLE_KNOWLEDGE_IMPORT_RUNS)} ("importerId", "binding", "queuedAt" DESC);`
9177
+ },
9178
+ {
9179
+ name: "idx_knowledge_activity_import_run",
9180
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_activity_import_run ON ${table(TABLE_KNOWLEDGE_ACTIVITY)} ("importRunId", "id" DESC);`
8475
9181
  }
8476
9182
  ];
8477
9183
  }
8478
9184
  function knowledgeIndexDDL(schemaName) {
8479
9185
  return knowledgeIndexes(schemaName).map((index) => index.sql);
8480
9186
  }
9187
+ const KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE = "Knowledge curation cursors were removed: observation-time curate is the only Knowledge writer and needs no cursor.";
9188
+ const knowledgeTableDefinitions = [
9189
+ {
9190
+ tableName: TABLE_KNOWLEDGE_NODES,
9191
+ schema: KNOWLEDGE_V2_NODES_SCHEMA
9192
+ },
9193
+ {
9194
+ tableName: TABLE_KNOWLEDGE_RECORDS,
9195
+ schema: KNOWLEDGE_V2_RECORDS_SCHEMA
9196
+ },
9197
+ {
9198
+ tableName: TABLE_KNOWLEDGE_MENTIONS,
9199
+ schema: KNOWLEDGE_V2_MENTIONS_SCHEMA,
9200
+ compositePrimaryKey: ["recordId", "sourceId"]
9201
+ },
9202
+ {
9203
+ tableName: TABLE_KNOWLEDGE_NODE_SCOPES,
9204
+ schema: KNOWLEDGE_NODE_SCOPES_SCHEMA,
9205
+ compositePrimaryKey: ["nodeId", "scopeNodeId"]
9206
+ },
9207
+ {
9208
+ tableName: TABLE_KNOWLEDGE_RECORD_SCOPES,
9209
+ schema: KNOWLEDGE_RECORD_SCOPES_SCHEMA,
9210
+ compositePrimaryKey: ["recordId", "scopeNodeId"]
9211
+ },
9212
+ {
9213
+ tableName: TABLE_KNOWLEDGE_SCOPE_GRANTS,
9214
+ schema: KNOWLEDGE_SCOPE_GRANTS_SCHEMA,
9215
+ compositePrimaryKey: ["scopeNodeId", "scopeRefId"]
9216
+ },
9217
+ {
9218
+ tableName: TABLE_KNOWLEDGE_ACCESS_STATE,
9219
+ schema: KNOWLEDGE_ACCESS_STATE_SCHEMA
9220
+ },
9221
+ {
9222
+ tableName: TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
9223
+ schema: KNOWLEDGE_SCOPE_ADDRESSES_SCHEMA
9224
+ },
9225
+ {
9226
+ tableName: TABLE_KNOWLEDGE_NODE_ADDRESSES,
9227
+ schema: KNOWLEDGE_NODE_ADDRESSES_SCHEMA,
9228
+ compositePrimaryKey: ["source", "address"]
9229
+ },
9230
+ {
9231
+ tableName: TABLE_KNOWLEDGE_IMPORT_STATE,
9232
+ schema: KNOWLEDGE_IMPORT_STATE_SCHEMA,
9233
+ compositePrimaryKey: [
9234
+ "importerId",
9235
+ "binding",
9236
+ "key"
9237
+ ]
9238
+ },
9239
+ {
9240
+ tableName: TABLE_KNOWLEDGE_IMPORT_RUNS,
9241
+ schema: KNOWLEDGE_IMPORT_RUNS_SCHEMA
9242
+ },
9243
+ {
9244
+ tableName: TABLE_KNOWLEDGE_PROPOSALS,
9245
+ schema: KNOWLEDGE_PROPOSALS_SCHEMA
9246
+ },
9247
+ {
9248
+ tableName: TABLE_KNOWLEDGE_ACTIVITY,
9249
+ schema: KNOWLEDGE_V2_ACTIVITY_SCHEMA
9250
+ },
9251
+ {
9252
+ tableName: TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
9253
+ schema: KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA
9254
+ }
9255
+ ];
9256
+ function pgSetting(config, key) {
9257
+ const value = config[key] || process.env[`PG${key.toUpperCase()}`] || defaults[key];
9258
+ return value === void 0 || value === null || value === "" ? void 0 : String(value);
9259
+ }
9260
+ function effectivePgTarget(config) {
9261
+ const host = pgSetting(config, "host");
9262
+ const database = pgSetting(config, "database") ?? pgSetting(config, "user");
9263
+ if (!host || !database) return void 0;
9264
+ return `${host.startsWith("/") ? host : host.toLocaleLowerCase()}:${pgSetting(config, "port") ?? "5432"}/${database}`;
9265
+ }
9266
+ function canonicalPgTarget(config) {
9267
+ if (config.connectionString) try {
9268
+ const parsed = new URL(config.connectionString);
9269
+ return effectivePgTarget({
9270
+ host: decodeURIComponent(parsed.hostname) || config.host,
9271
+ port: parsed.port || config.port,
9272
+ database: decodeURIComponent(parsed.pathname.slice(1)) || config.database,
9273
+ user: decodeURIComponent(parsed.username) || config.user
9274
+ });
9275
+ } catch {
9276
+ return config.connectionString;
9277
+ }
9278
+ return effectivePgTarget(config);
9279
+ }
9280
+ function pgSourceConfig(source) {
9281
+ if (!source || !("options" in source)) return void 0;
9282
+ return source.options;
9283
+ }
9284
+ function pgClientPool(client) {
9285
+ if (!client || !("$pool" in client)) return void 0;
9286
+ return client.$pool;
9287
+ }
9288
+ function getPgKnowledgeIsolationKey(config) {
9289
+ const schema = config.schemaName ?? "public";
9290
+ const poolConfig = pgSourceConfig(config.pool ?? pgClientPool(config.client));
9291
+ const target = Boolean(config.connectionString || config.host || config.database) ? canonicalPgTarget(config) : poolConfig ? canonicalPgTarget(poolConfig) : void 0;
9292
+ if (target) return `pg:${target}:schema:${schema}`;
9293
+ return `pg:unidentified:schema:${schema}`;
9294
+ }
8481
9295
  var KnowledgePG = class extends KnowledgeStorage {
8482
- static MANAGED_TABLES = [
8483
- TABLE_KNOWLEDGE_NODES,
8484
- TABLE_KNOWLEDGE_RECORDS,
8485
- TABLE_KNOWLEDGE_MENTIONS,
8486
- TABLE_KNOWLEDGE_CURSORS,
8487
- TABLE_KNOWLEDGE_ACTIVITY,
8488
- TABLE_KNOWLEDGE_SEMANTIC_OUTBOX
8489
- ];
9296
+ static MANAGED_TABLES = KNOWLEDGE_TABLE_NAMES;
8490
9297
  static getExportDDL(schemaName) {
8491
- return [
8492
- generateTableSQL({
8493
- tableName: TABLE_KNOWLEDGE_NODES,
8494
- schema: KNOWLEDGE_NODES_SCHEMA,
8495
- schemaName,
8496
- includeAllConstraints: true
8497
- }),
8498
- generateTableSQL({
8499
- tableName: TABLE_KNOWLEDGE_RECORDS,
8500
- schema: KNOWLEDGE_RECORDS_SCHEMA,
8501
- schemaName,
8502
- includeAllConstraints: true
8503
- }),
8504
- generateTableSQL({
8505
- tableName: TABLE_KNOWLEDGE_MENTIONS,
8506
- schema: KNOWLEDGE_MENTIONS_SCHEMA,
8507
- schemaName,
8508
- compositePrimaryKey: [
8509
- "sourceType",
8510
- "sourceId",
8511
- "recordId"
8512
- ],
8513
- includeAllConstraints: true
8514
- }),
8515
- generateTableSQL({
8516
- tableName: TABLE_KNOWLEDGE_CURSORS,
8517
- schema: KNOWLEDGE_CURSORS_SCHEMA,
8518
- schemaName,
8519
- compositePrimaryKey: ["sourceThreadId", "agent"],
8520
- includeAllConstraints: true
8521
- }),
8522
- generateTableSQL({
8523
- tableName: TABLE_KNOWLEDGE_ACTIVITY,
8524
- schema: KNOWLEDGE_ACTIVITY_SCHEMA,
8525
- schemaName,
8526
- includeAllConstraints: true
8527
- }),
8528
- generateTableSQL({
8529
- tableName: TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
8530
- schema: KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA,
8531
- schemaName,
8532
- includeAllConstraints: true
8533
- }),
8534
- ...knowledgeIndexDDL(schemaName)
8535
- ];
9298
+ return [...knowledgeTableDefinitions.map((definition) => generateTableSQL({
9299
+ ...definition,
9300
+ schemaName,
9301
+ includeAllConstraints: true
9302
+ })), ...knowledgeIndexDDL(schemaName)];
8536
9303
  }
8537
9304
  #client;
8538
9305
  #executor;
@@ -8541,7 +9308,7 @@ var KnowledgePG = class extends KnowledgeStorage {
8541
9308
  #db;
8542
9309
  #schemaName;
8543
9310
  constructor(config) {
8544
- super();
9311
+ super({ storageIsolationKey: config.storageIsolationKey ?? getPgKnowledgeIsolationKey(config) });
8545
9312
  const { client, readClient, schemaName, skipDefaultIndexes } = resolvePgConfig(config);
8546
9313
  this.#client = client;
8547
9314
  this.#schemaName = schemaName;
@@ -8554,55 +9321,358 @@ var KnowledgePG = class extends KnowledgeStorage {
8554
9321
  skipDefaultIndexes
8555
9322
  });
8556
9323
  }
8557
- async init() {
8558
- await this.#db.createTable({
8559
- tableName: TABLE_KNOWLEDGE_NODES,
8560
- schema: KNOWLEDGE_NODES_SCHEMA
8561
- });
8562
- await this.#db.alterTable({
8563
- tableName: TABLE_KNOWLEDGE_NODES,
8564
- schema: KNOWLEDGE_NODES_SCHEMA,
8565
- ifNotExists: ["description"]
9324
+ getCapabilities() {
9325
+ return {
9326
+ contractVersion: 2,
9327
+ schemaVersion: 2,
9328
+ supportsV2: true,
9329
+ supportsSchemaInspection: true,
9330
+ supportsExplicitReset: true
9331
+ };
9332
+ }
9333
+ async inspectSchema() {
9334
+ const { inspectKnowledgeSchema } = await loadKnowledgeV2Core();
9335
+ try {
9336
+ const snapshot = getSchemaSnapshot(this.#client, this.#schemaName);
9337
+ if (snapshot) {
9338
+ const tableNames = KNOWLEDGE_TABLE_NAMES.filter((table) => snapshot.tables.has(table));
9339
+ return this.#classifySchema(this.#executor, tableNames, snapshot.columns);
9340
+ }
9341
+ return this.#inspectSchemaWithExecutor(this.#executor);
9342
+ } catch (error) {
9343
+ return inspectKnowledgeSchema({
9344
+ available: false,
9345
+ tableNames: [],
9346
+ reason: error instanceof Error ? error.message : String(error)
9347
+ });
9348
+ }
9349
+ }
9350
+ async #inspectSchemaWithExecutor(executor) {
9351
+ const tableNames = (await executor.execute({
9352
+ sql: `SELECT table_name FROM information_schema.tables WHERE table_schema = COALESCE(?, current_schema()) AND table_name = ANY(?::text[])`,
9353
+ args: [this.#schemaName ?? null, [...KNOWLEDGE_TABLE_NAMES]]
9354
+ })).rows.map((row) => String(row.table_name));
9355
+ const columnResult = await executor.execute({
9356
+ sql: `SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = COALESCE(?, current_schema()) AND table_name = ANY(?::text[])`,
9357
+ args: [this.#schemaName ?? null, [...KNOWLEDGE_TABLE_NAMES]]
9358
+ });
9359
+ const columns = /* @__PURE__ */ new Map();
9360
+ for (const row of columnResult.rows) {
9361
+ const table = String(row.table_name);
9362
+ const names = columns.get(table) ?? /* @__PURE__ */ new Set();
9363
+ names.add(String(row.column_name));
9364
+ columns.set(table, names);
9365
+ }
9366
+ return this.#classifySchema(executor, tableNames, columns);
9367
+ }
9368
+ async #classifySchema(executor, tableNames, columns) {
9369
+ const { inspectKnowledgeSchema } = await loadKnowledgeV2Core();
9370
+ if (tableNames.length === 0) return inspectKnowledgeSchema({
9371
+ available: true,
9372
+ tableNames
9373
+ });
9374
+ const missingTables = KNOWLEDGE_TABLE_NAMES.filter((table) => !tableNames.includes(table));
9375
+ if (missingTables.length > 0) return inspectKnowledgeSchema({
9376
+ available: true,
9377
+ tableNames,
9378
+ reason: `Missing Knowledge v2 tables: ${missingTables.join(", ")}`
9379
+ });
9380
+ for (const { tableName: table, schema } of knowledgeTableDefinitions) {
9381
+ const actual = columns.get(table) ?? /* @__PURE__ */ new Set();
9382
+ const missing = Object.keys(schema).filter((column) => !actual.has(column));
9383
+ if (missing.length > 0) return inspectKnowledgeSchema({
9384
+ available: true,
9385
+ tableNames,
9386
+ reason: `Knowledge table ${table} is missing v2 columns: ${missing.join(", ")}`
9387
+ });
9388
+ }
9389
+ const accessState = await executor.execute(`SELECT schemaVersion FROM "${TABLE_KNOWLEDGE_ACCESS_STATE}" WHERE id='global'`);
9390
+ if (Number(accessState.rows[0]?.schemaVersion) !== 2) return inspectKnowledgeSchema({
9391
+ available: true,
9392
+ tableNames,
9393
+ reason: "Knowledge schema version marker is missing or incompatible"
9394
+ });
9395
+ return inspectKnowledgeSchema({
9396
+ available: true,
9397
+ tableNames,
9398
+ schemaVersion: 2
8566
9399
  });
8567
- await this.#db.createTable({
8568
- tableName: TABLE_KNOWLEDGE_RECORDS,
8569
- schema: KNOWLEDGE_RECORDS_SCHEMA
9400
+ }
9401
+ async init() {
9402
+ await this.#transaction(async (tx) => {
9403
+ await tx.execute({
9404
+ sql: "SELECT pg_advisory_xact_lock(hashtext(?))",
9405
+ args: [`mastra-knowledge-v2:${this.#schemaName ?? "current_schema"}`]
9406
+ });
9407
+ const { assertKnowledgeSchemaCompatible } = await loadKnowledgeV2Core();
9408
+ let inspection = await this.#inspectSchemaWithExecutor(tx);
9409
+ if (inspection.status === "incompatible-reset-required" && await this.#isPublishedV1Layout(tx)) {
9410
+ const tables = [...RETIRED_KNOWLEDGE_TABLE_NAMES, ...[...KNOWLEDGE_TABLE_NAMES].reverse()].map((table) => `"${table}"`).join(", ");
9411
+ await tx.execute(`DROP TABLE IF EXISTS ${tables}`);
9412
+ inspection = await this.#inspectSchemaWithExecutor(tx);
9413
+ }
9414
+ assertKnowledgeSchemaCompatible(inspection);
9415
+ for (const definition of knowledgeTableDefinitions) await tx.executeRaw(generateTableSQL({
9416
+ ...definition,
9417
+ schemaName: this.#schemaName,
9418
+ includeAllConstraints: true
9419
+ }));
9420
+ for (const index of knowledgeIndexes(this.#schemaName)) await tx.executeRaw(index.sql);
9421
+ await tx.execute({
9422
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_ACCESS_STATE}" (id, epoch, schemaVersion) VALUES ('global', 0, ?) ON CONFLICT (id) DO NOTHING`,
9423
+ args: [2]
9424
+ });
8570
9425
  });
8571
- await this.#db.createTable({
8572
- tableName: TABLE_KNOWLEDGE_MENTIONS,
8573
- schema: KNOWLEDGE_MENTIONS_SCHEMA,
8574
- compositePrimaryKey: [
8575
- "sourceType",
8576
- "sourceId",
8577
- "recordId"
9426
+ }
9427
+ /**
9428
+ * True when the only Knowledge objects are the tables and indexes published v1 adapters created,
9429
+ * with or without rows. Anything else (v2 tables, unknown tables, views, triggers, extra indexes)
9430
+ * needs an explicit reset.
9431
+ */
9432
+ async #isPublishedV1Layout(executor) {
9433
+ const schema = this.#schemaName ?? null;
9434
+ const relations = await executor.execute({
9435
+ sql: `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = COALESCE(?, current_schema()) AND table_name LIKE 'mastra\\_knowledge\\_%'`,
9436
+ args: [schema]
9437
+ });
9438
+ const tables = [];
9439
+ for (const row of relations.rows) {
9440
+ const name = String(row.table_name);
9441
+ if (row.table_type !== "BASE TABLE" || !PUBLISHED_KNOWLEDGE_V1_TABLE_NAMES.has(name)) return false;
9442
+ tables.push(name);
9443
+ }
9444
+ const indexes = await executor.execute({
9445
+ sql: `SELECT indexname FROM pg_indexes WHERE schemaname = COALESCE(?, current_schema()) AND tablename = ANY(?::text[])`,
9446
+ args: [schema, tables]
9447
+ });
9448
+ for (const row of indexes.rows) {
9449
+ const name = String(row.indexname);
9450
+ if (!PUBLISHED_KNOWLEDGE_V1_INDEX_NAMES.has(name) && !name.endsWith("_pkey")) return false;
9451
+ }
9452
+ if ((await executor.execute({
9453
+ sql: `SELECT 1 FROM information_schema.view_table_usage WHERE table_schema = COALESCE(?, current_schema()) AND table_name = ANY(?::text[]) UNION ALL SELECT 1 FROM information_schema.triggers WHERE event_object_schema = COALESCE(?, current_schema()) AND event_object_table = ANY(?::text[]) LIMIT 1`,
9454
+ args: [
9455
+ schema,
9456
+ tables,
9457
+ schema,
9458
+ tables
8578
9459
  ]
9460
+ })).rows.length > 0) return false;
9461
+ return true;
9462
+ }
9463
+ async dangerouslyClearAll() {
9464
+ await this.#transaction(async (tx) => {
9465
+ for (const table of [
9466
+ TABLE_KNOWLEDGE_RECORD_SCOPES,
9467
+ TABLE_KNOWLEDGE_NODE_SCOPES,
9468
+ TABLE_KNOWLEDGE_SCOPE_GRANTS,
9469
+ TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
9470
+ TABLE_KNOWLEDGE_NODE_ADDRESSES,
9471
+ TABLE_KNOWLEDGE_MENTIONS,
9472
+ TABLE_KNOWLEDGE_PROPOSALS,
9473
+ TABLE_KNOWLEDGE_ACTIVITY,
9474
+ TABLE_KNOWLEDGE_IMPORT_STATE,
9475
+ TABLE_KNOWLEDGE_IMPORT_RUNS,
9476
+ TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
9477
+ TABLE_KNOWLEDGE_RECORDS,
9478
+ TABLE_KNOWLEDGE_NODES
9479
+ ]) await tx.execute(`DELETE FROM "${table}"`);
9480
+ await tx.execute(`UPDATE "${TABLE_KNOWLEDGE_ACCESS_STATE}" SET epoch = 0 WHERE id = 'global'`);
8579
9481
  });
8580
- await this.#db.createTable({
8581
- tableName: TABLE_KNOWLEDGE_CURSORS,
8582
- schema: KNOWLEDGE_CURSORS_SCHEMA,
8583
- compositePrimaryKey: ["sourceThreadId", "agent"]
8584
- });
8585
- await this.#db.createTable({
8586
- tableName: TABLE_KNOWLEDGE_ACTIVITY,
8587
- schema: KNOWLEDGE_ACTIVITY_SCHEMA
8588
- });
8589
- await this.#db.createTable({
8590
- tableName: TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
8591
- schema: KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA
9482
+ }
9483
+ async dangerouslyReset() {
9484
+ const tables = [...RETIRED_KNOWLEDGE_TABLE_NAMES, ...[...KNOWLEDGE_TABLE_NAMES].reverse()].map((table) => `"${table}"`).join(", ");
9485
+ await this.#executor.execute(`DROP TABLE IF EXISTS ${tables}`);
9486
+ await this.init();
9487
+ }
9488
+ async reconcileStructure(plan) {
9489
+ return this.#transaction(async (tx) => {
9490
+ await tx.execute({
9491
+ sql: `SELECT pg_advisory_xact_lock(hashtext(?))`,
9492
+ args: [`mastra-knowledge-reconcile:${this.#schemaName}`]
9493
+ });
9494
+ const scopes = {};
9495
+ const createdScopeIds = [];
9496
+ const createdAddresses = /* @__PURE__ */ new Set();
9497
+ const retrofit = plan.retrofit ?? true;
9498
+ let structureChanged = false;
9499
+ const deletedScopeAddresses = /* @__PURE__ */ new Set();
9500
+ const resolveAddress = async (address) => {
9501
+ if (scopes[address]) return scopes[address];
9502
+ const row = (await tx.execute({
9503
+ sql: `SELECT a."scopeNodeId",n."isScope",n."deletedAt" FROM "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" a JOIN "${TABLE_KNOWLEDGE_NODES}" n ON n.id=a."scopeNodeId" WHERE a.address=?`,
9504
+ args: [address]
9505
+ })).rows[0];
9506
+ if (!row) return void 0;
9507
+ if (!row.isScope) throw new Error(`Knowledge address ${address} does not reference a scope`);
9508
+ if (row.deletedAt) deletedScopeAddresses.add(address);
9509
+ scopes[address] = String(row.scopeNodeId);
9510
+ return scopes[address];
9511
+ };
9512
+ for (const scope of plan.scopes) {
9513
+ if (await resolveAddress(scope.address)) continue;
9514
+ const id = randomUUID();
9515
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9516
+ await tx.execute({
9517
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,"canonicalName",kind,description,"isScope",metadata,version,"createdAt","updatedAt") VALUES (?,'node',?,?,?,?,TRUE,?,1,?,?)`,
9518
+ args: [
9519
+ id,
9520
+ scope.name,
9521
+ canonicalName(scope.name),
9522
+ scope.kind ?? null,
9523
+ scope.description ?? null,
9524
+ scope.metadata ?? null,
9525
+ now,
9526
+ now
9527
+ ]
9528
+ });
9529
+ await tx.execute({
9530
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" (address,"scopeNodeId") VALUES (?,?)`,
9531
+ args: [scope.address, id]
9532
+ });
9533
+ scopes[scope.address] = id;
9534
+ createdAddresses.add(scope.address);
9535
+ createdScopeIds.push(id);
9536
+ structureChanged = true;
9537
+ }
9538
+ for (const scope of plan.scopes) {
9539
+ if (!createdAddresses.has(scope.address) && (!retrofit || deletedScopeAddresses.has(scope.address))) continue;
9540
+ const scopeNodeId = scopes[scope.address];
9541
+ for (const parentAddress of scope.parentAddresses ?? []) {
9542
+ const parentId = await resolveAddress(parentAddress);
9543
+ if (!parentId || deletedScopeAddresses.has(parentAddress)) {
9544
+ const deletedParentId = scopes[parentAddress];
9545
+ if ((deletedParentId ? await tx.execute({
9546
+ sql: `SELECT 1 FROM "mastra_knowledge_node_scopes" WHERE "nodeId"=? AND "scopeNodeId"=?`,
9547
+ args: [scopeNodeId, deletedParentId]
9548
+ }) : void 0)?.rows.length) continue;
9549
+ throw new Error(`Knowledge parent scope does not exist: ${parentAddress}`);
9550
+ }
9551
+ if ((await tx.execute({
9552
+ sql: `SELECT 1 FROM "mastra_knowledge_node_scopes" WHERE "nodeId"=? AND "scopeNodeId"=?`,
9553
+ args: [scopeNodeId, parentId]
9554
+ })).rows.length) continue;
9555
+ if ((await tx.execute({
9556
+ sql: `SELECT n.id FROM "mastra_knowledge_node_scopes" ns JOIN "${TABLE_KNOWLEDGE_NODES}" n ON n.id=ns."nodeId" WHERE ns."scopeNodeId"=? AND n."isScope" AND n."canonicalName"=? AND n."deletedAt" IS NULL AND n.id<>? LIMIT 1`,
9557
+ args: [
9558
+ parentId,
9559
+ canonicalName(scope.name),
9560
+ scopeNodeId
9561
+ ]
9562
+ })).rows.length) throw new Error(`Knowledge scope name ${scope.name} already exists under ${parentAddress}`);
9563
+ const inserted = await tx.execute({
9564
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" ("nodeId","scopeNodeId","addedAt") VALUES (?,?,?) ON CONFLICT DO NOTHING`,
9565
+ args: [
9566
+ scopeNodeId,
9567
+ parentId,
9568
+ (/* @__PURE__ */ new Date()).toISOString()
9569
+ ]
9570
+ });
9571
+ structureChanged ||= inserted.rowsAffected > 0;
9572
+ }
9573
+ for (const grant of scope.grants ?? []) {
9574
+ const scopeRefId = await resolveAddress(grant.scopeRefAddress);
9575
+ if (!scopeRefId || deletedScopeAddresses.has(grant.scopeRefAddress)) {
9576
+ const deletedScopeRefId = scopes[grant.scopeRefAddress];
9577
+ if ((deletedScopeRefId ? await tx.execute({
9578
+ sql: `SELECT 1 FROM "mastra_knowledge_scope_grants" WHERE "scopeNodeId"=? AND "scopeRefId"=?`,
9579
+ args: [scopeNodeId, deletedScopeRefId]
9580
+ }) : void 0)?.rows.length) continue;
9581
+ throw new Error(`Knowledge grant scope does not exist: ${grant.scopeRefAddress}`);
9582
+ }
9583
+ const inserted = await tx.execute({
9584
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_SCOPE_GRANTS}" ("scopeNodeId","scopeRefId",role,"canSuggest") VALUES (?,?,?,?) ON CONFLICT DO NOTHING`,
9585
+ args: [
9586
+ scopeNodeId,
9587
+ scopeRefId,
9588
+ grant.role,
9589
+ grant.canSuggest ?? null
9590
+ ]
9591
+ });
9592
+ structureChanged ||= inserted.rowsAffected > 0;
9593
+ }
9594
+ }
9595
+ if (structureChanged) await tx.execute(`UPDATE "${TABLE_KNOWLEDGE_ACCESS_STATE}" SET epoch=epoch+1 WHERE id='global'`);
9596
+ const state = await tx.execute(`SELECT epoch FROM "${TABLE_KNOWLEDGE_ACCESS_STATE}" WHERE id='global'`);
9597
+ return {
9598
+ scopes,
9599
+ createdScopeIds,
9600
+ deletedScopeAddresses: [...deletedScopeAddresses],
9601
+ changed: structureChanged,
9602
+ accessEpoch: Number(state.rows[0]?.epoch ?? 0)
9603
+ };
8592
9604
  });
8593
- await Promise.all(knowledgeIndexes(this.#schemaName).map((index) => this.#db.createIndexFromStatement(index.name, index.sql)));
8594
9605
  }
8595
- async dangerouslyClearAll() {
8596
- await this.#transaction(async (tx) => {
8597
- for (const table of [
8598
- TABLE_KNOWLEDGE_MENTIONS,
8599
- TABLE_KNOWLEDGE_RECORDS,
8600
- TABLE_KNOWLEDGE_NODES,
8601
- TABLE_KNOWLEDGE_CURSORS,
8602
- TABLE_KNOWLEDGE_ACTIVITY,
8603
- TABLE_KNOWLEDGE_SEMANTIC_OUTBOX
8604
- ]) await tx.execute(`DELETE FROM "${table}"`);
8605
- });
9606
+ async listScopeNodes(input = {}) {
9607
+ const { limit, after } = parseListKnowledgeScopeNodesInput(input);
9608
+ if (input.addresses?.length === 0 || input.ids?.length === 0) return {
9609
+ scopes: [],
9610
+ nextCursor: null
9611
+ };
9612
+ const args = [];
9613
+ const where = [`n."isScope"`, `n."deletedAt" IS NULL`];
9614
+ let within = "";
9615
+ if (input.withinAddress !== void 0) {
9616
+ within = `WITH RECURSIVE within_scope(id) AS (SELECT r.id FROM "${TABLE_KNOWLEDGE_NODES}" r JOIN "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" ra ON ra."scopeNodeId"=r.id WHERE ra.address=? AND r."isScope" AND r."deletedAt" IS NULL UNION SELECT ns."nodeId" FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" ns JOIN within_scope w ON ns."scopeNodeId"=w.id JOIN "${TABLE_KNOWLEDGE_NODES}" c ON c.id=ns."nodeId" WHERE c."isScope" AND c."deletedAt" IS NULL) `;
9617
+ args.push(input.withinAddress);
9618
+ where.push("n.id IN (SELECT id FROM within_scope)");
9619
+ }
9620
+ if (input.addresses) {
9621
+ where.push(`a.address IN (${input.addresses.map(() => "?").join(",")})`);
9622
+ args.push(...input.addresses);
9623
+ }
9624
+ if (input.ids) {
9625
+ where.push(`n.id IN (${input.ids.map(() => "?").join(",")})`);
9626
+ args.push(...input.ids);
9627
+ }
9628
+ if (after) {
9629
+ where.push("(n.name COLLATE \"C\" > ? OR (n.name = ? AND n.id > ?))");
9630
+ args.push(after.name, after.name, after.id);
9631
+ }
9632
+ args.push(limit + 1);
9633
+ const rows = (await this.#readExecutor.execute({
9634
+ sql: `${within}SELECT n.id,n.name,n.kind,n.description,a.address FROM "${TABLE_KNOWLEDGE_NODES}" n LEFT JOIN "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" a ON a."scopeNodeId"=n.id WHERE ${where.join(" AND ")} ORDER BY n.name COLLATE "C", n.id LIMIT ?`,
9635
+ args
9636
+ })).rows;
9637
+ if (rows.length === 0) return {
9638
+ scopes: [],
9639
+ nextCursor: null
9640
+ };
9641
+ const parents = await this.#readExecutor.execute({
9642
+ sql: `SELECT ns."nodeId",ns."scopeNodeId" FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" ns WHERE ns."nodeId" IN (${rows.map(() => "?").join(",")}) ORDER BY ns."scopeNodeId" COLLATE "C"`,
9643
+ args: rows.map((row) => String(row.id))
9644
+ });
9645
+ const parentsByScopeId = /* @__PURE__ */ new Map();
9646
+ for (const row of parents.rows) {
9647
+ const scopeId = String(row.nodeId);
9648
+ const parentId = String(row.scopeNodeId);
9649
+ const existing = parentsByScopeId.get(scopeId);
9650
+ if (existing) existing.push(parentId);
9651
+ else parentsByScopeId.set(scopeId, [parentId]);
9652
+ }
9653
+ return pageKnowledgeScopeNodes(rows.map((row) => ({
9654
+ id: String(row.id),
9655
+ address: String(row.address),
9656
+ name: String(row.name),
9657
+ ...row.kind == null ? {} : { kind: String(row.kind) },
9658
+ ...row.description == null ? {} : { description: String(row.description) },
9659
+ parentIds: parentsByScopeId.get(String(row.id)) ?? []
9660
+ })), limit, input);
9661
+ }
9662
+ async listScopeMembers(input) {
9663
+ const { limit, after } = parseListKnowledgeScopeMembersInput(input);
9664
+ const args = [input.scopeNodeId];
9665
+ let keyset = "";
9666
+ if (after) {
9667
+ const updatedAt = postgresTimestamp(after.updatedAt);
9668
+ keyset = ` AND (n."updatedAt" < ? OR (n."updatedAt" = ? AND (n.name > ? OR (n.name = ? AND n.id > ?))))`;
9669
+ args.push(updatedAt, updatedAt, after.name, after.name, after.id);
9670
+ }
9671
+ args.push(limit + 1);
9672
+ return pageKnowledgeScopeMembers((await this.#readExecutor.execute({
9673
+ sql: `SELECT *, scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_NODES}" n WHERE EXISTS (SELECT 1 FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" ns WHERE ns."nodeId"=n.id AND ns."scopeNodeId"=?) AND n."deletedAt" IS NULL AND n."mergedInto" IS NULL${keyset} ORDER BY n."updatedAt" DESC, n.name ASC, n.id ASC LIMIT ?`,
9674
+ args
9675
+ })).rows.map(parseNode), limit, input);
8606
9676
  }
8607
9677
  async createNode(input) {
8608
9678
  await assertKnowledgeDescriptionWithinBoundCompat(input.description);
@@ -8612,6 +9682,7 @@ var KnowledgePG = class extends KnowledgeStorage {
8612
9682
  if (existing) {
8613
9683
  const terminal = await this.#resolveTerminalNode(tx, existing.id);
8614
9684
  if (!isKnowledgeScopeVisible(terminal.scope, scope)) throw new Error(`Merged knowledge node is not visible from scope: ${input.name}`);
9685
+ await this.#placeNodeInScopes(tx, terminal.id, input.scopeAddresses, /* @__PURE__ */ new Date());
8615
9686
  return terminal;
8616
9687
  }
8617
9688
  const now = /* @__PURE__ */ new Date();
@@ -8628,7 +9699,7 @@ var KnowledgePG = class extends KnowledgeStorage {
8628
9699
  updatedAt: now
8629
9700
  };
8630
9701
  await tx.execute({
8631
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,description,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,?,?,jsonb(?),?,?,NULL,?,?)`,
9702
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,description,isScope,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,?,?,FALSE,jsonb(?),?,?,NULL,?,?)`,
8632
9703
  args: [
8633
9704
  node.id,
8634
9705
  "node",
@@ -8644,6 +9715,8 @@ var KnowledgePG = class extends KnowledgeStorage {
8644
9715
  now.toISOString()
8645
9716
  ]
8646
9717
  });
9718
+ await this.#replaceNodeScopes(tx, node.id, scope, now);
9719
+ await this.#placeNodeInScopes(tx, node.id, input.scopeAddresses, now);
8647
9720
  await this.#replaceMentions(tx, "node", node.id, node.content ?? "", input.resolutionScope ?? scope, scope);
8648
9721
  await this.#activity(tx, "node-created", "node", node.id, scope);
8649
9722
  await this.#outbox(tx, "node", node.id, "upsert", node.version, scope);
@@ -8660,13 +9733,13 @@ var KnowledgePG = class extends KnowledgeStorage {
8660
9733
  return this.#resolveNode(this.#readExecutor, input.name, canonicalizeKnowledgeScope(input.scope));
8661
9734
  }
8662
9735
  async listNodes(input) {
8663
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
9736
+ const keys = visibleScopeKeys(canonicalizeKnowledgeScope(input.scope));
8664
9737
  const clauses = [
8665
9738
  `type = 'node'`,
8666
9739
  "mergedInto IS NULL",
8667
- visibleSql
9740
+ visibleSql(keys)
8668
9741
  ];
8669
- const args = [key, key];
9742
+ const args = [...keys];
8670
9743
  if (input.namePrefix) {
8671
9744
  clauses.push("canonicalName LIKE ? ESCAPE '='");
8672
9745
  args.push(`${escapeLikePattern(canonicalName(input.namePrefix))}%`);
@@ -8718,6 +9791,7 @@ var KnowledgePG = class extends KnowledgeStorage {
8718
9791
  input.version
8719
9792
  ]
8720
9793
  })).rowsAffected === 0) throw new KnowledgeConflictError(input.id);
9794
+ if (knowledgeScopeKey(existing.scope) !== knowledgeScopeKey(scope)) await this.#swapIdentityScopes(tx, input.id, existing.scope, scope, now);
8721
9795
  if (input.content !== void 0 || input.name !== void 0 || input.scope !== void 0) await this.#replaceMentions(tx, "node", input.id, content ?? "", input.resolutionScope ?? scope, scope);
8722
9796
  await this.#activity(tx, "node-updated", "node", input.id, scope);
8723
9797
  if (knowledgeScopeKey(existing.scope) !== knowledgeScopeKey(scope)) {
@@ -8772,8 +9846,12 @@ var KnowledgePG = class extends KnowledgeStorage {
8772
9846
  ]
8773
9847
  })).rowsAffected === 0) throw new KnowledgeConflictError(source.id);
8774
9848
  await tx.execute({
8775
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET node=? WHERE node=?`,
8776
- args: [target.id, source.id]
9849
+ sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET node=?,version=version+1,updatedAt=? WHERE node=?`,
9850
+ args: [
9851
+ target.id,
9852
+ (/* @__PURE__ */ new Date()).toISOString(),
9853
+ source.id
9854
+ ]
8777
9855
  });
8778
9856
  await tx.execute({
8779
9857
  sql: `DELETE FROM "${TABLE_KNOWLEDGE_MENTIONS}" WHERE recordId=? AND EXISTS (SELECT 1 FROM "${TABLE_KNOWLEDGE_MENTIONS}" target WHERE target.sourceType="${TABLE_KNOWLEDGE_MENTIONS}".sourceType AND target.sourceId="${TABLE_KNOWLEDGE_MENTIONS}".sourceId AND target.recordId=?)`,
@@ -8813,10 +9891,10 @@ var KnowledgePG = class extends KnowledgeStorage {
8813
9891
  });
8814
9892
  }
8815
9893
  async appendKnowledge(input) {
9894
+ await assertKnowledgeRecordTextWithinBoundCompat(input.text);
8816
9895
  const scope = canonicalizeKnowledgeScope(input.scope);
8817
9896
  const resolutionScope = canonicalizeKnowledgeScope(input.resolutionScope);
8818
9897
  const defaultScope = canonicalizeKnowledgeScope(input.defaultScope);
8819
- assertKnowledgeScopeWithinCeiling(scope, input.maxScope);
8820
9898
  return this.#transaction(async (tx) => {
8821
9899
  const parent = await this.#resolveTerminalNode(tx, nodeReferenceId(input.node));
8822
9900
  if (!parent) throw new KnowledgeNotFoundError("node", nodeReferenceId(input.node));
@@ -8829,11 +9907,10 @@ var KnowledgePG = class extends KnowledgeStorage {
8829
9907
  sourceThreadId: input.sourceThreadId,
8830
9908
  capturedAt: /* @__PURE__ */ new Date(),
8831
9909
  when: input.when ? new Date(input.when) : void 0,
8832
- maxScope: input.maxScope,
8833
9910
  metadata: input.metadata
8834
9911
  };
8835
9912
  await tx.execute({
8836
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_RECORDS}" (id,node,text,scope,scopeKey,sourceThreadId,capturedAt,"when",maxScope,metadata,deletedAt,deletedBy) VALUES (?,?,?,jsonb(?),?,?,?,?,?,jsonb(?),NULL,NULL)`,
9913
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_RECORDS}" (id,node,text,scope,scopeKey,sourceThreadId,capturedAt,"when",metadata,version,updatedAt,deletedAt,deletedBy) VALUES (?,?,?,jsonb(?),?,?,?,?,jsonb(?),?,?,NULL,NULL)`,
8837
9914
  args: [
8838
9915
  record.id,
8839
9916
  record.node,
@@ -8843,10 +9920,12 @@ var KnowledgePG = class extends KnowledgeStorage {
8843
9920
  record.sourceThreadId,
8844
9921
  record.capturedAt.toISOString(),
8845
9922
  record.when?.toISOString() ?? null,
8846
- record.maxScope ?? null,
8847
- metadataJson
9923
+ metadataJson,
9924
+ 1,
9925
+ record.capturedAt.toISOString()
8848
9926
  ]
8849
9927
  });
9928
+ await this.#replaceRecordScopes(tx, record.id, scope, record.capturedAt);
8850
9929
  await this.#replaceMentions(tx, "record", record.id, record.text, resolutionScope, defaultScope);
8851
9930
  await this.#activity(tx, "record-created", "record", record.id, scope, record.sourceThreadId);
8852
9931
  await this.#outbox(tx, "record", record.id, "upsert", record.id, scope);
@@ -8873,17 +9952,13 @@ var KnowledgePG = class extends KnowledgeStorage {
8873
9952
  return this.#queryKnowledge(input, "related");
8874
9953
  }
8875
9954
  async knowledgeBySource(input) {
8876
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
8877
- const args = [
8878
- input.sourceThreadId,
8879
- key,
8880
- key
8881
- ];
9955
+ const keys = visibleScopeKeys(canonicalizeKnowledgeScope(input.scope));
9956
+ const args = [input.sourceThreadId, ...keys];
8882
9957
  if (input.after) args.push(input.after);
8883
9958
  const limit = input.limit ?? 100;
8884
9959
  args.push(limit + 1);
8885
9960
  const records = (await this.#readExecutor.execute({
8886
- sql: `SELECT *,scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_RECORDS}" WHERE sourceThreadId=? AND ${visibleSql}${input.includeDeleted ? "" : " AND deletedAt IS NULL"}${input.after ? " AND id > ?" : ""} ORDER BY id ASC LIMIT ?`,
9961
+ sql: `SELECT *,scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_RECORDS}" WHERE sourceThreadId=? AND ${visibleSql(keys)}${input.includeDeleted ? "" : " AND deletedAt IS NULL"}${input.after ? " AND id > ?" : ""} ORDER BY id ASC LIMIT ?`,
8887
9962
  args
8888
9963
  })).rows.map(parseKnowledge);
8889
9964
  return {
@@ -8898,10 +9973,11 @@ var KnowledgePG = class extends KnowledgeStorage {
8898
9973
  if (record.deletedAt) return record;
8899
9974
  const deletedAt = /* @__PURE__ */ new Date();
8900
9975
  await tx.execute({
8901
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=?,deletedBy=? WHERE id=? AND deletedAt IS NULL`,
9976
+ sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=?,deletedBy=?,version=version+1,updatedAt=? WHERE id=? AND deletedAt IS NULL`,
8902
9977
  args: [
8903
9978
  deletedAt.toISOString(),
8904
9979
  input.deletedBy,
9980
+ deletedAt.toISOString(),
8905
9981
  input.id
8906
9982
  ]
8907
9983
  });
@@ -8920,8 +9996,8 @@ var KnowledgePG = class extends KnowledgeStorage {
8920
9996
  if (!record) throw new KnowledgeNotFoundError("record", input.id);
8921
9997
  if (!record.deletedAt) return record;
8922
9998
  await tx.execute({
8923
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=NULL,deletedBy=NULL WHERE id=?`,
8924
- args: [input.id]
9999
+ sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=NULL,deletedBy=NULL,version=version+1,updatedAt=? WHERE id=?`,
10000
+ args: [(/* @__PURE__ */ new Date()).toISOString(), input.id]
8925
10001
  });
8926
10002
  await this.#activity(tx, "record-restored", "record", input.id, record.scope, record.sourceThreadId);
8927
10003
  await this.#outbox(tx, "record", input.id, "upsert", createKnowledgeUlid(), record.scope);
@@ -8937,15 +10013,17 @@ var KnowledgePG = class extends KnowledgeStorage {
8937
10013
  return this.#transaction(async (tx) => {
8938
10014
  const record = await this.#getKnowledge(tx, input.id, true);
8939
10015
  if (!record) throw new KnowledgeNotFoundError("record", input.id);
8940
- assertKnowledgeScopeWithinCeiling(scope, record.maxScope);
10016
+ const updatedAt = /* @__PURE__ */ new Date();
8941
10017
  await tx.execute({
8942
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET scope=jsonb(?),scopeKey=? WHERE id=?`,
10018
+ sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET scope=jsonb(?),scopeKey=?,version=version+1,updatedAt=? WHERE id=?`,
8943
10019
  args: [
8944
10020
  JSON.stringify(scope),
8945
10021
  knowledgeScopeKey(scope),
10022
+ updatedAt.toISOString(),
8946
10023
  input.id
8947
10024
  ]
8948
10025
  });
10026
+ await this.#replaceRecordScopes(tx, input.id, scope, updatedAt);
8949
10027
  await this.#activity(tx, "record-rescoped", "record", input.id, scope, record.sourceThreadId);
8950
10028
  if (knowledgeScopeKey(record.scope) !== knowledgeScopeKey(scope)) await this.#outbox(tx, "record", input.id, "delete", createKnowledgeUlid(), record.scope);
8951
10029
  if (!record.deletedAt) await this.#outbox(tx, "record", input.id, "upsert", createKnowledgeUlid(), scope);
@@ -8955,33 +10033,16 @@ var KnowledgePG = class extends KnowledgeStorage {
8955
10033
  };
8956
10034
  });
8957
10035
  }
8958
- async raiseKnowledgeCeiling(input) {
8959
- return this.#transaction(async (tx) => {
8960
- const record = await this.#getKnowledge(tx, input.id, true);
8961
- if (!record) throw new KnowledgeNotFoundError("record", input.id);
8962
- assertKnowledgeScopeWithinCeiling(record.scope, input.maxScope);
8963
- assertKnowledgeCeilingRaised(record.maxScope, input.maxScope);
8964
- await tx.execute({
8965
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET maxScope=? WHERE id=?`,
8966
- args: [input.maxScope ?? null, input.id]
8967
- });
8968
- return {
8969
- ...record,
8970
- maxScope: input.maxScope
8971
- };
8972
- });
8973
- }
8974
10036
  async search(input) {
8975
10037
  const scope = canonicalizeKnowledgeScope(input.scope);
8976
- const key = knowledgeScopeKey(scope);
10038
+ const keys = visibleScopeKeys(scope);
8977
10039
  const normalizedQuery = input.query.trim().toLocaleLowerCase();
8978
10040
  if (!normalizedQuery) return [];
8979
10041
  const query = `%${escapeLikePattern(normalizedQuery)}%`;
8980
10042
  const results = (await this.#readExecutor.execute({
8981
- sql: `SELECT *,scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_NODES}" WHERE mergedInto IS NULL AND ${visibleSql} AND (canonicalName LIKE ? ESCAPE '=' OR lower(COALESCE(kind,'')) LIKE ? ESCAPE '=' OR lower(COALESCE(content,'')) LIKE ? ESCAPE '=' OR lower(COALESCE(description,'')) LIKE ? ESCAPE '=') ORDER BY updatedAt DESC LIMIT ?`,
10043
+ sql: `SELECT *,scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_NODES}" WHERE mergedInto IS NULL AND ${visibleSql(keys)} AND (canonicalName LIKE ? ESCAPE '=' OR lower(COALESCE(kind,'')) LIKE ? ESCAPE '=' OR lower(COALESCE(content,'')) LIKE ? ESCAPE '=' OR lower(COALESCE(description,'')) LIKE ? ESCAPE '=') ORDER BY updatedAt DESC LIMIT ?`,
8982
10044
  args: [
8983
- key,
8984
- key,
10045
+ ...keys,
8985
10046
  query,
8986
10047
  query,
8987
10048
  query,
@@ -9002,10 +10063,9 @@ var KnowledgePG = class extends KnowledgeStorage {
9002
10063
  }));
9003
10064
  if (results.length < (input.limit ?? 20)) {
9004
10065
  const records = await this.#readExecutor.execute({
9005
- sql: `SELECT f.*,f.scope AS "scopeJson",r.name,r.scope AS "parentScopeJson" FROM "${TABLE_KNOWLEDGE_RECORDS}" f JOIN "${TABLE_KNOWLEDGE_NODES}" r ON r.id=f.node AND r.type='node' AND r.mergedInto IS NULL WHERE f.deletedAt IS NULL AND ${visibleSql.replaceAll("scopeKey", "f.scopeKey")} AND lower(f.text) LIKE ? ESCAPE '=' ORDER BY f.id DESC LIMIT ?`,
10066
+ sql: `SELECT f.*,f.scope AS "scopeJson",r.name,r.scope AS "parentScopeJson" FROM "${TABLE_KNOWLEDGE_RECORDS}" f JOIN "${TABLE_KNOWLEDGE_NODES}" r ON r.id=f.node AND r.type='node' AND r.mergedInto IS NULL WHERE f.deletedAt IS NULL AND ${visibleSql(keys, "f.scopeKey")} AND lower(f.text) LIKE ? ESCAPE '=' ORDER BY f.id DESC LIMIT ?`,
9006
10067
  args: [
9007
- key,
9008
- key,
10068
+ ...keys,
9009
10069
  query,
9010
10070
  (input.limit ?? 20) - results.length
9011
10071
  ]
@@ -9015,7 +10075,7 @@ var KnowledgePG = class extends KnowledgeStorage {
9015
10075
  return {
9016
10076
  type: "record",
9017
10077
  id: String(row.id),
9018
- recordId: String(row.node),
10078
+ recordId: parentVisible ? String(row.node) : String(row.id),
9019
10079
  name: parentVisible ? String(row.name) : "(private node)",
9020
10080
  text: String(row.text),
9021
10081
  scope: parseJson$1(row.scopeJson)
@@ -9024,41 +10084,26 @@ var KnowledgePG = class extends KnowledgeStorage {
9024
10084
  }
9025
10085
  return results;
9026
10086
  }
9027
- async getCurationCursor(input) {
9028
- const row = (await this.#executor.execute({
9029
- sql: `SELECT * FROM "${TABLE_KNOWLEDGE_CURSORS}" WHERE sourceThreadId=? AND agent=?`,
9030
- args: [input.sourceThreadId, input.agent]
9031
- })).rows[0];
9032
- return row ? {
9033
- sourceThreadId: String(row.sourceThreadId),
9034
- agent: String(row.agent),
9035
- lastKnowledgeId: String(row.lastKnowledgeId),
9036
- updatedAt: toDate$1(row.updatedAt)
9037
- } : null;
9038
- }
9039
- async advanceCurationCursor(input) {
9040
- const updatedAt = /* @__PURE__ */ new Date();
9041
- if ((await this.#executor.execute({
9042
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_CURSORS}" (sourceThreadId,agent,lastKnowledgeId,updatedAt) VALUES (?,?,?,?) ON CONFLICT(sourceThreadId,agent) DO UPDATE SET lastKnowledgeId=excluded.lastKnowledgeId,updatedAt=excluded.updatedAt WHERE excluded.lastKnowledgeId >= "${TABLE_KNOWLEDGE_CURSORS}".lastKnowledgeId`,
9043
- args: [
9044
- input.sourceThreadId,
9045
- input.agent,
9046
- input.lastKnowledgeId,
9047
- updatedAt.toISOString()
9048
- ]
9049
- })).rowsAffected === 0) throw new Error("Knowledge curation cursor cannot move backwards");
9050
- return {
9051
- ...input,
9052
- updatedAt
9053
- };
10087
+ /**
10088
+ * @deprecated Curation cursors were removed. Observation-time curate is the only Knowledge writer and needs no
10089
+ * cursor. Always throws.
10090
+ */
10091
+ async getCurationCursor(_input) {
10092
+ throw new Error(KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE);
10093
+ }
10094
+ /**
10095
+ * @deprecated Curation cursors were removed. Observation-time curate is the only Knowledge writer and needs no
10096
+ * cursor. Always throws.
10097
+ */
10098
+ async advanceCurationCursor(_input) {
10099
+ throw new Error(KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE);
9054
10100
  }
9055
10101
  async listActivity(input) {
9056
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
10102
+ const keys = visibleScopeKeys(canonicalizeKnowledgeScope(input.scope));
9057
10103
  return (await this.#readExecutor.execute({
9058
- sql: `SELECT *,scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_ACTIVITY}" WHERE ${visibleSql}${input.after ? " AND id < ?" : ""} ORDER BY id DESC LIMIT ?`,
10104
+ sql: `SELECT *,scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_ACTIVITY}" WHERE ${visibleSql(keys)}${input.after ? " AND id < ?" : ""} ORDER BY id DESC LIMIT ?`,
9059
10105
  args: [
9060
- key,
9061
- key,
10106
+ ...keys,
9062
10107
  ...input.after ? [input.after] : [],
9063
10108
  input.limit ?? 100
9064
10109
  ]
@@ -9080,9 +10125,9 @@ var KnowledgePG = class extends KnowledgeStorage {
9080
10125
  args.push(input.status);
9081
10126
  }
9082
10127
  if (input.scope) {
9083
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
9084
- clauses.push(visibleSql);
9085
- args.push(key, key);
10128
+ const keys = visibleScopeKeys(input.scope);
10129
+ clauses.push(visibleSql(keys));
10130
+ args.push(...keys);
9086
10131
  }
9087
10132
  args.push(input.limit ?? 100);
9088
10133
  return (await this.#executor.execute({
@@ -9101,9 +10146,9 @@ var KnowledgePG = class extends KnowledgeStorage {
9101
10146
  ];
9102
10147
  const args = [now.toISOString(), stale.toISOString()];
9103
10148
  if (input.scope) {
9104
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
9105
- clauses.push(visibleSql);
9106
- args.push(key, key);
10149
+ const keys = visibleScopeKeys(input.scope);
10150
+ clauses.push(visibleSql(keys));
10151
+ args.push(...keys);
9107
10152
  }
9108
10153
  args.push(input.limit ?? 100);
9109
10154
  const ids = (await tx.execute({
@@ -9170,12 +10215,14 @@ var KnowledgePG = class extends KnowledgeStorage {
9170
10215
  return result.rows[0] ? parseNode(result.rows[0]) : null;
9171
10216
  }
9172
10217
  async #resolveNode(executor, name, scope) {
9173
- for (let length = scope.length; length > 0; length--) {
9174
- const node = await this.#getNodeByName(executor, name, scope.slice(0, length));
9175
- if (node) {
9176
- const terminal = await this.#resolveTerminalNode(executor, node.id);
9177
- if (terminal && isKnowledgeScopeVisible(terminal.scope, scope)) return terminal;
9178
- }
10218
+ const keys = visibleScopeKeys(scope);
10219
+ const candidates = (await executor.execute({
10220
+ sql: `SELECT *,scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_NODES}" WHERE type='node' AND canonicalName=? AND ("scopeKey" IN (${keys.map(() => "?").join(",")}) OR "mergedInto" IS NOT NULL)`,
10221
+ args: [canonicalName(name), ...keys]
10222
+ })).rows.map(parseNode).sort((left, right) => right.scope.length - left.scope.length);
10223
+ for (const candidate of candidates) {
10224
+ const terminal = await this.#resolveTerminalNode(executor, candidate.id);
10225
+ if (terminal && isKnowledgeScopeVisible(terminal.scope, scope)) return terminal;
9179
10226
  }
9180
10227
  return null;
9181
10228
  }
@@ -9200,17 +10247,16 @@ var KnowledgePG = class extends KnowledgeStorage {
9200
10247
  const scope = canonicalizeKnowledgeScope(input.scope);
9201
10248
  const node = await this.#resolveTerminalNode(this.#readExecutor, nodeReferenceId(input.node));
9202
10249
  if (!node) return { records: [] };
9203
- const key = knowledgeScopeKey(scope);
10250
+ const keys = visibleScopeKeys(scope);
9204
10251
  const args = [
9205
10252
  node.id,
9206
10253
  ...relationship === "related" ? [node.id] : [],
9207
- key,
9208
- key
10254
+ ...keys
9209
10255
  ];
9210
10256
  if (input.after) args.push(input.after);
9211
10257
  args.push((input.limit ?? 100) + 1);
9212
10258
  const records = (await this.#readExecutor.execute({
9213
- sql: `SELECT DISTINCT f.*,f.scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_RECORDS}" f${relationship === "about" ? "" : ` LEFT JOIN "${TABLE_KNOWLEDGE_MENTIONS}" m ON m.sourceType='record' AND m.sourceId=f.id`} WHERE ${relationship === "about" ? "f.node=?" : relationship === "mentioning" ? "m.recordId=?" : "(f.node=? OR m.recordId=?)"} AND ${visibleSql.replaceAll("scopeKey", "f.scopeKey")}${input.includeDeleted ? "" : " AND f.deletedAt IS NULL"}${input.after ? " AND f.id < ?" : ""} ORDER BY f.id DESC LIMIT ?`,
10259
+ sql: `SELECT DISTINCT f.*,f.scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_RECORDS}" f${relationship === "about" ? "" : ` LEFT JOIN "${TABLE_KNOWLEDGE_MENTIONS}" m ON m.sourceType='record' AND m.sourceId=f.id`} WHERE ${relationship === "about" ? "f.node=?" : relationship === "mentioning" ? "m.recordId=?" : "(f.node=? OR m.recordId=?)"} AND ${visibleSql(keys, "f.scopeKey")}${input.includeDeleted ? "" : " AND f.deletedAt IS NULL"}${input.after ? " AND f.id < ?" : ""} ORDER BY f.id DESC LIMIT ?`,
9214
10260
  args
9215
10261
  })).rows.map(parseKnowledge);
9216
10262
  const limit = input.limit ?? 100;
@@ -9219,6 +10265,90 @@ var KnowledgePG = class extends KnowledgeStorage {
9219
10265
  nextCursor: records.length > limit ? records[limit - 1]?.id : void 0
9220
10266
  };
9221
10267
  }
10268
+ async #resolveScopeNodeIds(executor, addresses) {
10269
+ const scopeNodeIds = /* @__PURE__ */ new Set();
10270
+ for (const address of addresses) {
10271
+ const result = await executor.execute({
10272
+ sql: `SELECT scopeNodeId FROM "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" WHERE address=?`,
10273
+ args: [address]
10274
+ });
10275
+ if (result.rows[0]?.scopeNodeId != null) scopeNodeIds.add(String(result.rows[0].scopeNodeId));
10276
+ }
10277
+ return [...scopeNodeIds];
10278
+ }
10279
+ async #replaceNodeScopes(executor, nodeId, addresses, addedAt) {
10280
+ await executor.execute({
10281
+ sql: `DELETE FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" WHERE nodeId=?`,
10282
+ args: [nodeId]
10283
+ });
10284
+ for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, addresses)) await executor.execute({
10285
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?) ON CONFLICT DO NOTHING`,
10286
+ args: [
10287
+ nodeId,
10288
+ scopeNodeId,
10289
+ addedAt.toISOString()
10290
+ ]
10291
+ });
10292
+ }
10293
+ /**
10294
+ * Move a node's identity memberships from `from` to `to` without touching structural
10295
+ * placements added by `#placeNodeInScopes`, which a scope change must not erase.
10296
+ */
10297
+ async #swapIdentityScopes(executor, nodeId, from, to, addedAt) {
10298
+ const next = await this.#resolveScopeNodeIds(executor, to);
10299
+ for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, from)) {
10300
+ if (next.includes(scopeNodeId)) continue;
10301
+ await executor.execute({
10302
+ sql: `DELETE FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" WHERE nodeId=? AND scopeNodeId=?`,
10303
+ args: [nodeId, scopeNodeId]
10304
+ });
10305
+ }
10306
+ for (const scopeNodeId of next) await executor.execute({
10307
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?) ON CONFLICT DO NOTHING`,
10308
+ args: [
10309
+ nodeId,
10310
+ scopeNodeId,
10311
+ addedAt.toISOString()
10312
+ ]
10313
+ });
10314
+ }
10315
+ /**
10316
+ * Additive structural placement: every address must resolve to a live scope node
10317
+ * (unlike identity membership, an unknown address is a caller error, not a lazy
10318
+ * materialization miss). Runs inside the caller's transaction, so a throw aborts
10319
+ * the whole mutation.
10320
+ */
10321
+ async #placeNodeInScopes(executor, nodeId, addresses, addedAt) {
10322
+ for (const address of addresses ?? []) {
10323
+ const scopeNodeId = (await executor.execute({
10324
+ sql: `SELECT sa.scopeNodeId AS "scopeNodeId" FROM "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" sa JOIN "${TABLE_KNOWLEDGE_NODES}" n ON n.id=sa."scopeNodeId" WHERE sa.address=? AND n."isScope" AND n."deletedAt" IS NULL`,
10325
+ args: [address]
10326
+ })).rows[0]?.scopeNodeId;
10327
+ if (scopeNodeId == null) throw new KnowledgeNotFoundError("scope", address);
10328
+ await executor.execute({
10329
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" ("nodeId","scopeNodeId","addedAt") VALUES (?,?,?) ON CONFLICT DO NOTHING`,
10330
+ args: [
10331
+ nodeId,
10332
+ String(scopeNodeId),
10333
+ addedAt.toISOString()
10334
+ ]
10335
+ });
10336
+ }
10337
+ }
10338
+ async #replaceRecordScopes(executor, recordId, addresses, addedAt) {
10339
+ await executor.execute({
10340
+ sql: `DELETE FROM "${TABLE_KNOWLEDGE_RECORD_SCOPES}" WHERE recordId=?`,
10341
+ args: [recordId]
10342
+ });
10343
+ for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, addresses)) await executor.execute({
10344
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_RECORD_SCOPES}" (recordId,scopeNodeId,addedAt) VALUES (?,?,?) ON CONFLICT DO NOTHING`,
10345
+ args: [
10346
+ recordId,
10347
+ scopeNodeId,
10348
+ addedAt.toISOString()
10349
+ ]
10350
+ });
10351
+ }
9222
10352
  async #replaceMentions(tx, sourceType, sourceId, text, resolutionScope, defaultScope) {
9223
10353
  await tx.execute({
9224
10354
  sql: `DELETE FROM "${TABLE_KNOWLEDGE_MENTIONS}" WHERE sourceType=? AND sourceId=?`,
@@ -9242,7 +10372,7 @@ var KnowledgePG = class extends KnowledgeStorage {
9242
10372
  updatedAt: now
9243
10373
  };
9244
10374
  await tx.execute({
9245
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,NULL,jsonb(?),?,?,NULL,?,?)`,
10375
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,isScope,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,NULL,FALSE,jsonb(?),?,?,NULL,?,?)`,
9246
10376
  args: [
9247
10377
  node.id,
9248
10378
  "node",
@@ -9256,6 +10386,7 @@ var KnowledgePG = class extends KnowledgeStorage {
9256
10386
  now.toISOString()
9257
10387
  ]
9258
10388
  });
10389
+ await this.#replaceNodeScopes(tx, node.id, defaultScope, now);
9259
10390
  await this.#activity(tx, "node-created", "node", node.id, defaultScope);
9260
10391
  await this.#outbox(tx, "node", node.id, "upsert", 1, defaultScope);
9261
10392
  }
@@ -9272,13 +10403,17 @@ var KnowledgePG = class extends KnowledgeStorage {
9272
10403
  }
9273
10404
  async #activity(executor, action, recordType, recordId, scope, sourceThreadId) {
9274
10405
  const now = /* @__PURE__ */ new Date();
10406
+ const [contextScopeId] = await this.#resolveScopeNodeIds(executor, scope);
9275
10407
  await executor.execute({
9276
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_ACTIVITY}" (id,action,recordType,recordId,scope,scopeKey,sourceThreadId,createdAt) VALUES (?,?,?,?,jsonb(?),?,?,?)`,
10408
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_ACTIVITY}" (id,action,targetType,targetId,contextScopeId,recordType,recordId,scope,scopeKey,sourceThreadId,createdAt) VALUES (?,?,?,?,?,?,?,jsonb(?),?,?,?)`,
9277
10409
  args: [
9278
10410
  createKnowledgeUlid(),
9279
10411
  action,
9280
10412
  recordType,
9281
10413
  recordId,
10414
+ contextScopeId ?? null,
10415
+ recordType,
10416
+ recordId,
9282
10417
  JSON.stringify(scope),
9283
10418
  knowledgeScopeKey(scope),
9284
10419
  sourceThreadId ?? null,
@@ -9415,6 +10550,7 @@ var MCPClientsPG = class MCPClientsPG extends MCPClientsStorage {
9415
10550
  }
9416
10551
  async create(input) {
9417
10552
  const { mcpClient } = input;
10553
+ let draftInserted = false;
9418
10554
  try {
9419
10555
  const tableName = getTableName$5({
9420
10556
  indexName: TABLE_MCP_CLIENTS,
@@ -9437,6 +10573,7 @@ var MCPClientsPG = class MCPClientsPG extends MCPClientsStorage {
9437
10573
  nowIso,
9438
10574
  nowIso
9439
10575
  ]);
10576
+ draftInserted = true;
9440
10577
  const { id: _id, authorId: _authorId, metadata: _metadata, ...snapshotConfig } = mcpClient;
9441
10578
  const versionId = crypto.randomUUID();
9442
10579
  await this.createVersion({
@@ -9457,7 +10594,7 @@ var MCPClientsPG = class MCPClientsPG extends MCPClientsStorage {
9457
10594
  updatedAt: now
9458
10595
  };
9459
10596
  } catch (error) {
9460
- try {
10597
+ if (draftInserted) try {
9461
10598
  const tableName = getTableName$5({
9462
10599
  indexName: TABLE_MCP_CLIENTS,
9463
10600
  schemaName: getSchemaName$5(this.#schema)
@@ -9983,6 +11120,7 @@ var MCPServersPG = class MCPServersPG extends MCPServersStorage {
9983
11120
  }
9984
11121
  async create(input) {
9985
11122
  const { mcpServer } = input;
11123
+ let draftInserted = false;
9986
11124
  try {
9987
11125
  const tableName = getTableName$5({
9988
11126
  indexName: TABLE_MCP_SERVERS,
@@ -10004,6 +11142,7 @@ var MCPServersPG = class MCPServersPG extends MCPServersStorage {
10004
11142
  nowIso,
10005
11143
  nowIso
10006
11144
  ]);
11145
+ draftInserted = true;
10007
11146
  const { id: _id, authorId: _authorId, metadata: _metadata, ...snapshotConfig } = mcpServer;
10008
11147
  const versionId = crypto.randomUUID();
10009
11148
  await this.createVersion({
@@ -10024,7 +11163,7 @@ var MCPServersPG = class MCPServersPG extends MCPServersStorage {
10024
11163
  updatedAt: now
10025
11164
  };
10026
11165
  } catch (error) {
10027
- try {
11166
+ if (draftInserted) try {
10028
11167
  const tableName = getTableName$5({
10029
11168
  indexName: TABLE_MCP_SERVERS,
10030
11169
  schemaName: getSchemaName$5(this.#schema)
@@ -10448,6 +11587,38 @@ var MCPServersPG = class MCPServersPG extends MCPServersStorage {
10448
11587
  }
10449
11588
  };
10450
11589
  //#endregion
11590
+ //#region src/storage/db/run-fencing.ts
11591
+ /**
11592
+ * Epoch milliseconds on the database clock. `clock_timestamp()` rather than
11593
+ * `now()`: a statement can wait on a row lock, and lease math must use the
11594
+ * time it actually runs, not when its transaction started.
11595
+ */
11596
+ const DB_NOW_MS = `(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint`;
11597
+ /**
11598
+ * The check half of a fenced write; call it first inside the write's
11599
+ * transaction. `FOR SHARE` holds the claim row until commit, so a claim that
11600
+ * updates the row waits for the write to finish, and every later write sees
11601
+ * the new claim.
11602
+ */
11603
+ async function assertRunFence(t, claimsTable, fence, operation) {
11604
+ const row = await t.oneOrNone(`SELECT generation, "ownerId" FROM ${claimsTable} WHERE "runId" = $1 FOR SHARE`, [fence.runId]);
11605
+ if (!matchesRunFence(row && {
11606
+ generation: Number(row.generation),
11607
+ ownerId: row.ownerId
11608
+ }, fence)) throw new RunFenceConflictError(fence, operation);
11609
+ }
11610
+ /**
11611
+ * Runs `write` directly when it carries no fence, or in a transaction behind
11612
+ * the fence check when it does.
11613
+ */
11614
+ function withRunFence(client, claimsTable, fence, operation, write) {
11615
+ if (!fence) return write(client);
11616
+ return client.tx(async (t) => {
11617
+ await assertRunFence(t, claimsTable, fence, operation);
11618
+ return write(t);
11619
+ });
11620
+ }
11621
+ //#endregion
10451
11622
  //#region src/storage/domains/memory/index.ts
10452
11623
  /**
10453
11624
  * Local constant for the observational memory table name.
@@ -10548,6 +11719,10 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
10548
11719
  * and are indexed for fast batched deletes. Cascade order is enforced in
10549
11720
  * `prune()` (children before threads), not here. Observational memory has no
10550
11721
  * timestamp anchor and is deliberately excluded.
11722
+ *
11723
+ * Run fences anchor on `retiredAt` (epoch ms), which stays NULL until the
11724
+ * execution holding the fence settles, so a running execution's fence is
11725
+ * never pruned.
10551
11726
  */
10552
11727
  static retentionTables = {
10553
11728
  messages: {
@@ -10564,6 +11739,12 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
10564
11739
  table: TABLE_THREADS,
10565
11740
  column: "createdAtZ",
10566
11741
  indexed: true
11742
+ },
11743
+ runFences: {
11744
+ table: TABLE_MEMORY_RUN_FENCES,
11745
+ column: "retiredAt",
11746
+ indexed: true,
11747
+ anchorType: "epoch-ms"
10567
11748
  }
10568
11749
  };
10569
11750
  #db;
@@ -10627,6 +11808,10 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
10627
11808
  });
10628
11809
  await this.#db.createIndexFromStatement("idx_om_lookup_key", `CREATE INDEX IF NOT EXISTS idx_om_lookup_key ON ${omTableName} ("lookupKey")`);
10629
11810
  }
11811
+ await this.#db.createTable({
11812
+ tableName: TABLE_MEMORY_RUN_FENCES,
11813
+ schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_MEMORY_RUN_FENCES]
11814
+ });
10630
11815
  await this.createDefaultIndexes();
10631
11816
  await this.createCustomIndexes();
10632
11817
  }
@@ -10704,6 +11889,12 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
10704
11889
  statements.push(`CREATE INDEX IF NOT EXISTS "${idxPrefix}idx_om_lookup_key" ON ${fullOmTableName} ("lookupKey");`);
10705
11890
  }
10706
11891
  for (const idx of MemoryPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
11892
+ statements.push(generateTableSQL({
11893
+ tableName: TABLE_MEMORY_RUN_FENCES,
11894
+ schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_MEMORY_RUN_FENCES],
11895
+ schemaName,
11896
+ includeAllConstraints: true
11897
+ }));
10707
11898
  return statements;
10708
11899
  }
10709
11900
  /**
@@ -10739,6 +11930,61 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
10739
11930
  await this.#db.clearTable({ tableName: TABLE_MESSAGES });
10740
11931
  await this.#db.clearTable({ tableName: TABLE_THREADS });
10741
11932
  await this.#db.clearTable({ tableName: TABLE_RESOURCES });
11933
+ await this.#db.clearTable({ tableName: TABLE_MEMORY_RUN_FENCES });
11934
+ }
11935
+ supportsRunFencing() {
11936
+ return true;
11937
+ }
11938
+ #runFencesTable() {
11939
+ return getTableName$3({
11940
+ indexName: TABLE_MEMORY_RUN_FENCES,
11941
+ schemaName: getSchemaName$3(this.#schema)
11942
+ });
11943
+ }
11944
+ /** Runs `write` behind the run's fence check when it carries a fence. */
11945
+ #fenced(fence, operation, write) {
11946
+ return withRunFence(this.#db.client, this.#runFencesTable(), resolveRunFence(this, fence), operation, write);
11947
+ }
11948
+ async raiseRunFence(fence) {
11949
+ try {
11950
+ return await this.#db.client.oneOrNone(`INSERT INTO ${this.#runFencesTable()} AS f ("runId", generation, "ownerId")
11951
+ VALUES ($1, $2, $3)
11952
+ ON CONFLICT ("runId") DO UPDATE
11953
+ SET generation = EXCLUDED.generation, "ownerId" = EXCLUDED."ownerId", "retiredAt" = NULL
11954
+ WHERE f.generation < EXCLUDED.generation
11955
+ OR (f.generation = EXCLUDED.generation AND f."ownerId" = EXCLUDED."ownerId")
11956
+ RETURNING "runId"`, [
11957
+ fence.runId,
11958
+ fence.generation,
11959
+ fence.ownerId
11960
+ ]) !== null;
11961
+ } catch (error) {
11962
+ throw new MastraError({
11963
+ id: createStorageErrorId("PG", "RAISE_RUN_FENCE", "FAILED"),
11964
+ domain: ErrorDomain.STORAGE,
11965
+ category: ErrorCategory.THIRD_PARTY,
11966
+ details: { runId: fence.runId }
11967
+ }, error);
11968
+ }
11969
+ }
11970
+ async retireRunFence(fence) {
11971
+ try {
11972
+ return await this.#db.client.oneOrNone(`UPDATE ${this.#runFencesTable()}
11973
+ SET "retiredAt" = ${DB_NOW_MS}
11974
+ WHERE "runId" = $1 AND generation = $2 AND "ownerId" = $3
11975
+ RETURNING "runId"`, [
11976
+ fence.runId,
11977
+ fence.generation,
11978
+ fence.ownerId
11979
+ ]) !== null;
11980
+ } catch (error) {
11981
+ throw new MastraError({
11982
+ id: createStorageErrorId("PG", "RETIRE_RUN_FENCE", "FAILED"),
11983
+ domain: ErrorDomain.STORAGE,
11984
+ category: ErrorCategory.THIRD_PARTY,
11985
+ details: { runId: fence.runId }
11986
+ }, error);
11987
+ }
10742
11988
  }
10743
11989
  /**
10744
11990
  * Deletes rows older than the configured `maxAge` per table, in bounded,
@@ -10759,7 +12005,8 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
10759
12005
  order: [
10760
12006
  "messages",
10761
12007
  "resources",
10762
- "threads"
12008
+ "threads",
12009
+ "runFences"
10763
12010
  ]
10764
12011
  });
10765
12012
  const results = await runPrune({
@@ -11017,7 +12264,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11017
12264
  throw mastraError;
11018
12265
  }
11019
12266
  }
11020
- async saveThread({ thread }) {
12267
+ async saveThread({ thread, fence }) {
11021
12268
  try {
11022
12269
  const tableName = getTableName$3({
11023
12270
  indexName: TABLE_THREADS,
@@ -11026,7 +12273,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11026
12273
  const createdAt = toUtcISOString(thread.createdAt);
11027
12274
  const updatedAt = toUtcISOString(thread.updatedAt);
11028
12275
  const metadataJson = thread.metadata ? toPgJson(thread.metadata) : null;
11029
- await this.#db.client.none(`INSERT INTO ${tableName} (
12276
+ await this.#fenced(fence, "saveThread", (q) => q.none(`INSERT INTO ${tableName} (
11030
12277
  id,
11031
12278
  "resourceId",
11032
12279
  title,
@@ -11052,12 +12299,13 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11052
12299
  createdAt,
11053
12300
  updatedAt,
11054
12301
  updatedAt
11055
- ]);
12302
+ ]));
11056
12303
  return {
11057
12304
  ...thread,
11058
12305
  metadata: metadataJson ? JSON.parse(metadataJson) : thread.metadata
11059
12306
  };
11060
12307
  } catch (error) {
12308
+ if (isRunFenceConflictError(error)) throw error;
11061
12309
  throw new MastraError({
11062
12310
  id: createStorageErrorId("PG", "SAVE_THREAD", "FAILED"),
11063
12311
  domain: ErrorDomain.STORAGE,
@@ -11066,29 +12314,30 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11066
12314
  }, error);
11067
12315
  }
11068
12316
  }
11069
- async updateThread({ id, title, metadata }) {
12317
+ async updateThread({ id, title, metadata, fence }) {
11070
12318
  const threadTableName = getTableName$3({
11071
12319
  indexName: TABLE_THREADS,
11072
12320
  schemaName: getSchemaName$3(this.#schema)
11073
12321
  });
11074
- const existingThread = await this.#getThreadById(this.#db.client, { threadId: id });
11075
- if (!existingThread) throw new MastraError({
11076
- id: createStorageErrorId("PG", "UPDATE_THREAD", "FAILED"),
11077
- domain: ErrorDomain.STORAGE,
11078
- category: ErrorCategory.USER,
11079
- text: `Thread ${id} not found`,
11080
- details: {
11081
- threadId: id,
11082
- title: title ?? null
11083
- }
11084
- });
11085
- const mergedMetadata = {
11086
- ...existingThread.metadata,
11087
- ...metadata
11088
- };
11089
12322
  try {
11090
- const nowStr = toUtcISOString(/* @__PURE__ */ new Date());
11091
- const thread = await this.#db.client.one(`UPDATE ${threadTableName}
12323
+ return await this.#fenced(fence, "updateThread", async (q) => {
12324
+ const existingThread = await this.#getThreadById(q, { threadId: id });
12325
+ if (!existingThread) throw new MastraError({
12326
+ id: createStorageErrorId("PG", "UPDATE_THREAD", "FAILED"),
12327
+ domain: ErrorDomain.STORAGE,
12328
+ category: ErrorCategory.USER,
12329
+ text: `Thread ${id} not found`,
12330
+ details: {
12331
+ threadId: id,
12332
+ title: title ?? null
12333
+ }
12334
+ });
12335
+ const mergedMetadata = {
12336
+ ...existingThread.metadata,
12337
+ ...metadata
12338
+ };
12339
+ const nowStr = toUtcISOString(/* @__PURE__ */ new Date());
12340
+ const thread = await q.one(`UPDATE ${threadTableName}
11092
12341
  SET
11093
12342
  title = COALESCE($1, title),
11094
12343
  metadata = $2,
@@ -11097,21 +12346,23 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11097
12346
  WHERE id = $5
11098
12347
  RETURNING *
11099
12348
  `, [
11100
- title ?? null,
11101
- mergedMetadata,
11102
- nowStr,
11103
- nowStr,
11104
- id
11105
- ]);
11106
- return {
11107
- id: thread.id,
11108
- resourceId: thread.resourceId,
11109
- title: thread.title,
11110
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
11111
- createdAt: thread.createdAtZ || thread.createdAt,
11112
- updatedAt: thread.updatedAtZ || thread.updatedAt
11113
- };
12349
+ title ?? null,
12350
+ mergedMetadata,
12351
+ nowStr,
12352
+ nowStr,
12353
+ id
12354
+ ]);
12355
+ return {
12356
+ id: thread.id,
12357
+ resourceId: thread.resourceId,
12358
+ title: thread.title,
12359
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
12360
+ createdAt: thread.createdAtZ || thread.createdAt,
12361
+ updatedAt: thread.updatedAtZ || thread.updatedAt
12362
+ };
12363
+ });
11114
12364
  } catch (error) {
12365
+ if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
11115
12366
  throw new MastraError({
11116
12367
  id: createStorageErrorId("PG", "UPDATE_THREAD", "FAILED"),
11117
12368
  domain: ErrorDomain.STORAGE,
@@ -11664,8 +12915,9 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11664
12915
  throw mastraError;
11665
12916
  }
11666
12917
  }
11667
- async saveMessages({ messages }) {
12918
+ async saveMessages({ messages, fence: explicitFence }) {
11668
12919
  if (messages.length === 0) return { messages: [] };
12920
+ const fence = resolveRunFence(this, explicitFence);
11669
12921
  const threadId = messages[0]?.threadId;
11670
12922
  if (!threadId) throw new MastraError({
11671
12923
  id: createStorageErrorId("PG", "SAVE_MESSAGES", "FAILED"),
@@ -11693,6 +12945,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11693
12945
  });
11694
12946
  const messagesToSave = dedupeMessagesForSave(messages);
11695
12947
  await this.#db.client.tx(async (t) => {
12948
+ if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "saveMessages");
11696
12949
  for (let offset = 0; offset < messagesToSave.length; offset += MAX_MESSAGES_PER_INSERT) {
11697
12950
  const batch = messagesToSave.slice(offset, offset + MAX_MESSAGES_PER_INSERT);
11698
12951
  const values = [];
@@ -11739,7 +12992,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11739
12992
  });
11740
12993
  return { messages: new MessageList().add(messagesWithParsedContent, "memory").get.all.db() };
11741
12994
  } catch (error) {
11742
- if (error instanceof MastraError) throw error;
12995
+ if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
11743
12996
  throw new MastraError({
11744
12997
  id: createStorageErrorId("PG", "SAVE_MESSAGES", "FAILED"),
11745
12998
  domain: ErrorDomain.STORAGE,
@@ -11748,8 +13001,9 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11748
13001
  }, error);
11749
13002
  }
11750
13003
  }
11751
- async updateMessages({ messages }) {
13004
+ async updateMessages({ messages, fence: explicitFence }) {
11752
13005
  if (messages.length === 0) return [];
13006
+ const fence = resolveRunFence(this, explicitFence);
11753
13007
  const messageIds = messages.map((m) => m.id);
11754
13008
  const selectQuery = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId" FROM ${getTableName$3({
11755
13009
  indexName: TABLE_MESSAGES,
@@ -11765,6 +13019,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11765
13019
  });
11766
13020
  const threadIdsToUpdate = /* @__PURE__ */ new Set();
11767
13021
  await this.#db.client.tx(async (t) => {
13022
+ if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "updateMessages");
11768
13023
  const queries = [];
11769
13024
  const columnMapping = { threadId: "thread_id" };
11770
13025
  for (const existingMessage of existingMessages) {
@@ -11825,8 +13080,9 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11825
13080
  return message;
11826
13081
  });
11827
13082
  }
11828
- async deleteMessages(messageIds) {
13083
+ async deleteMessages(messageIds, options) {
11829
13084
  if (!messageIds || messageIds.length === 0) return;
13085
+ const fence = resolveRunFence(this, options?.fence);
11830
13086
  try {
11831
13087
  const messageTableName = getTableName$3({
11832
13088
  indexName: TABLE_MESSAGES,
@@ -11837,12 +13093,14 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11837
13093
  schemaName: getSchemaName$3(this.#schema)
11838
13094
  });
11839
13095
  await this.#db.client.tx(async (t) => {
13096
+ if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "deleteMessages");
11840
13097
  const placeholders = messageIds.map((_, idx) => `$${idx + 1}`).join(",");
11841
13098
  const threadIds = (await t.manyOrNone(`SELECT DISTINCT thread_id FROM ${messageTableName} WHERE id IN (${placeholders})`, messageIds))?.map((msg) => msg.thread_id).filter(Boolean) || [];
11842
13099
  await t.none(`DELETE FROM ${messageTableName} WHERE id IN (${placeholders})`, messageIds);
11843
13100
  if (threadIds.length > 0) await t.none(`UPDATE ${threadTableName} SET "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id IN (${inPlaceholders(threadIds.length)})`, threadIds);
11844
13101
  });
11845
13102
  } catch (error) {
13103
+ if (isRunFenceConflictError(error)) throw error;
11846
13104
  throw new MastraError({
11847
13105
  id: createStorageErrorId("PG", "DELETE_MESSAGES", "FAILED"),
11848
13106
  domain: ErrorDomain.STORAGE,
@@ -11870,6 +13128,9 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11870
13128
  };
11871
13129
  }
11872
13130
  async saveResource({ resource }) {
13131
+ return this.#insertResource(this.#db.client, resource);
13132
+ }
13133
+ async #insertResource(q, resource) {
11873
13134
  const createdAt = toUtcISOString(resource.createdAt);
11874
13135
  const updatedAt = toUtcISOString(resource.updatedAt);
11875
13136
  const metadataJson = toPgJson(resource.metadata);
@@ -11881,7 +13142,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11881
13142
  createdAt,
11882
13143
  updatedAt
11883
13144
  }
11884
- });
13145
+ }, q);
11885
13146
  return {
11886
13147
  ...resource,
11887
13148
  metadata: metadataJson ? JSON.parse(metadataJson) : resource.metadata
@@ -11892,9 +13153,11 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11892
13153
  indexName: TABLE_RESOURCES,
11893
13154
  schemaName: getSchemaName$3(this.#schema)
11894
13155
  });
13156
+ const fence = resolveRunFence(this, void 0);
11895
13157
  const callbackError = {};
11896
13158
  try {
11897
13159
  return await this.#db.client.tx(async (t) => {
13160
+ if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "mergeResourceWorkingMemory");
11898
13161
  const now = (/* @__PURE__ */ new Date()).toISOString();
11899
13162
  await t.none(`INSERT INTO ${tableName} (id, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ")
11900
13163
  VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (id) DO NOTHING`, [
@@ -11938,8 +13201,15 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11938
13201
  }, error);
11939
13202
  }
11940
13203
  }
11941
- async updateResource({ resourceId, workingMemory, metadata }) {
11942
- const existingResource = await this.#getResourceById(this.#db.client, resourceId);
13204
+ async updateResource({ resourceId, workingMemory, metadata, fence }) {
13205
+ return this.#fenced(fence, "updateResource", (q) => this.#updateResource(q, {
13206
+ resourceId,
13207
+ workingMemory,
13208
+ metadata
13209
+ }));
13210
+ }
13211
+ async #updateResource(q, { resourceId, workingMemory, metadata }) {
13212
+ const existingResource = await this.#getResourceById(q, resourceId);
11943
13213
  if (!existingResource) {
11944
13214
  const newResource = {
11945
13215
  id: resourceId,
@@ -11948,7 +13218,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11948
13218
  createdAt: /* @__PURE__ */ new Date(),
11949
13219
  updatedAt: /* @__PURE__ */ new Date()
11950
13220
  };
11951
- return this.saveResource({ resource: newResource });
13221
+ return this.#insertResource(q, newResource);
11952
13222
  }
11953
13223
  const updatedResource = {
11954
13224
  ...existingResource,
@@ -11984,7 +13254,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11984
13254
  updates.push(`"updatedAtZ" = $${paramIndex++}`);
11985
13255
  values.push(updatedAtStr);
11986
13256
  values.push(resourceId);
11987
- await this.#db.client.none(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = $${paramIndex}`, values);
13257
+ await q.none(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = $${paramIndex}`, values);
11988
13258
  return metadataJson ? {
11989
13259
  ...updatedResource,
11990
13260
  metadata: JSON.parse(metadataJson)
@@ -12123,9 +13393,11 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12123
13393
  * serializes generation creation across processes instead. It is
12124
13394
  * transaction-scoped so it also works behind transaction-pooling proxies.
12125
13395
  */
12126
- async #withOMLookupKeyLock(tableName, lookupKey, fn) {
13396
+ async #withOMLookupKeyLock(tableName, lookupKey, fn, fencedOperation) {
13397
+ const fence = fencedOperation ? resolveRunFence(this, void 0) : void 0;
12127
13398
  return this.#db.client.tx(async (t) => {
12128
13399
  await t.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${tableName}:${lookupKey}`]);
13400
+ if (fence && fencedOperation) await assertRunFence(t, this.#runFencesTable(), fence, fencedOperation);
12129
13401
  return fn(t);
12130
13402
  });
12131
13403
  }
@@ -12422,7 +13694,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12422
13694
  const lastObservedAtStr = input.lastObservedAt.toISOString();
12423
13695
  const nowStr = now.toISOString();
12424
13696
  const observedMessageIdsJson = input.observedMessageIds ? toPgJson(input.observedMessageIds) : null;
12425
- if ((await this.#db.client.query(`UPDATE ${tableName} SET
13697
+ if ((await this.#fenced(void 0, "updateActiveObservations", (q) => q.query(`UPDATE ${tableName} SET
12426
13698
  "activeObservations" = $1,
12427
13699
  "lastObservedAt" = $2,
12428
13700
  "lastObservedAtZ" = $3,
@@ -12442,7 +13714,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12442
13714
  nowStr,
12443
13715
  nowStr,
12444
13716
  input.id
12445
- ])).rowCount === 0) throw new MastraError({
13717
+ ]))).rowCount === 0) throw new MastraError({
12446
13718
  id: createStorageErrorId("PG", "UPDATE_ACTIVE_OBSERVATIONS", "NOT_FOUND"),
12447
13719
  text: `Observational memory record not found: ${input.id}`,
12448
13720
  domain: ErrorDomain.STORAGE,
@@ -12450,7 +13722,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12450
13722
  details: { id: input.id }
12451
13723
  });
12452
13724
  } catch (error) {
12453
- if (error instanceof MastraError) throw error;
13725
+ if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
12454
13726
  throw new MastraError({
12455
13727
  id: createStorageErrorId("PG", "UPDATE_ACTIVE_OBSERVATIONS", "FAILED"),
12456
13728
  domain: ErrorDomain.STORAGE,
@@ -12470,8 +13742,9 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12470
13742
  const newer = await this.#getNewerOMGeneration(t, tableName, lookupKey, input.currentRecord.generationCount);
12471
13743
  if (newer) return newer;
12472
13744
  return this.#insertReflectionGeneration(t, tableName, lookupKey, input);
12473
- });
13745
+ }, "createReflectionGeneration");
12474
13746
  } catch (error) {
13747
+ if (isRunFenceConflictError(error)) throw error;
12475
13748
  throw new MastraError({
12476
13749
  id: createStorageErrorId("PG", "CREATE_REFLECTION_GENERATION", "FAILED"),
12477
13750
  domain: ErrorDomain.STORAGE,
@@ -12831,7 +14104,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12831
14104
  extractionFailures: input.chunk.extractionFailures
12832
14105
  };
12833
14106
  const lastBufferedAtTime = input.lastBufferedAtTime ? input.lastBufferedAtTime.toISOString() : null;
12834
- if ((await this.#db.client.query(`UPDATE ${tableName} SET
14107
+ if ((await this.#fenced(void 0, "updateBufferedObservations", (q) => q.query(`UPDATE ${tableName} SET
12835
14108
  "bufferedObservationChunks" = CASE
12836
14109
  WHEN EXISTS (
12837
14110
  SELECT 1
@@ -12850,7 +14123,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12850
14123
  nowStr,
12851
14124
  nowStr,
12852
14125
  input.id
12853
- ])).rowCount === 0) throw new MastraError({
14126
+ ]))).rowCount === 0) throw new MastraError({
12854
14127
  id: createStorageErrorId("PG", "UPDATE_BUFFERED_OBSERVATIONS", "NOT_FOUND"),
12855
14128
  text: `Observational memory record not found: ${input.id}`,
12856
14129
  domain: ErrorDomain.STORAGE,
@@ -12858,7 +14131,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12858
14131
  details: { id: input.id }
12859
14132
  });
12860
14133
  } catch (error) {
12861
- if (error instanceof MastraError) throw error;
14134
+ if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
12862
14135
  throw new MastraError({
12863
14136
  id: createStorageErrorId("PG", "UPDATE_BUFFERED_OBSERVATIONS", "FAILED"),
12864
14137
  domain: ErrorDomain.STORAGE,
@@ -12940,7 +14213,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12940
14213
  const lastObservedAt = input.lastObservedAt ?? (latestChunk?.lastObservedAt ? new Date(latestChunk.lastObservedAt) : /* @__PURE__ */ new Date());
12941
14214
  const lastObservedAtStr = lastObservedAt.toISOString();
12942
14215
  const boundary = `\n\n--- message boundary (${lastObservedAt.toISOString()}) ---\n\n`;
12943
- if ((await this.#db.client.query(`UPDATE ${tableName} SET
14216
+ if ((await this.#fenced(void 0, "swapBufferedToActive", (q) => q.query(`UPDATE ${tableName} SET
12944
14217
  "activeObservations" = CASE
12945
14218
  WHEN "activeObservations" IS NOT NULL AND "activeObservations" != ''
12946
14219
  THEN "activeObservations" || $10 || $1
@@ -12966,7 +14239,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12966
14239
  nowStr,
12967
14240
  input.id,
12968
14241
  boundary
12969
- ])).rowCount === 0) return {
14242
+ ]))).rowCount === 0) return {
12970
14243
  chunksActivated: 0,
12971
14244
  messageTokensActivated: 0,
12972
14245
  observationTokensActivated: 0,
@@ -12994,7 +14267,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12994
14267
  currentTask: latestChunkHints?.currentTask ?? void 0
12995
14268
  };
12996
14269
  } catch (error) {
12997
- if (error instanceof MastraError) throw error;
14270
+ if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
12998
14271
  throw new MastraError({
12999
14272
  id: createStorageErrorId("PG", "SWAP_BUFFERED_TO_ACTIVE", "FAILED"),
13000
14273
  domain: ErrorDomain.STORAGE,
@@ -13010,7 +14283,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13010
14283
  schemaName: getSchemaName$3(this.#schema)
13011
14284
  });
13012
14285
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
13013
- if ((await this.#db.client.query(`UPDATE ${tableName} SET
14286
+ if ((await this.#fenced(void 0, "updateBufferedReflection", (q) => q.query(`UPDATE ${tableName} SET
13014
14287
  "bufferedReflection" = CASE
13015
14288
  WHEN "bufferedReflection" IS NOT NULL AND "bufferedReflection" != ''
13016
14289
  THEN "bufferedReflection" || E'\\n\\n' || $1
@@ -13029,7 +14302,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13029
14302
  nowStr,
13030
14303
  nowStr,
13031
14304
  input.id
13032
- ])).rowCount === 0) throw new MastraError({
14305
+ ]))).rowCount === 0) throw new MastraError({
13033
14306
  id: createStorageErrorId("PG", "UPDATE_BUFFERED_REFLECTION", "NOT_FOUND"),
13034
14307
  text: `Observational memory record not found: ${input.id}`,
13035
14308
  domain: ErrorDomain.STORAGE,
@@ -13037,7 +14310,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13037
14310
  details: { id: input.id }
13038
14311
  });
13039
14312
  } catch (error) {
13040
- if (error instanceof MastraError) throw error;
14313
+ if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
13041
14314
  throw new MastraError({
13042
14315
  id: createStorageErrorId("PG", "UPDATE_BUFFERED_REFLECTION", "FAILED"),
13043
14316
  domain: ErrorDomain.STORAGE,
@@ -13094,9 +14367,9 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13094
14367
  input.currentRecord.id
13095
14368
  ]);
13096
14369
  return newRecord;
13097
- });
14370
+ }, "swapBufferedReflectionToActive");
13098
14371
  } catch (error) {
13099
- if (error instanceof MastraError) throw error;
14372
+ if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
13100
14373
  throw new MastraError({
13101
14374
  id: createStorageErrorId("PG", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "FAILED"),
13102
14375
  domain: ErrorDomain.STORAGE,
@@ -20092,6 +21365,7 @@ var PromptBlocksPG = class PromptBlocksPG extends PromptBlocksStorage {
20092
21365
  }
20093
21366
  async create(input) {
20094
21367
  const { promptBlock } = input;
21368
+ let draftInserted = false;
20095
21369
  try {
20096
21370
  const tableName = getTableName$5({
20097
21371
  indexName: TABLE_PROMPT_BLOCKS,
@@ -20113,6 +21387,7 @@ var PromptBlocksPG = class PromptBlocksPG extends PromptBlocksStorage {
20113
21387
  nowIso,
20114
21388
  nowIso
20115
21389
  ]);
21390
+ draftInserted = true;
20116
21391
  const { id: _id, authorId: _authorId, metadata: _metadata, ...snapshotConfig } = promptBlock;
20117
21392
  const versionId = crypto.randomUUID();
20118
21393
  await this.createVersion({
@@ -20133,14 +21408,14 @@ var PromptBlocksPG = class PromptBlocksPG extends PromptBlocksStorage {
20133
21408
  updatedAt: now
20134
21409
  };
20135
21410
  } catch (error) {
20136
- if (error instanceof MastraError) throw error;
20137
- try {
21411
+ if (draftInserted) try {
20138
21412
  const tableName = getTableName$5({
20139
21413
  indexName: TABLE_PROMPT_BLOCKS,
20140
21414
  schemaName: getSchemaName$5(this.#schema)
20141
21415
  });
20142
21416
  await this.#db.client.none(`DELETE FROM ${tableName} WHERE id = $1 AND status = 'draft' AND "activeVersionId" IS NULL`, [promptBlock.id]);
20143
21417
  } catch {}
21418
+ if (error instanceof MastraError) throw error;
20144
21419
  throw new MastraError({
20145
21420
  id: createStorageErrorId("PG", "CREATE_PROMPT_BLOCK", "FAILED"),
20146
21421
  domain: ErrorDomain.STORAGE,
@@ -21036,6 +22311,7 @@ var ScorerDefinitionsPG = class ScorerDefinitionsPG extends ScorerDefinitionsSto
21036
22311
  }
21037
22312
  async create(input) {
21038
22313
  const { scorerDefinition } = input;
22314
+ let draftInserted = false;
21039
22315
  try {
21040
22316
  const tableName = getTableName$5({
21041
22317
  indexName: TABLE_SCORER_DEFINITIONS,
@@ -21059,6 +22335,7 @@ var ScorerDefinitionsPG = class ScorerDefinitionsPG extends ScorerDefinitionsSto
21059
22335
  nowIso,
21060
22336
  nowIso
21061
22337
  ]);
22338
+ draftInserted = true;
21062
22339
  const { id: _id, authorId: _authorId, organizationId: _organizationId, projectId: _projectId, metadata: _metadata, ...snapshotConfig } = scorerDefinition;
21063
22340
  const versionId = crypto.randomUUID();
21064
22341
  await this.createVersion({
@@ -21081,7 +22358,7 @@ var ScorerDefinitionsPG = class ScorerDefinitionsPG extends ScorerDefinitionsSto
21081
22358
  updatedAt: now
21082
22359
  };
21083
22360
  } catch (error) {
21084
- try {
22361
+ if (draftInserted) try {
21085
22362
  const tableName = getTableName$5({
21086
22363
  indexName: TABLE_SCORER_DEFINITIONS,
21087
22364
  schemaName: getSchemaName$5(this.#schema)
@@ -22099,6 +23376,7 @@ var SkillsPG = class SkillsPG extends SkillsStorage {
22099
23376
  }
22100
23377
  async create(input) {
22101
23378
  const { skill } = input;
23379
+ let draftInserted = false;
22102
23380
  try {
22103
23381
  const tableName = getTableName$5({
22104
23382
  indexName: TABLE_SKILLS,
@@ -22122,6 +23400,7 @@ var SkillsPG = class SkillsPG extends SkillsStorage {
22122
23400
  nowIso,
22123
23401
  nowIso
22124
23402
  ]);
23403
+ draftInserted = true;
22125
23404
  const { id: _id, authorId: _authorId, visibility: _visibility, ...snapshotConfig } = skill;
22126
23405
  const versionId = crypto.randomUUID();
22127
23406
  await this.createVersion({
@@ -22143,7 +23422,7 @@ var SkillsPG = class SkillsPG extends SkillsStorage {
22143
23422
  updatedAt: now
22144
23423
  };
22145
23424
  } catch (error) {
22146
- try {
23425
+ if (draftInserted) try {
22147
23426
  const tableName = getTableName$5({
22148
23427
  indexName: TABLE_SKILLS,
22149
23428
  schemaName: getSchemaName$5(this.#schema)
@@ -23288,6 +24567,17 @@ function getTableName({ indexName, schemaName }) {
23288
24567
  const quotedIndexName = `"${indexName}"`;
23289
24568
  return schemaName ? `${schemaName}.${quotedIndexName}` : quotedIndexName;
23290
24569
  }
24570
+ const RUN_OWNER_COLUMNS = `generation, "ownerId", "leaseExpiresAt", ${DB_NOW_MS} AS "nowMs"`;
24571
+ function toRunOwnershipRecord(runId, row) {
24572
+ const leaseExpiresAt = row.leaseExpiresAt === null ? null : Number(row.leaseExpiresAt);
24573
+ return {
24574
+ runId,
24575
+ generation: Number(row.generation),
24576
+ ownerId: row.ownerId,
24577
+ leaseExpiresAt: leaseExpiresAt === null ? null : new Date(leaseExpiresAt),
24578
+ live: leaseExpiresAt !== null && leaseExpiresAt > Number(row.nowMs)
24579
+ };
24580
+ }
23291
24581
  /** Base name (before any schema prefix) of the expression index backing the status filter. */
23292
24582
  const WORKFLOW_SNAPSHOT_STATUS_INDEX = "mastra_workflow_snapshot_name_status_createdat_idx";
23293
24583
  /**
@@ -23372,12 +24662,24 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
23372
24662
  * Workflow run snapshots accumulate as runs execute. Anchored on the
23373
24663
  * timezone-aware `updatedAtZ` mirror column (last activity) so suspended or
23374
24664
  * long-running runs are not pruned by start age.
24665
+ *
24666
+ * Run ownership rows are anchored on `updatedAt` (epoch ms), which every
24667
+ * claim, renewal and release refreshes, so a live execution's row is never
24668
+ * older than its renew interval.
23375
24669
  */
23376
- static retentionTables = { workflowSnapshot: {
23377
- table: TABLE_WORKFLOW_SNAPSHOT,
23378
- column: "updatedAtZ",
23379
- indexed: true
23380
- } };
24670
+ static retentionTables = {
24671
+ workflowSnapshot: {
24672
+ table: TABLE_WORKFLOW_SNAPSHOT,
24673
+ column: "updatedAtZ",
24674
+ indexed: true
24675
+ },
24676
+ runOwnership: {
24677
+ table: TABLE_WORKFLOW_RUN_OWNERS,
24678
+ column: "updatedAt",
24679
+ indexed: true,
24680
+ anchorType: "epoch-ms"
24681
+ }
24682
+ };
23381
24683
  constructor(config) {
23382
24684
  super();
23383
24685
  const { client, readClient, schemaName, skipDefaultIndexes, indexes } = resolvePgConfig(config);
@@ -23434,6 +24736,12 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
23434
24736
  for (const idx of WorkflowsPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
23435
24737
  statements.push(`${workflowSnapshotStatusIndexSQL(workflowSnapshotStatusIndexName(parsedSchema), schemaName)};`);
23436
24738
  statements.push(`${workflowSnapshotThreadIdIndexSQL(workflowSnapshotThreadIdIndexName(parsedSchema), schemaName)};`);
24739
+ statements.push(generateTableSQL({
24740
+ tableName: TABLE_WORKFLOW_RUN_OWNERS,
24741
+ schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_WORKFLOW_RUN_OWNERS],
24742
+ schemaName,
24743
+ includeAllConstraints: true
24744
+ }));
23437
24745
  return statements;
23438
24746
  }
23439
24747
  /**
@@ -23477,6 +24785,10 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
23477
24785
  schema: TABLE_SCHEMAS[TABLE_WORKFLOW_SNAPSHOT],
23478
24786
  ifNotExists: ["resourceId"]
23479
24787
  });
24788
+ await this.#db.createTable({
24789
+ tableName: TABLE_WORKFLOW_RUN_OWNERS,
24790
+ schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_WORKFLOW_RUN_OWNERS]
24791
+ });
23480
24792
  await this.createDefaultIndexes();
23481
24793
  await this.createCustomIndexes();
23482
24794
  }
@@ -23504,13 +24816,13 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
23504
24816
  }
23505
24817
  }
23506
24818
  }
23507
- /** Delete workflow run snapshots older than the `workflowSnapshot` policy's `maxAge`, batched. */
24819
+ /** Delete workflow run snapshots and run ownership rows older than their policy's `maxAge`, batched. */
23508
24820
  async prune(policies, options) {
23509
24821
  await this.ensureRetentionIndexes(policies);
23510
24822
  const targets = resolveTargets({
23511
24823
  policies,
23512
24824
  descriptor: WorkflowsPG.retentionTables,
23513
- order: ["workflowSnapshot"]
24825
+ order: ["workflowSnapshot", "runOwnership"]
23514
24826
  });
23515
24827
  return runPrune({
23516
24828
  db: this.#db,
@@ -23532,10 +24844,126 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
23532
24844
  }
23533
24845
  async dangerouslyClearAll() {
23534
24846
  await this.#db.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
24847
+ await this.#db.clearTable({ tableName: TABLE_WORKFLOW_RUN_OWNERS });
24848
+ }
24849
+ supportsRunFencing() {
24850
+ return true;
24851
+ }
24852
+ #runOwnersTable() {
24853
+ return getTableName({
24854
+ indexName: TABLE_WORKFLOW_RUN_OWNERS,
24855
+ schemaName: getSchemaName(this.#schema)
24856
+ });
24857
+ }
24858
+ async #readRunOwner(q, runId) {
24859
+ const row = await q.oneOrNone(`SELECT ${RUN_OWNER_COLUMNS} FROM ${this.#runOwnersTable()} WHERE "runId" = $1`, [runId]);
24860
+ return row ? toRunOwnershipRecord(runId, row) : null;
24861
+ }
24862
+ #ownershipError(operation, runId, error) {
24863
+ return new MastraError({
24864
+ id: createStorageErrorId("PG", operation, "FAILED"),
24865
+ domain: ErrorDomain.STORAGE,
24866
+ category: ErrorCategory.THIRD_PARTY,
24867
+ details: { runId }
24868
+ }, error);
24869
+ }
24870
+ async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
24871
+ const table = this.#runOwnersTable();
24872
+ try {
24873
+ return await this.#db.client.tx(async (t) => {
24874
+ if (!await t.oneOrNone(`SELECT 1 FROM ${table} WHERE "runId" = $1 FOR UPDATE`, [runId])) {
24875
+ if (expectedGeneration !== void 0 && expectedGeneration !== 0) return {
24876
+ acquired: false,
24877
+ record: null
24878
+ };
24879
+ const inserted = await t.oneOrNone(`INSERT INTO ${table} ("runId", generation, "ownerId", "leaseExpiresAt", "updatedAt")
24880
+ VALUES ($1, GREATEST(1, ${DB_NOW_MS}), $2, ${DB_NOW_MS} + $3, ${DB_NOW_MS})
24881
+ ON CONFLICT ("runId") DO NOTHING
24882
+ RETURNING ${RUN_OWNER_COLUMNS}`, [
24883
+ runId,
24884
+ ownerId,
24885
+ leaseMs
24886
+ ]);
24887
+ if (inserted) return {
24888
+ acquired: true,
24889
+ record: toRunOwnershipRecord(runId, inserted)
24890
+ };
24891
+ }
24892
+ const claimed = await t.oneOrNone(`UPDATE ${table}
24893
+ SET generation = generation + 1, "ownerId" = $2, "leaseExpiresAt" = ${DB_NOW_MS} + $3,
24894
+ "updatedAt" = ${DB_NOW_MS}
24895
+ WHERE "runId" = $1
24896
+ AND ($4::bigint IS NULL OR generation = $4)
24897
+ AND ($5::boolean OR "leaseExpiresAt" IS NULL OR "leaseExpiresAt" <= ${DB_NOW_MS})
24898
+ RETURNING ${RUN_OWNER_COLUMNS}`, [
24899
+ runId,
24900
+ ownerId,
24901
+ leaseMs,
24902
+ expectedGeneration ?? null,
24903
+ force === true
24904
+ ]);
24905
+ if (claimed) return {
24906
+ acquired: true,
24907
+ record: toRunOwnershipRecord(runId, claimed)
24908
+ };
24909
+ return {
24910
+ acquired: false,
24911
+ record: await this.#readRunOwner(t, runId)
24912
+ };
24913
+ });
24914
+ } catch (error) {
24915
+ throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", runId, error);
24916
+ }
24917
+ }
24918
+ async renewRunOwnership({ leaseMs, ...fence }) {
24919
+ try {
24920
+ const renewed = await this.#db.client.oneOrNone(`UPDATE ${this.#runOwnersTable()}
24921
+ SET "leaseExpiresAt" = ${DB_NOW_MS} + $4, "updatedAt" = ${DB_NOW_MS}
24922
+ WHERE "runId" = $1 AND generation = $2 AND "ownerId" = $3 AND "leaseExpiresAt" IS NOT NULL
24923
+ RETURNING ${RUN_OWNER_COLUMNS}`, [
24924
+ fence.runId,
24925
+ fence.generation,
24926
+ fence.ownerId,
24927
+ leaseMs
24928
+ ]);
24929
+ if (renewed) return {
24930
+ renewed: true,
24931
+ record: toRunOwnershipRecord(fence.runId, renewed)
24932
+ };
24933
+ return {
24934
+ renewed: false,
24935
+ record: await this.#readRunOwner(this.#db.client, fence.runId)
24936
+ };
24937
+ } catch (error) {
24938
+ throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, error);
24939
+ }
24940
+ }
24941
+ async releaseRunOwnership(fence) {
24942
+ try {
24943
+ return await this.#db.client.oneOrNone(`UPDATE ${this.#runOwnersTable()}
24944
+ SET "leaseExpiresAt" = NULL, "updatedAt" = ${DB_NOW_MS}
24945
+ WHERE "runId" = $1 AND generation = $2 AND "ownerId" = $3
24946
+ RETURNING "runId"`, [
24947
+ fence.runId,
24948
+ fence.generation,
24949
+ fence.ownerId
24950
+ ]) !== null;
24951
+ } catch (error) {
24952
+ throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
24953
+ }
24954
+ }
24955
+ async getRunOwnership({ runId }) {
24956
+ try {
24957
+ return await this.#readRunOwner(this.#db.client, runId);
24958
+ } catch (error) {
24959
+ throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
24960
+ }
23535
24961
  }
23536
- async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state }) {
24962
+ async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence: explicitFence }) {
24963
+ const fence = resolveRunFence(this, explicitFence, runId);
23537
24964
  try {
23538
24965
  return await this.#db.client.tx(async (t) => {
24966
+ if (fence) await assertRunFence(t, this.#runOwnersTable(), fence, "updateWorkflowResults");
23539
24967
  const tableName = getTableName({
23540
24968
  indexName: TABLE_WORKFLOW_SNAPSHOT,
23541
24969
  schemaName: getSchemaName(this.#schema)
@@ -23585,6 +25013,7 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
23585
25013
  return snapshot.context;
23586
25014
  });
23587
25015
  } catch (error) {
25016
+ if (isRunFenceConflictError(error)) throw error;
23588
25017
  throw new MastraError({
23589
25018
  id: createStorageErrorId("PG", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
23590
25019
  domain: ErrorDomain.STORAGE,
@@ -23597,9 +25026,11 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
23597
25026
  }, error);
23598
25027
  }
23599
25028
  }
23600
- async updateWorkflowState({ workflowName, runId, opts }) {
25029
+ async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
25030
+ const fence = resolveRunFence(this, explicitFence, runId);
23601
25031
  try {
23602
25032
  return await this.#db.client.tx(async (t) => {
25033
+ if (fence) await assertRunFence(t, this.#runOwnersTable(), fence, "updateWorkflowState");
23603
25034
  const tableName = getTableName({
23604
25035
  indexName: TABLE_WORKFLOW_SNAPSHOT,
23605
25036
  schemaName: getSchemaName(this.#schema)
@@ -23629,6 +25060,7 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
23629
25060
  return updatedSnapshot;
23630
25061
  });
23631
25062
  } catch (error) {
25063
+ if (isRunFenceConflictError(error)) throw error;
23632
25064
  throw new MastraError({
23633
25065
  id: createStorageErrorId("PG", "UPDATE_WORKFLOW_STATE", "FAILED"),
23634
25066
  domain: ErrorDomain.STORAGE,
@@ -23640,13 +25072,13 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
23640
25072
  }, error);
23641
25073
  }
23642
25074
  }
23643
- async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
25075
+ async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
23644
25076
  try {
23645
25077
  const now = /* @__PURE__ */ new Date();
23646
25078
  const createdAtValue = createdAt ? createdAt : now;
23647
25079
  const updatedAtValue = updatedAt ? updatedAt : now;
23648
25080
  const sanitizedSnapshot = toPgJson(snapshot);
23649
- await this.#db.client.none(`INSERT INTO ${getTableName({
25081
+ await withRunFence(this.#db.client, this.#runOwnersTable(), resolveRunFence(this, fence, runId), "persistWorkflowSnapshot", (q) => q.none(`INSERT INTO ${getTableName({
23650
25082
  indexName: TABLE_WORKFLOW_SNAPSHOT,
23651
25083
  schemaName: getSchemaName(this.#schema)
23652
25084
  })} AS t
@@ -23662,8 +25094,9 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
23662
25094
  updatedAtValue,
23663
25095
  createdAtValue,
23664
25096
  updatedAtValue
23665
- ]);
25097
+ ]));
23666
25098
  } catch (error) {
25099
+ if (isRunFenceConflictError(error)) throw error;
23667
25100
  throw new MastraError({
23668
25101
  id: createStorageErrorId("PG", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
23669
25102
  domain: ErrorDomain.STORAGE,
@@ -23729,13 +25162,14 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
23729
25162
  }, error);
23730
25163
  }
23731
25164
  }
23732
- async deleteWorkflowRunById({ runId, workflowName }) {
25165
+ async deleteWorkflowRunById({ runId, workflowName, fence }) {
23733
25166
  try {
23734
- await this.#db.client.none(`DELETE FROM ${getTableName({
25167
+ await withRunFence(this.#db.client, this.#runOwnersTable(), resolveRunFence(this, fence, runId), "deleteWorkflowRunById", (q) => q.none(`DELETE FROM ${getTableName({
23735
25168
  indexName: TABLE_WORKFLOW_SNAPSHOT,
23736
25169
  schemaName: getSchemaName(this.#schema)
23737
- })} WHERE run_id = $1 AND workflow_name = $2`, [runId, workflowName]);
25170
+ })} WHERE run_id = $1 AND workflow_name = $2`, [runId, workflowName]));
23738
25171
  } catch (error) {
25172
+ if (isRunFenceConflictError(error)) throw error;
23739
25173
  throw new MastraError({
23740
25174
  id: createStorageErrorId("PG", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
23741
25175
  domain: ErrorDomain.STORAGE,
@@ -23947,6 +25381,7 @@ var WorkspacesPG = class WorkspacesPG extends WorkspacesStorage {
23947
25381
  }
23948
25382
  async create(input) {
23949
25383
  const { workspace } = input;
25384
+ let draftInserted = false;
23950
25385
  try {
23951
25386
  const tableName = getTableName$5({
23952
25387
  indexName: TABLE_WORKSPACES,
@@ -23968,6 +25403,7 @@ var WorkspacesPG = class WorkspacesPG extends WorkspacesStorage {
23968
25403
  nowIso,
23969
25404
  nowIso
23970
25405
  ]);
25406
+ draftInserted = true;
23971
25407
  const { id: _id, authorId: _authorId, metadata: _metadata, ...snapshotConfig } = workspace;
23972
25408
  const versionId = crypto.randomUUID();
23973
25409
  await this.createVersion({
@@ -23988,7 +25424,7 @@ var WorkspacesPG = class WorkspacesPG extends WorkspacesStorage {
23988
25424
  updatedAt: now
23989
25425
  };
23990
25426
  } catch (error) {
23991
- try {
25427
+ if (draftInserted) try {
23992
25428
  const tableName = getTableName$5({
23993
25429
  indexName: TABLE_WORKSPACES,
23994
25430
  schemaName: getSchemaName$5(this.#schema)
@@ -25038,7 +26474,10 @@ var PostgresStore = class extends MastraCompositeStore {
25038
26474
  workflows: new WorkflowsPG(domainConfig),
25039
26475
  workflowDefinitions: new WorkflowDefinitionsPG(domainConfig),
25040
26476
  memory: new MemoryPG(domainConfig),
25041
- knowledge: new KnowledgePG(domainConfig),
26477
+ knowledge: new KnowledgePG({
26478
+ ...domainConfig,
26479
+ storageIsolationKey: config.storageIsolationKey ?? getPgKnowledgeIsolationKey(config)
26480
+ }),
25042
26481
  notifications: new NotificationsPG(domainConfig),
25043
26482
  observability: new ObservabilityPG(domainConfig),
25044
26483
  agents: new AgentsPG(domainConfig),
@@ -25092,7 +26531,7 @@ var PostgresStore = class extends MastraCompositeStore {
25092
26531
  const pinned = new PinnedClientAdapter(this.#writePool, pinnedClient);
25093
26532
  this.#db.pin(pinned);
25094
26533
  this.#db.setSchemaSnapshot(await loadSchemaSnapshot(pinned, this.schema));
25095
- await super.init();
26534
+ await Promise.all(Object.entries(this.stores).filter(([name, store]) => name !== "knowledge" && store).map(([, store]) => store.init()));
25096
26535
  this.isInitialized = true;
25097
26536
  } catch (error) {
25098
26537
  this.#initPromise = null;