@semiont/core 0.5.23 → 0.5.25

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.
@@ -0,0 +1,293 @@
1
+ import { parse } from 'smol-toml';
2
+
3
+ // src/config/toml-loader.ts
4
+ function deepMerge(base, override) {
5
+ const result = { ...base };
6
+ for (const key of Object.keys(override)) {
7
+ const b = base[key];
8
+ const o = override[key];
9
+ if (o !== void 0 && o !== null && typeof o === "object" && !Array.isArray(o) && b !== void 0 && b !== null && typeof b === "object" && !Array.isArray(b)) {
10
+ result[key] = deepMerge(b, o);
11
+ } else if (o !== void 0) {
12
+ result[key] = o;
13
+ }
14
+ }
15
+ return result;
16
+ }
17
+ function resolveEnvVars(obj, env) {
18
+ if (typeof obj === "string") {
19
+ return obj.replace(/\$\{([^}]+)\}/g, (match, expr) => {
20
+ const sepIdx = expr.indexOf(":-");
21
+ const varName = sepIdx >= 0 ? expr.slice(0, sepIdx) : expr;
22
+ const defaultValue = sepIdx >= 0 ? expr.slice(sepIdx + 2) : void 0;
23
+ const value = env[varName];
24
+ if (value !== void 0) return value;
25
+ if (defaultValue !== void 0) return defaultValue;
26
+ throw new Error(`Environment variable ${varName} is not set (referenced in config as ${match})`);
27
+ });
28
+ }
29
+ if (Array.isArray(obj)) {
30
+ return obj.map((item) => resolveEnvVars(item, env));
31
+ }
32
+ if (obj !== null && typeof obj === "object") {
33
+ const resolved = {};
34
+ for (const key in obj) {
35
+ resolved[key] = resolveEnvVars(obj[key], env);
36
+ }
37
+ return resolved;
38
+ }
39
+ return obj;
40
+ }
41
+ function requirePlatform(value, serviceName) {
42
+ if (!value) {
43
+ throw new Error(`platform is required for service '${serviceName}' \u2014 add 'platform = "posix"|"container"|"external"' to its config section`);
44
+ }
45
+ return value;
46
+ }
47
+ function loadTomlConfig(projectRoot, environment, globalConfigPath, reader, env) {
48
+ const projectConfigContent = projectRoot ? reader.readIfExists(`${projectRoot}/.semiont/config`) : null;
49
+ const projectConfig = projectConfigContent ? parse(projectConfigContent) : void 0;
50
+ const projectName = projectConfig?.project?.name ?? "semiont-project";
51
+ const projectVersion = projectConfig?.project?.version;
52
+ const projectSite = projectConfig?.site;
53
+ const globalContent = reader.readIfExists(globalConfigPath);
54
+ const raw = globalContent ? parse(globalContent) : {};
55
+ const resolvedEnvironment = environment ?? raw.defaults?.environment;
56
+ if (!resolvedEnvironment) {
57
+ throw new Error(
58
+ "No environment selected: pass one explicitly, or declare `[defaults] environment` in ~/.semiontconfig."
59
+ );
60
+ }
61
+ const projectHasSection = projectConfig?.environments != null && resolvedEnvironment in projectConfig.environments;
62
+ const globalHasSection = raw.environments != null && resolvedEnvironment in raw.environments;
63
+ if (!projectHasSection && !globalHasSection) {
64
+ throw new Error(
65
+ `Environment "${resolvedEnvironment}" is selected but no [environments.${resolvedEnvironment}] section exists in the project (.semiont/config) or global (~/.semiontconfig) config. Declare the section, or select an environment that exists.`
66
+ );
67
+ }
68
+ const projectEnvSection = projectConfig?.environments?.[resolvedEnvironment] ?? {};
69
+ const userEnvSection = raw.environments?.[resolvedEnvironment] ?? {};
70
+ const envSection = deepMerge(
71
+ projectEnvSection,
72
+ userEnvSection
73
+ );
74
+ const resolved = resolveEnvVars(envSection, env);
75
+ const flatInference = resolved.inference;
76
+ const makeMeaningSection = resolved["make-meaning"];
77
+ const workersSection = resolved.workers ?? {};
78
+ const actorsSection = resolved.actors ?? {};
79
+ const defaultWorkerInference = workersSection["default"]?.inference;
80
+ const defaultMakeMeaningInference = makeMeaningSection?.default?.inference;
81
+ function mergeWithFlatInference(specific) {
82
+ if (!flatInference) return specific;
83
+ const providerDefaults = {};
84
+ if (specific.type === "anthropic") {
85
+ const a = flatInference.anthropic;
86
+ if (a) {
87
+ providerDefaults.apiKey = a.apiKey;
88
+ providerDefaults.endpoint = a.endpoint;
89
+ } else {
90
+ if (!flatInference.type) {
91
+ throw new Error(
92
+ `[environments.${resolvedEnvironment}.inference] is missing 'type'. Add type = "anthropic" or use [inference.anthropic] sub-section.`
93
+ );
94
+ }
95
+ providerDefaults.apiKey = flatInference.apiKey;
96
+ providerDefaults.endpoint = flatInference.endpoint;
97
+ }
98
+ } else if (specific.type === "ollama") {
99
+ const o = flatInference.ollama;
100
+ if (o) {
101
+ providerDefaults.baseURL = o.baseURL;
102
+ } else {
103
+ if (!flatInference.type) {
104
+ throw new Error(
105
+ `[environments.${resolvedEnvironment}.inference] is missing 'type'. Add type = "ollama" or use [inference.ollama] sub-section.`
106
+ );
107
+ }
108
+ providerDefaults.baseURL = flatInference.baseURL;
109
+ }
110
+ }
111
+ return {
112
+ maxTokens: flatInference.maxTokens,
113
+ ...providerDefaults,
114
+ ...specific
115
+ };
116
+ }
117
+ function resolveActorInference(fromMakeMeaning, fromActors) {
118
+ const base = fromMakeMeaning ?? fromActors ?? defaultMakeMeaningInference;
119
+ if (!base) return void 0;
120
+ return mergeWithFlatInference(base);
121
+ }
122
+ const actors = {};
123
+ const gathererInference = resolveActorInference(
124
+ makeMeaningSection?.actors?.gatherer?.inference,
125
+ actorsSection["gatherer"]?.inference
126
+ );
127
+ if (gathererInference) actors.gatherer = gathererInference;
128
+ const matcherInference = resolveActorInference(
129
+ makeMeaningSection?.actors?.matcher?.inference,
130
+ actorsSection["matcher"]?.inference
131
+ );
132
+ if (matcherInference) actors.matcher = matcherInference;
133
+ const workers = {};
134
+ const workerTypes = ["reference-annotation", "highlight-annotation", "assessment-annotation", "comment-annotation", "tag-annotation", "generation"];
135
+ if (defaultWorkerInference) {
136
+ workers.default = mergeWithFlatInference(defaultWorkerInference);
137
+ }
138
+ for (const wt of workerTypes) {
139
+ const specific = workersSection[wt]?.inference;
140
+ if (specific) {
141
+ workers[wt] = mergeWithFlatInference(specific);
142
+ }
143
+ }
144
+ const backend = resolved.backend;
145
+ const site = resolved.site ?? projectSite;
146
+ const inferenceSection = resolved.inference;
147
+ let inferenceProviders;
148
+ if (inferenceSection) {
149
+ inferenceProviders = {};
150
+ if (inferenceSection.anthropic) {
151
+ const a = inferenceSection.anthropic;
152
+ inferenceProviders.anthropic = {
153
+ platform: requirePlatform(a.platform, "inference.anthropic"),
154
+ endpoint: a.endpoint ?? "https://api.anthropic.com",
155
+ apiKey: a.apiKey ?? ""
156
+ };
157
+ } else if (inferenceSection.type === "anthropic") {
158
+ inferenceProviders.anthropic = {
159
+ platform: requirePlatform(inferenceSection.platform, "inference"),
160
+ endpoint: inferenceSection.endpoint ?? "https://api.anthropic.com",
161
+ apiKey: inferenceSection.apiKey ?? ""
162
+ };
163
+ }
164
+ if (inferenceSection.ollama) {
165
+ const o = inferenceSection.ollama;
166
+ inferenceProviders.ollama = {
167
+ platform: { type: requirePlatform(o.platform, "inference.ollama") },
168
+ baseURL: o.baseURL,
169
+ port: o.baseURL ? void 0 : o.port ?? 11434
170
+ };
171
+ } else if (inferenceSection.type === "ollama") {
172
+ inferenceProviders.ollama = {
173
+ platform: { type: requirePlatform(inferenceSection.platform, "inference") },
174
+ baseURL: inferenceSection.baseURL,
175
+ port: inferenceSection.baseURL ? void 0 : 11434
176
+ };
177
+ }
178
+ }
179
+ const topLevelWorkers = {};
180
+ for (const [name, w] of Object.entries(workersSection)) {
181
+ if (w.inference) {
182
+ topLevelWorkers[name] = { inference: { type: w.inference.type, model: w.inference.model } };
183
+ }
184
+ }
185
+ const topLevelActors = {};
186
+ for (const [name, a] of Object.entries(actorsSection)) {
187
+ if (a.inference) {
188
+ topLevelActors[name] = { inference: { type: a.inference.type, model: a.inference.model } };
189
+ }
190
+ }
191
+ if (makeMeaningSection?.actors?.gatherer?.inference) {
192
+ topLevelActors["gatherer"] = { inference: { type: makeMeaningSection.actors.gatherer.inference.type, model: makeMeaningSection.actors.gatherer.inference.model } };
193
+ }
194
+ if (makeMeaningSection?.actors?.matcher?.inference) {
195
+ topLevelActors["matcher"] = { inference: { type: makeMeaningSection.actors.matcher.inference.type, model: makeMeaningSection.actors.matcher.inference.model } };
196
+ }
197
+ const frontend = resolved.frontend;
198
+ const services = {};
199
+ if (backend) {
200
+ services.backend = {
201
+ platform: { type: requirePlatform(backend.platform, "backend") },
202
+ port: backend.port ?? 4e3,
203
+ publicURL: backend.publicURL ?? `http://localhost:${backend.port ?? 4e3}`
204
+ };
205
+ }
206
+ if (frontend) {
207
+ services.frontend = {
208
+ platform: { type: requirePlatform(frontend.platform, "frontend") },
209
+ port: frontend.port ?? 3e3,
210
+ siteName: site?.siteName ?? "Semiont",
211
+ publicURL: frontend.publicURL
212
+ };
213
+ }
214
+ if (resolved.graph) {
215
+ services.graph = {
216
+ ...resolved.graph,
217
+ platform: { type: requirePlatform(resolved.graph.platform, "graph") },
218
+ type: resolved.graph.type ?? "neo4j"
219
+ };
220
+ } else if (makeMeaningSection?.graph) {
221
+ services.graph = makeMeaningSection.graph;
222
+ }
223
+ if (resolved.database) {
224
+ services.database = {
225
+ platform: { type: requirePlatform(resolved.database.platform, "database") },
226
+ type: "postgres",
227
+ image: resolved.database.image,
228
+ host: resolved.database.host ?? "localhost",
229
+ port: resolved.database.port ?? 5432,
230
+ name: resolved.database.name,
231
+ user: resolved.database.user,
232
+ password: resolved.database.password
233
+ };
234
+ }
235
+ if (resolved.vectors) {
236
+ services.vectors = {
237
+ platform: { type: "external" },
238
+ type: resolved.vectors.type ?? "qdrant",
239
+ host: resolved.vectors.host,
240
+ port: resolved.vectors.port ?? 6333
241
+ };
242
+ }
243
+ const embeddingSource = resolved.embedding ?? resolved.vectors?.embedding;
244
+ if (embeddingSource) {
245
+ services.embedding = {
246
+ platform: { type: "external" },
247
+ type: embeddingSource.type,
248
+ model: embeddingSource.model,
249
+ apiKey: embeddingSource.apiKey,
250
+ baseURL: embeddingSource.baseURL,
251
+ endpoint: embeddingSource.endpoint,
252
+ chunking: resolved.embedding?.chunking ?? resolved.vectors?.chunking ? {
253
+ chunkSize: (resolved.embedding?.chunking ?? resolved.vectors?.chunking)?.chunkSize ?? 512,
254
+ overlap: (resolved.embedding?.chunking ?? resolved.vectors?.chunking)?.overlap ?? 64
255
+ } : void 0
256
+ };
257
+ }
258
+ const config = {
259
+ services,
260
+ ...inferenceProviders ? { inference: inferenceProviders } : {},
261
+ ...Object.keys(topLevelWorkers).length > 0 ? { workers: topLevelWorkers } : {},
262
+ ...Object.keys(topLevelActors).length > 0 ? { actors: topLevelActors } : {},
263
+ site: site ? {
264
+ domain: site.domain ?? "localhost",
265
+ siteName: site.siteName,
266
+ adminEmail: site.adminEmail,
267
+ oauthAllowedDomains: site.oauthAllowedDomains
268
+ } : void 0,
269
+ logLevel: resolved.logLevel,
270
+ _metadata: {
271
+ environment: resolvedEnvironment,
272
+ projectRoot,
273
+ projectName,
274
+ projectVersion,
275
+ ...Object.keys(actors).length > 0 ? { actors } : {},
276
+ ...Object.keys(workers).length > 0 ? { workers } : {},
277
+ // Always set — the loader is the ONE home of this default (D5).
278
+ // Consuming code (make-meaning's gather path) receives a required
279
+ // value and defaults nothing.
280
+ gather: { settleTimeoutMs: makeMeaningSection?.gather?.settleTimeoutMs ?? 15e3 }
281
+ }
282
+ };
283
+ return config;
284
+ }
285
+ function createTomlConfigLoader(reader, globalConfigPath, env) {
286
+ return (projectRoot, environment) => {
287
+ return loadTomlConfig(projectRoot, environment, globalConfigPath, reader, env);
288
+ };
289
+ }
290
+
291
+ export { createTomlConfigLoader, loadTomlConfig };
292
+ //# sourceMappingURL=chunk-XQTWEBJ5.js.map
293
+ //# sourceMappingURL=chunk-XQTWEBJ5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/toml-loader.ts"],"names":["parseToml"],"mappings":"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","file":"chunk-XQTWEBJ5.js","sourcesContent":["/**\n * TOML Config Loader\n *\n * Reads ~/.semiontconfig (TOML) and .semiont/config (TOML) and produces\n * an EnvironmentConfig for the requested environment.\n *\n * File format: see TOML-XDG-CONFIG.md\n *\n * Loading sequence:\n * 1. Read .semiont/config → projectName, site, environments.<env>.* (project base)\n * 2. Read ~/.semiontconfig → defaults, environments.<env>.* (user overrides)\n * 3. Deep-merge: project base ← user overrides (user wins on conflicts)\n * Any environment name is valid (local, staging, production, custom, ...)\n * 4. Resolve ${VAR} references from process.env\n * 5. Apply inheritance: workers.<name> → workers.default → error\n * 6. Map to EnvironmentConfig shape\n */\n\nimport { parse as parseToml } from 'smol-toml';\nimport type { EnvironmentConfig, OllamaProviderConfig, AnthropicProviderConfig } from './config.types';\nimport type { PlatformType } from './config.types';\n\n/**\n * Deep merge two plain objects. Arrays and primitives in `override` replace those in `base`.\n * Nested objects are merged recursively. `override` takes precedence on conflicts.\n */\nfunction deepMerge<T extends Record<string, unknown>>(base: T, override: Partial<T>): T {\n const result = { ...base } as Record<string, unknown>;\n for (const key of Object.keys(override)) {\n const b = base[key];\n const o = override[key];\n if (o !== undefined && o !== null && typeof o === 'object' && !Array.isArray(o) &&\n b !== undefined && b !== null && typeof b === 'object' && !Array.isArray(b)) {\n result[key] = deepMerge(b as Record<string, unknown>, o as Record<string, unknown>);\n } else if (o !== undefined) {\n result[key] = o;\n }\n }\n return result as T;\n}\n\nfunction resolveEnvVars(obj: unknown, env: Record<string, string | undefined>): unknown {\n if (typeof obj === 'string') {\n return obj.replace(/\\$\\{([^}]+)\\}/g, (match, expr: string) => {\n const sepIdx = expr.indexOf(':-');\n const varName = sepIdx >= 0 ? expr.slice(0, sepIdx) : expr;\n const defaultValue = sepIdx >= 0 ? expr.slice(sepIdx + 2) : undefined;\n const value = env[varName];\n if (value !== undefined) return value;\n if (defaultValue !== undefined) return defaultValue;\n throw new Error(`Environment variable ${varName} is not set (referenced in config as ${match})`);\n });\n }\n if (Array.isArray(obj)) {\n return obj.map(item => resolveEnvVars(item, env));\n }\n if (obj !== null && typeof obj === 'object') {\n const resolved: Record<string, unknown> = {};\n for (const key in obj as Record<string, unknown>) {\n resolved[key] = resolveEnvVars((obj as Record<string, unknown>)[key], env);\n }\n return resolved;\n }\n return obj;\n}\n\n// ── Inference config types (mirrored from packages/make-meaning/src/config.ts) ─\n// Kept here to avoid a circular dependency: core cannot import make-meaning.\n\nexport interface InferenceConfig {\n type: 'anthropic' | 'ollama';\n model: string;\n maxTokens?: number;\n apiKey?: string;\n endpoint?: string;\n baseURL?: string;\n}\n\nexport interface ActorInferenceConfig {\n gatherer?: InferenceConfig;\n matcher?: InferenceConfig;\n}\n\nexport interface WorkerInferenceConfig {\n default?: InferenceConfig;\n 'reference-annotation'?: InferenceConfig;\n 'highlight-annotation'?: InferenceConfig;\n 'assessment-annotation'?: InferenceConfig;\n 'comment-annotation'?: InferenceConfig;\n 'tag-annotation'?: InferenceConfig;\n 'generation'?: InferenceConfig;\n}\n\n// ── Types for ~/.semiontconfig ────────────────────────────────────────────────\n\ninterface SemiontConfigFile {\n user?: {\n name?: string;\n email?: string;\n };\n defaults?: {\n environment?: string;\n platform?: string;\n };\n environments?: Record<string, EnvironmentSection>;\n}\n\ninterface GraphSection {\n platform?: string;\n type?: string;\n name?: string;\n uri?: string;\n username?: string;\n password?: string;\n database?: string;\n [key: string]: unknown;\n}\n\ninterface InferenceFlatSection {\n // Flat (single-provider) format: type = \"anthropic\"|\"ollama\" at this level\n type?: 'anthropic' | 'ollama';\n platform?: string;\n model?: string;\n maxTokens?: number;\n apiKey?: string;\n endpoint?: string;\n baseURL?: string;\n // Keyed (multi-provider) format: [inference.anthropic] / [inference.ollama]\n anthropic?: { platform?: string; apiKey?: string; endpoint?: string };\n ollama?: { platform?: string; baseURL?: string; port?: number };\n}\n\ninterface EnvironmentSection {\n backend?: {\n platform?: string;\n port?: number;\n publicURL?: string;\n frontendURL?: string;\n };\n frontend?: {\n platform?: string;\n port?: number;\n publicURL?: string;\n };\n site?: {\n domain?: string;\n siteName?: string;\n adminEmail?: string;\n oauthAllowedDomains?: string[];\n enableLocalAuth?: boolean;\n };\n database?: {\n platform?: string;\n image?: string;\n host?: string;\n port?: number;\n name?: string;\n user?: string;\n password?: string;\n };\n graph?: GraphSection;\n vectors?: {\n type?: 'qdrant' | 'memory';\n host?: string;\n port?: number;\n // Legacy: embedding nested under vectors (migrated to top-level)\n embedding?: {\n type?: 'voyage' | 'ollama';\n model?: string;\n apiKey?: string;\n baseURL?: string;\n endpoint?: string;\n };\n chunking?: {\n chunkSize?: number;\n overlap?: number;\n };\n };\n embedding?: {\n type?: 'voyage' | 'ollama';\n model?: string;\n apiKey?: string;\n baseURL?: string;\n endpoint?: string;\n chunking?: {\n chunkSize?: number;\n overlap?: number;\n };\n };\n inference?: InferenceFlatSection;\n 'make-meaning'?: {\n graph?: Record<string, unknown>;\n actors?: {\n gatherer?: { inference?: InferenceConfig };\n matcher?: { inference?: InferenceConfig };\n };\n default?: { inference?: InferenceConfig };\n /**\n * Resource-gather knobs. `settleTimeoutMs` bounds the semanticContext\n * read-your-writes barrier (SMELTER-INDEX-SYNC D3/D5) — how long a gather\n * waits for the vector projection to settle before degrading to an absent\n * semanticContext. Must nest INSIDE downstream watchdogs (job-worker\n * liveness, client stall watchdogs) — see SMELTER-INDEX-SYNC A4.\n */\n gather?: { settleTimeoutMs?: number };\n };\n workers?: Record<string, { inference?: InferenceConfig }>;\n actors?: Record<string, { inference?: InferenceConfig }>;\n logLevel?: 'error' | 'warn' | 'info' | 'http' | 'debug';\n}\n\n// ── File reader abstraction (same pattern as createConfigLoader) ──────────────\n\nexport type TomlFileReader = {\n readIfExists: (path: string) => string | null;\n};\n\nfunction requirePlatform(value: string | undefined, serviceName: string): PlatformType {\n if (!value) {\n throw new Error(`platform is required for service '${serviceName}' — add 'platform = \"posix\"|\"container\"|\"external\"' to its config section`);\n }\n return value as PlatformType;\n}\n\n// ── Main loader function ──────────────────────────────────────────────────────\n\n/**\n * Parse ~/.semiontconfig and .semiont/config and return EnvironmentConfig.\n *\n * @param projectRoot - Path to the project root (contains .semiont/config)\n * @param environment - Environment name (e.g. 'local', 'production'); when\n * undefined, resolved from `[defaults] environment`\n * @param globalConfigPath - Path to ~/.semiontconfig (caller resolves ~ expansion)\n * @param reader - File reader abstraction\n * @param env - Environment variables for ${VAR} resolution\n */\nexport function loadTomlConfig(\n projectRoot: string | null,\n environment: string | undefined,\n globalConfigPath: string,\n reader: TomlFileReader,\n env: Record<string, string | undefined>\n): EnvironmentConfig {\n // 1. Read + parse project config from .semiont/config (skipped when no project root)\n const projectConfigContent = projectRoot ? reader.readIfExists(`${projectRoot}/.semiont/config`) : null;\n const projectConfig = projectConfigContent\n ? (parseToml(projectConfigContent) as {\n project?: { name?: string; version?: string };\n site?: EnvironmentSection['site'];\n environments?: Record<string, EnvironmentSection>;\n })\n : undefined;\n const projectName = projectConfig?.project?.name ?? 'semiont-project';\n const projectVersion = projectConfig?.project?.version;\n const projectSite = projectConfig?.site;\n\n // 2. Read global config (optional — missing config yields empty environments)\n const globalContent = reader.readIfExists(globalConfigPath);\n const raw = globalContent ? (parseToml(globalContent) as SemiontConfigFile) : ({} as SemiontConfigFile);\n\n // 3. Resolve WHICH environment to load. `[defaults] environment` is the key the\n // launcher selects from (config.go: cfg.Defaults.Environment); the backend\n // resolves from the SAME key so one config selects the environment for both\n // halves. TWO inputs only — an explicit argument (tests pass one) and the\n // committed config — and they cannot contradict each other, because the\n // explicit one is legible at the call site. There is deliberately NO ambient\n // environment variable in this chain: `SEMIONT_ENV` was removed because an\n // invisible input that can disagree with the staged config is precisely the\n // bug shape #1108 fixed. And no silent 'local'/'development' fallback — an\n // unselected environment is a config error, not a default.\n const resolvedEnvironment = environment ?? raw.defaults?.environment;\n if (!resolvedEnvironment) {\n throw new Error(\n 'No environment selected: pass one explicitly, or declare ' +\n '`[defaults] environment` in ~/.semiontconfig.',\n );\n }\n\n // 4. A named environment with no [environments.X] section ANYWHERE is a config\n // error, not a silent empty {}. The silent {} is what let a KB declaring\n // `environment = \"staging\"` (with no [environments.staging]) load nothing and\n // let every downstream default fire — including site.domain -> 'localhost',\n // the fabricated colliding did:web:localhost identity. Fail loud instead.\n const projectHasSection =\n projectConfig?.environments != null && resolvedEnvironment in projectConfig.environments;\n const globalHasSection = raw.environments != null && resolvedEnvironment in raw.environments;\n if (!projectHasSection && !globalHasSection) {\n throw new Error(\n `Environment \"${resolvedEnvironment}\" is selected but no [environments.${resolvedEnvironment}] ` +\n 'section exists in the project (.semiont/config) or global (~/.semiontconfig) config. ' +\n 'Declare the section, or select an environment that exists.',\n );\n }\n\n // 5. Deep-merge: project base + user overrides (user wins on conflicts)\n const projectEnvSection: EnvironmentSection = projectConfig?.environments?.[resolvedEnvironment] ?? {};\n const userEnvSection: EnvironmentSection = raw.environments?.[resolvedEnvironment] ?? {};\n const envSection: EnvironmentSection = deepMerge(\n projectEnvSection as Record<string, unknown>,\n userEnvSection as Record<string, unknown>\n ) as EnvironmentSection;\n\n // 4. Resolve ${VAR} references\n const resolved = resolveEnvVars(envSection, env) as EnvironmentSection;\n\n // 5. Build make-meaning actor/worker inference config with inheritance\n // The flat [inference] section provides defaults (apiKey, maxTokens, endpoint/baseURL).\n // Actor/worker sections only need to specify type and model; missing fields fall back\n // to the flat inference section.\n const flatInference = resolved.inference;\n const makeMeaningSection = resolved['make-meaning'];\n const workersSection = resolved.workers ?? {};\n const actorsSection = resolved.actors ?? {};\n const defaultWorkerInference = workersSection['default']?.inference;\n const defaultMakeMeaningInference = makeMeaningSection?.default?.inference;\n\n function mergeWithFlatInference(specific: InferenceConfig): InferenceConfig {\n if (!flatInference) return specific;\n // For keyed sub-sections, inherit credentials from the matching provider sub-section.\n // For flat (legacy) format, flatInference.type is required to know which fields apply.\n const providerDefaults: Partial<InferenceConfig> = {};\n if (specific.type === 'anthropic') {\n const a = flatInference.anthropic;\n if (a) {\n providerDefaults.apiKey = a.apiKey;\n providerDefaults.endpoint = a.endpoint;\n } else {\n if (!flatInference.type) {\n throw new Error(\n `[environments.${resolvedEnvironment}.inference] is missing 'type'. ` +\n `Add type = \"anthropic\" or use [inference.anthropic] sub-section.`\n );\n }\n providerDefaults.apiKey = flatInference.apiKey;\n providerDefaults.endpoint = flatInference.endpoint;\n }\n } else if (specific.type === 'ollama') {\n const o = flatInference.ollama;\n if (o) {\n providerDefaults.baseURL = o.baseURL;\n } else {\n if (!flatInference.type) {\n throw new Error(\n `[environments.${resolvedEnvironment}.inference] is missing 'type'. ` +\n `Add type = \"ollama\" or use [inference.ollama] sub-section.`\n );\n }\n providerDefaults.baseURL = flatInference.baseURL;\n }\n }\n return {\n maxTokens: flatInference.maxTokens,\n ...providerDefaults,\n ...specific,\n };\n }\n\n function resolveActorInference(fromMakeMeaning?: InferenceConfig, fromActors?: InferenceConfig): InferenceConfig | undefined {\n const base = fromMakeMeaning ?? fromActors ?? defaultMakeMeaningInference;\n if (!base) return undefined;\n return mergeWithFlatInference(base);\n }\n\n const actors: ActorInferenceConfig = {};\n const gathererInference = resolveActorInference(\n makeMeaningSection?.actors?.gatherer?.inference,\n actorsSection['gatherer']?.inference\n );\n if (gathererInference) actors.gatherer = gathererInference;\n\n const matcherInference = resolveActorInference(\n makeMeaningSection?.actors?.matcher?.inference,\n actorsSection['matcher']?.inference\n );\n if (matcherInference) actors.matcher = matcherInference;\n\n const workers: WorkerInferenceConfig = {};\n const workerTypes = ['reference-annotation', 'highlight-annotation', 'assessment-annotation', 'comment-annotation', 'tag-annotation', 'generation'] as const;\n if (defaultWorkerInference) {\n workers.default = mergeWithFlatInference(defaultWorkerInference);\n }\n for (const wt of workerTypes) {\n const specific = workersSection[wt]?.inference;\n if (specific) {\n (workers as Record<string, InferenceConfig>)[wt] = mergeWithFlatInference(specific);\n }\n }\n\n // 6. Map to EnvironmentConfig\n const backend = resolved.backend;\n const site = resolved.site ?? projectSite;\n const inferenceSection = resolved.inference;\n\n // Build inference providers config.\n // Supports two formats:\n // Flat: [environments.local.inference] type = \"anthropic\"|\"ollama\" (single provider)\n // Keyed: [environments.local.inference.anthropic] / [environments.local.inference.ollama] (multi-provider)\n let inferenceProviders: EnvironmentConfig['inference'] | undefined;\n if (inferenceSection) {\n inferenceProviders = {};\n // Keyed sub-sections take priority\n if (inferenceSection.anthropic) {\n const a = inferenceSection.anthropic;\n inferenceProviders.anthropic = {\n platform: requirePlatform(a.platform, 'inference.anthropic'),\n endpoint: a.endpoint ?? 'https://api.anthropic.com',\n apiKey: a.apiKey ?? '',\n } as AnthropicProviderConfig;\n } else if (inferenceSection.type === 'anthropic') {\n inferenceProviders.anthropic = {\n platform: requirePlatform(inferenceSection.platform, 'inference'),\n endpoint: inferenceSection.endpoint ?? 'https://api.anthropic.com',\n apiKey: inferenceSection.apiKey ?? '',\n } as AnthropicProviderConfig;\n }\n if (inferenceSection.ollama) {\n const o = inferenceSection.ollama;\n inferenceProviders.ollama = {\n platform: { type: requirePlatform(o.platform, 'inference.ollama') },\n baseURL: o.baseURL,\n port: o.baseURL ? undefined : (o.port ?? 11434),\n } as OllamaProviderConfig;\n } else if (inferenceSection.type === 'ollama') {\n inferenceProviders.ollama = {\n platform: { type: requirePlatform(inferenceSection.platform, 'inference') },\n baseURL: inferenceSection.baseURL,\n port: inferenceSection.baseURL ? undefined : 11434,\n } as OllamaProviderConfig;\n }\n }\n\n // Build top-level workers/actors maps for EnvironmentConfig\n const topLevelWorkers: EnvironmentConfig['workers'] = {};\n for (const [name, w] of Object.entries(workersSection)) {\n if (w.inference) {\n topLevelWorkers[name] = { inference: { type: w.inference.type, model: w.inference.model } };\n }\n }\n const topLevelActors: EnvironmentConfig['actors'] = {};\n for (const [name, a] of Object.entries(actorsSection)) {\n if (a.inference) {\n topLevelActors[name] = { inference: { type: a.inference.type, model: a.inference.model } };\n }\n }\n // Also include make-meaning actors\n if (makeMeaningSection?.actors?.gatherer?.inference) {\n topLevelActors['gatherer'] = { inference: { type: makeMeaningSection.actors.gatherer.inference.type, model: makeMeaningSection.actors.gatherer.inference.model } };\n }\n if (makeMeaningSection?.actors?.matcher?.inference) {\n topLevelActors['matcher'] = { inference: { type: makeMeaningSection.actors.matcher.inference.type, model: makeMeaningSection.actors.matcher.inference.model } };\n }\n\n const frontend = resolved.frontend;\n\n const services: EnvironmentConfig['services'] = {};\n\n if (backend) {\n services.backend = {\n platform: { type: requirePlatform(backend.platform, 'backend') },\n port: backend.port ?? 4000,\n publicURL: backend.publicURL ?? `http://localhost:${backend.port ?? 4000}`,\n };\n }\n\n if (frontend) {\n services.frontend = {\n platform: { type: requirePlatform(frontend.platform, 'frontend') },\n port: frontend.port ?? 3000,\n siteName: site?.siteName ?? 'Semiont',\n publicURL: frontend.publicURL,\n };\n }\n\n if (resolved.graph) {\n services.graph = {\n ...resolved.graph,\n platform: { type: requirePlatform(resolved.graph.platform as string | undefined, 'graph') },\n type: (resolved.graph.type ?? 'neo4j') as import('./config.types').GraphDatabaseType,\n } as EnvironmentConfig['services']['graph'];\n } else if (makeMeaningSection?.graph) {\n services.graph = makeMeaningSection.graph as EnvironmentConfig['services']['graph'];\n }\n\n if (resolved.database) {\n services.database = {\n platform: { type: requirePlatform(resolved.database.platform, 'database') },\n type: 'postgres',\n image: resolved.database.image,\n host: resolved.database.host ?? 'localhost',\n port: resolved.database.port ?? 5432,\n name: resolved.database.name,\n user: resolved.database.user,\n password: resolved.database.password,\n } as EnvironmentConfig['services']['database'];\n }\n\n if (resolved.vectors) {\n services.vectors = {\n platform: { type: 'external' as PlatformType },\n type: (resolved.vectors.type ?? 'qdrant') as 'qdrant' | 'memory',\n host: resolved.vectors.host,\n port: resolved.vectors.port ?? 6333,\n } as EnvironmentConfig['services']['vectors'];\n }\n\n // Embedding: top-level takes precedence, fall back to legacy vectors.embedding\n const embeddingSource = resolved.embedding ?? resolved.vectors?.embedding;\n if (embeddingSource) {\n services.embedding = {\n platform: { type: 'external' as PlatformType },\n type: embeddingSource.type!,\n model: embeddingSource.model!,\n apiKey: embeddingSource.apiKey,\n baseURL: embeddingSource.baseURL,\n endpoint: embeddingSource.endpoint,\n chunking: (resolved.embedding?.chunking ?? resolved.vectors?.chunking) ? {\n chunkSize: (resolved.embedding?.chunking ?? resolved.vectors?.chunking)?.chunkSize ?? 512,\n overlap: (resolved.embedding?.chunking ?? resolved.vectors?.chunking)?.overlap ?? 64,\n } : undefined,\n } as EnvironmentConfig['services']['embedding'];\n }\n\n const config: EnvironmentConfig = {\n services,\n ...(inferenceProviders ? { inference: inferenceProviders } : {}),\n ...(Object.keys(topLevelWorkers).length > 0 ? { workers: topLevelWorkers } : {}),\n ...(Object.keys(topLevelActors).length > 0 ? { actors: topLevelActors } : {}),\n site: site ? {\n domain: site.domain ?? 'localhost',\n siteName: site.siteName,\n adminEmail: site.adminEmail,\n oauthAllowedDomains: site.oauthAllowedDomains as [string, ...string[]] | undefined,\n } : undefined,\n logLevel: resolved.logLevel,\n _metadata: {\n environment: resolvedEnvironment,\n projectRoot,\n projectName,\n projectVersion,\n ...(Object.keys(actors).length > 0 ? { actors } : {}),\n ...(Object.keys(workers).length > 0 ? { workers } : {}),\n // Always set — the loader is the ONE home of this default (D5).\n // Consuming code (make-meaning's gather path) receives a required\n // value and defaults nothing.\n gather: { settleTimeoutMs: makeMeaningSection?.gather?.settleTimeoutMs ?? 15_000 },\n },\n };\n\n return config;\n}\n\n/**\n * Create a TOML config loader backed by a file reader.\n * Drop-in replacement for createConfigLoader that reads TOML instead of JSON.\n * The caller must resolve globalConfigPath (e.g. expand '~' using process.env.HOME).\n */\nexport function createTomlConfigLoader(\n reader: TomlFileReader,\n globalConfigPath: string,\n env: Record<string, string | undefined>\n) {\n return (projectRoot: string | null, environment?: string): EnvironmentConfig => {\n return loadTomlConfig(projectRoot, environment, globalConfigPath, reader, env);\n };\n}\n"]}
@@ -551,19 +551,35 @@ interface EnvironmentConfig {
551
551
  *
552
552
  * Ephemeral paths (outside the project root, never committed):
553
553
  * configDir — $XDG_CONFIG_HOME/semiont/{name}/ (generated config for managed processes)
554
- * dataHome — $XDG_DATA_HOME/semiont/{name}/ (persistent user data, e.g. database files)
555
554
  * stateDir — $XDG_STATE_HOME/semiont/{name}/
556
555
  * projectionsDir — stateDir/projections/
557
556
  * jobsDir — stateDir/jobs/
557
+ * anchoredTextDir — supplied by the caller; required, no default
558
558
  * backendLogsDir — stateDir/backend/
559
559
  * backendAppLogFile — backendLogsDir/app.log
560
560
  * backendErrorLogFile — backendLogsDir/error.log
561
561
  * runtimeDir — $XDG_RUNTIME_DIR/semiont/{name}/ (or $TMPDIR fallback)
562
562
  * backendPidFile — runtimeDir/backend.pid
563
563
  *
564
- * Note: frontend paths are NOT project-scoped. The frontend service is bundled
565
- * with the CLI and uses fixed XDG paths keyed by "frontend", not project name.
566
- * See apps/cli/src/platforms/posix/handlers/frontend-paths.ts.
564
+ * Everything ephemeral that is DERIVED sits under stateDir together
565
+ * projections (from the event log) and jobs. The anchored-text store is
566
+ * derived too, but its location is declared by the deployment rather than
567
+ * composed here (see anchoredTextDir). That is the XDG distinction, not a
568
+ * filing habit:
569
+ * $XDG_STATE_HOME is for data that persists between restarts but "is not
570
+ * important or portable enough" for $XDG_DATA_HOME, and losing any of these
571
+ * costs recomputation rather than information.
572
+ *
573
+ * There is no $XDG_DATA_HOME path here, deliberately. Semiont's own system of
574
+ * record is the committed event log above; the databases live under the
575
+ * launcher's per-root state, not the backend's. A `dataHome` field existed and
576
+ * had exactly one consumer — the anchored-text store, which belonged in state
577
+ * all along — so it went with the move rather than being left for a
578
+ * hypothetical future user of the DATA tier.
579
+ *
580
+ * Note: the frontend has no entry here, deliberately. It serves static assets
581
+ * from its own container image and keeps no per-project state on the host, so
582
+ * there is nothing to derive from a project root.
567
583
  */
568
584
  declare class SemiontProject {
569
585
  readonly root: string;
@@ -573,16 +589,33 @@ declare class SemiontProject {
573
589
  readonly gitSync: boolean;
574
590
  readonly eventsDir: string;
575
591
  readonly configDir: string;
576
- readonly dataHome: string;
577
592
  readonly stateDir: string;
578
593
  readonly projectionsDir: string;
579
594
  readonly jobsDir: string;
595
+ readonly anchoredTextDir: string;
580
596
  readonly backendLogsDir: string;
581
597
  readonly backendAppLogFile: string;
582
598
  readonly backendErrorLogFile: string;
583
599
  readonly runtimeDir: string;
584
600
  readonly backendPidFile: string;
585
- constructor(projectRoot: string, name?: string);
601
+ /**
602
+ * @param projectRoot the KB clone this project describes
603
+ * @param opts.name seed value: written to `.semiont/config` when that
604
+ * file does not yet exist. It does NOT override an existing config — the
605
+ * name always comes from the file, so a KB's committed identity wins over
606
+ * anything a caller passes.
607
+ * @param opts.anchoredTextDir where this deployment keeps the anchored-text
608
+ * store. Passed IN, never read from the environment here: the entry point
609
+ * owns that read, exactly as it owns SEMIONT_ROOT. Required, and with no
610
+ * default — a default would let a deployment that forgot it write a full
611
+ * OCR pass per representation into a directory nobody mounted, lose it on
612
+ * the next `stop`, and re-derive it forever: silent, expensive, and
613
+ * indistinguishable from working.
614
+ */
615
+ constructor(projectRoot: string, opts: {
616
+ anchoredTextDir: string;
617
+ name?: string;
618
+ });
586
619
  /**
587
620
  * Read the current git branch for the project root.
588
621
  * Returns null if the project is not a git repo or git is not available.
@@ -590,7 +623,7 @@ declare class SemiontProject {
590
623
  gitBranch(): string | null;
591
624
  /**
592
625
  * Delete all ephemeral state for this project (stateDir + runtimeDir).
593
- * Does not touch eventsDir or dataDir.
626
+ * Does not touch eventsDir the event log is the system of record.
594
627
  */
595
628
  destroy(): Promise<void>;
596
629
  /**
@@ -628,10 +661,10 @@ declare class SemiontProject {
628
661
  * then selects the given environment overlay.
629
662
  *
630
663
  * This is the canonical config loader for any Node.js process. The environment
631
- * is resolved by the loader itself — explicit `environment` arg > SEMIONT_ENV >
632
- * `[defaults] environment` — so entry points call this WITHOUT reading
633
- * SEMIONT_ENV (or inventing a default); one config selects the environment for
634
- * the backend the same way the launcher selects it.
664
+ * is resolved by the loader itself — an explicit `environment` argument, else
665
+ * `[defaults] environment` from the committed config — so entry points call this
666
+ * without selecting one; one config selects the environment for the backend the
667
+ * same way the launcher selects it. There is no environment-variable override.
635
668
  */
636
669
  declare function loadEnvironmentConfig(projectRoot: string, environment?: string): EnvironmentConfig;
637
670