@semiont/core 0.5.28 → 0.5.30
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.
- package/README.md +9 -9
- package/dist/{chunk-CUPZGI7I.js → chunk-EWOITHBH.js} +24 -4
- package/dist/chunk-EWOITHBH.js.map +1 -0
- package/dist/{chunk-PPUO5IPX.js → chunk-IQGH6UJN.js} +7 -7
- package/dist/{chunk-PPUO5IPX.js.map → chunk-IQGH6UJN.js.map} +1 -1
- package/dist/{chunk-JWTQJVKM.js → chunk-UGI6IOOX.js} +35 -17
- package/dist/chunk-UGI6IOOX.js.map +1 -0
- package/dist/config/node-config-loader.d.ts +147 -93
- package/dist/config/node-config-loader.js +77 -50
- package/dist/config/node-config-loader.js.map +1 -1
- package/dist/index.d.ts +390 -746
- package/dist/index.js +68 -6
- package/dist/index.js.map +1 -1
- package/dist/openapi.d.ts +21 -0
- package/dist/openapi.js +3002 -1373
- package/dist/openapi.js.map +1 -1
- package/dist/testing/axioms.js +2 -2
- package/dist/testing.d.ts +4 -4
- package/dist/testing.js +2 -2
- package/package.json +4 -4
- package/dist/chunk-CUPZGI7I.js.map +0 -1
- package/dist/chunk-JWTQJVKM.js.map +0 -1
|
@@ -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` "
|
|
131
|
+
* via the `definition` "GatewayServiceConfig".
|
|
132
132
|
*/
|
|
133
|
-
interface
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
585
|
-
|
|
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
|
|
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
|
|
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.
|
|
609
|
-
*
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
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
|
|
637
|
-
*
|
|
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
|
|
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,74 +1,91 @@
|
|
|
1
|
-
import { createTomlConfigLoader } from '../chunk-
|
|
1
|
+
import { createTomlConfigLoader, DEFAULT_ARCHIVIST_PORT } from '../chunk-UGI6IOOX.js';
|
|
2
2
|
import '../chunk-YLJ4XMA6.js';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as os from 'os';
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { execFileSync } from 'child_process';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
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 {
|
|
10
13
|
name;
|
|
11
|
-
/** True if [git] sync = true in .semiont/config. When true, semiont stages
|
|
12
|
-
* working-tree and event-log changes in the git index automatically. */
|
|
13
|
-
gitSync;
|
|
14
|
-
// Durable
|
|
15
|
-
eventsDir;
|
|
16
14
|
// Ephemeral — config (generated config files for managed processes)
|
|
17
15
|
configDir;
|
|
18
16
|
// Ephemeral — state
|
|
19
17
|
stateDir;
|
|
18
|
+
resourcesDir;
|
|
20
19
|
projectionsDir;
|
|
21
20
|
jobsDir;
|
|
22
|
-
anchoredTextDir;
|
|
23
|
-
backendLogsDir;
|
|
24
|
-
backendAppLogFile;
|
|
25
|
-
backendErrorLogFile;
|
|
26
21
|
// Ephemeral — runtime
|
|
27
22
|
runtimeDir;
|
|
28
|
-
|
|
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;
|
|
29
46
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* store. Passed IN, never read from the environment here: the entry point
|
|
37
|
-
* owns that read, exactly as it owns SEMIONT_ROOT. Required, and with no
|
|
38
|
-
* default — a default would let a deployment that forgot it write a full
|
|
39
|
-
* OCR pass per representation into a directory nobody mounted, lose it on
|
|
40
|
-
* the next `stop`, and re-derive it forever: silent, expensive, and
|
|
41
|
-
* 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.
|
|
42
53
|
*/
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
this.anchoredTextDir = opts.anchoredTextDir;
|
|
46
|
-
this.root = projectRoot;
|
|
47
|
-
if (name !== void 0) {
|
|
54
|
+
static seedAndReadName(projectRoot, seed) {
|
|
55
|
+
if (seed !== void 0) {
|
|
48
56
|
const configPath = path.join(projectRoot, ".semiont", "config");
|
|
49
57
|
if (!fs.existsSync(configPath)) {
|
|
50
58
|
fs.mkdirSync(path.join(projectRoot, ".semiont"), { recursive: true });
|
|
51
59
|
fs.writeFileSync(configPath, `[project]
|
|
52
|
-
name = "${
|
|
60
|
+
name = "${seed}"
|
|
53
61
|
`);
|
|
54
62
|
}
|
|
55
63
|
}
|
|
56
|
-
|
|
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;
|
|
57
87
|
this.gitSync = _SemiontProject.readGitSync(projectRoot);
|
|
58
88
|
this.eventsDir = path.join(projectRoot, ".semiont", "events");
|
|
59
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
60
|
-
this.configDir = path.join(xdgConfig, "semiont", this.name);
|
|
61
|
-
const xdgState = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
|
|
62
|
-
this.stateDir = path.join(xdgState, "semiont", this.name);
|
|
63
|
-
this.projectionsDir = path.join(this.stateDir, "projections");
|
|
64
|
-
this.jobsDir = path.join(this.stateDir, "jobs");
|
|
65
|
-
this.backendLogsDir = path.join(this.stateDir, "backend");
|
|
66
|
-
this.backendAppLogFile = path.join(this.backendLogsDir, "app.log");
|
|
67
|
-
this.backendErrorLogFile = path.join(this.backendLogsDir, "error.log");
|
|
68
|
-
const xdgRuntime = process.env.XDG_RUNTIME_DIR;
|
|
69
|
-
const runtimeBase = xdgRuntime ?? process.env.TMPDIR ?? "/tmp";
|
|
70
|
-
this.runtimeDir = path.join(runtimeBase, "semiont", this.name);
|
|
71
|
-
this.backendPidFile = path.join(this.runtimeDir, "backend.pid");
|
|
72
89
|
}
|
|
73
90
|
/**
|
|
74
91
|
* Read the current git branch for the project root.
|
|
@@ -103,10 +120,8 @@ name = "${name}"
|
|
|
103
120
|
*
|
|
104
121
|
* Reads the committed file DIRECTLY, and deliberately not
|
|
105
122
|
* `EnvironmentConfig.site.domain`, which is the same value only by
|
|
106
|
-
* accident: the
|
|
107
|
-
*
|
|
108
|
-
* fabricated `did:web:localhost`) and lets the environment section
|
|
109
|
-
* 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
|
|
110
125
|
* launcher never minted — an address wearing a name, which is the whole
|
|
111
126
|
* category error .plans/KB-IDENTITY-VS-ADDRESS.md exists to end. Identity
|
|
112
127
|
* is declared or absent; it is never defaulted.
|
|
@@ -194,7 +209,19 @@ function loadEnvironmentConfig(projectRoot, environment) {
|
|
|
194
209
|
process.env
|
|
195
210
|
)(projectRoot, environment);
|
|
196
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 ?? DEFAULT_ARCHIVIST_PORT;
|
|
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
|
+
}
|
|
197
224
|
|
|
198
|
-
export { SemiontProject, loadEnvironmentConfig };
|
|
225
|
+
export { SemiontProject, SemiontState, archivistEndpoint, loadEnvironmentConfig, stateDirFor };
|
|
199
226
|
//# sourceMappingURL=node-config-loader.js.map
|
|
200
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, DEFAULT_ARCHIVIST_PORT } 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 ?? DEFAULT_ARCHIVIST_PORT;\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"]}
|