@mastra/pg 1.31.0-alpha.1 → 1.31.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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, 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_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, 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";
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";
@@ -2475,7 +2475,8 @@ async function loadSchemaSnapshot(client, schemaName) {
2475
2475
  const schema = schemaName || "public";
2476
2476
  const [tableRows, columnRows, indexRows] = await Promise.all([
2477
2477
  client.manyOrNone(`SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = $1`, [schema]),
2478
- client.manyOrNone(`SELECT c.relname AS table_name, a.attname AS column_name, format_type(a.atttypid, a.atttypmod) AS data_type
2478
+ client.manyOrNone(`SELECT c.relname AS table_name, a.attname AS column_name, format_type(a.atttypid, a.atttypmod) AS data_type,
2479
+ a.attnotnull AS not_null
2479
2480
  FROM pg_catalog.pg_class c
2480
2481
  JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
2481
2482
  JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid
@@ -2491,6 +2492,7 @@ async function loadSchemaSnapshot(client, schemaName) {
2491
2492
  ]);
2492
2493
  const columns = /* @__PURE__ */ new Map();
2493
2494
  const columnTypes = /* @__PURE__ */ new Map();
2495
+ const notNullColumns = /* @__PURE__ */ new Map();
2494
2496
  for (const row of columnRows) {
2495
2497
  let set = columns.get(row.table_name);
2496
2498
  if (!set) {
@@ -2504,6 +2506,14 @@ async function loadSchemaSnapshot(client, schemaName) {
2504
2506
  columnTypes.set(row.table_name, types);
2505
2507
  }
2506
2508
  types.set(row.column_name, row.data_type);
2509
+ if (row.not_null) {
2510
+ let notNull = notNullColumns.get(row.table_name);
2511
+ if (!notNull) {
2512
+ notNull = /* @__PURE__ */ new Set();
2513
+ notNullColumns.set(row.table_name, notNull);
2514
+ }
2515
+ notNull.add(row.column_name);
2516
+ }
2507
2517
  }
2508
2518
  const indexes = /* @__PURE__ */ new Set();
2509
2519
  const replicaIdentityIndexes = /* @__PURE__ */ new Set();
@@ -2520,6 +2530,7 @@ async function loadSchemaSnapshot(client, schemaName) {
2520
2530
  tables: new Set(tableRows.map((r) => r.tablename)),
2521
2531
  columns,
2522
2532
  columnTypes,
2533
+ notNullColumns,
2523
2534
  indexes,
2524
2535
  replicaIdentityIndexes,
2525
2536
  primaryKeyIndexes,
@@ -3598,6 +3609,33 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
3598
3609
  this.columnTypeCache.delete(tableName);
3599
3610
  }
3600
3611
  }
3612
+ /**
3613
+ * Drops NOT NULL from `columns` on `tableName` where it is still set. Answered
3614
+ * from the init snapshot when one is installed, so a converged schema issues
3615
+ * no query and never takes the ACCESS EXCLUSIVE lock.
3616
+ */
3617
+ async dropNotNull({ tableName, columns }) {
3618
+ const parsedColumns = columns.map((c) => parseSqlIdentifier(c, "column name"));
3619
+ const snapshot = this.schemaSnapshot;
3620
+ let toAlter;
3621
+ if (snapshot) {
3622
+ const notNull = snapshot.notNullColumns.get(tableName);
3623
+ toAlter = parsedColumns.filter((c) => notNull?.has(c));
3624
+ } else toAlter = (await this.client.any(`SELECT column_name FROM information_schema.columns
3625
+ WHERE table_schema = $1 AND table_name = $2 AND column_name = ANY($3) AND is_nullable = 'NO'`, [
3626
+ this.schemaName || "public",
3627
+ tableName,
3628
+ parsedColumns
3629
+ ])).map((r) => r.column_name);
3630
+ if (toAlter.length === 0) return;
3631
+ const fullTableName = getTableName$6({
3632
+ indexName: tableName,
3633
+ schemaName: getSchemaName$6(this.schemaName)
3634
+ });
3635
+ await this.client.none(`ALTER TABLE ${fullTableName} ${toAlter.map((c) => `ALTER COLUMN "${c}" DROP NOT NULL`).join(", ")}`);
3636
+ const notNull = snapshot?.notNullColumns.get(tableName);
3637
+ for (const c of toAlter) notNull?.delete(c);
3638
+ }
3601
3639
  async load({ tableName, keys }) {
3602
3640
  try {
3603
3641
  const keyEntries = Object.entries(keys).map(([key, value]) => [parseSqlIdentifier(key, "column name"), value]);
@@ -7088,6 +7126,10 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7088
7126
  "scorerIds"
7089
7127
  ]
7090
7128
  });
7129
+ await this.#db.dropNotNull({
7130
+ tableName: TABLE_EXPERIMENTS,
7131
+ columns: ["targetType", "targetId"]
7132
+ });
7091
7133
  await this.#db.alterTable({
7092
7134
  tableName: TABLE_EXPERIMENT_RESULTS,
7093
7135
  schema: EXPERIMENT_RESULTS_SCHEMA,
@@ -8235,18 +8277,650 @@ var FavoritesPG = class FavoritesPG extends FavoritesStorage {
8235
8277
  }
8236
8278
  };
8237
8279
  //#endregion
8280
+ //#region ../../packages/_internal-core/dist/knowledge-compat/index.js
8281
+ const KNOWLEDGE_V2_CORE_FEATURE = "knowledge-v2";
8282
+ /** Hard cap on scope nodes returned by one `listScopeNodes` read. */
8283
+ const MAX_KNOWLEDGE_SCOPE_NODES = 1e3;
8284
+ function knowledgeScopeNodeFilterKey(input) {
8285
+ return JSON.stringify([
8286
+ input.withinAddress ?? null,
8287
+ input.addresses ? [...input.addresses].sort() : null,
8288
+ input.ids ? [...input.ids].sort() : null
8289
+ ]);
8290
+ }
8291
+ function createKnowledgeScopeNodeCursor(scope, input) {
8292
+ return encodeURIComponent(JSON.stringify({
8293
+ version: 1,
8294
+ type: "scope",
8295
+ name: scope.name,
8296
+ id: scope.id,
8297
+ filter: knowledgeScopeNodeFilterKey(input)
8298
+ }));
8299
+ }
8300
+ /** Validates the page size and cursor of a `listScopeNodes` query; throws on a cursor from a different query. */
8301
+ function parseListKnowledgeScopeNodesInput(input = {}) {
8302
+ const requested = Number.isFinite(input.limit) ? Math.trunc(input.limit) : MAX_KNOWLEDGE_SCOPE_NODES;
8303
+ const limit = Math.min(Math.max(requested, 1), MAX_KNOWLEDGE_SCOPE_NODES);
8304
+ if (!input.cursor) return {
8305
+ limit,
8306
+ after: null
8307
+ };
8308
+ let value;
8309
+ try {
8310
+ value = JSON.parse(decodeURIComponent(input.cursor));
8311
+ } catch {
8312
+ throw new Error("Invalid Knowledge scope node cursor.");
8313
+ }
8314
+ const parsed = value && typeof value === "object" ? value : {};
8315
+ 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.");
8316
+ return {
8317
+ limit,
8318
+ after: {
8319
+ name: parsed.name,
8320
+ id: parsed.id
8321
+ }
8322
+ };
8323
+ }
8324
+ /** Builds a page from up to `limit + 1` name/id-ordered scope nodes. */
8325
+ function pageKnowledgeScopeNodes(rows, limit, input) {
8326
+ const scopes = rows.slice(0, limit);
8327
+ const last = scopes.at(-1);
8328
+ return {
8329
+ scopes,
8330
+ nextCursor: rows.length > limit && last ? createKnowledgeScopeNodeCursor(last, input) : null
8331
+ };
8332
+ }
8333
+ /** Clamps a requested member limit to 1–{@link MAX_KNOWLEDGE_SCOPE_MEMBERS_LIMIT}. */
8334
+ function knowledgeScopeMembersLimit(limit) {
8335
+ if (limit === void 0 || !Number.isFinite(limit)) return 500;
8336
+ return Math.min(Math.max(Math.trunc(limit), 1), 500);
8337
+ }
8338
+ /** Validates the page size and cursor of a `listScopeMembers` query; throws on a cursor from another scope. */
8339
+ function parseListKnowledgeScopeMembersInput(input) {
8340
+ const limit = knowledgeScopeMembersLimit(input.limit);
8341
+ if (!input.cursor) return {
8342
+ limit,
8343
+ after: null
8344
+ };
8345
+ let value;
8346
+ try {
8347
+ value = JSON.parse(decodeURIComponent(input.cursor));
8348
+ } catch {
8349
+ throw new Error("Invalid Knowledge scope member cursor.");
8350
+ }
8351
+ const parsed = value && typeof value === "object" ? value : {};
8352
+ const updatedAt = typeof parsed.updatedAt === "string" ? new Date(parsed.updatedAt) : /* @__PURE__ */ new Date(NaN);
8353
+ 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.");
8354
+ return {
8355
+ limit,
8356
+ after: {
8357
+ updatedAt,
8358
+ name: parsed.name,
8359
+ id: parsed.id
8360
+ }
8361
+ };
8362
+ }
8363
+ /** Builds a page from up to `limit + 1` members in `listScopeMembers` order. */
8364
+ function pageKnowledgeScopeMembers(rows, limit, input) {
8365
+ const members = rows.slice(0, limit);
8366
+ const last = members.at(-1);
8367
+ const hasMore = rows.length > limit;
8368
+ return {
8369
+ members,
8370
+ hasMore,
8371
+ nextCursor: hasMore && last ? encodeURIComponent(JSON.stringify({
8372
+ version: 1,
8373
+ type: "scope-member",
8374
+ scopeNodeId: input.scopeNodeId,
8375
+ updatedAt: last.updatedAt.toISOString(),
8376
+ name: last.name,
8377
+ id: last.id
8378
+ })) : null
8379
+ };
8380
+ }
8381
+ const TABLE_KNOWLEDGE_NODES$1 = "mastra_knowledge_nodes";
8382
+ const TABLE_KNOWLEDGE_RECORDS$1 = "mastra_knowledge_records";
8383
+ const TABLE_KNOWLEDGE_MENTIONS$1 = "mastra_knowledge_mentions";
8384
+ const TABLE_KNOWLEDGE_ACTIVITY$1 = "mastra_knowledge_activity";
8385
+ const TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1 = "mastra_knowledge_semantic_outbox";
8386
+ const TABLE_KNOWLEDGE_NODE_SCOPES = "mastra_knowledge_node_scopes";
8387
+ const TABLE_KNOWLEDGE_RECORD_SCOPES = "mastra_knowledge_record_scopes";
8388
+ const TABLE_KNOWLEDGE_SCOPE_GRANTS = "mastra_knowledge_scope_grants";
8389
+ const TABLE_KNOWLEDGE_ACCESS_STATE = "mastra_knowledge_access_state";
8390
+ const TABLE_KNOWLEDGE_SCOPE_ADDRESSES = "mastra_knowledge_scope_addresses";
8391
+ const TABLE_KNOWLEDGE_NODE_ADDRESSES = "mastra_knowledge_node_addresses";
8392
+ const TABLE_KNOWLEDGE_IMPORT_STATE = "mastra_knowledge_import_state";
8393
+ const TABLE_KNOWLEDGE_IMPORT_RUNS = "mastra_knowledge_import_runs";
8394
+ const TABLE_KNOWLEDGE_PROPOSALS = "mastra_knowledge_proposals";
8395
+ const KNOWLEDGE_TABLE_NAMES = [
8396
+ TABLE_KNOWLEDGE_NODES$1,
8397
+ TABLE_KNOWLEDGE_RECORDS$1,
8398
+ TABLE_KNOWLEDGE_MENTIONS$1,
8399
+ TABLE_KNOWLEDGE_ACTIVITY$1,
8400
+ TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1,
8401
+ TABLE_KNOWLEDGE_NODE_SCOPES,
8402
+ TABLE_KNOWLEDGE_RECORD_SCOPES,
8403
+ TABLE_KNOWLEDGE_SCOPE_GRANTS,
8404
+ TABLE_KNOWLEDGE_ACCESS_STATE,
8405
+ TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
8406
+ TABLE_KNOWLEDGE_NODE_ADDRESSES,
8407
+ TABLE_KNOWLEDGE_IMPORT_STATE,
8408
+ TABLE_KNOWLEDGE_IMPORT_RUNS,
8409
+ TABLE_KNOWLEDGE_PROPOSALS
8410
+ ];
8411
+ /** Knowledge v1 tables with no v2 equivalent; an explicit schema reset drops them. */
8412
+ const RETIRED_KNOWLEDGE_TABLE_NAMES = ["mastra_knowledge_cursors"];
8413
+ /** Tables and indexes published v1 adapters created for every app, whether or not it used Knowledge. */
8414
+ const PUBLISHED_KNOWLEDGE_V1_TABLE_NAMES = /* @__PURE__ */ new Set([
8415
+ TABLE_KNOWLEDGE_NODES$1,
8416
+ TABLE_KNOWLEDGE_RECORDS$1,
8417
+ TABLE_KNOWLEDGE_MENTIONS$1,
8418
+ TABLE_KNOWLEDGE_ACTIVITY$1,
8419
+ TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1,
8420
+ ...RETIRED_KNOWLEDGE_TABLE_NAMES
8421
+ ]);
8422
+ const PUBLISHED_KNOWLEDGE_V1_INDEX_NAMES = /* @__PURE__ */ new Set([
8423
+ "idx_knowledge_nodes_identity",
8424
+ "idx_knowledge_nodes_scope",
8425
+ "idx_knowledge_records_node_latest",
8426
+ "idx_knowledge_records_thread_latest",
8427
+ "idx_knowledge_mentions_record",
8428
+ "idx_knowledge_activity_latest",
8429
+ "idx_knowledge_outbox_idempotency",
8430
+ "idx_knowledge_outbox_claim"
8431
+ ]);
8432
+ const KNOWLEDGE_V2_NODES_SCHEMA = {
8433
+ id: {
8434
+ type: "text",
8435
+ nullable: false,
8436
+ primaryKey: true
8437
+ },
8438
+ name: {
8439
+ type: "text",
8440
+ nullable: false
8441
+ },
8442
+ kind: {
8443
+ type: "text",
8444
+ nullable: true
8445
+ },
8446
+ isScope: {
8447
+ type: "boolean",
8448
+ nullable: false
8449
+ },
8450
+ metadata: {
8451
+ type: "jsonb",
8452
+ nullable: true
8453
+ },
8454
+ version: {
8455
+ type: "integer",
8456
+ nullable: false
8457
+ },
8458
+ createdAt: {
8459
+ type: "timestamp",
8460
+ nullable: false
8461
+ },
8462
+ updatedAt: {
8463
+ type: "timestamp",
8464
+ nullable: false
8465
+ },
8466
+ deletedAt: {
8467
+ type: "timestamp",
8468
+ nullable: true
8469
+ },
8470
+ deletedBy: {
8471
+ type: "text",
8472
+ nullable: true
8473
+ },
8474
+ type: {
8475
+ type: "text",
8476
+ nullable: true
8477
+ },
8478
+ canonicalName: {
8479
+ type: "text",
8480
+ nullable: true
8481
+ },
8482
+ content: {
8483
+ type: "text",
8484
+ nullable: true
8485
+ },
8486
+ description: {
8487
+ type: "text",
8488
+ nullable: true
8489
+ },
8490
+ scope: {
8491
+ type: "jsonb",
8492
+ nullable: true
8493
+ },
8494
+ scopeKey: {
8495
+ type: "text",
8496
+ nullable: true
8497
+ },
8498
+ mergedInto: {
8499
+ type: "text",
8500
+ nullable: true
8501
+ }
8502
+ };
8503
+ const KNOWLEDGE_V2_RECORDS_SCHEMA = {
8504
+ id: {
8505
+ type: "text",
8506
+ nullable: false,
8507
+ primaryKey: true
8508
+ },
8509
+ node: {
8510
+ type: "text",
8511
+ nullable: false,
8512
+ references: {
8513
+ table: TABLE_KNOWLEDGE_NODES$1,
8514
+ column: "id"
8515
+ }
8516
+ },
8517
+ text: {
8518
+ type: "text",
8519
+ nullable: false
8520
+ },
8521
+ metadata: {
8522
+ type: "jsonb",
8523
+ nullable: true
8524
+ },
8525
+ version: {
8526
+ type: "integer",
8527
+ nullable: false
8528
+ },
8529
+ capturedAt: {
8530
+ type: "timestamp",
8531
+ nullable: false
8532
+ },
8533
+ updatedAt: {
8534
+ type: "timestamp",
8535
+ nullable: false
8536
+ },
8537
+ deletedAt: {
8538
+ type: "timestamp",
8539
+ nullable: true
8540
+ },
8541
+ deletedBy: {
8542
+ type: "text",
8543
+ nullable: true
8544
+ },
8545
+ scope: {
8546
+ type: "jsonb",
8547
+ nullable: true
8548
+ },
8549
+ scopeKey: {
8550
+ type: "text",
8551
+ nullable: true
8552
+ },
8553
+ sourceThreadId: {
8554
+ type: "text",
8555
+ nullable: true
8556
+ },
8557
+ when: {
8558
+ type: "timestamp",
8559
+ nullable: true
8560
+ }
8561
+ };
8562
+ const KNOWLEDGE_V2_MENTIONS_SCHEMA = {
8563
+ recordId: {
8564
+ type: "text",
8565
+ nullable: false,
8566
+ references: {
8567
+ table: TABLE_KNOWLEDGE_RECORDS$1,
8568
+ column: "id"
8569
+ }
8570
+ },
8571
+ targetNodeId: {
8572
+ type: "text",
8573
+ nullable: true,
8574
+ references: {
8575
+ table: TABLE_KNOWLEDGE_NODES$1,
8576
+ column: "id"
8577
+ }
8578
+ },
8579
+ sourceType: {
8580
+ type: "text",
8581
+ nullable: true
8582
+ },
8583
+ sourceId: {
8584
+ type: "text",
8585
+ nullable: true
8586
+ }
8587
+ };
8588
+ const KNOWLEDGE_NODE_SCOPES_SCHEMA = {
8589
+ nodeId: {
8590
+ type: "text",
8591
+ nullable: false,
8592
+ references: {
8593
+ table: TABLE_KNOWLEDGE_NODES$1,
8594
+ column: "id"
8595
+ }
8596
+ },
8597
+ scopeNodeId: {
8598
+ type: "text",
8599
+ nullable: false,
8600
+ references: {
8601
+ table: TABLE_KNOWLEDGE_NODES$1,
8602
+ column: "id"
8603
+ }
8604
+ },
8605
+ addedAt: {
8606
+ type: "timestamp",
8607
+ nullable: false
8608
+ }
8609
+ };
8610
+ const KNOWLEDGE_RECORD_SCOPES_SCHEMA = {
8611
+ recordId: {
8612
+ type: "text",
8613
+ nullable: false,
8614
+ references: {
8615
+ table: TABLE_KNOWLEDGE_RECORDS$1,
8616
+ column: "id"
8617
+ }
8618
+ },
8619
+ scopeNodeId: {
8620
+ type: "text",
8621
+ nullable: false,
8622
+ references: {
8623
+ table: TABLE_KNOWLEDGE_NODES$1,
8624
+ column: "id"
8625
+ }
8626
+ },
8627
+ addedAt: {
8628
+ type: "timestamp",
8629
+ nullable: false
8630
+ }
8631
+ };
8632
+ const KNOWLEDGE_SCOPE_GRANTS_SCHEMA = {
8633
+ scopeNodeId: {
8634
+ type: "text",
8635
+ nullable: false,
8636
+ references: {
8637
+ table: TABLE_KNOWLEDGE_NODES$1,
8638
+ column: "id"
8639
+ }
8640
+ },
8641
+ scopeRefId: {
8642
+ type: "text",
8643
+ nullable: false,
8644
+ references: {
8645
+ table: TABLE_KNOWLEDGE_NODES$1,
8646
+ column: "id"
8647
+ }
8648
+ },
8649
+ role: {
8650
+ type: "text",
8651
+ nullable: false
8652
+ },
8653
+ canSuggest: {
8654
+ type: "boolean",
8655
+ nullable: true
8656
+ }
8657
+ };
8658
+ const KNOWLEDGE_ACCESS_STATE_SCHEMA = {
8659
+ id: {
8660
+ type: "text",
8661
+ nullable: false,
8662
+ primaryKey: true
8663
+ },
8664
+ epoch: {
8665
+ type: "integer",
8666
+ nullable: false
8667
+ },
8668
+ schemaVersion: {
8669
+ type: "integer",
8670
+ nullable: false
8671
+ }
8672
+ };
8673
+ const KNOWLEDGE_SCOPE_ADDRESSES_SCHEMA = {
8674
+ address: {
8675
+ type: "text",
8676
+ nullable: false,
8677
+ primaryKey: true
8678
+ },
8679
+ scopeNodeId: {
8680
+ type: "text",
8681
+ nullable: false,
8682
+ references: {
8683
+ table: TABLE_KNOWLEDGE_NODES$1,
8684
+ column: "id"
8685
+ }
8686
+ }
8687
+ };
8688
+ const KNOWLEDGE_NODE_ADDRESSES_SCHEMA = {
8689
+ source: {
8690
+ type: "text",
8691
+ nullable: false
8692
+ },
8693
+ address: {
8694
+ type: "text",
8695
+ nullable: false
8696
+ },
8697
+ nodeId: {
8698
+ type: "text",
8699
+ nullable: false,
8700
+ references: {
8701
+ table: TABLE_KNOWLEDGE_NODES$1,
8702
+ column: "id"
8703
+ }
8704
+ }
8705
+ };
8706
+ const KNOWLEDGE_IMPORT_STATE_SCHEMA = {
8707
+ importerId: {
8708
+ type: "text",
8709
+ nullable: false
8710
+ },
8711
+ binding: {
8712
+ type: "text",
8713
+ nullable: false
8714
+ },
8715
+ key: {
8716
+ type: "text",
8717
+ nullable: false
8718
+ },
8719
+ value: {
8720
+ type: "text",
8721
+ nullable: false
8722
+ }
8723
+ };
8724
+ const KNOWLEDGE_IMPORT_RUNS_SCHEMA = {
8725
+ id: {
8726
+ type: "text",
8727
+ nullable: false,
8728
+ primaryKey: true
8729
+ },
8730
+ importerId: {
8731
+ type: "text",
8732
+ nullable: false
8733
+ },
8734
+ binding: {
8735
+ type: "text",
8736
+ nullable: false
8737
+ },
8738
+ importKind: {
8739
+ type: "text",
8740
+ nullable: false
8741
+ },
8742
+ triggerKind: {
8743
+ type: "text",
8744
+ nullable: false
8745
+ },
8746
+ status: {
8747
+ type: "text",
8748
+ nullable: false
8749
+ },
8750
+ error: {
8751
+ type: "text",
8752
+ nullable: true
8753
+ },
8754
+ transcriptThreadId: {
8755
+ type: "text",
8756
+ nullable: true
8757
+ },
8758
+ traceId: {
8759
+ type: "text",
8760
+ nullable: true
8761
+ },
8762
+ queuedAt: {
8763
+ type: "timestamp",
8764
+ nullable: false
8765
+ },
8766
+ startedAt: {
8767
+ type: "timestamp",
8768
+ nullable: true
8769
+ },
8770
+ completedAt: {
8771
+ type: "timestamp",
8772
+ nullable: true
8773
+ }
8774
+ };
8775
+ const KNOWLEDGE_V2_ACTIVITY_SCHEMA = {
8776
+ id: {
8777
+ type: "text",
8778
+ nullable: false,
8779
+ primaryKey: true
8780
+ },
8781
+ action: {
8782
+ type: "text",
8783
+ nullable: false
8784
+ },
8785
+ targetType: {
8786
+ type: "text",
8787
+ nullable: true
8788
+ },
8789
+ targetId: {
8790
+ type: "text",
8791
+ nullable: true
8792
+ },
8793
+ contextScopeId: {
8794
+ type: "text",
8795
+ nullable: true,
8796
+ references: {
8797
+ table: TABLE_KNOWLEDGE_NODES$1,
8798
+ column: "id"
8799
+ }
8800
+ },
8801
+ importRunId: {
8802
+ type: "text",
8803
+ nullable: true,
8804
+ references: {
8805
+ table: TABLE_KNOWLEDGE_IMPORT_RUNS,
8806
+ column: "id"
8807
+ }
8808
+ },
8809
+ details: {
8810
+ type: "jsonb",
8811
+ nullable: true
8812
+ },
8813
+ createdAt: {
8814
+ type: "timestamp",
8815
+ nullable: false
8816
+ },
8817
+ recordType: {
8818
+ type: "text",
8819
+ nullable: true
8820
+ },
8821
+ recordId: {
8822
+ type: "text",
8823
+ nullable: true
8824
+ },
8825
+ scope: {
8826
+ type: "jsonb",
8827
+ nullable: true
8828
+ },
8829
+ scopeKey: {
8830
+ type: "text",
8831
+ nullable: true
8832
+ },
8833
+ sourceThreadId: {
8834
+ type: "text",
8835
+ nullable: true
8836
+ }
8837
+ };
8838
+ const KNOWLEDGE_PROPOSALS_SCHEMA = {
8839
+ id: {
8840
+ type: "text",
8841
+ nullable: false,
8842
+ primaryKey: true
8843
+ },
8844
+ targetType: {
8845
+ type: "text",
8846
+ nullable: false
8847
+ },
8848
+ targetId: {
8849
+ type: "text",
8850
+ nullable: false
8851
+ },
8852
+ action: {
8853
+ type: "text",
8854
+ nullable: false
8855
+ },
8856
+ changes: {
8857
+ type: "jsonb",
8858
+ nullable: false
8859
+ },
8860
+ reason: {
8861
+ type: "text",
8862
+ nullable: true
8863
+ },
8864
+ proposerContextScopeId: {
8865
+ type: "text",
8866
+ nullable: false
8867
+ },
8868
+ expectedVersion: {
8869
+ type: "integer",
8870
+ nullable: false
8871
+ },
8872
+ status: {
8873
+ type: "text",
8874
+ nullable: false
8875
+ },
8876
+ reviewerContextScopeId: {
8877
+ type: "text",
8878
+ nullable: true
8879
+ },
8880
+ reviewedAt: {
8881
+ type: "timestamp",
8882
+ nullable: true
8883
+ },
8884
+ createdAt: {
8885
+ type: "timestamp",
8886
+ nullable: false
8887
+ }
8888
+ };
8889
+ function assertKnowledgeV2CoreSupport(features) {
8890
+ 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.`);
8891
+ }
8892
+ function resolveKnowledgeV2Core(storageModule) {
8893
+ if (typeof storageModule !== "object" || storageModule === null) throw new Error("@mastra/core advertises Knowledge v2 without the required storage API");
8894
+ const { assertKnowledgeDescriptionWithinBound: assertDescription, assertKnowledgeRecordTextWithinBound: assertRecordText, assertKnowledgeSchemaCompatible: assertCompatible, inspectKnowledgeSchema: inspectSchema } = storageModule;
8895
+ 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");
8896
+ return {
8897
+ assertKnowledgeDescriptionWithinBound: (description) => assertDescription(description),
8898
+ assertKnowledgeRecordTextWithinBound: (text) => assertRecordText(text),
8899
+ assertKnowledgeSchemaCompatible: (inspection) => assertCompatible(inspection),
8900
+ inspectKnowledgeSchema: (snapshot) => inspectSchema(snapshot)
8901
+ };
8902
+ }
8903
+ function createKnowledgeV2CoreLoader(features, loadStorageModule) {
8904
+ let knowledgeV2Core;
8905
+ return async () => {
8906
+ assertKnowledgeV2CoreSupport(features);
8907
+ knowledgeV2Core ??= loadStorageModule().then(resolveKnowledgeV2Core).catch((error) => {
8908
+ knowledgeV2Core = void 0;
8909
+ throw error;
8910
+ });
8911
+ return knowledgeV2Core;
8912
+ };
8913
+ }
8914
+ //#endregion
8238
8915
  //#region src/storage/domains/knowledge/index.ts
8239
- let assertDescriptionWithinBound;
8916
+ const loadKnowledgeV2Core = createKnowledgeV2CoreLoader(coreFeatures, () => import("@mastra/core/storage"));
8240
8917
  async function assertKnowledgeDescriptionWithinBoundCompat(description) {
8241
- let assertWithinBound = assertDescriptionWithinBound;
8242
- if (!assertWithinBound) {
8243
- const resolvedAssert = (await import("@mastra/core/storage")).assertKnowledgeDescriptionWithinBound ?? ((value) => {
8244
- if (value !== void 0 && value.length > 400) throw new Error("Knowledge node description exceeds the 400 UTF-16 code unit limit");
8245
- });
8246
- assertDescriptionWithinBound = resolvedAssert;
8247
- assertWithinBound = resolvedAssert;
8248
- }
8249
- assertWithinBound(description);
8918
+ const { assertKnowledgeDescriptionWithinBound } = await loadKnowledgeV2Core();
8919
+ assertKnowledgeDescriptionWithinBound(description);
8920
+ }
8921
+ async function assertKnowledgeRecordTextWithinBoundCompat(text) {
8922
+ const { assertKnowledgeRecordTextWithinBound } = await loadKnowledgeV2Core();
8923
+ assertKnowledgeRecordTextWithinBound(text);
8250
8924
  }
8251
8925
  const camelCaseColumns = [
8252
8926
  "canonicalName",
@@ -8257,7 +8931,6 @@ const camelCaseColumns = [
8257
8931
  "node",
8258
8932
  "sourceThreadId",
8259
8933
  "capturedAt",
8260
- "maxScope",
8261
8934
  "deletedAt",
8262
8935
  "deletedBy",
8263
8936
  "sourceType",
@@ -8271,7 +8944,30 @@ const camelCaseColumns = [
8271
8944
  "availableAt",
8272
8945
  "claimedAt",
8273
8946
  "claimedBy",
8274
- "completedAt"
8947
+ "completedAt",
8948
+ "isScope",
8949
+ "nodeId",
8950
+ "targetNodeId",
8951
+ "scopeNodeId",
8952
+ "scopeRefId",
8953
+ "canSuggest",
8954
+ "schemaVersion",
8955
+ "importerId",
8956
+ "importKind",
8957
+ "triggerKind",
8958
+ "transcriptThreadId",
8959
+ "traceId",
8960
+ "queuedAt",
8961
+ "startedAt",
8962
+ "targetType",
8963
+ "targetId",
8964
+ "contextScopeId",
8965
+ "importRunId",
8966
+ "proposerContextScopeId",
8967
+ "expectedVersion",
8968
+ "reviewerContextScopeId",
8969
+ "reviewedAt",
8970
+ "addedAt"
8275
8971
  ];
8276
8972
  function transformSqlCode(sql, transform) {
8277
8973
  return sql.split(/('(?:''|[^'])*')/g).map((part, index) => index % 2 === 0 ? transform(part) : part).join("");
@@ -8286,14 +8982,7 @@ function postgresSql(sql, schemaName) {
8286
8982
  const quotedSchema = `"${parseSchemaName(schemaName)}"`;
8287
8983
  normalized = transformSqlCode(normalized, (code) => {
8288
8984
  let transformed = code;
8289
- for (const table of [
8290
- TABLE_KNOWLEDGE_NODES,
8291
- TABLE_KNOWLEDGE_RECORDS,
8292
- TABLE_KNOWLEDGE_MENTIONS,
8293
- TABLE_KNOWLEDGE_CURSORS,
8294
- TABLE_KNOWLEDGE_ACTIVITY,
8295
- TABLE_KNOWLEDGE_SEMANTIC_OUTBOX
8296
- ]) transformed = transformed.replaceAll(`"${table}"`, `${quotedSchema}."${table}"`);
8985
+ for (const table of [...KNOWLEDGE_TABLE_NAMES, ...RETIRED_KNOWLEDGE_TABLE_NAMES]) transformed = transformed.replaceAll(`"${table}"`, `${quotedSchema}."${table}"`);
8297
8986
  return transformed;
8298
8987
  });
8299
8988
  }
@@ -8301,17 +8990,35 @@ function postgresSql(sql, schemaName) {
8301
8990
  return transformSqlCode(normalized, (code) => code.replace(/\?/g, () => `$${++index}`));
8302
8991
  }
8303
8992
  function createExecutor(client, schemaName) {
8304
- return { async execute(statement) {
8993
+ const execute = async (statement, qualifyWithSchema) => {
8305
8994
  const sql = typeof statement === "string" ? statement : statement.sql;
8306
8995
  const args = typeof statement === "string" ? [] : statement.args ?? [];
8307
- const result = await client.query(postgresSql(sql, schemaName), args);
8996
+ const result = await client.query(postgresSql(sql, qualifyWithSchema ? schemaName : void 0), args);
8308
8997
  return {
8309
8998
  rows: result.rows,
8310
8999
  rowsAffected: result.rowCount ?? 0
8311
9000
  };
8312
- } };
9001
+ };
9002
+ return {
9003
+ execute: (statement) => execute(statement, true),
9004
+ executeRaw: (statement) => execute(statement, false)
9005
+ };
9006
+ }
9007
+ /**
9008
+ * Scope keys visible to a caller holding `scope`: a row is visible when its scope is a subset of the
9009
+ * caller's (companion entries such as `resource:x:uncurated` make this more than a prefix match).
9010
+ * Matching rows by `scopeKey` keeps visibility filters on an index; a caller scope has only a few
9011
+ * entries, so the list stays small.
9012
+ */
9013
+ function visibleScopeKeys(scope) {
9014
+ const canonical = canonicalizeKnowledgeScope(scope);
9015
+ const keys = /* @__PURE__ */ new Set();
9016
+ for (let mask = 1; mask < 1 << canonical.length; mask++) try {
9017
+ keys.add(knowledgeScopeKey(canonical.filter((_, index) => mask & 1 << index)));
9018
+ } catch {}
9019
+ return [...keys];
8313
9020
  }
8314
- const visibleSql = `(scopeKey = ? OR LEFT(?, LENGTH(scopeKey) + 1) = scopeKey || chr(31))`;
9021
+ const visibleSql = (keys, scopeKeyColumn = "scopeKey") => `${scopeKeyColumn} IN (${keys.map(() => "?").join(",")})`;
8315
9022
  function parseJson$1(value) {
8316
9023
  if (typeof value === "string") return JSON.parse(value);
8317
9024
  if (value instanceof Uint8Array) return JSON.parse(new TextDecoder().decode(value));
@@ -8351,9 +9058,10 @@ function parseNode(row) {
8351
9058
  id: String(row.id),
8352
9059
  type: "node",
8353
9060
  name: String(row.name),
8354
- kind: String(row.kind),
9061
+ kind: row.kind == null ? "" : String(row.kind),
8355
9062
  content: row.content == null ? void 0 : String(row.content),
8356
9063
  description: row.description == null ? void 0 : String(row.description),
9064
+ isScope: row.isScope === true || row.isScope === 1 ? true : void 0,
8357
9065
  scope: parseJson$1(row.scopeJson ?? row.scope),
8358
9066
  version: Number(row.version),
8359
9067
  mergedInto: row.mergedInto == null ? void 0 : String(row.mergedInto),
@@ -8370,7 +9078,6 @@ function parseKnowledge(row) {
8370
9078
  sourceThreadId: String(row.sourceThreadId),
8371
9079
  capturedAt: toDate$1(row.capturedAt),
8372
9080
  when: optionalDate(row.when),
8373
- maxScope: row.maxScope == null ? void 0 : String(row.maxScope),
8374
9081
  metadata: row.metadata == null ? void 0 : parseJson$1(row.metadata),
8375
9082
  deletedAt: optionalDate(row.deletedAt),
8376
9083
  deletedBy: row.deletedBy == null ? void 0 : String(row.deletedBy)
@@ -8407,6 +9114,10 @@ function knowledgeIndexes(schemaName) {
8407
9114
  name: "idx_knowledge_nodes_scope",
8408
9115
  sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_nodes_scope ON ${table(TABLE_KNOWLEDGE_NODES)} ("scopeKey", "type");`
8409
9116
  },
9117
+ {
9118
+ name: "idx_knowledge_nodes_name",
9119
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_nodes_name ON ${table(TABLE_KNOWLEDGE_NODES)} ("type", "canonicalName");`
9120
+ },
8410
9121
  {
8411
9122
  name: "idx_knowledge_records_node_latest",
8412
9123
  sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_records_node_latest ON ${table(TABLE_KNOWLEDGE_RECORDS)} ("node", "id" DESC);`
@@ -8423,6 +9134,14 @@ function knowledgeIndexes(schemaName) {
8423
9134
  name: "idx_knowledge_activity_latest",
8424
9135
  sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_activity_latest ON ${table(TABLE_KNOWLEDGE_ACTIVITY)} ("id" DESC);`
8425
9136
  },
9137
+ {
9138
+ name: "idx_knowledge_records_scope",
9139
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_records_scope ON ${table(TABLE_KNOWLEDGE_RECORDS)} ("scopeKey", "id");`
9140
+ },
9141
+ {
9142
+ name: "idx_knowledge_activity_scope",
9143
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_activity_scope ON ${table(TABLE_KNOWLEDGE_ACTIVITY)} ("scopeKey", "id" DESC);`
9144
+ },
8426
9145
  {
8427
9146
  name: "idx_knowledge_outbox_idempotency",
8428
9147
  sql: `CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_outbox_idempotency ON ${table(TABLE_KNOWLEDGE_SEMANTIC_OUTBOX)} ("idempotencyKey");`
@@ -8430,67 +9149,152 @@ function knowledgeIndexes(schemaName) {
8430
9149
  {
8431
9150
  name: "idx_knowledge_outbox_claim",
8432
9151
  sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_outbox_claim ON ${table(TABLE_KNOWLEDGE_SEMANTIC_OUTBOX)} ("status", "availableAt", "createdAt");`
9152
+ },
9153
+ {
9154
+ name: "idx_knowledge_node_scopes_scope",
9155
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_node_scopes_scope ON ${table(TABLE_KNOWLEDGE_NODE_SCOPES)} ("scopeNodeId", "nodeId");`
9156
+ },
9157
+ {
9158
+ name: "idx_knowledge_record_scopes_scope",
9159
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_record_scopes_scope ON ${table(TABLE_KNOWLEDGE_RECORD_SCOPES)} ("scopeNodeId", "recordId");`
9160
+ },
9161
+ {
9162
+ name: "idx_knowledge_scope_grants_ref",
9163
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_scope_grants_ref ON ${table(TABLE_KNOWLEDGE_SCOPE_GRANTS)} ("scopeRefId", "scopeNodeId");`
9164
+ },
9165
+ {
9166
+ name: "idx_knowledge_node_addresses_node",
9167
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_node_addresses_node ON ${table(TABLE_KNOWLEDGE_NODE_ADDRESSES)} ("nodeId");`
9168
+ },
9169
+ {
9170
+ name: "idx_knowledge_import_runs_lookup",
9171
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_import_runs_lookup ON ${table(TABLE_KNOWLEDGE_IMPORT_RUNS)} ("importerId", "binding", "queuedAt" DESC);`
9172
+ },
9173
+ {
9174
+ name: "idx_knowledge_activity_import_run",
9175
+ sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_activity_import_run ON ${table(TABLE_KNOWLEDGE_ACTIVITY)} ("importRunId", "id" DESC);`
8433
9176
  }
8434
9177
  ];
8435
9178
  }
8436
9179
  function knowledgeIndexDDL(schemaName) {
8437
9180
  return knowledgeIndexes(schemaName).map((index) => index.sql);
8438
9181
  }
9182
+ const KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE = "Knowledge curation cursors were removed: observation-time curate is the only Knowledge writer and needs no cursor.";
9183
+ const knowledgeTableDefinitions = [
9184
+ {
9185
+ tableName: TABLE_KNOWLEDGE_NODES,
9186
+ schema: KNOWLEDGE_V2_NODES_SCHEMA
9187
+ },
9188
+ {
9189
+ tableName: TABLE_KNOWLEDGE_RECORDS,
9190
+ schema: KNOWLEDGE_V2_RECORDS_SCHEMA
9191
+ },
9192
+ {
9193
+ tableName: TABLE_KNOWLEDGE_MENTIONS,
9194
+ schema: KNOWLEDGE_V2_MENTIONS_SCHEMA,
9195
+ compositePrimaryKey: ["recordId", "sourceId"]
9196
+ },
9197
+ {
9198
+ tableName: TABLE_KNOWLEDGE_NODE_SCOPES,
9199
+ schema: KNOWLEDGE_NODE_SCOPES_SCHEMA,
9200
+ compositePrimaryKey: ["nodeId", "scopeNodeId"]
9201
+ },
9202
+ {
9203
+ tableName: TABLE_KNOWLEDGE_RECORD_SCOPES,
9204
+ schema: KNOWLEDGE_RECORD_SCOPES_SCHEMA,
9205
+ compositePrimaryKey: ["recordId", "scopeNodeId"]
9206
+ },
9207
+ {
9208
+ tableName: TABLE_KNOWLEDGE_SCOPE_GRANTS,
9209
+ schema: KNOWLEDGE_SCOPE_GRANTS_SCHEMA,
9210
+ compositePrimaryKey: ["scopeNodeId", "scopeRefId"]
9211
+ },
9212
+ {
9213
+ tableName: TABLE_KNOWLEDGE_ACCESS_STATE,
9214
+ schema: KNOWLEDGE_ACCESS_STATE_SCHEMA
9215
+ },
9216
+ {
9217
+ tableName: TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
9218
+ schema: KNOWLEDGE_SCOPE_ADDRESSES_SCHEMA
9219
+ },
9220
+ {
9221
+ tableName: TABLE_KNOWLEDGE_NODE_ADDRESSES,
9222
+ schema: KNOWLEDGE_NODE_ADDRESSES_SCHEMA,
9223
+ compositePrimaryKey: ["source", "address"]
9224
+ },
9225
+ {
9226
+ tableName: TABLE_KNOWLEDGE_IMPORT_STATE,
9227
+ schema: KNOWLEDGE_IMPORT_STATE_SCHEMA,
9228
+ compositePrimaryKey: [
9229
+ "importerId",
9230
+ "binding",
9231
+ "key"
9232
+ ]
9233
+ },
9234
+ {
9235
+ tableName: TABLE_KNOWLEDGE_IMPORT_RUNS,
9236
+ schema: KNOWLEDGE_IMPORT_RUNS_SCHEMA
9237
+ },
9238
+ {
9239
+ tableName: TABLE_KNOWLEDGE_PROPOSALS,
9240
+ schema: KNOWLEDGE_PROPOSALS_SCHEMA
9241
+ },
9242
+ {
9243
+ tableName: TABLE_KNOWLEDGE_ACTIVITY,
9244
+ schema: KNOWLEDGE_V2_ACTIVITY_SCHEMA
9245
+ },
9246
+ {
9247
+ tableName: TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
9248
+ schema: KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA
9249
+ }
9250
+ ];
9251
+ function pgSetting(config, key) {
9252
+ const value = config[key] || process.env[`PG${key.toUpperCase()}`] || defaults[key];
9253
+ return value === void 0 || value === null || value === "" ? void 0 : String(value);
9254
+ }
9255
+ function effectivePgTarget(config) {
9256
+ const host = pgSetting(config, "host");
9257
+ const database = pgSetting(config, "database") ?? pgSetting(config, "user");
9258
+ if (!host || !database) return void 0;
9259
+ return `${host.startsWith("/") ? host : host.toLocaleLowerCase()}:${pgSetting(config, "port") ?? "5432"}/${database}`;
9260
+ }
9261
+ function canonicalPgTarget(config) {
9262
+ if (config.connectionString) try {
9263
+ const parsed = new URL(config.connectionString);
9264
+ return effectivePgTarget({
9265
+ host: decodeURIComponent(parsed.hostname) || config.host,
9266
+ port: parsed.port || config.port,
9267
+ database: decodeURIComponent(parsed.pathname.slice(1)) || config.database,
9268
+ user: decodeURIComponent(parsed.username) || config.user
9269
+ });
9270
+ } catch {
9271
+ return config.connectionString;
9272
+ }
9273
+ return effectivePgTarget(config);
9274
+ }
9275
+ function pgSourceConfig(source) {
9276
+ if (!source || !("options" in source)) return void 0;
9277
+ return source.options;
9278
+ }
9279
+ function pgClientPool(client) {
9280
+ if (!client || !("$pool" in client)) return void 0;
9281
+ return client.$pool;
9282
+ }
9283
+ function getPgKnowledgeIsolationKey(config) {
9284
+ const schema = config.schemaName ?? "public";
9285
+ const poolConfig = pgSourceConfig(config.pool ?? pgClientPool(config.client));
9286
+ const target = Boolean(config.connectionString || config.host || config.database) ? canonicalPgTarget(config) : poolConfig ? canonicalPgTarget(poolConfig) : void 0;
9287
+ if (target) return `pg:${target}:schema:${schema}`;
9288
+ return `pg:unidentified:schema:${schema}`;
9289
+ }
8439
9290
  var KnowledgePG = class extends KnowledgeStorage {
8440
- static MANAGED_TABLES = [
8441
- TABLE_KNOWLEDGE_NODES,
8442
- TABLE_KNOWLEDGE_RECORDS,
8443
- TABLE_KNOWLEDGE_MENTIONS,
8444
- TABLE_KNOWLEDGE_CURSORS,
8445
- TABLE_KNOWLEDGE_ACTIVITY,
8446
- TABLE_KNOWLEDGE_SEMANTIC_OUTBOX
8447
- ];
9291
+ static MANAGED_TABLES = KNOWLEDGE_TABLE_NAMES;
8448
9292
  static getExportDDL(schemaName) {
8449
- return [
8450
- generateTableSQL({
8451
- tableName: TABLE_KNOWLEDGE_NODES,
8452
- schema: KNOWLEDGE_NODES_SCHEMA,
8453
- schemaName,
8454
- includeAllConstraints: true
8455
- }),
8456
- generateTableSQL({
8457
- tableName: TABLE_KNOWLEDGE_RECORDS,
8458
- schema: KNOWLEDGE_RECORDS_SCHEMA,
8459
- schemaName,
8460
- includeAllConstraints: true
8461
- }),
8462
- generateTableSQL({
8463
- tableName: TABLE_KNOWLEDGE_MENTIONS,
8464
- schema: KNOWLEDGE_MENTIONS_SCHEMA,
8465
- schemaName,
8466
- compositePrimaryKey: [
8467
- "sourceType",
8468
- "sourceId",
8469
- "recordId"
8470
- ],
8471
- includeAllConstraints: true
8472
- }),
8473
- generateTableSQL({
8474
- tableName: TABLE_KNOWLEDGE_CURSORS,
8475
- schema: KNOWLEDGE_CURSORS_SCHEMA,
8476
- schemaName,
8477
- compositePrimaryKey: ["sourceThreadId", "agent"],
8478
- includeAllConstraints: true
8479
- }),
8480
- generateTableSQL({
8481
- tableName: TABLE_KNOWLEDGE_ACTIVITY,
8482
- schema: KNOWLEDGE_ACTIVITY_SCHEMA,
8483
- schemaName,
8484
- includeAllConstraints: true
8485
- }),
8486
- generateTableSQL({
8487
- tableName: TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
8488
- schema: KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA,
8489
- schemaName,
8490
- includeAllConstraints: true
8491
- }),
8492
- ...knowledgeIndexDDL(schemaName)
8493
- ];
9293
+ return [...knowledgeTableDefinitions.map((definition) => generateTableSQL({
9294
+ ...definition,
9295
+ schemaName,
9296
+ includeAllConstraints: true
9297
+ })), ...knowledgeIndexDDL(schemaName)];
8494
9298
  }
8495
9299
  #client;
8496
9300
  #executor;
@@ -8499,7 +9303,7 @@ var KnowledgePG = class extends KnowledgeStorage {
8499
9303
  #db;
8500
9304
  #schemaName;
8501
9305
  constructor(config) {
8502
- super();
9306
+ super({ storageIsolationKey: config.storageIsolationKey ?? getPgKnowledgeIsolationKey(config) });
8503
9307
  const { client, readClient, schemaName, skipDefaultIndexes } = resolvePgConfig(config);
8504
9308
  this.#client = client;
8505
9309
  this.#schemaName = schemaName;
@@ -8512,56 +9316,359 @@ var KnowledgePG = class extends KnowledgeStorage {
8512
9316
  skipDefaultIndexes
8513
9317
  });
8514
9318
  }
8515
- async init() {
8516
- await this.#db.createTable({
8517
- tableName: TABLE_KNOWLEDGE_NODES,
8518
- schema: KNOWLEDGE_NODES_SCHEMA
8519
- });
8520
- await this.#db.alterTable({
8521
- tableName: TABLE_KNOWLEDGE_NODES,
8522
- schema: KNOWLEDGE_NODES_SCHEMA,
8523
- ifNotExists: ["description"]
9319
+ getCapabilities() {
9320
+ return {
9321
+ contractVersion: 2,
9322
+ schemaVersion: 2,
9323
+ supportsV2: true,
9324
+ supportsSchemaInspection: true,
9325
+ supportsExplicitReset: true
9326
+ };
9327
+ }
9328
+ async inspectSchema() {
9329
+ const { inspectKnowledgeSchema } = await loadKnowledgeV2Core();
9330
+ try {
9331
+ const snapshot = getSchemaSnapshot(this.#client, this.#schemaName);
9332
+ if (snapshot) {
9333
+ const tableNames = KNOWLEDGE_TABLE_NAMES.filter((table) => snapshot.tables.has(table));
9334
+ return this.#classifySchema(this.#executor, tableNames, snapshot.columns);
9335
+ }
9336
+ return this.#inspectSchemaWithExecutor(this.#executor);
9337
+ } catch (error) {
9338
+ return inspectKnowledgeSchema({
9339
+ available: false,
9340
+ tableNames: [],
9341
+ reason: error instanceof Error ? error.message : String(error)
9342
+ });
9343
+ }
9344
+ }
9345
+ async #inspectSchemaWithExecutor(executor) {
9346
+ const tableNames = (await executor.execute({
9347
+ sql: `SELECT table_name FROM information_schema.tables WHERE table_schema = COALESCE(?, current_schema()) AND table_name = ANY(?::text[])`,
9348
+ args: [this.#schemaName ?? null, [...KNOWLEDGE_TABLE_NAMES]]
9349
+ })).rows.map((row) => String(row.table_name));
9350
+ const columnResult = await executor.execute({
9351
+ sql: `SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = COALESCE(?, current_schema()) AND table_name = ANY(?::text[])`,
9352
+ args: [this.#schemaName ?? null, [...KNOWLEDGE_TABLE_NAMES]]
9353
+ });
9354
+ const columns = /* @__PURE__ */ new Map();
9355
+ for (const row of columnResult.rows) {
9356
+ const table = String(row.table_name);
9357
+ const names = columns.get(table) ?? /* @__PURE__ */ new Set();
9358
+ names.add(String(row.column_name));
9359
+ columns.set(table, names);
9360
+ }
9361
+ return this.#classifySchema(executor, tableNames, columns);
9362
+ }
9363
+ async #classifySchema(executor, tableNames, columns) {
9364
+ const { inspectKnowledgeSchema } = await loadKnowledgeV2Core();
9365
+ if (tableNames.length === 0) return inspectKnowledgeSchema({
9366
+ available: true,
9367
+ tableNames
9368
+ });
9369
+ const missingTables = KNOWLEDGE_TABLE_NAMES.filter((table) => !tableNames.includes(table));
9370
+ if (missingTables.length > 0) return inspectKnowledgeSchema({
9371
+ available: true,
9372
+ tableNames,
9373
+ reason: `Missing Knowledge v2 tables: ${missingTables.join(", ")}`
9374
+ });
9375
+ for (const { tableName: table, schema } of knowledgeTableDefinitions) {
9376
+ const actual = columns.get(table) ?? /* @__PURE__ */ new Set();
9377
+ const missing = Object.keys(schema).filter((column) => !actual.has(column));
9378
+ if (missing.length > 0) return inspectKnowledgeSchema({
9379
+ available: true,
9380
+ tableNames,
9381
+ reason: `Knowledge table ${table} is missing v2 columns: ${missing.join(", ")}`
9382
+ });
9383
+ }
9384
+ const accessState = await executor.execute(`SELECT schemaVersion FROM "${TABLE_KNOWLEDGE_ACCESS_STATE}" WHERE id='global'`);
9385
+ if (Number(accessState.rows[0]?.schemaVersion) !== 2) return inspectKnowledgeSchema({
9386
+ available: true,
9387
+ tableNames,
9388
+ reason: "Knowledge schema version marker is missing or incompatible"
9389
+ });
9390
+ return inspectKnowledgeSchema({
9391
+ available: true,
9392
+ tableNames,
9393
+ schemaVersion: 2
8524
9394
  });
8525
- await this.#db.createTable({
8526
- tableName: TABLE_KNOWLEDGE_RECORDS,
8527
- schema: KNOWLEDGE_RECORDS_SCHEMA
9395
+ }
9396
+ async init() {
9397
+ await this.#transaction(async (tx) => {
9398
+ await tx.execute({
9399
+ sql: "SELECT pg_advisory_xact_lock(hashtext(?))",
9400
+ args: [`mastra-knowledge-v2:${this.#schemaName ?? "current_schema"}`]
9401
+ });
9402
+ const { assertKnowledgeSchemaCompatible } = await loadKnowledgeV2Core();
9403
+ let inspection = await this.#inspectSchemaWithExecutor(tx);
9404
+ if (inspection.status === "incompatible-reset-required" && await this.#isPublishedV1Layout(tx)) {
9405
+ const tables = [...RETIRED_KNOWLEDGE_TABLE_NAMES, ...[...KNOWLEDGE_TABLE_NAMES].reverse()].map((table) => `"${table}"`).join(", ");
9406
+ await tx.execute(`DROP TABLE IF EXISTS ${tables}`);
9407
+ inspection = await this.#inspectSchemaWithExecutor(tx);
9408
+ }
9409
+ assertKnowledgeSchemaCompatible(inspection);
9410
+ for (const definition of knowledgeTableDefinitions) await tx.executeRaw(generateTableSQL({
9411
+ ...definition,
9412
+ schemaName: this.#schemaName,
9413
+ includeAllConstraints: true
9414
+ }));
9415
+ for (const index of knowledgeIndexes(this.#schemaName)) await tx.executeRaw(index.sql);
9416
+ await tx.execute({
9417
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_ACCESS_STATE}" (id, epoch, schemaVersion) VALUES ('global', 0, ?) ON CONFLICT (id) DO NOTHING`,
9418
+ args: [2]
9419
+ });
8528
9420
  });
8529
- await this.#db.createTable({
8530
- tableName: TABLE_KNOWLEDGE_MENTIONS,
8531
- schema: KNOWLEDGE_MENTIONS_SCHEMA,
8532
- compositePrimaryKey: [
8533
- "sourceType",
8534
- "sourceId",
8535
- "recordId"
9421
+ }
9422
+ /**
9423
+ * True when the only Knowledge objects are the tables and indexes published v1 adapters created,
9424
+ * with or without rows. Anything else (v2 tables, unknown tables, views, triggers, extra indexes)
9425
+ * needs an explicit reset.
9426
+ */
9427
+ async #isPublishedV1Layout(executor) {
9428
+ const schema = this.#schemaName ?? null;
9429
+ const relations = await executor.execute({
9430
+ sql: `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = COALESCE(?, current_schema()) AND table_name LIKE 'mastra\\_knowledge\\_%'`,
9431
+ args: [schema]
9432
+ });
9433
+ const tables = [];
9434
+ for (const row of relations.rows) {
9435
+ const name = String(row.table_name);
9436
+ if (row.table_type !== "BASE TABLE" || !PUBLISHED_KNOWLEDGE_V1_TABLE_NAMES.has(name)) return false;
9437
+ tables.push(name);
9438
+ }
9439
+ const indexes = await executor.execute({
9440
+ sql: `SELECT indexname FROM pg_indexes WHERE schemaname = COALESCE(?, current_schema()) AND tablename = ANY(?::text[])`,
9441
+ args: [schema, tables]
9442
+ });
9443
+ for (const row of indexes.rows) {
9444
+ const name = String(row.indexname);
9445
+ if (!PUBLISHED_KNOWLEDGE_V1_INDEX_NAMES.has(name) && !name.endsWith("_pkey")) return false;
9446
+ }
9447
+ if ((await executor.execute({
9448
+ 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`,
9449
+ args: [
9450
+ schema,
9451
+ tables,
9452
+ schema,
9453
+ tables
8536
9454
  ]
8537
- });
8538
- await this.#db.createTable({
8539
- tableName: TABLE_KNOWLEDGE_CURSORS,
8540
- schema: KNOWLEDGE_CURSORS_SCHEMA,
8541
- compositePrimaryKey: ["sourceThreadId", "agent"]
8542
- });
8543
- await this.#db.createTable({
8544
- tableName: TABLE_KNOWLEDGE_ACTIVITY,
8545
- schema: KNOWLEDGE_ACTIVITY_SCHEMA
8546
- });
8547
- await this.#db.createTable({
8548
- tableName: TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
8549
- schema: KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA
8550
- });
8551
- await Promise.all(knowledgeIndexes(this.#schemaName).map((index) => this.#db.createIndexFromStatement(index.name, index.sql)));
9455
+ })).rows.length > 0) return false;
9456
+ return true;
8552
9457
  }
8553
9458
  async dangerouslyClearAll() {
8554
9459
  await this.#transaction(async (tx) => {
8555
9460
  for (const table of [
9461
+ TABLE_KNOWLEDGE_RECORD_SCOPES,
9462
+ TABLE_KNOWLEDGE_NODE_SCOPES,
9463
+ TABLE_KNOWLEDGE_SCOPE_GRANTS,
9464
+ TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
9465
+ TABLE_KNOWLEDGE_NODE_ADDRESSES,
8556
9466
  TABLE_KNOWLEDGE_MENTIONS,
8557
- TABLE_KNOWLEDGE_RECORDS,
8558
- TABLE_KNOWLEDGE_NODES,
8559
- TABLE_KNOWLEDGE_CURSORS,
9467
+ TABLE_KNOWLEDGE_PROPOSALS,
8560
9468
  TABLE_KNOWLEDGE_ACTIVITY,
8561
- TABLE_KNOWLEDGE_SEMANTIC_OUTBOX
9469
+ TABLE_KNOWLEDGE_IMPORT_STATE,
9470
+ TABLE_KNOWLEDGE_IMPORT_RUNS,
9471
+ TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
9472
+ TABLE_KNOWLEDGE_RECORDS,
9473
+ TABLE_KNOWLEDGE_NODES
8562
9474
  ]) await tx.execute(`DELETE FROM "${table}"`);
9475
+ await tx.execute(`UPDATE "${TABLE_KNOWLEDGE_ACCESS_STATE}" SET epoch = 0 WHERE id = 'global'`);
9476
+ });
9477
+ }
9478
+ async dangerouslyReset() {
9479
+ const tables = [...RETIRED_KNOWLEDGE_TABLE_NAMES, ...[...KNOWLEDGE_TABLE_NAMES].reverse()].map((table) => `"${table}"`).join(", ");
9480
+ await this.#executor.execute(`DROP TABLE IF EXISTS ${tables}`);
9481
+ await this.init();
9482
+ }
9483
+ async reconcileStructure(plan) {
9484
+ return this.#transaction(async (tx) => {
9485
+ await tx.execute({
9486
+ sql: `SELECT pg_advisory_xact_lock(hashtext(?))`,
9487
+ args: [`mastra-knowledge-reconcile:${this.#schemaName}`]
9488
+ });
9489
+ const scopes = {};
9490
+ const createdScopeIds = [];
9491
+ const createdAddresses = /* @__PURE__ */ new Set();
9492
+ const retrofit = plan.retrofit ?? true;
9493
+ let structureChanged = false;
9494
+ const deletedScopeAddresses = /* @__PURE__ */ new Set();
9495
+ const resolveAddress = async (address) => {
9496
+ if (scopes[address]) return scopes[address];
9497
+ const row = (await tx.execute({
9498
+ 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=?`,
9499
+ args: [address]
9500
+ })).rows[0];
9501
+ if (!row) return void 0;
9502
+ if (!row.isScope) throw new Error(`Knowledge address ${address} does not reference a scope`);
9503
+ if (row.deletedAt) deletedScopeAddresses.add(address);
9504
+ scopes[address] = String(row.scopeNodeId);
9505
+ return scopes[address];
9506
+ };
9507
+ for (const scope of plan.scopes) {
9508
+ if (await resolveAddress(scope.address)) continue;
9509
+ const id = randomUUID();
9510
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9511
+ await tx.execute({
9512
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,"canonicalName",kind,description,"isScope",metadata,version,"createdAt","updatedAt") VALUES (?,'node',?,?,?,?,TRUE,?,1,?,?)`,
9513
+ args: [
9514
+ id,
9515
+ scope.name,
9516
+ canonicalName(scope.name),
9517
+ scope.kind ?? null,
9518
+ scope.description ?? null,
9519
+ scope.metadata ?? null,
9520
+ now,
9521
+ now
9522
+ ]
9523
+ });
9524
+ await tx.execute({
9525
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" (address,"scopeNodeId") VALUES (?,?)`,
9526
+ args: [scope.address, id]
9527
+ });
9528
+ scopes[scope.address] = id;
9529
+ createdAddresses.add(scope.address);
9530
+ createdScopeIds.push(id);
9531
+ structureChanged = true;
9532
+ }
9533
+ for (const scope of plan.scopes) {
9534
+ if (!createdAddresses.has(scope.address) && (!retrofit || deletedScopeAddresses.has(scope.address))) continue;
9535
+ const scopeNodeId = scopes[scope.address];
9536
+ for (const parentAddress of scope.parentAddresses ?? []) {
9537
+ const parentId = await resolveAddress(parentAddress);
9538
+ if (!parentId || deletedScopeAddresses.has(parentAddress)) {
9539
+ const deletedParentId = scopes[parentAddress];
9540
+ if ((deletedParentId ? await tx.execute({
9541
+ sql: `SELECT 1 FROM "mastra_knowledge_node_scopes" WHERE "nodeId"=? AND "scopeNodeId"=?`,
9542
+ args: [scopeNodeId, deletedParentId]
9543
+ }) : void 0)?.rows.length) continue;
9544
+ throw new Error(`Knowledge parent scope does not exist: ${parentAddress}`);
9545
+ }
9546
+ if ((await tx.execute({
9547
+ sql: `SELECT 1 FROM "mastra_knowledge_node_scopes" WHERE "nodeId"=? AND "scopeNodeId"=?`,
9548
+ args: [scopeNodeId, parentId]
9549
+ })).rows.length) continue;
9550
+ if ((await tx.execute({
9551
+ 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`,
9552
+ args: [
9553
+ parentId,
9554
+ canonicalName(scope.name),
9555
+ scopeNodeId
9556
+ ]
9557
+ })).rows.length) throw new Error(`Knowledge scope name ${scope.name} already exists under ${parentAddress}`);
9558
+ const inserted = await tx.execute({
9559
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" ("nodeId","scopeNodeId","addedAt") VALUES (?,?,?) ON CONFLICT DO NOTHING`,
9560
+ args: [
9561
+ scopeNodeId,
9562
+ parentId,
9563
+ (/* @__PURE__ */ new Date()).toISOString()
9564
+ ]
9565
+ });
9566
+ structureChanged ||= inserted.rowsAffected > 0;
9567
+ }
9568
+ for (const grant of scope.grants ?? []) {
9569
+ const scopeRefId = await resolveAddress(grant.scopeRefAddress);
9570
+ if (!scopeRefId || deletedScopeAddresses.has(grant.scopeRefAddress)) {
9571
+ const deletedScopeRefId = scopes[grant.scopeRefAddress];
9572
+ if ((deletedScopeRefId ? await tx.execute({
9573
+ sql: `SELECT 1 FROM "mastra_knowledge_scope_grants" WHERE "scopeNodeId"=? AND "scopeRefId"=?`,
9574
+ args: [scopeNodeId, deletedScopeRefId]
9575
+ }) : void 0)?.rows.length) continue;
9576
+ throw new Error(`Knowledge grant scope does not exist: ${grant.scopeRefAddress}`);
9577
+ }
9578
+ const inserted = await tx.execute({
9579
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_SCOPE_GRANTS}" ("scopeNodeId","scopeRefId",role,"canSuggest") VALUES (?,?,?,?) ON CONFLICT DO NOTHING`,
9580
+ args: [
9581
+ scopeNodeId,
9582
+ scopeRefId,
9583
+ grant.role,
9584
+ grant.canSuggest ?? null
9585
+ ]
9586
+ });
9587
+ structureChanged ||= inserted.rowsAffected > 0;
9588
+ }
9589
+ }
9590
+ if (structureChanged) await tx.execute(`UPDATE "${TABLE_KNOWLEDGE_ACCESS_STATE}" SET epoch=epoch+1 WHERE id='global'`);
9591
+ const state = await tx.execute(`SELECT epoch FROM "${TABLE_KNOWLEDGE_ACCESS_STATE}" WHERE id='global'`);
9592
+ return {
9593
+ scopes,
9594
+ createdScopeIds,
9595
+ deletedScopeAddresses: [...deletedScopeAddresses],
9596
+ changed: structureChanged,
9597
+ accessEpoch: Number(state.rows[0]?.epoch ?? 0)
9598
+ };
8563
9599
  });
8564
9600
  }
9601
+ async listScopeNodes(input = {}) {
9602
+ const { limit, after } = parseListKnowledgeScopeNodesInput(input);
9603
+ if (input.addresses?.length === 0 || input.ids?.length === 0) return {
9604
+ scopes: [],
9605
+ nextCursor: null
9606
+ };
9607
+ const args = [];
9608
+ const where = [`n."isScope"`, `n."deletedAt" IS NULL`];
9609
+ let within = "";
9610
+ if (input.withinAddress !== void 0) {
9611
+ 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) `;
9612
+ args.push(input.withinAddress);
9613
+ where.push("n.id IN (SELECT id FROM within_scope)");
9614
+ }
9615
+ if (input.addresses) {
9616
+ where.push(`a.address IN (${input.addresses.map(() => "?").join(",")})`);
9617
+ args.push(...input.addresses);
9618
+ }
9619
+ if (input.ids) {
9620
+ where.push(`n.id IN (${input.ids.map(() => "?").join(",")})`);
9621
+ args.push(...input.ids);
9622
+ }
9623
+ if (after) {
9624
+ where.push("(n.name COLLATE \"C\" > ? OR (n.name = ? AND n.id > ?))");
9625
+ args.push(after.name, after.name, after.id);
9626
+ }
9627
+ args.push(limit + 1);
9628
+ const rows = (await this.#readExecutor.execute({
9629
+ 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 ?`,
9630
+ args
9631
+ })).rows;
9632
+ if (rows.length === 0) return {
9633
+ scopes: [],
9634
+ nextCursor: null
9635
+ };
9636
+ const parents = await this.#readExecutor.execute({
9637
+ 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"`,
9638
+ args: rows.map((row) => String(row.id))
9639
+ });
9640
+ const parentsByScopeId = /* @__PURE__ */ new Map();
9641
+ for (const row of parents.rows) {
9642
+ const scopeId = String(row.nodeId);
9643
+ const parentId = String(row.scopeNodeId);
9644
+ const existing = parentsByScopeId.get(scopeId);
9645
+ if (existing) existing.push(parentId);
9646
+ else parentsByScopeId.set(scopeId, [parentId]);
9647
+ }
9648
+ return pageKnowledgeScopeNodes(rows.map((row) => ({
9649
+ id: String(row.id),
9650
+ address: String(row.address),
9651
+ name: String(row.name),
9652
+ ...row.kind == null ? {} : { kind: String(row.kind) },
9653
+ ...row.description == null ? {} : { description: String(row.description) },
9654
+ parentIds: parentsByScopeId.get(String(row.id)) ?? []
9655
+ })), limit, input);
9656
+ }
9657
+ async listScopeMembers(input) {
9658
+ const { limit, after } = parseListKnowledgeScopeMembersInput(input);
9659
+ const args = [input.scopeNodeId];
9660
+ let keyset = "";
9661
+ if (after) {
9662
+ const updatedAt = postgresTimestamp(after.updatedAt);
9663
+ keyset = ` AND (n."updatedAt" < ? OR (n."updatedAt" = ? AND (n.name > ? OR (n.name = ? AND n.id > ?))))`;
9664
+ args.push(updatedAt, updatedAt, after.name, after.name, after.id);
9665
+ }
9666
+ args.push(limit + 1);
9667
+ return pageKnowledgeScopeMembers((await this.#readExecutor.execute({
9668
+ 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 ?`,
9669
+ args
9670
+ })).rows.map(parseNode), limit, input);
9671
+ }
8565
9672
  async createNode(input) {
8566
9673
  await assertKnowledgeDescriptionWithinBoundCompat(input.description);
8567
9674
  const scope = canonicalizeKnowledgeScope(input.scope);
@@ -8570,6 +9677,7 @@ var KnowledgePG = class extends KnowledgeStorage {
8570
9677
  if (existing) {
8571
9678
  const terminal = await this.#resolveTerminalNode(tx, existing.id);
8572
9679
  if (!isKnowledgeScopeVisible(terminal.scope, scope)) throw new Error(`Merged knowledge node is not visible from scope: ${input.name}`);
9680
+ await this.#placeNodeInScopes(tx, terminal.id, input.scopeAddresses, /* @__PURE__ */ new Date());
8573
9681
  return terminal;
8574
9682
  }
8575
9683
  const now = /* @__PURE__ */ new Date();
@@ -8586,7 +9694,7 @@ var KnowledgePG = class extends KnowledgeStorage {
8586
9694
  updatedAt: now
8587
9695
  };
8588
9696
  await tx.execute({
8589
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,description,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,?,?,jsonb(?),?,?,NULL,?,?)`,
9697
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,description,isScope,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,?,?,FALSE,jsonb(?),?,?,NULL,?,?)`,
8590
9698
  args: [
8591
9699
  node.id,
8592
9700
  "node",
@@ -8602,6 +9710,8 @@ var KnowledgePG = class extends KnowledgeStorage {
8602
9710
  now.toISOString()
8603
9711
  ]
8604
9712
  });
9713
+ await this.#replaceNodeScopes(tx, node.id, scope, now);
9714
+ await this.#placeNodeInScopes(tx, node.id, input.scopeAddresses, now);
8605
9715
  await this.#replaceMentions(tx, "node", node.id, node.content ?? "", input.resolutionScope ?? scope, scope);
8606
9716
  await this.#activity(tx, "node-created", "node", node.id, scope);
8607
9717
  await this.#outbox(tx, "node", node.id, "upsert", node.version, scope);
@@ -8618,13 +9728,13 @@ var KnowledgePG = class extends KnowledgeStorage {
8618
9728
  return this.#resolveNode(this.#readExecutor, input.name, canonicalizeKnowledgeScope(input.scope));
8619
9729
  }
8620
9730
  async listNodes(input) {
8621
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
9731
+ const keys = visibleScopeKeys(canonicalizeKnowledgeScope(input.scope));
8622
9732
  const clauses = [
8623
9733
  `type = 'node'`,
8624
9734
  "mergedInto IS NULL",
8625
- visibleSql
9735
+ visibleSql(keys)
8626
9736
  ];
8627
- const args = [key, key];
9737
+ const args = [...keys];
8628
9738
  if (input.namePrefix) {
8629
9739
  clauses.push("canonicalName LIKE ? ESCAPE '='");
8630
9740
  args.push(`${escapeLikePattern(canonicalName(input.namePrefix))}%`);
@@ -8676,6 +9786,7 @@ var KnowledgePG = class extends KnowledgeStorage {
8676
9786
  input.version
8677
9787
  ]
8678
9788
  })).rowsAffected === 0) throw new KnowledgeConflictError(input.id);
9789
+ if (knowledgeScopeKey(existing.scope) !== knowledgeScopeKey(scope)) await this.#swapIdentityScopes(tx, input.id, existing.scope, scope, now);
8679
9790
  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);
8680
9791
  await this.#activity(tx, "node-updated", "node", input.id, scope);
8681
9792
  if (knowledgeScopeKey(existing.scope) !== knowledgeScopeKey(scope)) {
@@ -8730,8 +9841,12 @@ var KnowledgePG = class extends KnowledgeStorage {
8730
9841
  ]
8731
9842
  })).rowsAffected === 0) throw new KnowledgeConflictError(source.id);
8732
9843
  await tx.execute({
8733
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET node=? WHERE node=?`,
8734
- args: [target.id, source.id]
9844
+ sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET node=?,version=version+1,updatedAt=? WHERE node=?`,
9845
+ args: [
9846
+ target.id,
9847
+ (/* @__PURE__ */ new Date()).toISOString(),
9848
+ source.id
9849
+ ]
8735
9850
  });
8736
9851
  await tx.execute({
8737
9852
  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=?)`,
@@ -8771,10 +9886,10 @@ var KnowledgePG = class extends KnowledgeStorage {
8771
9886
  });
8772
9887
  }
8773
9888
  async appendKnowledge(input) {
9889
+ await assertKnowledgeRecordTextWithinBoundCompat(input.text);
8774
9890
  const scope = canonicalizeKnowledgeScope(input.scope);
8775
9891
  const resolutionScope = canonicalizeKnowledgeScope(input.resolutionScope);
8776
9892
  const defaultScope = canonicalizeKnowledgeScope(input.defaultScope);
8777
- assertKnowledgeScopeWithinCeiling(scope, input.maxScope);
8778
9893
  return this.#transaction(async (tx) => {
8779
9894
  const parent = await this.#resolveTerminalNode(tx, nodeReferenceId(input.node));
8780
9895
  if (!parent) throw new KnowledgeNotFoundError("node", nodeReferenceId(input.node));
@@ -8787,11 +9902,10 @@ var KnowledgePG = class extends KnowledgeStorage {
8787
9902
  sourceThreadId: input.sourceThreadId,
8788
9903
  capturedAt: /* @__PURE__ */ new Date(),
8789
9904
  when: input.when ? new Date(input.when) : void 0,
8790
- maxScope: input.maxScope,
8791
9905
  metadata: input.metadata
8792
9906
  };
8793
9907
  await tx.execute({
8794
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_RECORDS}" (id,node,text,scope,scopeKey,sourceThreadId,capturedAt,"when",maxScope,metadata,deletedAt,deletedBy) VALUES (?,?,?,jsonb(?),?,?,?,?,?,jsonb(?),NULL,NULL)`,
9908
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_RECORDS}" (id,node,text,scope,scopeKey,sourceThreadId,capturedAt,"when",metadata,version,updatedAt,deletedAt,deletedBy) VALUES (?,?,?,jsonb(?),?,?,?,?,jsonb(?),?,?,NULL,NULL)`,
8795
9909
  args: [
8796
9910
  record.id,
8797
9911
  record.node,
@@ -8801,10 +9915,12 @@ var KnowledgePG = class extends KnowledgeStorage {
8801
9915
  record.sourceThreadId,
8802
9916
  record.capturedAt.toISOString(),
8803
9917
  record.when?.toISOString() ?? null,
8804
- record.maxScope ?? null,
8805
- metadataJson
9918
+ metadataJson,
9919
+ 1,
9920
+ record.capturedAt.toISOString()
8806
9921
  ]
8807
9922
  });
9923
+ await this.#replaceRecordScopes(tx, record.id, scope, record.capturedAt);
8808
9924
  await this.#replaceMentions(tx, "record", record.id, record.text, resolutionScope, defaultScope);
8809
9925
  await this.#activity(tx, "record-created", "record", record.id, scope, record.sourceThreadId);
8810
9926
  await this.#outbox(tx, "record", record.id, "upsert", record.id, scope);
@@ -8831,17 +9947,13 @@ var KnowledgePG = class extends KnowledgeStorage {
8831
9947
  return this.#queryKnowledge(input, "related");
8832
9948
  }
8833
9949
  async knowledgeBySource(input) {
8834
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
8835
- const args = [
8836
- input.sourceThreadId,
8837
- key,
8838
- key
8839
- ];
9950
+ const keys = visibleScopeKeys(canonicalizeKnowledgeScope(input.scope));
9951
+ const args = [input.sourceThreadId, ...keys];
8840
9952
  if (input.after) args.push(input.after);
8841
9953
  const limit = input.limit ?? 100;
8842
9954
  args.push(limit + 1);
8843
9955
  const records = (await this.#readExecutor.execute({
8844
- 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 ?`,
9956
+ 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 ?`,
8845
9957
  args
8846
9958
  })).rows.map(parseKnowledge);
8847
9959
  return {
@@ -8856,10 +9968,11 @@ var KnowledgePG = class extends KnowledgeStorage {
8856
9968
  if (record.deletedAt) return record;
8857
9969
  const deletedAt = /* @__PURE__ */ new Date();
8858
9970
  await tx.execute({
8859
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=?,deletedBy=? WHERE id=? AND deletedAt IS NULL`,
9971
+ sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=?,deletedBy=?,version=version+1,updatedAt=? WHERE id=? AND deletedAt IS NULL`,
8860
9972
  args: [
8861
9973
  deletedAt.toISOString(),
8862
9974
  input.deletedBy,
9975
+ deletedAt.toISOString(),
8863
9976
  input.id
8864
9977
  ]
8865
9978
  });
@@ -8878,8 +9991,8 @@ var KnowledgePG = class extends KnowledgeStorage {
8878
9991
  if (!record) throw new KnowledgeNotFoundError("record", input.id);
8879
9992
  if (!record.deletedAt) return record;
8880
9993
  await tx.execute({
8881
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=NULL,deletedBy=NULL WHERE id=?`,
8882
- args: [input.id]
9994
+ sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=NULL,deletedBy=NULL,version=version+1,updatedAt=? WHERE id=?`,
9995
+ args: [(/* @__PURE__ */ new Date()).toISOString(), input.id]
8883
9996
  });
8884
9997
  await this.#activity(tx, "record-restored", "record", input.id, record.scope, record.sourceThreadId);
8885
9998
  await this.#outbox(tx, "record", input.id, "upsert", createKnowledgeUlid(), record.scope);
@@ -8895,15 +10008,17 @@ var KnowledgePG = class extends KnowledgeStorage {
8895
10008
  return this.#transaction(async (tx) => {
8896
10009
  const record = await this.#getKnowledge(tx, input.id, true);
8897
10010
  if (!record) throw new KnowledgeNotFoundError("record", input.id);
8898
- assertKnowledgeScopeWithinCeiling(scope, record.maxScope);
10011
+ const updatedAt = /* @__PURE__ */ new Date();
8899
10012
  await tx.execute({
8900
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET scope=jsonb(?),scopeKey=? WHERE id=?`,
10013
+ sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET scope=jsonb(?),scopeKey=?,version=version+1,updatedAt=? WHERE id=?`,
8901
10014
  args: [
8902
10015
  JSON.stringify(scope),
8903
10016
  knowledgeScopeKey(scope),
10017
+ updatedAt.toISOString(),
8904
10018
  input.id
8905
10019
  ]
8906
10020
  });
10021
+ await this.#replaceRecordScopes(tx, input.id, scope, updatedAt);
8907
10022
  await this.#activity(tx, "record-rescoped", "record", input.id, scope, record.sourceThreadId);
8908
10023
  if (knowledgeScopeKey(record.scope) !== knowledgeScopeKey(scope)) await this.#outbox(tx, "record", input.id, "delete", createKnowledgeUlid(), record.scope);
8909
10024
  if (!record.deletedAt) await this.#outbox(tx, "record", input.id, "upsert", createKnowledgeUlid(), scope);
@@ -8913,33 +10028,16 @@ var KnowledgePG = class extends KnowledgeStorage {
8913
10028
  };
8914
10029
  });
8915
10030
  }
8916
- async raiseKnowledgeCeiling(input) {
8917
- return this.#transaction(async (tx) => {
8918
- const record = await this.#getKnowledge(tx, input.id, true);
8919
- if (!record) throw new KnowledgeNotFoundError("record", input.id);
8920
- assertKnowledgeScopeWithinCeiling(record.scope, input.maxScope);
8921
- assertKnowledgeCeilingRaised(record.maxScope, input.maxScope);
8922
- await tx.execute({
8923
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET maxScope=? WHERE id=?`,
8924
- args: [input.maxScope ?? null, input.id]
8925
- });
8926
- return {
8927
- ...record,
8928
- maxScope: input.maxScope
8929
- };
8930
- });
8931
- }
8932
10031
  async search(input) {
8933
10032
  const scope = canonicalizeKnowledgeScope(input.scope);
8934
- const key = knowledgeScopeKey(scope);
10033
+ const keys = visibleScopeKeys(scope);
8935
10034
  const normalizedQuery = input.query.trim().toLocaleLowerCase();
8936
10035
  if (!normalizedQuery) return [];
8937
10036
  const query = `%${escapeLikePattern(normalizedQuery)}%`;
8938
10037
  const results = (await this.#readExecutor.execute({
8939
- 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 ?`,
10038
+ 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 ?`,
8940
10039
  args: [
8941
- key,
8942
- key,
10040
+ ...keys,
8943
10041
  query,
8944
10042
  query,
8945
10043
  query,
@@ -8960,10 +10058,9 @@ var KnowledgePG = class extends KnowledgeStorage {
8960
10058
  }));
8961
10059
  if (results.length < (input.limit ?? 20)) {
8962
10060
  const records = await this.#readExecutor.execute({
8963
- 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 ?`,
10061
+ 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 ?`,
8964
10062
  args: [
8965
- key,
8966
- key,
10063
+ ...keys,
8967
10064
  query,
8968
10065
  (input.limit ?? 20) - results.length
8969
10066
  ]
@@ -8973,7 +10070,7 @@ var KnowledgePG = class extends KnowledgeStorage {
8973
10070
  return {
8974
10071
  type: "record",
8975
10072
  id: String(row.id),
8976
- recordId: String(row.node),
10073
+ recordId: parentVisible ? String(row.node) : String(row.id),
8977
10074
  name: parentVisible ? String(row.name) : "(private node)",
8978
10075
  text: String(row.text),
8979
10076
  scope: parseJson$1(row.scopeJson)
@@ -8982,41 +10079,26 @@ var KnowledgePG = class extends KnowledgeStorage {
8982
10079
  }
8983
10080
  return results;
8984
10081
  }
8985
- async getCurationCursor(input) {
8986
- const row = (await this.#executor.execute({
8987
- sql: `SELECT * FROM "${TABLE_KNOWLEDGE_CURSORS}" WHERE sourceThreadId=? AND agent=?`,
8988
- args: [input.sourceThreadId, input.agent]
8989
- })).rows[0];
8990
- return row ? {
8991
- sourceThreadId: String(row.sourceThreadId),
8992
- agent: String(row.agent),
8993
- lastKnowledgeId: String(row.lastKnowledgeId),
8994
- updatedAt: toDate$1(row.updatedAt)
8995
- } : null;
8996
- }
8997
- async advanceCurationCursor(input) {
8998
- const updatedAt = /* @__PURE__ */ new Date();
8999
- if ((await this.#executor.execute({
9000
- 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`,
9001
- args: [
9002
- input.sourceThreadId,
9003
- input.agent,
9004
- input.lastKnowledgeId,
9005
- updatedAt.toISOString()
9006
- ]
9007
- })).rowsAffected === 0) throw new Error("Knowledge curation cursor cannot move backwards");
9008
- return {
9009
- ...input,
9010
- updatedAt
9011
- };
10082
+ /**
10083
+ * @deprecated Curation cursors were removed. Observation-time curate is the only Knowledge writer and needs no
10084
+ * cursor. Always throws.
10085
+ */
10086
+ async getCurationCursor(_input) {
10087
+ throw new Error(KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE);
10088
+ }
10089
+ /**
10090
+ * @deprecated Curation cursors were removed. Observation-time curate is the only Knowledge writer and needs no
10091
+ * cursor. Always throws.
10092
+ */
10093
+ async advanceCurationCursor(_input) {
10094
+ throw new Error(KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE);
9012
10095
  }
9013
10096
  async listActivity(input) {
9014
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
10097
+ const keys = visibleScopeKeys(canonicalizeKnowledgeScope(input.scope));
9015
10098
  return (await this.#readExecutor.execute({
9016
- sql: `SELECT *,scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_ACTIVITY}" WHERE ${visibleSql}${input.after ? " AND id < ?" : ""} ORDER BY id DESC LIMIT ?`,
10099
+ sql: `SELECT *,scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_ACTIVITY}" WHERE ${visibleSql(keys)}${input.after ? " AND id < ?" : ""} ORDER BY id DESC LIMIT ?`,
9017
10100
  args: [
9018
- key,
9019
- key,
10101
+ ...keys,
9020
10102
  ...input.after ? [input.after] : [],
9021
10103
  input.limit ?? 100
9022
10104
  ]
@@ -9038,9 +10120,9 @@ var KnowledgePG = class extends KnowledgeStorage {
9038
10120
  args.push(input.status);
9039
10121
  }
9040
10122
  if (input.scope) {
9041
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
9042
- clauses.push(visibleSql);
9043
- args.push(key, key);
10123
+ const keys = visibleScopeKeys(input.scope);
10124
+ clauses.push(visibleSql(keys));
10125
+ args.push(...keys);
9044
10126
  }
9045
10127
  args.push(input.limit ?? 100);
9046
10128
  return (await this.#executor.execute({
@@ -9059,9 +10141,9 @@ var KnowledgePG = class extends KnowledgeStorage {
9059
10141
  ];
9060
10142
  const args = [now.toISOString(), stale.toISOString()];
9061
10143
  if (input.scope) {
9062
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
9063
- clauses.push(visibleSql);
9064
- args.push(key, key);
10144
+ const keys = visibleScopeKeys(input.scope);
10145
+ clauses.push(visibleSql(keys));
10146
+ args.push(...keys);
9065
10147
  }
9066
10148
  args.push(input.limit ?? 100);
9067
10149
  const ids = (await tx.execute({
@@ -9128,12 +10210,14 @@ var KnowledgePG = class extends KnowledgeStorage {
9128
10210
  return result.rows[0] ? parseNode(result.rows[0]) : null;
9129
10211
  }
9130
10212
  async #resolveNode(executor, name, scope) {
9131
- for (let length = scope.length; length > 0; length--) {
9132
- const node = await this.#getNodeByName(executor, name, scope.slice(0, length));
9133
- if (node) {
9134
- const terminal = await this.#resolveTerminalNode(executor, node.id);
9135
- if (terminal && isKnowledgeScopeVisible(terminal.scope, scope)) return terminal;
9136
- }
10213
+ const keys = visibleScopeKeys(scope);
10214
+ const candidates = (await executor.execute({
10215
+ 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)`,
10216
+ args: [canonicalName(name), ...keys]
10217
+ })).rows.map(parseNode).sort((left, right) => right.scope.length - left.scope.length);
10218
+ for (const candidate of candidates) {
10219
+ const terminal = await this.#resolveTerminalNode(executor, candidate.id);
10220
+ if (terminal && isKnowledgeScopeVisible(terminal.scope, scope)) return terminal;
9137
10221
  }
9138
10222
  return null;
9139
10223
  }
@@ -9158,17 +10242,16 @@ var KnowledgePG = class extends KnowledgeStorage {
9158
10242
  const scope = canonicalizeKnowledgeScope(input.scope);
9159
10243
  const node = await this.#resolveTerminalNode(this.#readExecutor, nodeReferenceId(input.node));
9160
10244
  if (!node) return { records: [] };
9161
- const key = knowledgeScopeKey(scope);
10245
+ const keys = visibleScopeKeys(scope);
9162
10246
  const args = [
9163
10247
  node.id,
9164
10248
  ...relationship === "related" ? [node.id] : [],
9165
- key,
9166
- key
10249
+ ...keys
9167
10250
  ];
9168
10251
  if (input.after) args.push(input.after);
9169
10252
  args.push((input.limit ?? 100) + 1);
9170
10253
  const records = (await this.#readExecutor.execute({
9171
- 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 ?`,
10254
+ 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 ?`,
9172
10255
  args
9173
10256
  })).rows.map(parseKnowledge);
9174
10257
  const limit = input.limit ?? 100;
@@ -9177,6 +10260,90 @@ var KnowledgePG = class extends KnowledgeStorage {
9177
10260
  nextCursor: records.length > limit ? records[limit - 1]?.id : void 0
9178
10261
  };
9179
10262
  }
10263
+ async #resolveScopeNodeIds(executor, addresses) {
10264
+ const scopeNodeIds = /* @__PURE__ */ new Set();
10265
+ for (const address of addresses) {
10266
+ const result = await executor.execute({
10267
+ sql: `SELECT scopeNodeId FROM "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" WHERE address=?`,
10268
+ args: [address]
10269
+ });
10270
+ if (result.rows[0]?.scopeNodeId != null) scopeNodeIds.add(String(result.rows[0].scopeNodeId));
10271
+ }
10272
+ return [...scopeNodeIds];
10273
+ }
10274
+ async #replaceNodeScopes(executor, nodeId, addresses, addedAt) {
10275
+ await executor.execute({
10276
+ sql: `DELETE FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" WHERE nodeId=?`,
10277
+ args: [nodeId]
10278
+ });
10279
+ for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, addresses)) await executor.execute({
10280
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?) ON CONFLICT DO NOTHING`,
10281
+ args: [
10282
+ nodeId,
10283
+ scopeNodeId,
10284
+ addedAt.toISOString()
10285
+ ]
10286
+ });
10287
+ }
10288
+ /**
10289
+ * Move a node's identity memberships from `from` to `to` without touching structural
10290
+ * placements added by `#placeNodeInScopes`, which a scope change must not erase.
10291
+ */
10292
+ async #swapIdentityScopes(executor, nodeId, from, to, addedAt) {
10293
+ const next = await this.#resolveScopeNodeIds(executor, to);
10294
+ for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, from)) {
10295
+ if (next.includes(scopeNodeId)) continue;
10296
+ await executor.execute({
10297
+ sql: `DELETE FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" WHERE nodeId=? AND scopeNodeId=?`,
10298
+ args: [nodeId, scopeNodeId]
10299
+ });
10300
+ }
10301
+ for (const scopeNodeId of next) await executor.execute({
10302
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?) ON CONFLICT DO NOTHING`,
10303
+ args: [
10304
+ nodeId,
10305
+ scopeNodeId,
10306
+ addedAt.toISOString()
10307
+ ]
10308
+ });
10309
+ }
10310
+ /**
10311
+ * Additive structural placement: every address must resolve to a live scope node
10312
+ * (unlike identity membership, an unknown address is a caller error, not a lazy
10313
+ * materialization miss). Runs inside the caller's transaction, so a throw aborts
10314
+ * the whole mutation.
10315
+ */
10316
+ async #placeNodeInScopes(executor, nodeId, addresses, addedAt) {
10317
+ for (const address of addresses ?? []) {
10318
+ const scopeNodeId = (await executor.execute({
10319
+ 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`,
10320
+ args: [address]
10321
+ })).rows[0]?.scopeNodeId;
10322
+ if (scopeNodeId == null) throw new KnowledgeNotFoundError("scope", address);
10323
+ await executor.execute({
10324
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" ("nodeId","scopeNodeId","addedAt") VALUES (?,?,?) ON CONFLICT DO NOTHING`,
10325
+ args: [
10326
+ nodeId,
10327
+ String(scopeNodeId),
10328
+ addedAt.toISOString()
10329
+ ]
10330
+ });
10331
+ }
10332
+ }
10333
+ async #replaceRecordScopes(executor, recordId, addresses, addedAt) {
10334
+ await executor.execute({
10335
+ sql: `DELETE FROM "${TABLE_KNOWLEDGE_RECORD_SCOPES}" WHERE recordId=?`,
10336
+ args: [recordId]
10337
+ });
10338
+ for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, addresses)) await executor.execute({
10339
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_RECORD_SCOPES}" (recordId,scopeNodeId,addedAt) VALUES (?,?,?) ON CONFLICT DO NOTHING`,
10340
+ args: [
10341
+ recordId,
10342
+ scopeNodeId,
10343
+ addedAt.toISOString()
10344
+ ]
10345
+ });
10346
+ }
9180
10347
  async #replaceMentions(tx, sourceType, sourceId, text, resolutionScope, defaultScope) {
9181
10348
  await tx.execute({
9182
10349
  sql: `DELETE FROM "${TABLE_KNOWLEDGE_MENTIONS}" WHERE sourceType=? AND sourceId=?`,
@@ -9200,7 +10367,7 @@ var KnowledgePG = class extends KnowledgeStorage {
9200
10367
  updatedAt: now
9201
10368
  };
9202
10369
  await tx.execute({
9203
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,NULL,jsonb(?),?,?,NULL,?,?)`,
10370
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,isScope,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,NULL,FALSE,jsonb(?),?,?,NULL,?,?)`,
9204
10371
  args: [
9205
10372
  node.id,
9206
10373
  "node",
@@ -9214,6 +10381,7 @@ var KnowledgePG = class extends KnowledgeStorage {
9214
10381
  now.toISOString()
9215
10382
  ]
9216
10383
  });
10384
+ await this.#replaceNodeScopes(tx, node.id, defaultScope, now);
9217
10385
  await this.#activity(tx, "node-created", "node", node.id, defaultScope);
9218
10386
  await this.#outbox(tx, "node", node.id, "upsert", 1, defaultScope);
9219
10387
  }
@@ -9230,13 +10398,17 @@ var KnowledgePG = class extends KnowledgeStorage {
9230
10398
  }
9231
10399
  async #activity(executor, action, recordType, recordId, scope, sourceThreadId) {
9232
10400
  const now = /* @__PURE__ */ new Date();
10401
+ const [contextScopeId] = await this.#resolveScopeNodeIds(executor, scope);
9233
10402
  await executor.execute({
9234
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_ACTIVITY}" (id,action,recordType,recordId,scope,scopeKey,sourceThreadId,createdAt) VALUES (?,?,?,?,jsonb(?),?,?,?)`,
10403
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_ACTIVITY}" (id,action,targetType,targetId,contextScopeId,recordType,recordId,scope,scopeKey,sourceThreadId,createdAt) VALUES (?,?,?,?,?,?,?,jsonb(?),?,?,?)`,
9235
10404
  args: [
9236
10405
  createKnowledgeUlid(),
9237
10406
  action,
9238
10407
  recordType,
9239
10408
  recordId,
10409
+ contextScopeId ?? null,
10410
+ recordType,
10411
+ recordId,
9240
10412
  JSON.stringify(scope),
9241
10413
  knowledgeScopeKey(scope),
9242
10414
  sourceThreadId ?? null,
@@ -23491,7 +24663,7 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
23491
24663
  async dangerouslyClearAll() {
23492
24664
  await this.#db.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
23493
24665
  }
23494
- async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext }) {
24666
+ async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state }) {
23495
24667
  try {
23496
24668
  return await this.#db.client.tx(async (t) => {
23497
24669
  const tableName = getTableName({
@@ -23522,7 +24694,8 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
23522
24694
  snapshot,
23523
24695
  stepId,
23524
24696
  result,
23525
- requestContext
24697
+ requestContext,
24698
+ state
23526
24699
  });
23527
24700
  const now = /* @__PURE__ */ new Date();
23528
24701
  const sanitizedSnapshot = toPgJson(snapshot);
@@ -24995,7 +26168,10 @@ var PostgresStore = class extends MastraCompositeStore {
24995
26168
  workflows: new WorkflowsPG(domainConfig),
24996
26169
  workflowDefinitions: new WorkflowDefinitionsPG(domainConfig),
24997
26170
  memory: new MemoryPG(domainConfig),
24998
- knowledge: new KnowledgePG(domainConfig),
26171
+ knowledge: new KnowledgePG({
26172
+ ...domainConfig,
26173
+ storageIsolationKey: config.storageIsolationKey ?? getPgKnowledgeIsolationKey(config)
26174
+ }),
24999
26175
  notifications: new NotificationsPG(domainConfig),
25000
26176
  observability: new ObservabilityPG(domainConfig),
25001
26177
  agents: new AgentsPG(domainConfig),
@@ -25049,7 +26225,7 @@ var PostgresStore = class extends MastraCompositeStore {
25049
26225
  const pinned = new PinnedClientAdapter(this.#writePool, pinnedClient);
25050
26226
  this.#db.pin(pinned);
25051
26227
  this.#db.setSchemaSnapshot(await loadSchemaSnapshot(pinned, this.schema));
25052
- await super.init();
26228
+ await Promise.all(Object.entries(this.stores).filter(([name, store]) => name !== "knowledge" && store).map(([, store]) => store.init()));
25053
26229
  this.isInitialized = true;
25054
26230
  } catch (error) {
25055
26231
  this.#initPromise = null;