@mastra/pg 1.31.0-alpha.2 → 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";
@@ -8277,18 +8277,650 @@ var FavoritesPG = class FavoritesPG extends FavoritesStorage {
8277
8277
  }
8278
8278
  };
8279
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
8280
8915
  //#region src/storage/domains/knowledge/index.ts
8281
- let assertDescriptionWithinBound;
8916
+ const loadKnowledgeV2Core = createKnowledgeV2CoreLoader(coreFeatures, () => import("@mastra/core/storage"));
8282
8917
  async function assertKnowledgeDescriptionWithinBoundCompat(description) {
8283
- let assertWithinBound = assertDescriptionWithinBound;
8284
- if (!assertWithinBound) {
8285
- const resolvedAssert = (await import("@mastra/core/storage")).assertKnowledgeDescriptionWithinBound ?? ((value) => {
8286
- if (value !== void 0 && value.length > 400) throw new Error("Knowledge node description exceeds the 400 UTF-16 code unit limit");
8287
- });
8288
- assertDescriptionWithinBound = resolvedAssert;
8289
- assertWithinBound = resolvedAssert;
8290
- }
8291
- assertWithinBound(description);
8918
+ const { assertKnowledgeDescriptionWithinBound } = await loadKnowledgeV2Core();
8919
+ assertKnowledgeDescriptionWithinBound(description);
8920
+ }
8921
+ async function assertKnowledgeRecordTextWithinBoundCompat(text) {
8922
+ const { assertKnowledgeRecordTextWithinBound } = await loadKnowledgeV2Core();
8923
+ assertKnowledgeRecordTextWithinBound(text);
8292
8924
  }
8293
8925
  const camelCaseColumns = [
8294
8926
  "canonicalName",
@@ -8299,7 +8931,6 @@ const camelCaseColumns = [
8299
8931
  "node",
8300
8932
  "sourceThreadId",
8301
8933
  "capturedAt",
8302
- "maxScope",
8303
8934
  "deletedAt",
8304
8935
  "deletedBy",
8305
8936
  "sourceType",
@@ -8313,7 +8944,30 @@ const camelCaseColumns = [
8313
8944
  "availableAt",
8314
8945
  "claimedAt",
8315
8946
  "claimedBy",
8316
- "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"
8317
8971
  ];
8318
8972
  function transformSqlCode(sql, transform) {
8319
8973
  return sql.split(/('(?:''|[^'])*')/g).map((part, index) => index % 2 === 0 ? transform(part) : part).join("");
@@ -8328,14 +8982,7 @@ function postgresSql(sql, schemaName) {
8328
8982
  const quotedSchema = `"${parseSchemaName(schemaName)}"`;
8329
8983
  normalized = transformSqlCode(normalized, (code) => {
8330
8984
  let transformed = code;
8331
- for (const table of [
8332
- TABLE_KNOWLEDGE_NODES,
8333
- TABLE_KNOWLEDGE_RECORDS,
8334
- TABLE_KNOWLEDGE_MENTIONS,
8335
- TABLE_KNOWLEDGE_CURSORS,
8336
- TABLE_KNOWLEDGE_ACTIVITY,
8337
- TABLE_KNOWLEDGE_SEMANTIC_OUTBOX
8338
- ]) transformed = transformed.replaceAll(`"${table}"`, `${quotedSchema}."${table}"`);
8985
+ for (const table of [...KNOWLEDGE_TABLE_NAMES, ...RETIRED_KNOWLEDGE_TABLE_NAMES]) transformed = transformed.replaceAll(`"${table}"`, `${quotedSchema}."${table}"`);
8339
8986
  return transformed;
8340
8987
  });
8341
8988
  }
@@ -8343,17 +8990,35 @@ function postgresSql(sql, schemaName) {
8343
8990
  return transformSqlCode(normalized, (code) => code.replace(/\?/g, () => `$${++index}`));
8344
8991
  }
8345
8992
  function createExecutor(client, schemaName) {
8346
- return { async execute(statement) {
8993
+ const execute = async (statement, qualifyWithSchema) => {
8347
8994
  const sql = typeof statement === "string" ? statement : statement.sql;
8348
8995
  const args = typeof statement === "string" ? [] : statement.args ?? [];
8349
- const result = await client.query(postgresSql(sql, schemaName), args);
8996
+ const result = await client.query(postgresSql(sql, qualifyWithSchema ? schemaName : void 0), args);
8350
8997
  return {
8351
8998
  rows: result.rows,
8352
8999
  rowsAffected: result.rowCount ?? 0
8353
9000
  };
8354
- } };
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];
8355
9020
  }
8356
- const visibleSql = `(scopeKey = ? OR LEFT(?, LENGTH(scopeKey) + 1) = scopeKey || chr(31))`;
9021
+ const visibleSql = (keys, scopeKeyColumn = "scopeKey") => `${scopeKeyColumn} IN (${keys.map(() => "?").join(",")})`;
8357
9022
  function parseJson$1(value) {
8358
9023
  if (typeof value === "string") return JSON.parse(value);
8359
9024
  if (value instanceof Uint8Array) return JSON.parse(new TextDecoder().decode(value));
@@ -8393,9 +9058,10 @@ function parseNode(row) {
8393
9058
  id: String(row.id),
8394
9059
  type: "node",
8395
9060
  name: String(row.name),
8396
- kind: String(row.kind),
9061
+ kind: row.kind == null ? "" : String(row.kind),
8397
9062
  content: row.content == null ? void 0 : String(row.content),
8398
9063
  description: row.description == null ? void 0 : String(row.description),
9064
+ isScope: row.isScope === true || row.isScope === 1 ? true : void 0,
8399
9065
  scope: parseJson$1(row.scopeJson ?? row.scope),
8400
9066
  version: Number(row.version),
8401
9067
  mergedInto: row.mergedInto == null ? void 0 : String(row.mergedInto),
@@ -8412,7 +9078,6 @@ function parseKnowledge(row) {
8412
9078
  sourceThreadId: String(row.sourceThreadId),
8413
9079
  capturedAt: toDate$1(row.capturedAt),
8414
9080
  when: optionalDate(row.when),
8415
- maxScope: row.maxScope == null ? void 0 : String(row.maxScope),
8416
9081
  metadata: row.metadata == null ? void 0 : parseJson$1(row.metadata),
8417
9082
  deletedAt: optionalDate(row.deletedAt),
8418
9083
  deletedBy: row.deletedBy == null ? void 0 : String(row.deletedBy)
@@ -8449,6 +9114,10 @@ function knowledgeIndexes(schemaName) {
8449
9114
  name: "idx_knowledge_nodes_scope",
8450
9115
  sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_nodes_scope ON ${table(TABLE_KNOWLEDGE_NODES)} ("scopeKey", "type");`
8451
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
+ },
8452
9121
  {
8453
9122
  name: "idx_knowledge_records_node_latest",
8454
9123
  sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_records_node_latest ON ${table(TABLE_KNOWLEDGE_RECORDS)} ("node", "id" DESC);`
@@ -8465,6 +9134,14 @@ function knowledgeIndexes(schemaName) {
8465
9134
  name: "idx_knowledge_activity_latest",
8466
9135
  sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_activity_latest ON ${table(TABLE_KNOWLEDGE_ACTIVITY)} ("id" DESC);`
8467
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
+ },
8468
9145
  {
8469
9146
  name: "idx_knowledge_outbox_idempotency",
8470
9147
  sql: `CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_outbox_idempotency ON ${table(TABLE_KNOWLEDGE_SEMANTIC_OUTBOX)} ("idempotencyKey");`
@@ -8472,67 +9149,152 @@ function knowledgeIndexes(schemaName) {
8472
9149
  {
8473
9150
  name: "idx_knowledge_outbox_claim",
8474
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);`
8475
9176
  }
8476
9177
  ];
8477
9178
  }
8478
9179
  function knowledgeIndexDDL(schemaName) {
8479
9180
  return knowledgeIndexes(schemaName).map((index) => index.sql);
8480
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
+ }
8481
9290
  var KnowledgePG = class extends KnowledgeStorage {
8482
- static MANAGED_TABLES = [
8483
- TABLE_KNOWLEDGE_NODES,
8484
- TABLE_KNOWLEDGE_RECORDS,
8485
- TABLE_KNOWLEDGE_MENTIONS,
8486
- TABLE_KNOWLEDGE_CURSORS,
8487
- TABLE_KNOWLEDGE_ACTIVITY,
8488
- TABLE_KNOWLEDGE_SEMANTIC_OUTBOX
8489
- ];
9291
+ static MANAGED_TABLES = KNOWLEDGE_TABLE_NAMES;
8490
9292
  static getExportDDL(schemaName) {
8491
- return [
8492
- generateTableSQL({
8493
- tableName: TABLE_KNOWLEDGE_NODES,
8494
- schema: KNOWLEDGE_NODES_SCHEMA,
8495
- schemaName,
8496
- includeAllConstraints: true
8497
- }),
8498
- generateTableSQL({
8499
- tableName: TABLE_KNOWLEDGE_RECORDS,
8500
- schema: KNOWLEDGE_RECORDS_SCHEMA,
8501
- schemaName,
8502
- includeAllConstraints: true
8503
- }),
8504
- generateTableSQL({
8505
- tableName: TABLE_KNOWLEDGE_MENTIONS,
8506
- schema: KNOWLEDGE_MENTIONS_SCHEMA,
8507
- schemaName,
8508
- compositePrimaryKey: [
8509
- "sourceType",
8510
- "sourceId",
8511
- "recordId"
8512
- ],
8513
- includeAllConstraints: true
8514
- }),
8515
- generateTableSQL({
8516
- tableName: TABLE_KNOWLEDGE_CURSORS,
8517
- schema: KNOWLEDGE_CURSORS_SCHEMA,
8518
- schemaName,
8519
- compositePrimaryKey: ["sourceThreadId", "agent"],
8520
- includeAllConstraints: true
8521
- }),
8522
- generateTableSQL({
8523
- tableName: TABLE_KNOWLEDGE_ACTIVITY,
8524
- schema: KNOWLEDGE_ACTIVITY_SCHEMA,
8525
- schemaName,
8526
- includeAllConstraints: true
8527
- }),
8528
- generateTableSQL({
8529
- tableName: TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
8530
- schema: KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA,
8531
- schemaName,
8532
- includeAllConstraints: true
8533
- }),
8534
- ...knowledgeIndexDDL(schemaName)
8535
- ];
9293
+ return [...knowledgeTableDefinitions.map((definition) => generateTableSQL({
9294
+ ...definition,
9295
+ schemaName,
9296
+ includeAllConstraints: true
9297
+ })), ...knowledgeIndexDDL(schemaName)];
8536
9298
  }
8537
9299
  #client;
8538
9300
  #executor;
@@ -8541,7 +9303,7 @@ var KnowledgePG = class extends KnowledgeStorage {
8541
9303
  #db;
8542
9304
  #schemaName;
8543
9305
  constructor(config) {
8544
- super();
9306
+ super({ storageIsolationKey: config.storageIsolationKey ?? getPgKnowledgeIsolationKey(config) });
8545
9307
  const { client, readClient, schemaName, skipDefaultIndexes } = resolvePgConfig(config);
8546
9308
  this.#client = client;
8547
9309
  this.#schemaName = schemaName;
@@ -8554,56 +9316,359 @@ var KnowledgePG = class extends KnowledgeStorage {
8554
9316
  skipDefaultIndexes
8555
9317
  });
8556
9318
  }
8557
- async init() {
8558
- await this.#db.createTable({
8559
- tableName: TABLE_KNOWLEDGE_NODES,
8560
- schema: KNOWLEDGE_NODES_SCHEMA
8561
- });
8562
- await this.#db.alterTable({
8563
- tableName: TABLE_KNOWLEDGE_NODES,
8564
- schema: KNOWLEDGE_NODES_SCHEMA,
8565
- ifNotExists: ["description"]
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
8566
9394
  });
8567
- await this.#db.createTable({
8568
- tableName: TABLE_KNOWLEDGE_RECORDS,
8569
- 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
+ });
8570
9420
  });
8571
- await this.#db.createTable({
8572
- tableName: TABLE_KNOWLEDGE_MENTIONS,
8573
- schema: KNOWLEDGE_MENTIONS_SCHEMA,
8574
- compositePrimaryKey: [
8575
- "sourceType",
8576
- "sourceId",
8577
- "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
8578
9454
  ]
8579
- });
8580
- await this.#db.createTable({
8581
- tableName: TABLE_KNOWLEDGE_CURSORS,
8582
- schema: KNOWLEDGE_CURSORS_SCHEMA,
8583
- compositePrimaryKey: ["sourceThreadId", "agent"]
8584
- });
8585
- await this.#db.createTable({
8586
- tableName: TABLE_KNOWLEDGE_ACTIVITY,
8587
- schema: KNOWLEDGE_ACTIVITY_SCHEMA
8588
- });
8589
- await this.#db.createTable({
8590
- tableName: TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
8591
- schema: KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA
8592
- });
8593
- await Promise.all(knowledgeIndexes(this.#schemaName).map((index) => this.#db.createIndexFromStatement(index.name, index.sql)));
9455
+ })).rows.length > 0) return false;
9456
+ return true;
8594
9457
  }
8595
9458
  async dangerouslyClearAll() {
8596
9459
  await this.#transaction(async (tx) => {
8597
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,
8598
9466
  TABLE_KNOWLEDGE_MENTIONS,
8599
- TABLE_KNOWLEDGE_RECORDS,
8600
- TABLE_KNOWLEDGE_NODES,
8601
- TABLE_KNOWLEDGE_CURSORS,
9467
+ TABLE_KNOWLEDGE_PROPOSALS,
8602
9468
  TABLE_KNOWLEDGE_ACTIVITY,
8603
- 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
8604
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
+ };
8605
9599
  });
8606
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
+ }
8607
9672
  async createNode(input) {
8608
9673
  await assertKnowledgeDescriptionWithinBoundCompat(input.description);
8609
9674
  const scope = canonicalizeKnowledgeScope(input.scope);
@@ -8612,6 +9677,7 @@ var KnowledgePG = class extends KnowledgeStorage {
8612
9677
  if (existing) {
8613
9678
  const terminal = await this.#resolveTerminalNode(tx, existing.id);
8614
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());
8615
9681
  return terminal;
8616
9682
  }
8617
9683
  const now = /* @__PURE__ */ new Date();
@@ -8628,7 +9694,7 @@ var KnowledgePG = class extends KnowledgeStorage {
8628
9694
  updatedAt: now
8629
9695
  };
8630
9696
  await tx.execute({
8631
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,description,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,?,?,jsonb(?),?,?,NULL,?,?)`,
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,?,?)`,
8632
9698
  args: [
8633
9699
  node.id,
8634
9700
  "node",
@@ -8644,6 +9710,8 @@ var KnowledgePG = class extends KnowledgeStorage {
8644
9710
  now.toISOString()
8645
9711
  ]
8646
9712
  });
9713
+ await this.#replaceNodeScopes(tx, node.id, scope, now);
9714
+ await this.#placeNodeInScopes(tx, node.id, input.scopeAddresses, now);
8647
9715
  await this.#replaceMentions(tx, "node", node.id, node.content ?? "", input.resolutionScope ?? scope, scope);
8648
9716
  await this.#activity(tx, "node-created", "node", node.id, scope);
8649
9717
  await this.#outbox(tx, "node", node.id, "upsert", node.version, scope);
@@ -8660,13 +9728,13 @@ var KnowledgePG = class extends KnowledgeStorage {
8660
9728
  return this.#resolveNode(this.#readExecutor, input.name, canonicalizeKnowledgeScope(input.scope));
8661
9729
  }
8662
9730
  async listNodes(input) {
8663
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
9731
+ const keys = visibleScopeKeys(canonicalizeKnowledgeScope(input.scope));
8664
9732
  const clauses = [
8665
9733
  `type = 'node'`,
8666
9734
  "mergedInto IS NULL",
8667
- visibleSql
9735
+ visibleSql(keys)
8668
9736
  ];
8669
- const args = [key, key];
9737
+ const args = [...keys];
8670
9738
  if (input.namePrefix) {
8671
9739
  clauses.push("canonicalName LIKE ? ESCAPE '='");
8672
9740
  args.push(`${escapeLikePattern(canonicalName(input.namePrefix))}%`);
@@ -8718,6 +9786,7 @@ var KnowledgePG = class extends KnowledgeStorage {
8718
9786
  input.version
8719
9787
  ]
8720
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);
8721
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);
8722
9791
  await this.#activity(tx, "node-updated", "node", input.id, scope);
8723
9792
  if (knowledgeScopeKey(existing.scope) !== knowledgeScopeKey(scope)) {
@@ -8772,8 +9841,12 @@ var KnowledgePG = class extends KnowledgeStorage {
8772
9841
  ]
8773
9842
  })).rowsAffected === 0) throw new KnowledgeConflictError(source.id);
8774
9843
  await tx.execute({
8775
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET node=? WHERE node=?`,
8776
- 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
+ ]
8777
9850
  });
8778
9851
  await tx.execute({
8779
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=?)`,
@@ -8813,10 +9886,10 @@ var KnowledgePG = class extends KnowledgeStorage {
8813
9886
  });
8814
9887
  }
8815
9888
  async appendKnowledge(input) {
9889
+ await assertKnowledgeRecordTextWithinBoundCompat(input.text);
8816
9890
  const scope = canonicalizeKnowledgeScope(input.scope);
8817
9891
  const resolutionScope = canonicalizeKnowledgeScope(input.resolutionScope);
8818
9892
  const defaultScope = canonicalizeKnowledgeScope(input.defaultScope);
8819
- assertKnowledgeScopeWithinCeiling(scope, input.maxScope);
8820
9893
  return this.#transaction(async (tx) => {
8821
9894
  const parent = await this.#resolveTerminalNode(tx, nodeReferenceId(input.node));
8822
9895
  if (!parent) throw new KnowledgeNotFoundError("node", nodeReferenceId(input.node));
@@ -8829,11 +9902,10 @@ var KnowledgePG = class extends KnowledgeStorage {
8829
9902
  sourceThreadId: input.sourceThreadId,
8830
9903
  capturedAt: /* @__PURE__ */ new Date(),
8831
9904
  when: input.when ? new Date(input.when) : void 0,
8832
- maxScope: input.maxScope,
8833
9905
  metadata: input.metadata
8834
9906
  };
8835
9907
  await tx.execute({
8836
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_RECORDS}" (id,node,text,scope,scopeKey,sourceThreadId,capturedAt,"when",maxScope,metadata,deletedAt,deletedBy) VALUES (?,?,?,jsonb(?),?,?,?,?,?,jsonb(?),NULL,NULL)`,
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)`,
8837
9909
  args: [
8838
9910
  record.id,
8839
9911
  record.node,
@@ -8843,10 +9915,12 @@ var KnowledgePG = class extends KnowledgeStorage {
8843
9915
  record.sourceThreadId,
8844
9916
  record.capturedAt.toISOString(),
8845
9917
  record.when?.toISOString() ?? null,
8846
- record.maxScope ?? null,
8847
- metadataJson
9918
+ metadataJson,
9919
+ 1,
9920
+ record.capturedAt.toISOString()
8848
9921
  ]
8849
9922
  });
9923
+ await this.#replaceRecordScopes(tx, record.id, scope, record.capturedAt);
8850
9924
  await this.#replaceMentions(tx, "record", record.id, record.text, resolutionScope, defaultScope);
8851
9925
  await this.#activity(tx, "record-created", "record", record.id, scope, record.sourceThreadId);
8852
9926
  await this.#outbox(tx, "record", record.id, "upsert", record.id, scope);
@@ -8873,17 +9947,13 @@ var KnowledgePG = class extends KnowledgeStorage {
8873
9947
  return this.#queryKnowledge(input, "related");
8874
9948
  }
8875
9949
  async knowledgeBySource(input) {
8876
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
8877
- const args = [
8878
- input.sourceThreadId,
8879
- key,
8880
- key
8881
- ];
9950
+ const keys = visibleScopeKeys(canonicalizeKnowledgeScope(input.scope));
9951
+ const args = [input.sourceThreadId, ...keys];
8882
9952
  if (input.after) args.push(input.after);
8883
9953
  const limit = input.limit ?? 100;
8884
9954
  args.push(limit + 1);
8885
9955
  const records = (await this.#readExecutor.execute({
8886
- sql: `SELECT *,scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_RECORDS}" WHERE sourceThreadId=? AND ${visibleSql}${input.includeDeleted ? "" : " AND deletedAt IS NULL"}${input.after ? " AND id > ?" : ""} ORDER BY id ASC LIMIT ?`,
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 ?`,
8887
9957
  args
8888
9958
  })).rows.map(parseKnowledge);
8889
9959
  return {
@@ -8898,10 +9968,11 @@ var KnowledgePG = class extends KnowledgeStorage {
8898
9968
  if (record.deletedAt) return record;
8899
9969
  const deletedAt = /* @__PURE__ */ new Date();
8900
9970
  await tx.execute({
8901
- 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`,
8902
9972
  args: [
8903
9973
  deletedAt.toISOString(),
8904
9974
  input.deletedBy,
9975
+ deletedAt.toISOString(),
8905
9976
  input.id
8906
9977
  ]
8907
9978
  });
@@ -8920,8 +9991,8 @@ var KnowledgePG = class extends KnowledgeStorage {
8920
9991
  if (!record) throw new KnowledgeNotFoundError("record", input.id);
8921
9992
  if (!record.deletedAt) return record;
8922
9993
  await tx.execute({
8923
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=NULL,deletedBy=NULL WHERE id=?`,
8924
- 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]
8925
9996
  });
8926
9997
  await this.#activity(tx, "record-restored", "record", input.id, record.scope, record.sourceThreadId);
8927
9998
  await this.#outbox(tx, "record", input.id, "upsert", createKnowledgeUlid(), record.scope);
@@ -8937,15 +10008,17 @@ var KnowledgePG = class extends KnowledgeStorage {
8937
10008
  return this.#transaction(async (tx) => {
8938
10009
  const record = await this.#getKnowledge(tx, input.id, true);
8939
10010
  if (!record) throw new KnowledgeNotFoundError("record", input.id);
8940
- assertKnowledgeScopeWithinCeiling(scope, record.maxScope);
10011
+ const updatedAt = /* @__PURE__ */ new Date();
8941
10012
  await tx.execute({
8942
- 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=?`,
8943
10014
  args: [
8944
10015
  JSON.stringify(scope),
8945
10016
  knowledgeScopeKey(scope),
10017
+ updatedAt.toISOString(),
8946
10018
  input.id
8947
10019
  ]
8948
10020
  });
10021
+ await this.#replaceRecordScopes(tx, input.id, scope, updatedAt);
8949
10022
  await this.#activity(tx, "record-rescoped", "record", input.id, scope, record.sourceThreadId);
8950
10023
  if (knowledgeScopeKey(record.scope) !== knowledgeScopeKey(scope)) await this.#outbox(tx, "record", input.id, "delete", createKnowledgeUlid(), record.scope);
8951
10024
  if (!record.deletedAt) await this.#outbox(tx, "record", input.id, "upsert", createKnowledgeUlid(), scope);
@@ -8955,33 +10028,16 @@ var KnowledgePG = class extends KnowledgeStorage {
8955
10028
  };
8956
10029
  });
8957
10030
  }
8958
- async raiseKnowledgeCeiling(input) {
8959
- return this.#transaction(async (tx) => {
8960
- const record = await this.#getKnowledge(tx, input.id, true);
8961
- if (!record) throw new KnowledgeNotFoundError("record", input.id);
8962
- assertKnowledgeScopeWithinCeiling(record.scope, input.maxScope);
8963
- assertKnowledgeCeilingRaised(record.maxScope, input.maxScope);
8964
- await tx.execute({
8965
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET maxScope=? WHERE id=?`,
8966
- args: [input.maxScope ?? null, input.id]
8967
- });
8968
- return {
8969
- ...record,
8970
- maxScope: input.maxScope
8971
- };
8972
- });
8973
- }
8974
10031
  async search(input) {
8975
10032
  const scope = canonicalizeKnowledgeScope(input.scope);
8976
- const key = knowledgeScopeKey(scope);
10033
+ const keys = visibleScopeKeys(scope);
8977
10034
  const normalizedQuery = input.query.trim().toLocaleLowerCase();
8978
10035
  if (!normalizedQuery) return [];
8979
10036
  const query = `%${escapeLikePattern(normalizedQuery)}%`;
8980
10037
  const results = (await this.#readExecutor.execute({
8981
- sql: `SELECT *,scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_NODES}" WHERE mergedInto IS NULL AND ${visibleSql} AND (canonicalName LIKE ? ESCAPE '=' OR lower(COALESCE(kind,'')) LIKE ? ESCAPE '=' OR lower(COALESCE(content,'')) LIKE ? ESCAPE '=' OR lower(COALESCE(description,'')) LIKE ? ESCAPE '=') ORDER BY updatedAt DESC LIMIT ?`,
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 ?`,
8982
10039
  args: [
8983
- key,
8984
- key,
10040
+ ...keys,
8985
10041
  query,
8986
10042
  query,
8987
10043
  query,
@@ -9002,10 +10058,9 @@ var KnowledgePG = class extends KnowledgeStorage {
9002
10058
  }));
9003
10059
  if (results.length < (input.limit ?? 20)) {
9004
10060
  const records = await this.#readExecutor.execute({
9005
- sql: `SELECT f.*,f.scope AS "scopeJson",r.name,r.scope AS "parentScopeJson" FROM "${TABLE_KNOWLEDGE_RECORDS}" f JOIN "${TABLE_KNOWLEDGE_NODES}" r ON r.id=f.node AND r.type='node' AND r.mergedInto IS NULL WHERE f.deletedAt IS NULL AND ${visibleSql.replaceAll("scopeKey", "f.scopeKey")} AND lower(f.text) LIKE ? ESCAPE '=' ORDER BY f.id DESC LIMIT ?`,
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 ?`,
9006
10062
  args: [
9007
- key,
9008
- key,
10063
+ ...keys,
9009
10064
  query,
9010
10065
  (input.limit ?? 20) - results.length
9011
10066
  ]
@@ -9015,7 +10070,7 @@ var KnowledgePG = class extends KnowledgeStorage {
9015
10070
  return {
9016
10071
  type: "record",
9017
10072
  id: String(row.id),
9018
- recordId: String(row.node),
10073
+ recordId: parentVisible ? String(row.node) : String(row.id),
9019
10074
  name: parentVisible ? String(row.name) : "(private node)",
9020
10075
  text: String(row.text),
9021
10076
  scope: parseJson$1(row.scopeJson)
@@ -9024,41 +10079,26 @@ var KnowledgePG = class extends KnowledgeStorage {
9024
10079
  }
9025
10080
  return results;
9026
10081
  }
9027
- async getCurationCursor(input) {
9028
- const row = (await this.#executor.execute({
9029
- sql: `SELECT * FROM "${TABLE_KNOWLEDGE_CURSORS}" WHERE sourceThreadId=? AND agent=?`,
9030
- args: [input.sourceThreadId, input.agent]
9031
- })).rows[0];
9032
- return row ? {
9033
- sourceThreadId: String(row.sourceThreadId),
9034
- agent: String(row.agent),
9035
- lastKnowledgeId: String(row.lastKnowledgeId),
9036
- updatedAt: toDate$1(row.updatedAt)
9037
- } : null;
9038
- }
9039
- async advanceCurationCursor(input) {
9040
- const updatedAt = /* @__PURE__ */ new Date();
9041
- if ((await this.#executor.execute({
9042
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_CURSORS}" (sourceThreadId,agent,lastKnowledgeId,updatedAt) VALUES (?,?,?,?) ON CONFLICT(sourceThreadId,agent) DO UPDATE SET lastKnowledgeId=excluded.lastKnowledgeId,updatedAt=excluded.updatedAt WHERE excluded.lastKnowledgeId >= "${TABLE_KNOWLEDGE_CURSORS}".lastKnowledgeId`,
9043
- args: [
9044
- input.sourceThreadId,
9045
- input.agent,
9046
- input.lastKnowledgeId,
9047
- updatedAt.toISOString()
9048
- ]
9049
- })).rowsAffected === 0) throw new Error("Knowledge curation cursor cannot move backwards");
9050
- return {
9051
- ...input,
9052
- updatedAt
9053
- };
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);
9054
10095
  }
9055
10096
  async listActivity(input) {
9056
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
10097
+ const keys = visibleScopeKeys(canonicalizeKnowledgeScope(input.scope));
9057
10098
  return (await this.#readExecutor.execute({
9058
- sql: `SELECT *,scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_ACTIVITY}" WHERE ${visibleSql}${input.after ? " AND id < ?" : ""} ORDER BY id DESC LIMIT ?`,
10099
+ sql: `SELECT *,scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_ACTIVITY}" WHERE ${visibleSql(keys)}${input.after ? " AND id < ?" : ""} ORDER BY id DESC LIMIT ?`,
9059
10100
  args: [
9060
- key,
9061
- key,
10101
+ ...keys,
9062
10102
  ...input.after ? [input.after] : [],
9063
10103
  input.limit ?? 100
9064
10104
  ]
@@ -9080,9 +10120,9 @@ var KnowledgePG = class extends KnowledgeStorage {
9080
10120
  args.push(input.status);
9081
10121
  }
9082
10122
  if (input.scope) {
9083
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
9084
- clauses.push(visibleSql);
9085
- args.push(key, key);
10123
+ const keys = visibleScopeKeys(input.scope);
10124
+ clauses.push(visibleSql(keys));
10125
+ args.push(...keys);
9086
10126
  }
9087
10127
  args.push(input.limit ?? 100);
9088
10128
  return (await this.#executor.execute({
@@ -9101,9 +10141,9 @@ var KnowledgePG = class extends KnowledgeStorage {
9101
10141
  ];
9102
10142
  const args = [now.toISOString(), stale.toISOString()];
9103
10143
  if (input.scope) {
9104
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
9105
- clauses.push(visibleSql);
9106
- args.push(key, key);
10144
+ const keys = visibleScopeKeys(input.scope);
10145
+ clauses.push(visibleSql(keys));
10146
+ args.push(...keys);
9107
10147
  }
9108
10148
  args.push(input.limit ?? 100);
9109
10149
  const ids = (await tx.execute({
@@ -9170,12 +10210,14 @@ var KnowledgePG = class extends KnowledgeStorage {
9170
10210
  return result.rows[0] ? parseNode(result.rows[0]) : null;
9171
10211
  }
9172
10212
  async #resolveNode(executor, name, scope) {
9173
- for (let length = scope.length; length > 0; length--) {
9174
- const node = await this.#getNodeByName(executor, name, scope.slice(0, length));
9175
- if (node) {
9176
- const terminal = await this.#resolveTerminalNode(executor, node.id);
9177
- if (terminal && isKnowledgeScopeVisible(terminal.scope, scope)) return terminal;
9178
- }
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;
9179
10221
  }
9180
10222
  return null;
9181
10223
  }
@@ -9200,17 +10242,16 @@ var KnowledgePG = class extends KnowledgeStorage {
9200
10242
  const scope = canonicalizeKnowledgeScope(input.scope);
9201
10243
  const node = await this.#resolveTerminalNode(this.#readExecutor, nodeReferenceId(input.node));
9202
10244
  if (!node) return { records: [] };
9203
- const key = knowledgeScopeKey(scope);
10245
+ const keys = visibleScopeKeys(scope);
9204
10246
  const args = [
9205
10247
  node.id,
9206
10248
  ...relationship === "related" ? [node.id] : [],
9207
- key,
9208
- key
10249
+ ...keys
9209
10250
  ];
9210
10251
  if (input.after) args.push(input.after);
9211
10252
  args.push((input.limit ?? 100) + 1);
9212
10253
  const records = (await this.#readExecutor.execute({
9213
- sql: `SELECT DISTINCT f.*,f.scope AS "scopeJson" FROM "${TABLE_KNOWLEDGE_RECORDS}" f${relationship === "about" ? "" : ` LEFT JOIN "${TABLE_KNOWLEDGE_MENTIONS}" m ON m.sourceType='record' AND m.sourceId=f.id`} WHERE ${relationship === "about" ? "f.node=?" : relationship === "mentioning" ? "m.recordId=?" : "(f.node=? OR m.recordId=?)"} AND ${visibleSql.replaceAll("scopeKey", "f.scopeKey")}${input.includeDeleted ? "" : " AND f.deletedAt IS NULL"}${input.after ? " AND f.id < ?" : ""} ORDER BY f.id DESC LIMIT ?`,
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 ?`,
9214
10255
  args
9215
10256
  })).rows.map(parseKnowledge);
9216
10257
  const limit = input.limit ?? 100;
@@ -9219,6 +10260,90 @@ var KnowledgePG = class extends KnowledgeStorage {
9219
10260
  nextCursor: records.length > limit ? records[limit - 1]?.id : void 0
9220
10261
  };
9221
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
+ }
9222
10347
  async #replaceMentions(tx, sourceType, sourceId, text, resolutionScope, defaultScope) {
9223
10348
  await tx.execute({
9224
10349
  sql: `DELETE FROM "${TABLE_KNOWLEDGE_MENTIONS}" WHERE sourceType=? AND sourceId=?`,
@@ -9242,7 +10367,7 @@ var KnowledgePG = class extends KnowledgeStorage {
9242
10367
  updatedAt: now
9243
10368
  };
9244
10369
  await tx.execute({
9245
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,NULL,jsonb(?),?,?,NULL,?,?)`,
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,?,?)`,
9246
10371
  args: [
9247
10372
  node.id,
9248
10373
  "node",
@@ -9256,6 +10381,7 @@ var KnowledgePG = class extends KnowledgeStorage {
9256
10381
  now.toISOString()
9257
10382
  ]
9258
10383
  });
10384
+ await this.#replaceNodeScopes(tx, node.id, defaultScope, now);
9259
10385
  await this.#activity(tx, "node-created", "node", node.id, defaultScope);
9260
10386
  await this.#outbox(tx, "node", node.id, "upsert", 1, defaultScope);
9261
10387
  }
@@ -9272,13 +10398,17 @@ var KnowledgePG = class extends KnowledgeStorage {
9272
10398
  }
9273
10399
  async #activity(executor, action, recordType, recordId, scope, sourceThreadId) {
9274
10400
  const now = /* @__PURE__ */ new Date();
10401
+ const [contextScopeId] = await this.#resolveScopeNodeIds(executor, scope);
9275
10402
  await executor.execute({
9276
- 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(?),?,?,?)`,
9277
10404
  args: [
9278
10405
  createKnowledgeUlid(),
9279
10406
  action,
9280
10407
  recordType,
9281
10408
  recordId,
10409
+ contextScopeId ?? null,
10410
+ recordType,
10411
+ recordId,
9282
10412
  JSON.stringify(scope),
9283
10413
  knowledgeScopeKey(scope),
9284
10414
  sourceThreadId ?? null,
@@ -25038,7 +26168,10 @@ var PostgresStore = class extends MastraCompositeStore {
25038
26168
  workflows: new WorkflowsPG(domainConfig),
25039
26169
  workflowDefinitions: new WorkflowDefinitionsPG(domainConfig),
25040
26170
  memory: new MemoryPG(domainConfig),
25041
- knowledge: new KnowledgePG(domainConfig),
26171
+ knowledge: new KnowledgePG({
26172
+ ...domainConfig,
26173
+ storageIsolationKey: config.storageIsolationKey ?? getPgKnowledgeIsolationKey(config)
26174
+ }),
25042
26175
  notifications: new NotificationsPG(domainConfig),
25043
26176
  observability: new ObservabilityPG(domainConfig),
25044
26177
  agents: new AgentsPG(domainConfig),
@@ -25092,7 +26225,7 @@ var PostgresStore = class extends MastraCompositeStore {
25092
26225
  const pinned = new PinnedClientAdapter(this.#writePool, pinnedClient);
25093
26226
  this.#db.pin(pinned);
25094
26227
  this.#db.setSchemaSnapshot(await loadSchemaSnapshot(pinned, this.schema));
25095
- await super.init();
26228
+ await Promise.all(Object.entries(this.stores).filter(([name, store]) => name !== "knowledge" && store).map(([, store]) => store.init()));
25096
26229
  this.isInitialized = true;
25097
26230
  } catch (error) {
25098
26231
  this.#initPromise = null;