// The enablement flow: one idempotent procedure pair an organization administrator runs, with the // platform's checks in front of any configuration change and every denial a typed reason code the // UI maps to an upgrade or policy state. // // enableDocumentAi: already enabled -> answers the current configuration, writes nothing. // Otherwise: eligibility (plan capability + rollout flags) -> quota -> the vector backing // (an operator-configured endpoint, validated before it is trusted, or a managed link through // the providers API) -> one transaction persists the configuration. A failure at any step // writes nothing and is retirable: the same call again resumes from the first failing check. // // disableDocumentAi: preserves everything — collections, documents, vectors and the recorded // configuration stay; only `enabled` flips, so re-enabling resumes without duplication. import { AppError } from "@conusai-kit/core" import { config, type EnablementReason } from "../contract/index.js" import type { DocumentAiPlatform, KnowledgeOrgScope } from "./ports.js" /** A typed enablement denial: the problem's code is the stable reason the UI maps to a state. */ export function enablementDenied(reason: EnablementReason, detail?: string): AppError { const status = reason === "quota_exhausted" ? 429 : 403 const title = reason === "plan_required" ? "The organization's plan does not include document AI" : reason === "feature_disabled" ? "Document AI is disabled by policy" : "The indexing quota is exhausted" return new AppError({ type: `/problems/${reason}`, title, status, code: reason, ...(detail === undefined ? {} : { detail }), }) } export interface SettingsView { enabled: boolean backend: "vectors" | "platform" | null source: "operator" | "managed" | null url: string | null } async function currentConfig(deps: { data: import("@conusai-kit/data").DataSource }, scope: KnowledgeOrgScope) { const row = (await deps.data.forScope(scope).of(config).list({ limit: 1 })).items[0] return row ?? null } export async function getSettings( deps: { data: import("@conusai-kit/data").DataSource }, scope: KnowledgeOrgScope, ): Promise { const row = await currentConfig(deps, scope) return { enabled: row?.enabled ?? false, backend: row?.backend ?? null, source: row?.source ?? null, url: row?.url ?? null, } } export interface SettingsDeps { data: import("@conusai-kit/data").DataSource platform: DocumentAiPlatform now?: () => number } export async function enableDocumentAi(deps: SettingsDeps, scope: KnowledgeOrgScope): Promise { const existing = await currentConfig(deps, scope) if (existing?.enabled) { return { enabled: true, backend: existing.backend, source: existing.source, url: existing.url } } const eligibility = await deps.platform.checkEligibility(scope) if (!eligibility.ok) throw enablementDenied(eligibility.reason, eligibility.detail) const remaining = await deps.platform.indexingQuota(scope) if (remaining <= 0) throw enablementDenied("quota_exhausted", "the indexing volume for today is spent") // The vector backing: an operator-configured endpoint+key when the deployment carries one // (validated before it is trusted), otherwise a managed link through the providers API. const operator = deps.platform.operatorQdrant() let source: "operator" | "managed" let serviceId: string | null let url: string | null if (operator !== null) { const probe = await deps.platform.probeQdrant(operator.url, operator.apiKey) if (!probe.ok) { throw new AppError({ type: "/problems/validation_failed", title: "The configured Qdrant endpoint did not answer", status: 422, code: "validation_failed", detail: probe.detail, }) } source = "operator" serviceId = null url = operator.url } else { const linked = await deps.platform.linkManagedQdrant(scope) source = "managed" serviceId = linked.serviceId url = linked.url } await deps.data.unitOfWork.run(scope, async (tx) => { const row = (await tx.of(config).list({ limit: 1 })).items[0] const patch = { enabled: true, backend: "vectors" as const, source, serviceId, url, updatedBy: scope.actor.userId, configuredAt: new Date((deps.now ?? Date.now)()).toISOString(), } if (row) await tx.of(config).update(row.id, patch, row.version) else await tx.of(config).insert(patch) }) return { enabled: true, backend: "vectors", source, url } } export async function disableDocumentAi(deps: SettingsDeps, scope: KnowledgeOrgScope): Promise<{ enabled: false }> { await deps.data.unitOfWork.run(scope, async (tx) => { const row = (await tx.of(config).list({ limit: 1 })).items[0] if (!row?.enabled) return // Everything else is preserved on purpose: backend, source, the service id, the endpoint, and // every collection, document and vector. Disabling fences new work; it deletes nothing. await tx.of(config).update( row.id, { enabled: false, updatedBy: scope.actor.userId, configuredAt: new Date((deps.now ?? Date.now)()).toISOString(), }, row.version, ) }) return { enabled: false } }