/** * Read-only Mastra `apiRoutes` exposing the factory project's knowledge graph. * * Serves the Knowledge page in factory-ui through bounded scope-tree, * selected-subgraph, node-detail, and activity reads. Every endpoint is a GET; * this module never writes knowledge. * * Scoping is fail-closed: the org and resource rungs are derived server-side * from the authenticated caller and the validated `:id` project. The DEFAULT * view queries `[org:, resource:]` (org + project records). * Thread-scoped records are reachable ONLY via an explicit, server-validated * `threadId` query parameter (the drill-down view), which appends the thread * rung to the query scope. A thread is drillable iff it produced knowledge * visible under the caller's org/project prefix; unknown or cross-org threads * 404 — never a silent fallback to the default view. */ import type { Knowledge } from '@mastra/core/knowledge'; import type { ApiRoute } from '@mastra/core/server'; import type { KnowledgeScope, KnowledgeScopeNodeSummary } from '@mastra/core/storage'; import type { FactoryProjectsStorage } from '../storage/domains/projects/base.js'; import type { RouteDependencies } from './route.js'; import { Route } from './route.js'; export type KnowledgeScopeLevel = 'org' | 'resource' | 'thread'; export interface KnowledgeScopeTreeNode extends KnowledgeScopeNodeSummary { /** Viewer-visible direct members, capped by the route's node window. */ memberCount: number; /** True when more direct members may exist beyond `memberCount`. */ memberCountTruncated: boolean; /** Viewer-visible content nodes directly assigned to this scope. */ contentNodeCount: number; /** Direct child scopes available for lazy expansion. */ childScopeCount: number; } export interface KnowledgeSearchResult { id: string; name: string; kind: string; type: 'scope' | 'node'; rung: KnowledgeScopeLevel | null; /** Present when a thread-scoped content result needs to restore its owning session lens. */ threadId?: string; } export interface KnowledgeSearchPayload { results: KnowledgeSearchResult[]; truncated: boolean; } export interface KnowledgeScopeTreePayload { roots: Array<{ level: KnowledgeScopeLevel; id: string; available: boolean; /** * Set when a reconciled structural scope node has this rung's canonical * address (`org:`, `resource:`, `thread:`) — * the client renders ONE merged entry (structural name, identity marker) * that opens the structural lens instead of duplicating the scope. */ scopeNodeId?: string; /** Structural name of the matched scope node (present iff scopeNodeId). */ name?: string; }>; defaultLevel: 'resource'; /** * Reconciled structural scope tree (isScope nodes with membership edges), * e.g. `mastra → features → memory` and `repo:mastra → issues/prs`. Omitted * only when the storage adapter does not expose structural scope nodes. */ scopeNodes?: KnowledgeScopeTreeNode[]; /** Cursor for the next sibling page (roots when parentId is omitted). */ nextCursor?: string; /** Initial child-page cursors keyed by parent scope id. */ childCursors?: Record; /** True when the org has more scopes than the route reads, so whole scopes are missing from the tree. */ truncated?: boolean; } /** Window caps. Injectable at construction only — never per-request. */ export interface KnowledgeRouteLimits { /** Max nodes in a graph snapshot (newest-first). */ maxNodes: number; /** Max records parsed for edges per snapshot (newest-first). */ maxRecords: number; /** Max fallback `resolveNode` store lookups per request (deduped per unique name+scope). */ maxFallbackLookups: number; /** Max `listScopeNodes` pages (of up to 1,000 scopes) read for one org; past it responses report `truncated`. */ maxOrgScopePages: number; } export interface KnowledgeRoutesDeps extends RouteDependencies { /** Factory projects domain — validates the `:id` project belongs to the caller's org. */ projects: FactoryProjectsStorage; /** Lazy handle to the host-registered Knowledge runtime; endpoints 503 when absent. */ knowledge: (key: string) => Promise; /** Host-selected Knowledge key. Requests cannot override it; endpoints 503 when it does not resolve. */ defaultKnowledgeKey?: string; limits?: Partial; /** * Current title of a thread, read when a session scope is listed so its label follows renames. * Sessions fall back to `session ` when absent or untitled. */ threadTitle?: (threadId: string) => Promise; } /** A graph node. `recordCount` is window-derived (records inside the snapshot window only). */ export interface KnowledgeGraphNode { id: string; name: string; kind: string; description?: string; /** Null for structural scope-node members (global, not identity-scoped). */ scope: KnowledgeScope | null; /** Deepest rung of the record's scope: org | resource | thread; null for scope nodes. */ rung: 'org' | 'resource' | 'thread' | null; /** True for structural scope nodes (member-lens drill targets); absent for content nodes. */ isScope?: boolean; /** * True when a non-deleted pinned record's wikilinks reference ONLY this * node (A9: multi-target pins mark their edges instead — the pin is * about the relationship; a single-target pin has no edge to carry it). */ pinned: boolean; /** Records owned by this node INSIDE the snapshot window (not a total). */ recordCount: number; /** Viewer-visible direct-member counts, present only for structural scope nodes. */ memberCount?: number; memberCountTruncated?: boolean; contentNodeCount?: number; childScopeCount?: number; /** Omitted for a structural lens root when the adapter cannot read it back as a node. */ createdAt?: string; updatedAt?: string; } export interface KnowledgeGraphEdge { id: string; /** The owning node of the record (its `node`); the scope node for containment edges. */ source: string; /** The wikilink-resolved node; the contained member for containment edges. */ target: string; /** * 'wikilink': derived from a record's wikilinks (the record's `node` is the * edge SOURCE, so the plan's "parent link" collapses into the wikilink edge). * 'contains': structural lens only — the selected scope node contains the * target member, so the clicked scope is visible as its own graph root. */ type: 'wikilink' | 'contains'; /** The record whose text produced the edge; absent on containment edges. */ recordId?: string; /** * True when the edge is derived from a PINNED record linking two nodes — * the pin marks the relationship, so the accent lives on the edge (A9). */ pinned?: boolean; } /** * A knowledge record as a first-class graph element (A11): every record in the window, * with the in-window nodes it touches. The client renders by arity — * 1 node: a small dot linked to it; 2: the connecting line; 3+: a midpoint * junction splitting to each node. Pin records have their hidden reserved * owner omitted, so their arity comes purely from wikilink targets. */ export interface KnowledgeGraphRecord { /** The record id. */ id: string; /** Owner node first (omitted for pins), then resolved wikilink targets. */ nodeIds: string[]; pinned: boolean; /** Record text, truncated for hover cards. */ text: string; } export interface KnowledgeGraphPayload { view: 'project' | 'thread'; threadId?: string; nodes: KnowledgeGraphNode[]; edges: KnowledgeGraphEdge[]; records: KnowledgeGraphRecord[]; /** True when the node or record window cap was hit (newest-first window). */ truncated: boolean; /** Authorized wikilink targets outside the node window, including enough scope context for a deep-link jump. */ outOfWindow: Array<{ id: string; name: string; scope: KnowledgeScope; rung: 'org' | 'resource' | 'thread'; }>; /** Unique unknown names skipped once the fallback-lookup cap was hit. */ unresolvedCapped: { count: number; names: string[]; }; /** Pin counts per rung of the active view (thread is null in the default view). */ pinCensus: { resource: number; thread: number | null; }; /** Change hint: newest knowledge activity id (per-process monotonic — hint only). */ version: string | null; } export interface KnowledgeNodeRecordPayload { id: string; node: string; /** 'owned' when the node is the record's parent, 'mentions' when it only wikilinks it. */ relation: 'owned' | 'mentions'; text: string; scope: KnowledgeScope; rung: 'org' | 'resource' | 'thread'; sourceThreadId: string; capturedAt: string; when?: string; pinned: boolean; metadata?: Record; } export interface KnowledgeActivityPayload { events: Array<{ id: string; action: string; recordType: string; recordId?: string; scope: KnowledgeScope; node: { id: string; name: string; rung: 'org' | 'resource' | 'thread'; threadId?: string; }; createdAt: string; }>; nextCursor?: string; /** The scope has more members than the activity filter reads; events for the rest are omitted. */ truncated?: true; } export interface KnowledgeNodePayload { node: { id: string; name: string; kind: string; content: string; description?: string; scope: KnowledgeScope; rung: 'org' | 'resource' | 'thread'; createdAt: string; updatedAt: string; }; records: KnowledgeNodeRecordPayload[]; } export declare class KnowledgeRoutes extends Route { #private; constructor(deps: KnowledgeRoutesDeps); routes(): ApiRoute[]; } //# sourceMappingURL=knowledge.d.ts.map