@semiont/core 0.5.27 → 0.5.29

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.
@@ -128,9 +128,9 @@ interface BuildRequirements {
128
128
  }
129
129
  /**
130
130
  * This interface was referenced by `HttpsSemiontOrgSchemasConfigJson`'s JSON-Schema
131
- * via the `definition` "BackendServiceConfig".
131
+ * via the `definition` "GatewayServiceConfig".
132
132
  */
133
- interface BackendServiceConfig {
133
+ interface GatewayServiceConfig {
134
134
  platform: ServicePlatformConfig;
135
135
  devMode?: boolean;
136
136
  command?: string;
@@ -179,58 +179,6 @@ interface BackendServiceConfig {
179
179
  [k: string]: string;
180
180
  };
181
181
  }
182
- /**
183
- * This interface was referenced by `HttpsSemiontOrgSchemasConfigJson`'s JSON-Schema
184
- * via the `definition` "FrontendServiceConfig".
185
- */
186
- interface FrontendServiceConfig {
187
- platform: ServicePlatformConfig;
188
- devMode?: boolean;
189
- command?: string;
190
- port: number;
191
- siteName: string;
192
- image?: string;
193
- projectRoot?: string;
194
- databaseUrl?: string;
195
- timeout?: number;
196
- wait?: number;
197
- logsEndpoint?: string;
198
- tag?: string;
199
- resources?: ResourceRequirements;
200
- security?: SecurityRequirements;
201
- build?: boolean | BuildRequirements;
202
- dockerfile?: string;
203
- buildContext?: string;
204
- buildArgs?: {
205
- [k: string]: string;
206
- };
207
- buildTarget?: string;
208
- prebuilt?: boolean;
209
- noCache?: boolean;
210
- secrets?: string[];
211
- labels?: {
212
- [k: string]: string;
213
- };
214
- annotations?: {
215
- [k: string]: string;
216
- };
217
- dependencies?: string[];
218
- externalDependencies?: (string | {
219
- name?: string;
220
- url?: string;
221
- required?: boolean;
222
- healthCheck?: string;
223
- [k: string]: unknown;
224
- })[];
225
- environment?: {
226
- [k: string]: string;
227
- };
228
- env?: {
229
- [k: string]: string;
230
- };
231
- publicURL?: string;
232
- allowedOrigins?: string[];
233
- }
234
182
  /**
235
183
  * This interface was referenced by `HttpsSemiontOrgSchemasConfigJson`'s JSON-Schema
236
184
  * via the `definition` "VectorsServiceConfig".
@@ -401,13 +349,26 @@ interface McpServiceConfig {
401
349
  * via the `definition` "ServicesConfig".
402
350
  */
403
351
  interface ServicesConfig {
404
- backend?: BackendServiceConfig;
405
- frontend?: FrontendServiceConfig;
352
+ gateway?: GatewayServiceConfig;
406
353
  database?: DatabaseServiceConfig;
407
354
  graph?: GraphServiceConfig;
408
355
  mcp?: McpServiceConfig;
409
356
  vectors: VectorsServiceConfig;
410
357
  embedding: EmbeddingServiceConfig;
358
+ archivist?: ArchivistServiceConfig;
359
+ [k: string]: unknown;
360
+ }
361
+ /**
362
+ * The Archivist service — the out-of-process keeper of the record (EXTRACT-ARCHIVIST). Internal-only: the gateway dials host:port for the D1 sequence-ranged event read path; nothing public.
363
+ *
364
+ * This interface was referenced by `HttpsSemiontOrgSchemasConfigJson`'s JSON-Schema
365
+ * via the `definition` "ArchivistServiceConfig".
366
+ */
367
+ interface ArchivistServiceConfig {
368
+ platform?: ServicePlatformConfig;
369
+ host?: string;
370
+ port?: number;
371
+ image?: string;
411
372
  [k: string]: unknown;
412
373
  }
413
374
  /**
@@ -422,7 +383,7 @@ interface SiteConfig {
422
383
  /**
423
384
  * Primary domain for the site
424
385
  */
425
- domain: string;
386
+ domain?: string;
426
387
  /**
427
388
  * Administrator email address
428
389
  */
@@ -497,6 +458,17 @@ interface EnvironmentConfig {
497
458
  * Environment name
498
459
  */
499
460
  name?: string;
461
+ /**
462
+ * The KB's committed identity, staged by the launcher (SINGLE-KB-MOUNT D4). Top-level in the staged file, out of any environment section's reach; never overridable.
463
+ */
464
+ kb?: {
465
+ name: string;
466
+ domain?: string;
467
+ /**
468
+ * Sign-in policy committed in the KB's .semiont/config, staged alongside the identity because the gateway no longer mounts the tree that holds it.
469
+ */
470
+ oauthAllowedDomains?: string[];
471
+ };
500
472
  platform?: {
501
473
  default?: PlatformType;
502
474
  [k: string]: unknown;
@@ -546,20 +518,26 @@ interface EnvironmentConfig {
546
518
  * Computes all paths — durable and ephemeral — once at construction time.
547
519
  * XDG environment variables are read here and nowhere else.
548
520
  *
549
- * Durable paths (inside the project root, committed or repo-local):
521
+ * **The paths divide along what they are derived FROM, and so does the type.**
522
+ * Everything ephemeral is composed from the KB's NAME, so it needs no working
523
+ * tree and lives on `SemiontState`. Only the durable half is composed from the
524
+ * root. `SemiontProject extends SemiontState` — a project is its state plus a
525
+ * working tree — which lets a consumer that has no KB root (the gateway, after
526
+ * SINGLE-KB-MOUNT P5) take the smaller type and still be checked by the
527
+ * compiler rather than by a throw at first read.
528
+ *
529
+ * Durable paths (inside the project root, committed or repo-local) — `SemiontProject`:
550
530
  * eventsDir — .semiont/events/ (system of record, committed)
551
531
  *
552
- * Ephemeral paths (outside the project root, never committed):
532
+ * Ephemeral paths (outside the project root, never committed) — `SemiontState`:
553
533
  * configDir — $XDG_CONFIG_HOME/semiont/{name}/ (generated config for managed processes)
554
534
  * stateDir — $XDG_STATE_HOME/semiont/{name}/
555
- * projectionsDir — stateDir/projections/
535
+ * resourcesDir — stateDir/resources/ (the per-resource materialized views)
536
+ * projectionsDir — stateDir/projections/ (KB-global projections + the storage-uri index)
556
537
  * jobsDir — stateDir/jobs/
557
538
  * anchoredTextDir — supplied by the caller; required, no default
558
- * backendLogsDir — stateDir/backend/
559
- * backendAppLogFile — backendLogsDir/app.log
560
- * backendErrorLogFile — backendLogsDir/error.log
561
539
  * runtimeDir — $XDG_RUNTIME_DIR/semiont/{name}/ (or $TMPDIR fallback)
562
- * backendPidFile — runtimeDir/backend.pid
540
+ * gatewayPidFile — runtimeDir/gateway.pid
563
541
  *
564
542
  * Everything ephemeral that is DERIVED sits under stateDir together —
565
543
  * projections (from the event log) and jobs. The anchored-text store is
@@ -572,7 +550,7 @@ interface EnvironmentConfig {
572
550
  *
573
551
  * There is no $XDG_DATA_HOME path here, deliberately. Semiont's own system of
574
552
  * 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
553
+ * launcher's per-root state, not the gateway's. A `dataHome` field existed and
576
554
  * had exactly one consumer — the anchored-text store, which belonged in state
577
555
  * all along — so it went with the move rather than being left for a
578
556
  * hypothetical future user of the DATA tier.
@@ -581,36 +559,81 @@ interface EnvironmentConfig {
581
559
  * from its own container image and keeps no per-project state on the host, so
582
560
  * there is nothing to derive from a project root.
583
561
  */
584
- declare class SemiontProject {
585
- readonly root: string;
562
+ /**
563
+ * The one composition of a project's state-tree root from its name. The
564
+ * Librarian resolves this WITHOUT a SemiontProject — it has no KB root to
565
+ * construct one from (SINGLE-KB-MOUNT P1) — so the join lives here, beside
566
+ * the constructor that also uses it, rather than being restated over there.
567
+ */
568
+ declare function stateDirFor(name: string): string;
569
+ /**
570
+ * A KB's state tree, addressed by NAME — everything that needs no working tree.
571
+ *
572
+ * This exists because consumers appeared that genuinely need half of
573
+ * `SemiontProject` and cannot supply the other half: the gateway reads
574
+ * `jobsDir` and the Librarian reads `resourcesDir`, both on the shared
575
+ * state mount, with no readable KB root at all (SINGLE-KB-MOUNT P1/P5).
576
+ *
577
+ * **Split rather than made optional, deliberately.** Relaxing
578
+ * `SemiontProject`'s root-derived fields to optional-and-throw-on-read would
579
+ * have traded a compile-time guarantee for a runtime one, and bought no extra
580
+ * safety doing it: a getter asserts PRESENCE exactly as weakly as a constructor
581
+ * does — neither can tell a real path from a typo. Two types keep "needs a
582
+ * working tree" a fact the compiler enforces. Handing a `SemiontState` to
583
+ * something that reads `eventsDir` does not compile.
584
+ *
585
+ * Every field here is required, and every one is derived from `name` alone —
586
+ * which is what makes the name the whole of this type's input. `anchoredTextDir`
587
+ * is deliberately NOT here (SINGLE-KB-MOUNT P6): it is a supplied path rather
588
+ * than a derived one, and its only readers hold a working tree too, so it sits
589
+ * on `SemiontProject` where they already are.
590
+ */
591
+ declare class SemiontState {
586
592
  readonly name: string;
587
- /** True if [git] sync = true in .semiont/config. When true, semiont stages
588
- * working-tree and event-log changes in the git index automatically. */
589
- readonly gitSync: boolean;
590
- readonly eventsDir: string;
591
593
  readonly configDir: string;
592
594
  readonly stateDir: string;
595
+ readonly resourcesDir: string;
593
596
  readonly projectionsDir: string;
594
597
  readonly jobsDir: string;
595
- readonly anchoredTextDir: string;
596
- readonly backendLogsDir: string;
597
- readonly backendAppLogFile: string;
598
- readonly backendErrorLogFile: string;
599
598
  readonly runtimeDir: string;
600
- readonly backendPidFile: string;
599
+ readonly gatewayPidFile: string;
600
+ constructor(opts: {
601
+ name: string;
602
+ });
603
+ }
604
+ /** A project is its state plus a working tree. */
605
+ declare class SemiontProject extends SemiontState {
606
+ readonly root: string;
607
+ readonly anchoredTextDir: string;
608
+ /** True if [git] sync = true in .semiont/config. When true, semiont stages
609
+ * working-tree and event-log changes in the git index automatically. */
610
+ readonly gitSync: boolean;
611
+ readonly eventsDir: string;
612
+ /**
613
+ * Seed `.semiont/config` if absent, then read the name back OUT of it.
614
+ *
615
+ * The order is the point, and it is why this is a static rather than inline
616
+ * in the constructor: `super()` needs the resolved name, and the resolved
617
+ * name is whatever the file says — a seed only applies when no file exists,
618
+ * so a KB's committed identity always wins over anything a caller passes.
619
+ */
620
+ private static seedAndReadName;
601
621
  /**
602
622
  * @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.
623
+ * @param opts.name seed value see `seedAndReadName`.
607
624
  * @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.
625
+ * store. Supplied by the caller; REQUIRED, no default.
626
+ *
627
+ * A default would let a deployment that forgot it write a full OCR pass per
628
+ * representation into a directory nobody mounted, lose it on the next
629
+ * `stop`, and re-derive it forever: silent, expensive, and indistinguishable
630
+ * from working. Passed IN, never read from the environment here — the entry
631
+ * point owns that read, exactly as it owns SEMIONT_ROOT.
632
+ *
633
+ * It lives on THIS type rather than `SemiontState` (SINGLE-KB-MOUNT P6)
634
+ * because every reader of it holds a working tree as well, and the one
635
+ * consumer that needs state paths without a tree — the gateway — does not
636
+ * read it at all.
614
637
  */
615
638
  constructor(projectRoot: string, opts: {
616
639
  anchoredTextDir: string;
@@ -633,10 +656,8 @@ declare class SemiontProject {
633
656
  *
634
657
  * Reads the committed file DIRECTLY, and deliberately not
635
658
  * `EnvironmentConfig.site.domain`, which is the same value only by
636
- * accident: the TOML loader defaults a domain-less `[site]` to the string
637
- * `'localhost'` (so every domain-less KB on a machine would claim one
638
- * fabricated `did:web:localhost`) and lets the environment section
639
- * override the KB's own declaration. Either would report an identity the
659
+ * accident: the environment section can override the KB's own declaration.
660
+ * That would report an identity the
640
661
  * launcher never minted — an address wearing a name, which is the whole
641
662
  * category error .plans/KB-IDENTITY-VS-ADDRESS.md exists to end. Identity
642
663
  * is declared or absent; it is never defaulted.
@@ -663,9 +684,42 @@ declare class SemiontProject {
663
684
  * This is the canonical config loader for any Node.js process. The environment
664
685
  * is resolved by the loader itself — an explicit `environment` argument, else
665
686
  * `[defaults] environment` from the committed config — so entry points call this
666
- * without selecting one; one config selects the environment for the backend the
687
+ * without selecting one; one config selects the environment for the gateway the
667
688
  * same way the launcher selects it. There is no environment-variable override.
668
689
  */
669
- declare function loadEnvironmentConfig(projectRoot: string, environment?: string): EnvironmentConfig;
690
+ declare function loadEnvironmentConfig(projectRoot: string | null, environment?: string): EnvironmentConfig;
691
+ /**
692
+ * The slice of config the Archivist's address needs — nothing wider, and
693
+ * DERIVED from the schema's own service type rather than restating
694
+ * `host`/`port`.
695
+ */
696
+ interface ArchivistAddressConfig {
697
+ services?: {
698
+ archivist?: Pick<ArchivistServiceConfig, 'host' | 'port'>;
699
+ };
700
+ }
701
+ /**
702
+ * Base URL and auth header for the Archivist, resolved together because they
703
+ * are useless apart. Throws on either absence.
704
+ *
705
+ * Lives HERE, and not with the byte reads that ride it, because it is neither
706
+ * a content concern nor a make-meaning one: it is a config value plus an
707
+ * environment variable, which is exactly what this module already is. Putting
708
+ * it in `@semiont/content` gave the gateway a runtime edge to a package it
709
+ * otherwise touches only for types — and since that package is a
710
+ * devDependency there, the bundler INLINED its PDF/OCR stack into an ESM
711
+ * bundle and the process died at load on a CJS `require`.
712
+ *
713
+ * Absence fails loudly. A missing host or secret is a misconfiguration, never
714
+ * a reason to fall back to reading a tree locally: the point of
715
+ * SINGLE-KB-MOUNT is that exactly one process touches it.
716
+ */
717
+ declare function archivistEndpoint(config: ArchivistAddressConfig): {
718
+ base: string;
719
+ headers: {
720
+ authorization: string;
721
+ };
722
+ };
670
723
 
671
- export { SemiontProject, loadEnvironmentConfig };
724
+ export { SemiontProject, SemiontState, archivistEndpoint, loadEnvironmentConfig, stateDirFor };
725
+ export type { ArchivistAddressConfig };
@@ -1,73 +1,91 @@
1
- import { createTomlConfigLoader } from '../chunk-JWTQJVKM.js';
1
+ import { createTomlConfigLoader } from '../chunk-VBMXNYPL.js';
2
+ import '../chunk-YLJ4XMA6.js';
2
3
  import * as fs from 'fs';
3
4
  import * as os from 'os';
4
5
  import * as path from 'path';
5
6
  import { execFileSync } from 'child_process';
6
7
 
7
- var SemiontProject = class _SemiontProject {
8
- root;
8
+ function stateDirFor(name) {
9
+ const xdgState = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
10
+ return path.join(xdgState, "semiont", name);
11
+ }
12
+ var SemiontState = class {
9
13
  name;
10
- /** True if [git] sync = true in .semiont/config. When true, semiont stages
11
- * working-tree and event-log changes in the git index automatically. */
12
- gitSync;
13
- // Durable
14
- eventsDir;
15
14
  // Ephemeral — config (generated config files for managed processes)
16
15
  configDir;
17
16
  // Ephemeral — state
18
17
  stateDir;
18
+ resourcesDir;
19
19
  projectionsDir;
20
20
  jobsDir;
21
- anchoredTextDir;
22
- backendLogsDir;
23
- backendAppLogFile;
24
- backendErrorLogFile;
25
21
  // Ephemeral — runtime
26
22
  runtimeDir;
27
- backendPidFile;
23
+ gatewayPidFile;
24
+ constructor(opts) {
25
+ this.name = opts.name;
26
+ const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
27
+ this.configDir = path.join(xdgConfig, "semiont", this.name);
28
+ this.stateDir = stateDirFor(this.name);
29
+ this.resourcesDir = path.join(this.stateDir, "resources");
30
+ this.projectionsDir = path.join(this.stateDir, "projections");
31
+ this.jobsDir = path.join(this.stateDir, "jobs");
32
+ const xdgRuntime = process.env.XDG_RUNTIME_DIR;
33
+ const runtimeBase = xdgRuntime ?? process.env.TMPDIR ?? "/tmp";
34
+ this.runtimeDir = path.join(runtimeBase, "semiont", this.name);
35
+ this.gatewayPidFile = path.join(this.runtimeDir, "gateway.pid");
36
+ }
37
+ };
38
+ var SemiontProject = class _SemiontProject extends SemiontState {
39
+ root;
40
+ anchoredTextDir;
41
+ /** True if [git] sync = true in .semiont/config. When true, semiont stages
42
+ * working-tree and event-log changes in the git index automatically. */
43
+ gitSync;
44
+ // Durable
45
+ eventsDir;
28
46
  /**
29
- * @param projectRoot the KB clone this project describes
30
- * @param opts.name seed value: written to `.semiont/config` when that
31
- * file does not yet exist. It does NOT override an existing config the
32
- * name always comes from the file, so a KB's committed identity wins over
33
- * anything a caller passes.
34
- * @param opts.anchoredTextDir where this deployment keeps the anchored-text
35
- * store. Passed IN, never read from the environment here: the entry point
36
- * owns that read, exactly as it owns SEMIONT_ROOT. Required, and with no
37
- * default — a default would let a deployment that forgot it write a full
38
- * OCR pass per representation into a directory nobody mounted, lose it on
39
- * the next `stop`, and re-derive it forever: silent, expensive, and
40
- * indistinguishable from working.
47
+ * Seed `.semiont/config` if absent, then read the name back OUT of it.
48
+ *
49
+ * The order is the point, and it is why this is a static rather than inline
50
+ * in the constructor: `super()` needs the resolved name, and the resolved
51
+ * name is whatever the file says — a seed only applies when no file exists,
52
+ * so a KB's committed identity always wins over anything a caller passes.
41
53
  */
42
- constructor(projectRoot, opts) {
43
- const name = opts.name;
44
- this.anchoredTextDir = opts.anchoredTextDir;
45
- this.root = projectRoot;
46
- if (name !== void 0) {
54
+ static seedAndReadName(projectRoot, seed) {
55
+ if (seed !== void 0) {
47
56
  const configPath = path.join(projectRoot, ".semiont", "config");
48
57
  if (!fs.existsSync(configPath)) {
49
58
  fs.mkdirSync(path.join(projectRoot, ".semiont"), { recursive: true });
50
59
  fs.writeFileSync(configPath, `[project]
51
- name = "${name}"
60
+ name = "${seed}"
52
61
  `);
53
62
  }
54
63
  }
55
- this.name = _SemiontProject.readName(projectRoot);
64
+ return _SemiontProject.readName(projectRoot);
65
+ }
66
+ /**
67
+ * @param projectRoot the KB clone this project describes
68
+ * @param opts.name seed value — see `seedAndReadName`.
69
+ * @param opts.anchoredTextDir where this deployment keeps the anchored-text
70
+ * store. Supplied by the caller; REQUIRED, no default.
71
+ *
72
+ * A default would let a deployment that forgot it write a full OCR pass per
73
+ * representation into a directory nobody mounted, lose it on the next
74
+ * `stop`, and re-derive it forever: silent, expensive, and indistinguishable
75
+ * from working. Passed IN, never read from the environment here — the entry
76
+ * point owns that read, exactly as it owns SEMIONT_ROOT.
77
+ *
78
+ * It lives on THIS type rather than `SemiontState` (SINGLE-KB-MOUNT P6)
79
+ * because every reader of it holds a working tree as well, and the one
80
+ * consumer that needs state paths without a tree — the gateway — does not
81
+ * read it at all.
82
+ */
83
+ constructor(projectRoot, opts) {
84
+ super({ name: _SemiontProject.seedAndReadName(projectRoot, opts.name) });
85
+ this.anchoredTextDir = opts.anchoredTextDir;
86
+ this.root = projectRoot;
56
87
  this.gitSync = _SemiontProject.readGitSync(projectRoot);
57
88
  this.eventsDir = path.join(projectRoot, ".semiont", "events");
58
- const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
59
- this.configDir = path.join(xdgConfig, "semiont", this.name);
60
- const xdgState = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
61
- this.stateDir = path.join(xdgState, "semiont", this.name);
62
- this.projectionsDir = path.join(this.stateDir, "projections");
63
- this.jobsDir = path.join(this.stateDir, "jobs");
64
- this.backendLogsDir = path.join(this.stateDir, "backend");
65
- this.backendAppLogFile = path.join(this.backendLogsDir, "app.log");
66
- this.backendErrorLogFile = path.join(this.backendLogsDir, "error.log");
67
- const xdgRuntime = process.env.XDG_RUNTIME_DIR;
68
- const runtimeBase = xdgRuntime ?? process.env.TMPDIR ?? "/tmp";
69
- this.runtimeDir = path.join(runtimeBase, "semiont", this.name);
70
- this.backendPidFile = path.join(this.runtimeDir, "backend.pid");
71
89
  }
72
90
  /**
73
91
  * Read the current git branch for the project root.
@@ -102,10 +120,8 @@ name = "${name}"
102
120
  *
103
121
  * Reads the committed file DIRECTLY, and deliberately not
104
122
  * `EnvironmentConfig.site.domain`, which is the same value only by
105
- * accident: the TOML loader defaults a domain-less `[site]` to the string
106
- * `'localhost'` (so every domain-less KB on a machine would claim one
107
- * fabricated `did:web:localhost`) and lets the environment section
108
- * override the KB's own declaration. Either would report an identity the
123
+ * accident: the environment section can override the KB's own declaration.
124
+ * That would report an identity the
109
125
  * launcher never minted — an address wearing a name, which is the whole
110
126
  * category error .plans/KB-IDENTITY-VS-ADDRESS.md exists to end. Identity
111
127
  * is declared or absent; it is never defaulted.
@@ -193,7 +209,19 @@ function loadEnvironmentConfig(projectRoot, environment) {
193
209
  process.env
194
210
  )(projectRoot, environment);
195
211
  }
212
+ function archivistEndpoint(config) {
213
+ const host = config.services?.archivist?.host;
214
+ if (!host) {
215
+ throw new Error("services.archivist.host is not configured \u2014 cannot reach the record");
216
+ }
217
+ const port = config.services?.archivist?.port ?? 9093;
218
+ const secret = process.env.SEMIONT_WORKER_SECRET;
219
+ if (!secret) {
220
+ throw new Error("SEMIONT_WORKER_SECRET is not set \u2014 cannot authenticate to the Archivist");
221
+ }
222
+ return { base: `http://${host}:${port}`, headers: { authorization: `Bearer ${secret}` } };
223
+ }
196
224
 
197
- export { SemiontProject, loadEnvironmentConfig };
225
+ export { SemiontProject, SemiontState, archivistEndpoint, loadEnvironmentConfig, stateDirFor };
198
226
  //# sourceMappingURL=node-config-loader.js.map
199
227
  //# sourceMappingURL=node-config-loader.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/project.ts","../../src/config/node-config-loader.ts"],"names":["fs2","path2","os2"],"mappings":"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","file":"node-config-loader.js","sourcesContent":["import * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport { execFileSync } from 'child_process';\n\n/**\n * Represents a Semiont project rooted at a given directory.\n *\n * Computes all paths — durable and ephemeral — once at construction time.\n * XDG environment variables are read here and nowhere else.\n *\n * Durable paths (inside the project root, committed or repo-local):\n * eventsDir — .semiont/events/ (system of record, committed)\n *\n * Ephemeral paths (outside the project root, never committed):\n * configDir — $XDG_CONFIG_HOME/semiont/{name}/ (generated config for managed processes)\n * stateDir — $XDG_STATE_HOME/semiont/{name}/\n * projectionsDir — stateDir/projections/\n * jobsDir — stateDir/jobs/\n * anchoredTextDir — supplied by the caller; required, no default\n * backendLogsDir — stateDir/backend/\n * backendAppLogFile — backendLogsDir/app.log\n * backendErrorLogFile — backendLogsDir/error.log\n * runtimeDir — $XDG_RUNTIME_DIR/semiont/{name}/ (or $TMPDIR fallback)\n * backendPidFile — runtimeDir/backend.pid\n *\n * Everything ephemeral that is DERIVED sits under stateDir together —\n * projections (from the event log) and jobs. The anchored-text store is\n * derived too, but its location is declared by the deployment rather than\n * composed here (see anchoredTextDir). That is the XDG distinction, not a\n * filing habit:\n * $XDG_STATE_HOME is for data that persists between restarts but \"is not\n * important or portable enough\" for $XDG_DATA_HOME, and losing any of these\n * costs recomputation rather than information.\n *\n * There is no $XDG_DATA_HOME path here, deliberately. Semiont's own system of\n * record is the committed event log above; the databases live under the\n * launcher's per-root state, not the backend's. A `dataHome` field existed and\n * had exactly one consumer — the anchored-text store, which belonged in state\n * all along — so it went with the move rather than being left for a\n * hypothetical future user of the DATA tier.\n *\n * Note: the frontend has no entry here, deliberately. It serves static assets\n * from its own container image and keeps no per-project state on the host, so\n * there is nothing to derive from a project root.\n */\nexport class SemiontProject {\n readonly root: string;\n readonly name: string;\n\n /** True if [git] sync = true in .semiont/config. When true, semiont stages\n * working-tree and event-log changes in the git index automatically. */\n readonly gitSync: boolean;\n\n // Durable\n readonly eventsDir: string;\n\n // Ephemeral — config (generated config files for managed processes)\n readonly configDir: string;\n\n // Ephemeral — state\n readonly stateDir: string;\n readonly projectionsDir: string;\n readonly jobsDir: string;\n readonly anchoredTextDir: string;\n readonly backendLogsDir: string;\n readonly backendAppLogFile: string;\n readonly backendErrorLogFile: string;\n\n // Ephemeral — runtime\n readonly runtimeDir: string;\n readonly backendPidFile: string;\n\n /**\n * @param projectRoot the KB clone this project describes\n * @param opts.name seed value: written to `.semiont/config` when that\n * file does not yet exist. It does NOT override an existing config — the\n * name always comes from the file, so a KB's committed identity wins over\n * anything a caller passes.\n * @param opts.anchoredTextDir where this deployment keeps the anchored-text\n * store. Passed IN, never read from the environment here: the entry point\n * owns that read, exactly as it owns SEMIONT_ROOT. Required, and with no\n * default — a default would let a deployment that forgot it write a full\n * OCR pass per representation into a directory nobody mounted, lose it on\n * the next `stop`, and re-derive it forever: silent, expensive, and\n * indistinguishable from working.\n */\n constructor(projectRoot: string, opts: { anchoredTextDir: string; name?: string }) {\n const name = opts.name;\n this.anchoredTextDir = opts.anchoredTextDir;\n this.root = projectRoot;\n if (name !== undefined) {\n const configPath = path.join(projectRoot, '.semiont', 'config');\n if (!fs.existsSync(configPath)) {\n fs.mkdirSync(path.join(projectRoot, '.semiont'), { recursive: true });\n fs.writeFileSync(configPath, `[project]\\nname = \"${name}\"\\n`);\n }\n }\n this.name = SemiontProject.readName(projectRoot);\n this.gitSync = SemiontProject.readGitSync(projectRoot);\n\n this.eventsDir = path.join(projectRoot, '.semiont', 'events');\n\n const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');\n this.configDir = path.join(xdgConfig, 'semiont', this.name);\n\n const xdgState = process.env.XDG_STATE_HOME || path.join(os.homedir(), '.local', 'state');\n this.stateDir = path.join(xdgState, 'semiont', this.name);\n this.projectionsDir = path.join(this.stateDir, 'projections');\n this.jobsDir = path.join(this.stateDir, 'jobs');\n this.backendLogsDir = path.join(this.stateDir, 'backend');\n this.backendAppLogFile = path.join(this.backendLogsDir, 'app.log');\n this.backendErrorLogFile = path.join(this.backendLogsDir, 'error.log');\n\n const xdgRuntime = process.env.XDG_RUNTIME_DIR;\n const runtimeBase = xdgRuntime ?? process.env.TMPDIR ?? '/tmp';\n this.runtimeDir = path.join(runtimeBase, 'semiont', this.name);\n this.backendPidFile = path.join(this.runtimeDir, 'backend.pid');\n }\n\n /**\n * Read the current git branch for the project root.\n * Returns null if the project is not a git repo or git is not available.\n */\n gitBranch(): string | null {\n try {\n return execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {\n cwd: this.root,\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n }).trim() || null;\n } catch {\n return null;\n }\n }\n\n /**\n * Delete all ephemeral state for this project (stateDir + runtimeDir).\n * Does not touch eventsDir — the event log is the system of record.\n */\n async destroy(): Promise<void> {\n await Promise.all([\n fs.promises.rm(this.configDir, { recursive: true, force: true }),\n fs.promises.rm(this.stateDir, { recursive: true, force: true }),\n fs.promises.rm(this.runtimeDir, { recursive: true, force: true }),\n ]);\n }\n\n /**\n * The KB's permanent identity literal — `[site] domain` from the committed\n * `.semiont/config`, which `kbDid()` renders as `did:web:<domain>`.\n * `undefined` when the section or key is absent.\n *\n * Reads the committed file DIRECTLY, and deliberately not\n * `EnvironmentConfig.site.domain`, which is the same value only by\n * accident: the TOML loader defaults a domain-less `[site]` to the string\n * `'localhost'` (so every domain-less KB on a machine would claim one\n * fabricated `did:web:localhost`) and lets the environment section\n * override the KB's own declaration. Either would report an identity the\n * launcher never minted — an address wearing a name, which is the whole\n * category error .plans/KB-IDENTITY-VS-ADDRESS.md exists to end. Identity\n * is declared or absent; it is never defaulted.\n */\n siteDomain(): string | undefined {\n const configPath = path.join(this.root, '.semiont', 'config');\n if (!fs.existsSync(configPath)) return undefined;\n const content = fs.readFileSync(configPath, 'utf-8');\n let inSiteSection = false;\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed.startsWith('#')) continue;\n if (trimmed === '[site]') { inSiteSection = true; continue; }\n if (trimmed.startsWith('[')) { inSiteSection = false; continue; }\n if (!inSiteSection) continue;\n const eq = trimmed.indexOf('=');\n if (eq < 0) continue;\n if (trimmed.slice(0, eq).trim() !== 'domain') continue;\n const value = trimmed.slice(eq + 1).trim().replace(/^\"(.*)\"$/, '$1');\n return value === '' ? undefined : value;\n }\n return undefined;\n }\n\n /**\n * Read [git] sync from .semiont/config.\n * Defaults to false if the section or key is absent.\n */\n private static readGitSync(projectRoot: string): boolean {\n const configPath = path.join(projectRoot, '.semiont', 'config');\n if (!fs.existsSync(configPath)) return false;\n const content = fs.readFileSync(configPath, 'utf-8');\n let inGitSection = false;\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed === '[git]') { inGitSection = true; continue; }\n if (trimmed.startsWith('[')) { inGitSection = false; continue; }\n if (inGitSection && trimmed.startsWith('sync') && trimmed.includes('=')) {\n const value = trimmed.split('=')[1]?.trim();\n return value === 'true';\n }\n }\n return false;\n }\n\n /**\n * Read the project name from .semiont/config [project] name = \"...\"\n * Falls back to the directory basename if the config is absent or has no name.\n */\n private static readName(projectRoot: string): string {\n const configPath = path.join(projectRoot, '.semiont', 'config');\n if (fs.existsSync(configPath)) {\n const content = fs.readFileSync(configPath, 'utf-8');\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed.startsWith('name') && trimmed.includes('=')) {\n const [, ...rest] = trimmed.split('=');\n return rest.join('=').trim().replace(/^\"(.*)\"$/, '$1');\n }\n }\n }\n return path.basename(projectRoot);\n }\n}\n","import * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport { createTomlConfigLoader } from './toml-loader.js';\nimport type { EnvironmentConfig } from './config.types.js';\n\nexport { SemiontProject } from '../project.js';\n\nconst nodeTomlFileReader = {\n readIfExists: (filePath: string): string | null =>\n fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : null,\n};\n\n/**\n * Load semiont environment config for a Node.js process.\n *\n * Reads ~/.semiontconfig (global) merged with .semiont/config (project-local),\n * then selects the given environment overlay.\n *\n * This is the canonical config loader for any Node.js process. The environment\n * is resolved by the loader itself — an explicit `environment` argument, else\n * `[defaults] environment` from the committed config — so entry points call this\n * without selecting one; one config selects the environment for the backend the\n * same way the launcher selects it. There is no environment-variable override.\n */\nexport function loadEnvironmentConfig(\n projectRoot: string,\n environment?: string\n): EnvironmentConfig {\n const globalConfigPath = path.join(os.homedir(), '.semiontconfig');\n return createTomlConfigLoader(\n nodeTomlFileReader,\n globalConfigPath,\n process.env\n )(projectRoot, environment);\n}\n"]}
1
+ {"version":3,"sources":["../../src/project.ts","../../src/config/node-config-loader.ts"],"names":["fs2","path2","os2"],"mappings":"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","file":"node-config-loader.js","sourcesContent":["import * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport { execFileSync } from 'child_process';\n\n/**\n * Represents a Semiont project rooted at a given directory.\n *\n * Computes all paths — durable and ephemeral — once at construction time.\n * XDG environment variables are read here and nowhere else.\n *\n * **The paths divide along what they are derived FROM, and so does the type.**\n * Everything ephemeral is composed from the KB's NAME, so it needs no working\n * tree and lives on `SemiontState`. Only the durable half is composed from the\n * root. `SemiontProject extends SemiontState` — a project is its state plus a\n * working tree — which lets a consumer that has no KB root (the gateway, after\n * SINGLE-KB-MOUNT P5) take the smaller type and still be checked by the\n * compiler rather than by a throw at first read.\n *\n * Durable paths (inside the project root, committed or repo-local) — `SemiontProject`:\n * eventsDir — .semiont/events/ (system of record, committed)\n *\n * Ephemeral paths (outside the project root, never committed) — `SemiontState`:\n * configDir — $XDG_CONFIG_HOME/semiont/{name}/ (generated config for managed processes)\n * stateDir — $XDG_STATE_HOME/semiont/{name}/\n * resourcesDir — stateDir/resources/ (the per-resource materialized views)\n * projectionsDir — stateDir/projections/ (KB-global projections + the storage-uri index)\n * jobsDir — stateDir/jobs/\n * anchoredTextDir — supplied by the caller; required, no default\n * runtimeDir — $XDG_RUNTIME_DIR/semiont/{name}/ (or $TMPDIR fallback)\n * gatewayPidFile — runtimeDir/gateway.pid\n *\n * Everything ephemeral that is DERIVED sits under stateDir together —\n * projections (from the event log) and jobs. The anchored-text store is\n * derived too, but its location is declared by the deployment rather than\n * composed here (see anchoredTextDir). That is the XDG distinction, not a\n * filing habit:\n * $XDG_STATE_HOME is for data that persists between restarts but \"is not\n * important or portable enough\" for $XDG_DATA_HOME, and losing any of these\n * costs recomputation rather than information.\n *\n * There is no $XDG_DATA_HOME path here, deliberately. Semiont's own system of\n * record is the committed event log above; the databases live under the\n * launcher's per-root state, not the gateway's. A `dataHome` field existed and\n * had exactly one consumer — the anchored-text store, which belonged in state\n * all along — so it went with the move rather than being left for a\n * hypothetical future user of the DATA tier.\n *\n * Note: the frontend has no entry here, deliberately. It serves static assets\n * from its own container image and keeps no per-project state on the host, so\n * there is nothing to derive from a project root.\n */\n/**\n * The one composition of a project's state-tree root from its name. The\n * Librarian resolves this WITHOUT a SemiontProject — it has no KB root to\n * construct one from (SINGLE-KB-MOUNT P1) — so the join lives here, beside\n * the constructor that also uses it, rather than being restated over there.\n */\nexport function stateDirFor(name: string): string {\n const xdgState = process.env.XDG_STATE_HOME || path.join(os.homedir(), '.local', 'state');\n return path.join(xdgState, 'semiont', name);\n}\n\n/**\n * A KB's state tree, addressed by NAME — everything that needs no working tree.\n *\n * This exists because consumers appeared that genuinely need half of\n * `SemiontProject` and cannot supply the other half: the gateway reads\n * `jobsDir` and the Librarian reads `resourcesDir`, both on the shared\n * state mount, with no readable KB root at all (SINGLE-KB-MOUNT P1/P5).\n *\n * **Split rather than made optional, deliberately.** Relaxing\n * `SemiontProject`'s root-derived fields to optional-and-throw-on-read would\n * have traded a compile-time guarantee for a runtime one, and bought no extra\n * safety doing it: a getter asserts PRESENCE exactly as weakly as a constructor\n * does — neither can tell a real path from a typo. Two types keep \"needs a\n * working tree\" a fact the compiler enforces. Handing a `SemiontState` to\n * something that reads `eventsDir` does not compile.\n *\n * Every field here is required, and every one is derived from `name` alone —\n * which is what makes the name the whole of this type's input. `anchoredTextDir`\n * is deliberately NOT here (SINGLE-KB-MOUNT P6): it is a supplied path rather\n * than a derived one, and its only readers hold a working tree too, so it sits\n * on `SemiontProject` where they already are.\n */\nexport class SemiontState {\n readonly name: string;\n\n // Ephemeral — config (generated config files for managed processes)\n readonly configDir: string;\n\n // Ephemeral — state\n readonly stateDir: string;\n readonly resourcesDir: string;\n readonly projectionsDir: string;\n readonly jobsDir: string;\n\n // Ephemeral — runtime\n readonly runtimeDir: string;\n readonly gatewayPidFile: string;\n\n constructor(opts: { name: string }) {\n this.name = opts.name;\n\n const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');\n this.configDir = path.join(xdgConfig, 'semiont', this.name);\n\n this.stateDir = stateDirFor(this.name);\n this.resourcesDir = path.join(this.stateDir, 'resources');\n this.projectionsDir = path.join(this.stateDir, 'projections');\n this.jobsDir = path.join(this.stateDir, 'jobs');\n\n const xdgRuntime = process.env.XDG_RUNTIME_DIR;\n const runtimeBase = xdgRuntime ?? process.env.TMPDIR ?? '/tmp';\n this.runtimeDir = path.join(runtimeBase, 'semiont', this.name);\n this.gatewayPidFile = path.join(this.runtimeDir, 'gateway.pid');\n }\n}\n\n/** A project is its state plus a working tree. */\nexport class SemiontProject extends SemiontState {\n readonly root: string;\n readonly anchoredTextDir: string;\n\n /** True if [git] sync = true in .semiont/config. When true, semiont stages\n * working-tree and event-log changes in the git index automatically. */\n readonly gitSync: boolean;\n\n // Durable\n readonly eventsDir: string;\n\n /**\n * Seed `.semiont/config` if absent, then read the name back OUT of it.\n *\n * The order is the point, and it is why this is a static rather than inline\n * in the constructor: `super()` needs the resolved name, and the resolved\n * name is whatever the file says — a seed only applies when no file exists,\n * so a KB's committed identity always wins over anything a caller passes.\n */\n private static seedAndReadName(projectRoot: string, seed?: string): string {\n if (seed !== undefined) {\n const configPath = path.join(projectRoot, '.semiont', 'config');\n if (!fs.existsSync(configPath)) {\n fs.mkdirSync(path.join(projectRoot, '.semiont'), { recursive: true });\n fs.writeFileSync(configPath, `[project]\\nname = \"${seed}\"\\n`);\n }\n }\n return SemiontProject.readName(projectRoot);\n }\n\n /**\n * @param projectRoot the KB clone this project describes\n * @param opts.name seed value — see `seedAndReadName`.\n * @param opts.anchoredTextDir where this deployment keeps the anchored-text\n * store. Supplied by the caller; REQUIRED, no default.\n *\n * A default would let a deployment that forgot it write a full OCR pass per\n * representation into a directory nobody mounted, lose it on the next\n * `stop`, and re-derive it forever: silent, expensive, and indistinguishable\n * from working. Passed IN, never read from the environment here — the entry\n * point owns that read, exactly as it owns SEMIONT_ROOT.\n *\n * It lives on THIS type rather than `SemiontState` (SINGLE-KB-MOUNT P6)\n * because every reader of it holds a working tree as well, and the one\n * consumer that needs state paths without a tree — the gateway — does not\n * read it at all.\n */\n constructor(projectRoot: string, opts: { anchoredTextDir: string; name?: string }) {\n super({ name: SemiontProject.seedAndReadName(projectRoot, opts.name) });\n this.anchoredTextDir = opts.anchoredTextDir;\n this.root = projectRoot;\n this.gitSync = SemiontProject.readGitSync(projectRoot);\n this.eventsDir = path.join(projectRoot, '.semiont', 'events');\n }\n\n /**\n * Read the current git branch for the project root.\n * Returns null if the project is not a git repo or git is not available.\n */\n gitBranch(): string | null {\n try {\n return execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {\n cwd: this.root,\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n }).trim() || null;\n } catch {\n return null;\n }\n }\n\n /**\n * Delete all ephemeral state for this project (stateDir + runtimeDir).\n * Does not touch eventsDir — the event log is the system of record.\n */\n async destroy(): Promise<void> {\n await Promise.all([\n fs.promises.rm(this.configDir, { recursive: true, force: true }),\n fs.promises.rm(this.stateDir, { recursive: true, force: true }),\n fs.promises.rm(this.runtimeDir, { recursive: true, force: true }),\n ]);\n }\n\n /**\n * The KB's permanent identity literal — `[site] domain` from the committed\n * `.semiont/config`, which `kbDid()` renders as `did:web:<domain>`.\n * `undefined` when the section or key is absent.\n *\n * Reads the committed file DIRECTLY, and deliberately not\n * `EnvironmentConfig.site.domain`, which is the same value only by\n * accident: the environment section can override the KB's own declaration.\n * That would report an identity the\n * launcher never minted — an address wearing a name, which is the whole\n * category error .plans/KB-IDENTITY-VS-ADDRESS.md exists to end. Identity\n * is declared or absent; it is never defaulted.\n */\n siteDomain(): string | undefined {\n const configPath = path.join(this.root, '.semiont', 'config');\n if (!fs.existsSync(configPath)) return undefined;\n const content = fs.readFileSync(configPath, 'utf-8');\n let inSiteSection = false;\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed.startsWith('#')) continue;\n if (trimmed === '[site]') { inSiteSection = true; continue; }\n if (trimmed.startsWith('[')) { inSiteSection = false; continue; }\n if (!inSiteSection) continue;\n const eq = trimmed.indexOf('=');\n if (eq < 0) continue;\n if (trimmed.slice(0, eq).trim() !== 'domain') continue;\n const value = trimmed.slice(eq + 1).trim().replace(/^\"(.*)\"$/, '$1');\n return value === '' ? undefined : value;\n }\n return undefined;\n }\n\n /**\n * Read [git] sync from .semiont/config.\n * Defaults to false if the section or key is absent.\n */\n private static readGitSync(projectRoot: string): boolean {\n const configPath = path.join(projectRoot, '.semiont', 'config');\n if (!fs.existsSync(configPath)) return false;\n const content = fs.readFileSync(configPath, 'utf-8');\n let inGitSection = false;\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed === '[git]') { inGitSection = true; continue; }\n if (trimmed.startsWith('[')) { inGitSection = false; continue; }\n if (inGitSection && trimmed.startsWith('sync') && trimmed.includes('=')) {\n const value = trimmed.split('=')[1]?.trim();\n return value === 'true';\n }\n }\n return false;\n }\n\n /**\n * Read the project name from .semiont/config [project] name = \"...\"\n * Falls back to the directory basename if the config is absent or has no name.\n */\n private static readName(projectRoot: string): string {\n const configPath = path.join(projectRoot, '.semiont', 'config');\n if (fs.existsSync(configPath)) {\n const content = fs.readFileSync(configPath, 'utf-8');\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed.startsWith('name') && trimmed.includes('=')) {\n const [, ...rest] = trimmed.split('=');\n return rest.join('=').trim().replace(/^\"(.*)\"$/, '$1');\n }\n }\n }\n return path.basename(projectRoot);\n }\n}\n","import * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport { createTomlConfigLoader } from './toml-loader.js';\nimport type { ArchivistServiceConfig, EnvironmentConfig } from './config.types.js';\n\nexport { SemiontProject, SemiontState, stateDirFor } from '../project.js';\n\nconst nodeTomlFileReader = {\n readIfExists: (filePath: string): string | null =>\n fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : null,\n};\n\n/**\n * Load semiont environment config for a Node.js process.\n *\n * Reads ~/.semiontconfig (global) merged with .semiont/config (project-local),\n * then selects the given environment overlay.\n *\n * This is the canonical config loader for any Node.js process. The environment\n * is resolved by the loader itself — an explicit `environment` argument, else\n * `[defaults] environment` from the committed config — so entry points call this\n * without selecting one; one config selects the environment for the gateway the\n * same way the launcher selects it. There is no environment-variable override.\n */\nexport function loadEnvironmentConfig(\n projectRoot: string | null,\n environment?: string\n): EnvironmentConfig {\n const globalConfigPath = path.join(os.homedir(), '.semiontconfig');\n return createTomlConfigLoader(\n nodeTomlFileReader,\n globalConfigPath,\n process.env\n )(projectRoot, environment);\n}\n\n/**\n * The slice of config the Archivist's address needs — nothing wider, and\n * DERIVED from the schema's own service type rather than restating\n * `host`/`port`.\n */\nexport interface ArchivistAddressConfig {\n services?: { archivist?: Pick<ArchivistServiceConfig, 'host' | 'port'> };\n}\n\n/**\n * Base URL and auth header for the Archivist, resolved together because they\n * are useless apart. Throws on either absence.\n *\n * Lives HERE, and not with the byte reads that ride it, because it is neither\n * a content concern nor a make-meaning one: it is a config value plus an\n * environment variable, which is exactly what this module already is. Putting\n * it in `@semiont/content` gave the gateway a runtime edge to a package it\n * otherwise touches only for types — and since that package is a\n * devDependency there, the bundler INLINED its PDF/OCR stack into an ESM\n * bundle and the process died at load on a CJS `require`.\n *\n * Absence fails loudly. A missing host or secret is a misconfiguration, never\n * a reason to fall back to reading a tree locally: the point of\n * SINGLE-KB-MOUNT is that exactly one process touches it.\n */\nexport function archivistEndpoint(config: ArchivistAddressConfig): {\n base: string;\n headers: { authorization: string };\n} {\n const host = config.services?.archivist?.host;\n if (!host) {\n throw new Error('services.archivist.host is not configured — cannot reach the record');\n }\n const port = config.services?.archivist?.port ?? 9093;\n const secret = process.env.SEMIONT_WORKER_SECRET;\n if (!secret) {\n throw new Error('SEMIONT_WORKER_SECRET is not set — cannot authenticate to the Archivist');\n }\n return { base: `http://${host}:${port}`, headers: { authorization: `Bearer ${secret}` } };\n}\n"]}