fix(cli): resolve managed-embeddings daemon URL at project boundary (#184)

A clean `ktx setup` was failing verification because the managed
local-embeddings daemon URL was passed library-side through
`process.env[KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL]`, and the setup
flow never wrote that variable. With no resolved URL the embedding
provider was null, the deep scan emitted
`scan_enrichment_backend_not_configured`, descriptions + embeddings
stayed `skipped`, and the agent-readiness check exited 1.

Replace the env-var indirection with CLI-side substitution at the
project-load boundary. New `loadKtxCliProject` wraps `loadKtxProject`,
ensures the managed daemon when `managed:local-embeddings` is present in
`config.ingest.embeddings` or `config.scan.enrichment.embeddings`, and
substitutes the resolved baseUrl into the in-memory config. Runtime
entry points (scan, ingest, public-ingest, admin-reindex) use the new
loader; setup-time persistence paths keep raw `loadKtxProject` so the
on-disk `ktx.yaml` keeps the portable sentinel.

Cleanup follows from the new design: drop
`MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV`, remove the env-var lookup
branch in `resolveSentenceTransformersBaseUrl`, drop the `env` field
from `ManagedLocalEmbeddingsDaemon`, and collapse the manual
daemon-ensure dance in `admin-reindex.ts`.
This commit is contained in:
Andrey Avtomonov 2026-05-20 14:43:02 +02:00 committed by GitHub
parent ad9c9eda0d
commit c24e07a115
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 318 additions and 95 deletions

View file

@ -18,7 +18,8 @@ import {
sanitizeMemoryFlowError,
} from '@ktx/context/ingest';
import type { KtxSqlQueryExecutorPort } from '@ktx/context/connections';
import { loadKtxProject, type KtxLocalProject } from '@ktx/context/project';
import { type KtxLocalProject } from '@ktx/context/project';
import { loadKtxCliProject } from './cli-project.js';
import { createKtxCliIngestQueryExecutor } from './ingest-query-executor.js';
import { readIngestReportSnapshotFile } from './ingest-report-file.js';
import { createCliOperationalLogger } from './io/logger.js';
@ -529,7 +530,7 @@ function assertReportMatchesReplayId(report: IngestReportSnapshot, requestedId:
}
async function readStoredIngestReport(
project: Awaited<ReturnType<typeof loadKtxProject>>,
project: KtxLocalProject,
runId: string | undefined,
): Promise<IngestReportSnapshot | null> {
return runId ? await getLocalIngestStatus(project, runId) : await getLatestLocalIngestStatus(project);
@ -681,7 +682,14 @@ export async function runKtxIngest(
deps: KtxIngestDeps = {},
): Promise<number> {
try {
const project = await loadKtxProject({ projectDir: args.projectDir });
const cliVersion = args.command === 'run' ? args.cliVersion : undefined;
const runtimeInstallPolicy = args.command === 'run' ? args.runtimeInstallPolicy : undefined;
const project = await loadKtxCliProject({
projectDir: args.projectDir,
cliVersion: cliVersion ?? '0.0.0-private',
installPolicy: runtimeInstallPolicy ?? 'never',
io,
});
const env = deps.env ?? process.env;
if (args.command === 'run') {
const ingestProject =