// The procedure contracts of the knowledge module: schemas and names, no handler and no port. // // The module is a retrieval layer over the app's documents: a collection of text files is indexed // (in the worker: chunk, embed through the AI gateway, upsert into the vector store), searched // with cited snippets, and managed through an explicit, idempotent enablement flow. The enablement // procedure is named enableDocumentAi per the Document AI enablement contract; its permission is the module's // manage permission, so the admin gate is one line in the manifest. import { procedure } from "@conusai-kit/core" import { z } from "zod" import { COLLECTION_CREATED, DOCUMENT_AI_DISABLED, DOCUMENT_AI_ENABLED } from "./constants.js" import type { DocumentStatus, ServiceState } from "./statuses.js" const inProject = { org: z.string(), workspace: z.string(), project: z.string() } const inOrg = { org: z.string() } export const collectionName = z .string() .min(1) .max(100) .regex(/^[a-z][a-z0-9-]*$/, "lower-case letters, digits and dashes") export const collectionViewSchema = z.object({ name: collectionName, documentCount: z.number().int(), indexedCount: z.number().int(), createdBy: z.string(), }) export type CollectionView = z.output export const documentViewSchema = z.object({ documentId: z.string(), collectionName: collectionName, fileId: z.string(), filename: z.string(), status: z.enum(["queued", "indexing", "indexed", "failed", "cancelled"]), error: z.string().nullable(), indexedAt: z.string().nullable(), requestedBy: z.string(), }) export type DocumentView = z.output export const searchResultSchema = z.object({ fileId: z.string(), filename: z.string(), collectionName: collectionName, snippet: z.string(), /** 0..1; higher is closer. */ score: z.number(), /** 1-based chunk position in the document: where a citation points. */ chunk: z.number().int().min(1), }) export type SearchResult = z.output export const collectionList = procedure("knowledge.collection.list") .kind("read") .scope("project") .permission("knowledge.knowledge.search") .input(z.object({ ...inProject })) .output(z.object({ collections: z.array(collectionViewSchema) })) .contract() export const collectionCreate = procedure("knowledge.collection.create") .kind("write") .scope("project") .permission("knowledge.collection.manage") .input(z.object({ ...inProject, name: collectionName })) .output(collectionViewSchema) .audit(COLLECTION_CREATED) .contract() export const documentIndex = procedure("knowledge.document.index") .kind("write") .scope("project") .permission("knowledge.knowledge.manage") .input(z.object({ ...inProject, collectionName: collectionName, fileId: z.string() })) .output(documentViewSchema) .contract() export const documentList = procedure("knowledge.document.list") .kind("read") .scope("project") .permission("knowledge.knowledge.search") .input(z.object({ ...inProject, collectionName: collectionName })) .output(z.object({ documents: z.array(documentViewSchema) })) .contract() export const documentRemove = procedure("knowledge.document.remove") .kind("write") .scope("project") .permission("knowledge.knowledge.manage") .input(z.object({ ...inProject, collectionName: collectionName, fileId: z.string() })) .output(documentViewSchema) .contract() /** * The retrieval call. It embeds the query through the AI gateway before it searches, so it is an * effect: a read procedure cannot leave the process. Exposed as an MCP tool on purpose (`.tool()`). */ export const knowledgeSearch = procedure("knowledge.search") .kind("effect") .scope("project") .permission("knowledge.knowledge.search") .tool() .input( z.object({ ...inProject, collectionName: collectionName, query: z.string().min(1).max(500), limit: z.number().int().min(1).max(25).optional(), }), ) .output(z.object({ results: z.array(searchResultSchema) })) .contract() export const knowledgeStatus = procedure("knowledge.status") .kind("read") .scope("project") .permission("knowledge.knowledge.search") .input(z.object({ ...inProject })) .output( z.object({ /** The module flag's answer for this project. */ enabled: z.boolean(), /** The service readiness, kept separate from the indexing indicator below. */ service: z.object({ state: z.enum(["registered", "indexing", "ready", "degraded", "disabled", "unavailable"]), reasons: z.array(z.string()), }), documents: z.object({ queued: z.number().int(), indexing: z.number().int(), indexed: z.number().int(), failed: z.number().int(), cancelled: z.number().int(), }), }), ) .contract() export const settingsGet = procedure("knowledge.settings.get") .kind("read") .scope("org") .permission("knowledge.settings.manage") .input(z.object({ ...inOrg })) .output( z.object({ enabled: z.boolean(), backend: z.enum(["vectors", "platform"]).nullable(), source: z.enum(["operator", "managed"]).nullable(), url: z.string().nullable(), }), ) .contract() export const settingsEnableDocumentAi = procedure("knowledge.settings.enableDocumentAi") .kind("write") .scope("org") .permission("knowledge.settings.manage") .input(z.object({ ...inOrg })) .output(z.object({ enabled: z.boolean(), backend: z.enum(["vectors", "platform"]) })) .audit(DOCUMENT_AI_ENABLED) .contract() export const settingsDisableDocumentAi = procedure("knowledge.settings.disableDocumentAi") .kind("write") .scope("org") .permission("knowledge.settings.manage") .input(z.object({ ...inOrg })) .output(z.object({ enabled: z.boolean() })) .audit(DOCUMENT_AI_DISABLED) .contract() export const KNOWLEDGE_PROCEDURES = [ collectionList, collectionCreate, documentIndex, documentList, documentRemove, knowledgeSearch, knowledgeStatus, settingsGet, settingsEnableDocumentAi, settingsDisableDocumentAi, ] export type { DocumentStatus, ServiceState }