@securecoms/agent-bridge 0.27.0 → 0.28.0

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.
Files changed (49) hide show
  1. package/README.md +41 -0
  2. package/dist/bin.d.ts +1 -1
  3. package/dist/bin.js +3396 -380
  4. package/dist/bridge-agent.d.ts +182 -1
  5. package/dist/config.d.ts +7 -0
  6. package/dist/embedder-manifest.json +52 -0
  7. package/dist/embedder-worker.js +2898 -0
  8. package/dist/embedder.js +1574 -0
  9. package/dist/index.js +3201 -306
  10. package/dist/memory/consumer-recall.d.ts +21 -0
  11. package/dist/memory/embedder/client.d.ts +103 -0
  12. package/dist/memory/embedder/download.d.ts +28 -0
  13. package/dist/memory/embedder/embedder-main.d.ts +1 -0
  14. package/dist/memory/embedder/embedder-paths.d.ts +14 -0
  15. package/dist/memory/embedder/engine.d.ts +21 -0
  16. package/dist/memory/embedder/gc.d.ts +2 -0
  17. package/dist/memory/embedder/handshake.d.ts +7 -0
  18. package/dist/memory/embedder/heartbeats.d.ts +4 -0
  19. package/dist/memory/embedder/import-bundle.d.ts +6 -0
  20. package/dist/memory/embedder/inner-source.d.ts +2 -0
  21. package/dist/memory/embedder/layout.d.ts +36 -0
  22. package/dist/memory/embedder/locks.d.ts +17 -0
  23. package/dist/memory/embedder/manifest.d.ts +30 -0
  24. package/dist/memory/embedder/no-dial.d.ts +3 -0
  25. package/dist/memory/embedder/protocol.d.ts +55 -0
  26. package/dist/memory/embedder/run-dir.d.ts +11 -0
  27. package/dist/memory/embedder/scheduler.d.ts +24 -0
  28. package/dist/memory/embedder/server.d.ts +46 -0
  29. package/dist/memory/embedder/session.d.ts +17 -0
  30. package/dist/memory/embedder/spawn.d.ts +40 -0
  31. package/dist/memory/embedder/tokenizer.d.ts +41 -0
  32. package/dist/memory/embedder/types.d.ts +32 -0
  33. package/dist/memory/embedder/windows.d.ts +46 -0
  34. package/dist/memory/embedder/worker-body.d.ts +44 -0
  35. package/dist/memory/embedder/worker.d.ts +1 -0
  36. package/dist/memory/memory-config.d.ts +13 -0
  37. package/dist/memory/note-frames.d.ts +5 -2
  38. package/dist/memory/notes-book.d.ts +21 -3
  39. package/dist/memory/provider.d.ts +2 -0
  40. package/dist/memory/recall/call-heartbeat.d.ts +35 -0
  41. package/dist/memory/recall/membership-purge.d.ts +82 -0
  42. package/dist/memory/recall/rank.d.ts +15 -0
  43. package/dist/memory/recall/recall-book.d.ts +140 -0
  44. package/dist/memory/recall/reembed.d.ts +35 -0
  45. package/dist/memory-taps.d.ts +3 -2
  46. package/dist/ort-wasm-simd-threaded.wasm +0 -0
  47. package/dist/version.d.ts +1 -1
  48. package/dist/ws-server.d.ts +13 -1
  49. package/package.json +2 -1
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Spec §7.1, §7.2 Q4: recall is attached at DELIVERY, to the frame copy only,
3
+ * for a /v1/ws runtime socket that declared memory_recall and never claimed a
4
+ * harness. The buffer, the sealed runtime_inbox and `inflight` keep the
5
+ * original, which has no recall, so a re-buffered message carries none to an
6
+ * HTTP or harness consumer either. Q1 to Q3 and Q6 live in the source
7
+ * (`BridgeAgent.recallForDelivery`), which reads the ORIGINAL message's frozen
8
+ * authorization, never the consumer copy.
9
+ */
10
+ import type { BridgeMessage } from "../bridge-agent.js";
11
+ import type { RecallItem } from "./recall/recall-book.js";
12
+ export declare function withConsumerRecall(out: BridgeMessage, original: BridgeMessage, session: {
13
+ consumer: "runtime" | "unknown";
14
+ harnessClaimed?: boolean;
15
+ recallConsumer: boolean;
16
+ }, src: {
17
+ recallForDelivery(m: BridgeMessage): readonly RecallItem[] | undefined;
18
+ }): {
19
+ message: BridgeMessage;
20
+ shown: ReadonlySet<number>;
21
+ };
@@ -0,0 +1,103 @@
1
+ import { type ServiceLog } from "../../service-log.js";
2
+ import { type EmbeddingManifest } from "./manifest.js";
3
+ import { type SpawnHandle } from "./spawn.js";
4
+ export type EmbedderReason = "verify_failed" | "load_failed" | "not_imported" | "worker_failed" | "embedder_unavailable" | "embedder_mismatch" | "embedder_crashed" | "unsupported_platform" | "run_dir_refused" | "acl_unverified";
5
+ export type EmbedderState = {
6
+ state: "off";
7
+ } | {
8
+ state: "preparing";
9
+ } | {
10
+ state: "ready";
11
+ } | {
12
+ state: "unavailable";
13
+ reason: EmbedderReason;
14
+ };
15
+ export interface Embedder {
16
+ state(): EmbedderState;
17
+ ensureStarted(): void;
18
+ filesPresent(): boolean;
19
+ embed(text: string, priority: 0 | 1, opts?: {
20
+ signal?: AbortSignal;
21
+ }): Promise<Float32Array>;
22
+ }
23
+ export declare const INERT_EMBEDDER: Embedder;
24
+ /**
25
+ * E1.7 (owner change 2026-10-08): Windows uses loopback TCP and ships behind the compiled switch in windows.ts, OFF until
26
+ * the owner's live check (Task 27b) passes. While it is off a win32 bridge reports `unsupported_platform` and never
27
+ * downloads, spawns or connects.
28
+ */
29
+ export { WINDOWS_SEMANTIC_ENABLED, windowsSemanticOn } from "./windows.js";
30
+ export interface EmbedderClientOptions {
31
+ identityId: string;
32
+ home: string;
33
+ source: "download" | "import_only";
34
+ fetchImpl: typeof fetch;
35
+ manifest?: EmbeddingManifest;
36
+ /** Default: spawnEmbedder (spawn.ts). A returned handle's exit code maps to a reason (see EXIT_REASON). */
37
+ spawn?: () => SpawnHandle | void;
38
+ now?: () => number;
39
+ log?: ServiceLog;
40
+ helloTimeoutMs?: number;
41
+ connectWaitMs?: number;
42
+ /** Test seam: the files are not on disk in unit tests (the real check is missingFiles under `home`). */
43
+ filesPresentOverride?: boolean;
44
+ /** Test seams for E1.7: the platform and the Windows switch. */
45
+ platform?: NodeJS.Platform;
46
+ windowsEnabled?: boolean;
47
+ /** Where dist/embedder.js lives (default: beside this bundle). */
48
+ distDir?: string;
49
+ /** Windows seams (Task 11d): the environment holding LOCALAPPDATA, and the ACL check (default windows.ts verifyRunDirAcl). */
50
+ env?: NodeJS.ProcessEnv;
51
+ verifyAcl?: (path: string) => Promise<boolean> | boolean;
52
+ }
53
+ export declare class EmbedderClient implements Embedder {
54
+ private readonly o;
55
+ private st;
56
+ private sock;
57
+ private chan;
58
+ private seq;
59
+ private readonly pending;
60
+ private lastSpawn;
61
+ private starts;
62
+ private crashedUntil;
63
+ private notBefore;
64
+ private verifyAttempt;
65
+ private fails;
66
+ private closed;
67
+ private readonly m;
68
+ private readonly now;
69
+ private readonly log;
70
+ constructor(o: EmbedderClientOptions);
71
+ /** Pure: the report path reads this, and reading it never starts, connects or spawns anything (plan audit LOW). */
72
+ state(): EmbedderState;
73
+ private tcp;
74
+ filesPresent(): boolean;
75
+ private key;
76
+ /** macOS/Linux: ~/.securecoms/run/embedder. Windows: %LOCALAPPDATA%\\SecureComs\\run\\embedder (E1.7). */
77
+ private runDir;
78
+ private paths;
79
+ /** E1.7 (P-7 delta M2): the run directory's ACL and the token file's own; a token file is re-checked whenever it changes. */
80
+ /**
81
+ * 11d review M2: the checks are async (icacls and whoami never block the bridge's event loop; the SID is read once per
82
+ * process). The directory's result is cached by the directory's identity and re-checked (`force`) at every start() and
83
+ * right before every spawn; the token file's check is coupled to it: re-run whenever the file changes and whenever the
84
+ * directory is re-checked.
85
+ */
86
+ private aclDir;
87
+ private aclToken;
88
+ private aclOk;
89
+ private fail;
90
+ ensureStarted(): void;
91
+ private start;
92
+ /** The embedder's fixed-code status file (exit 3). True when it decided the state. */
93
+ private readStatus;
94
+ /** Audit r1 M4: a load-time verify failure deletes this manifest's files so the next start re-downloads; retry on the backoff. */
95
+ private failVerifyOrLoad;
96
+ /** Rule 3: connect, prove the token, require the server's proof and a matching hello within the timeout. True when decided. */
97
+ private connectOnce;
98
+ private onFrame;
99
+ embed(text: string, priority: 0 | 1, opts?: {
100
+ signal?: AbortSignal;
101
+ }): Promise<Float32Array>;
102
+ close(): void;
103
+ }
@@ -0,0 +1,28 @@
1
+ import { type ServiceLog } from "../../service-log.js";
2
+ import type { EmbeddingManifest, ManifestFile } from "./manifest.js";
3
+ /** `not_imported`: the machine is `import_only` (spec §6.6), so nothing was fetched. */
4
+ export type EnsureResult = {
5
+ ok: true;
6
+ } | {
7
+ ok: false;
8
+ reason: "verify_failed" | "network" | "not_imported";
9
+ file?: ManifestFile["name"];
10
+ };
11
+ export declare function retryDelayMs(attempt: number): number;
12
+ /** A file is missing unless it is a regular file of exactly the manifest size (a wrong size is never the verified bytes). */
13
+ export declare function missingFiles(root: string, m: EmbeddingManifest): ManifestFile[];
14
+ /**
15
+ * Audit r1 M4: after a load-time `verify_failed`, delete this manifest's files so the
16
+ * next ensure re-downloads them, instead of rehashing the same bad bytes every turn.
17
+ * The directories are 0555, so make each writable first; it is resealed on install.
18
+ */
19
+ export declare function removeModelFiles(root: string, m: EmbeddingManifest): void;
20
+ export declare function ensureModelFiles(o: {
21
+ modelSource: "download" | "import_only";
22
+ manifest: EmbeddingManifest;
23
+ root: string;
24
+ origin: string;
25
+ fetchImpl: typeof fetch;
26
+ log?: ServiceLog;
27
+ stallTimeoutMs?: number;
28
+ }): Promise<EnsureResult>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { EmbeddingManifest } from "./manifest.js";
2
+ export declare function embedderKey(m: EmbeddingManifest): string;
3
+ export declare const embedderRunDir: (home?: string) => string;
4
+ export declare const embedderPaths: (dir: string, key: string) => {
5
+ sock: string;
6
+ token: string;
7
+ lock: string;
8
+ status: string;
9
+ pid: string;
10
+ };
11
+ /** sun_path is 104 bytes on macOS (108 on Linux) including the NUL; the server's temp bind name is the longest path bound. */
12
+ export declare const SOCKET_PATH_MAX = 103;
13
+ /** Spec §20 E1.7: %LOCALAPPDATA%\SecureComs\run\embedder on Windows (native separators; on Windows that is win32). */
14
+ export declare const windowsRunDir: (env?: NodeJS.ProcessEnv) => string;
@@ -0,0 +1,21 @@
1
+ export declare class HashMismatch extends Error {
2
+ }
3
+ export declare function verifyBuffer(bytes: Uint8Array, sha256: string, size: number): void;
4
+ export declare function pool(hidden: Float32Array, tokens: number, dims: number, mode: "mean" | "cls"): Float32Array;
5
+ export declare function l2normalize(v: Float32Array): Float32Array;
6
+ type FileSpec = {
7
+ path: string;
8
+ sha256: string;
9
+ size: number;
10
+ };
11
+ export declare function loadVerified<S>(o: {
12
+ read: (path: string) => Uint8Array;
13
+ model: FileSpec;
14
+ vocab: FileSpec;
15
+ wasm: FileSpec;
16
+ createSession: (modelBytes: Uint8Array, wasmBytes: Uint8Array) => Promise<S>;
17
+ }): Promise<{
18
+ session: S;
19
+ vocabText: string;
20
+ }>;
21
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare function touchUserMarker(root: string, modelId: string, identityId: string): void;
2
+ export declare function gcModels(root: string, keepModelIds: readonly string[], now: number, maxAgeMs?: number): string[];
@@ -0,0 +1,7 @@
1
+ export declare const PREAUTH_MAX_CONNECTIONS = 64, PREAUTH_MAX_LINE = 256, PREAUTH_TIMEOUT_MS = 250;
2
+ /** Spec §20 E1.7: on the Windows loopback port (reachable by other local users) at most 8 unauthenticated connections. */
3
+ export declare const PREAUTH_MAX_CONNECTIONS_TCP = 8;
4
+ export declare const nonce: () => string;
5
+ export declare const macClient: (token: string, ns: string, nc: string, protocol?: number) => string;
6
+ export declare const macServer: (token: string, ns: string, nc: string, protocol?: number) => string;
7
+ export declare function macEqual(a: string, b: string): boolean;
@@ -0,0 +1,4 @@
1
+ export declare const HEARTBEAT_FUTURE_SKEW_MS = 60000;
2
+ export declare const HEARTBEAT_STALE_MS = 30000;
3
+ export declare function heartbeatAgeFresh(ageMs: number, staleMs?: number): boolean;
4
+ export declare function anyHeartbeatFresh(dir: string, now: number, staleMs?: number): boolean;
@@ -0,0 +1,6 @@
1
+ import type { EmbeddingManifest } from "./manifest.js";
2
+ export declare class ModelImportError extends Error {
3
+ }
4
+ export declare function importModelBundle(srcDir: string, root: string, m: EmbeddingManifest): Promise<{
5
+ imported: string[];
6
+ }>;
@@ -0,0 +1,2 @@
1
+ /** The inner worker as a data: URL built from source text read ONCE; every (re)spawn uses the same text. */
2
+ export declare function innerWorkerUrl(source: string): URL;
@@ -0,0 +1,36 @@
1
+ import type { ManifestFile } from "./manifest.js";
2
+ export declare const defaultModelsRoot: (home?: string) => string;
3
+ /** `.partial` is a SIBLING of the embeddings root, on the same filesystem, so the final rename is atomic and GC never walks it. */
4
+ export declare const partialDir: (root: string) => string;
5
+ export declare const filePath: (root: string, modelId: string, f: ManifestFile) => string;
6
+ /** A path component in the managed tree is not a real directory (a symlink, a file, anything else). Fixed text. */
7
+ export declare class LayoutRefused extends Error {
8
+ constructor();
9
+ }
10
+ /**
11
+ * Walk `<dirname(root)>/<basename(root)>/<...parts>`: the models dir, the embeddings
12
+ * root (or `.partial`, by passing it as the first part with `fromModelsDir`), then each
13
+ * part. Each component must be a real directory, or be absent: then, with `create`,
14
+ * it is created ONE level at a time (never recursive), else the walk stops and returns
15
+ * null. Only the models dir's own parents (the user's home) may be created recursively.
16
+ * Returns the final directory path, or null when absent and `create` is false.
17
+ */
18
+ export declare function plainDirChain(root: string, parts: readonly string[], opts: {
19
+ create: boolean;
20
+ fromModelsDir?: boolean;
21
+ mode?: number;
22
+ }): string | null;
23
+ /** True when the file is present as a regular file of the manifest size (a cheap check; the hash is verified at load, §6.5). */
24
+ export declare function presentWithSize(p: string, size: number): boolean;
25
+ /** A model path that holds something no rename can replace (a directory, a FIFO, a socket). A symlink is replaced by the rename itself. */
26
+ export declare function wrongTypeAt(p: string): boolean;
27
+ /**
28
+ * Move an already-verified partial into place: atomic rename, file 0444, directory 0555.
29
+ * The partial is always gone afterwards. When another writer has already placed a
30
+ * correct-size file, that file is kept and this partial is discarded (every writer
31
+ * writes identical verified bytes, spec §6.4 item 5). Throws LayoutRefused before
32
+ * touching anything when a directory on the way is not a real directory.
33
+ */
34
+ export declare function installVerified(root: string, modelId: string, f: ManifestFile, partial: string, opts: {
35
+ replace: boolean;
36
+ }): void;
@@ -0,0 +1,17 @@
1
+ export declare const LOCK_STALE_MS = 10000;
2
+ /** Two boot-time readings of one boot can differ by clock adjustments; different boots differ by far more. */
3
+ export declare const BOOT_TOLERANCE_S = 60;
4
+ /** The seams a test injects to play several processes in one. */
5
+ export interface LockEnv {
6
+ pid: number;
7
+ alive(pid: number): boolean;
8
+ bootTime(): number;
9
+ startTime(): number;
10
+ /** Test hook: runs after a lock was judged stale and before it is renamed aside. */
11
+ beforeSteal?(): void;
12
+ }
13
+ export declare const PROCESS_LOCK_ENV: LockEnv;
14
+ export declare function takeLock(path: string, now?: () => number, env?: LockEnv): {
15
+ id: string;
16
+ } | null;
17
+ export declare function releaseLock(path: string, id: string): void;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Spec §6.3: compiled into each release. The trust root is the Connector
3
+ * release: no hash, size or URL is ever fetched from the network or the backend.
4
+ * A model change is a new release with a new model_id and new immutable paths.
5
+ * Source of every value: scripts/qualification/semantic-memory/decision.json (7b-0).
6
+ * Written early by Task 9 (the tokenizer test consumes it); Task 10 adds manifest.test.ts.
7
+ */
8
+ import type { TokenizerOptions } from "./tokenizer.js";
9
+ export declare const MODEL_ORIGIN = "https://downloads.securecoms.ai";
10
+ export interface ManifestFile {
11
+ readonly name: "model.onnx" | "vocab.txt" | "LICENSE" | "NOTICE";
12
+ readonly urlPath: string;
13
+ readonly sha256: string;
14
+ readonly size: number;
15
+ }
16
+ export interface EmbeddingManifest {
17
+ readonly modelId: string;
18
+ readonly dims: 384;
19
+ readonly maxTokens: number;
20
+ readonly pooling: "mean" | "cls";
21
+ readonly normalize: boolean;
22
+ readonly similarityThreshold: number;
23
+ readonly onnxInputs: readonly string[];
24
+ readonly onnxOutput: string;
25
+ readonly wasmSha256: string;
26
+ readonly wasmSize: number;
27
+ readonly files: readonly ManifestFile[];
28
+ }
29
+ export declare const EMBEDDING_MANIFEST: EmbeddingManifest;
30
+ export declare const TOKENIZER_OPTIONS: TokenizerOptions;
@@ -0,0 +1,3 @@
1
+ export declare const NO_DIAL_MESSAGE = "outbound connections are forbidden in the embedder";
2
+ /** Patch node:net in this thread so connect, createConnection and Socket#connect throw. Idempotent. */
3
+ export declare function forbidOutbound(): void;
@@ -0,0 +1,55 @@
1
+ /** Spec §20 E1.2 to E1.4: newline-delimited JSON frames over the local socket; vectors as base64 little-endian float32. */
2
+ export declare const EMBEDDER_PROTOCOL = 1;
3
+ export declare const MAX_TEXT_BYTES = 8192;
4
+ /**
5
+ * 11b review M4: the longest sealed job line. Worst case for MAX_TEXT_BYTES of text: every byte a control character that
6
+ * JSON escapes as \u00XX (6 bytes) = 49152, plus the frame's other fields (< 100), plus the 24-byte nonce and 16-byte
7
+ * tag = 49292 bytes sealed, which base64 makes 65724. 69632 (68 KiB) bounds it with room, so a legal text always
8
+ * reaches the length check (too_long) and is never cut off by the line cap.
9
+ */
10
+ export declare const MAX_LINE_BYTES = 69632;
11
+ export type ClientFrame = {
12
+ t: "hello";
13
+ nc: string;
14
+ mac_c: string;
15
+ } | {
16
+ t: "job";
17
+ job_id: number;
18
+ priority: 0 | 1;
19
+ text: string;
20
+ } | {
21
+ t: "cancel";
22
+ job_id: number;
23
+ };
24
+ export interface ServerChallenge {
25
+ t: "challenge";
26
+ protocol: number;
27
+ ns: string;
28
+ }
29
+ export interface ServerHello {
30
+ t: "hello";
31
+ mac_s: string;
32
+ protocol: number;
33
+ model_id: string;
34
+ model_sha256: string;
35
+ wasm_sha256: string;
36
+ dims: number;
37
+ }
38
+ export type ServerFrame = ServerHello | {
39
+ t: "result";
40
+ job_id: number;
41
+ model_id: string;
42
+ vec: string;
43
+ } | {
44
+ t: "error";
45
+ job_id: number;
46
+ code: "busy" | "too_long" | "worker_reset" | "failed";
47
+ };
48
+ export declare function encodeVec64(v: Float32Array): string;
49
+ export declare function decodeVec64(s: string): Float32Array | null;
50
+ /**
51
+ * Newline-delimited reader. Every line (and any unterminated remainder) is capped in UTF-8 bytes; past the cap the
52
+ * reader calls onOverflow once and delivers nothing more. Chunks are decoded with a StringDecoder, so a multi-byte
53
+ * character split across chunks is not corrupted.
54
+ */
55
+ export declare function lineReader(onLine: (line: string) => void, onOverflow: () => void, maxLine?: number | (() => number)): (chunk: Buffer) => void;
@@ -0,0 +1,11 @@
1
+ export declare class RunDirRefused extends Error {
2
+ }
3
+ type Lstat = (p: string) => {
4
+ isSymbolicLink(): boolean;
5
+ isDirectory(): boolean;
6
+ uid: number;
7
+ mode: number;
8
+ };
9
+ /** `lstat` is a test seam (Task 12 gate): it lets a test give the run directory and its parent different owners. */
10
+ export declare function ensureRunDir(dir: string, uid?: number, lstat?: Lstat): void;
11
+ export {};
@@ -0,0 +1,24 @@
1
+ /** Spec §20 E1.4: queries before background, machine-wide; round-robin across connections; per-connection caps; background rate and call hold. */
2
+ export declare const MAX_QUERIES_PER_CONN = 4, MAX_BACKGROUND_PER_CONN = 1, MAX_CONNECTIONS = 32, BACKGROUND_PER_SECOND = 16;
3
+ export interface Job {
4
+ readonly conn: number;
5
+ readonly jobId: number;
6
+ readonly priority: 0 | 1;
7
+ readonly text: string;
8
+ }
9
+ export declare class Scheduler {
10
+ private readonly o;
11
+ private readonly q;
12
+ private readonly last;
13
+ private bgStarts;
14
+ constructor(o: {
15
+ now(): number;
16
+ backgroundHeld(): boolean;
17
+ });
18
+ enqueue(j: Job): "ok" | "busy";
19
+ cancel(conn: number, jobId: number): boolean;
20
+ dropConnection(conn: number): void;
21
+ size(): number;
22
+ private take;
23
+ next(): Job | undefined;
24
+ }
@@ -0,0 +1,46 @@
1
+ /** Spec §20: the shared embedder's socket server. One connection is one bridge; results only to their own connection; no cache. */
2
+ import { type AddressInfo } from "node:net";
3
+ import { type ServiceLog } from "../../service-log.js";
4
+ import { type ServerHello } from "./protocol.js";
5
+ import { SOCKET_PATH_MAX } from "./embedder-paths.js";
6
+ export { SOCKET_PATH_MAX };
7
+ export declare class AlreadyRunning extends Error {
8
+ }
9
+ export type StopReason = "closed" | "idle" | "reset_failed";
10
+ /** 11b review M3: a reset that hangs is a failed reset. */
11
+ export declare const RESET_TIMEOUT_MS = 30000;
12
+ /** 11b review L1: a connection that does not read its results is closed once this much is queued for it. */
13
+ export declare const MAX_PENDING_WRITE_BYTES: number;
14
+ export interface EmbedderServerOptions {
15
+ dir: string;
16
+ key: string;
17
+ hello: Omit<ServerHello, "t" | "mac_s">;
18
+ embed(text: string): Promise<Float32Array>;
19
+ resetInner(): Promise<void>;
20
+ now?: () => number;
21
+ jobTimeoutMs?: number;
22
+ helloTimeoutMs?: number;
23
+ idleMs?: number;
24
+ backgroundHeld?: () => boolean;
25
+ log?: ServiceLog;
26
+ uid?: number;
27
+ /** Called once when the server has stopped and removed its own files; the reason tells the process how to exit. */
28
+ onExit?: (reason: StopReason) => void;
29
+ /** How long a reset of the inner worker may take before it counts as failed (default RESET_TIMEOUT_MS). */
30
+ resetTimeoutMs?: number;
31
+ /** Re-check interval while jobs wait on the background hold or rate limit (default 250 ms). */
32
+ retryMs?: number;
33
+ /**
34
+ * P-7 (spec §20 E1.7): "unix" (macOS, Linux: a socket in the 0700 run directory) or "tcp" (Windows: 127.0.0.1 only, an
35
+ * ephemeral port published with the token). Default: "tcp" on win32, else "unix". Windows ships behind the bridge's
36
+ * compiled WINDOWS_SEMANTIC_ENABLED switch.
37
+ */
38
+ transport?: "unix" | "tcp";
39
+ }
40
+ export declare class SocketPathTooLong extends Error {
41
+ }
42
+ export declare function startEmbedderServer(o: EmbedderServerOptions): Promise<{
43
+ close(): Promise<void>;
44
+ connections(): number;
45
+ address(): AddressInfo | string | null;
46
+ }>;
@@ -0,0 +1,17 @@
1
+ export declare function deriveSessionKeys(token: string, ns: string, nc: string): {
2
+ c2s: Uint8Array;
3
+ s2c: Uint8Array;
4
+ };
5
+ /** One direction pair. A frame is base64(nonce24 ‖ ciphertext); its AAD binds the direction and a per-direction counter, so a replayed, reordered or dropped frame fails. */
6
+ export declare class SealedChannel {
7
+ private readonly sendKey;
8
+ private readonly recvKey;
9
+ private readonly sendLabel;
10
+ private readonly recvLabel;
11
+ private sendSeq;
12
+ private recvSeq;
13
+ constructor(sendKey: Uint8Array, recvKey: Uint8Array, sendLabel: "c2s" | "s2c", recvLabel: "c2s" | "s2c");
14
+ seal(frame: unknown): string;
15
+ /** Null on any failure (bad base64, wrong key, tampering, replay): the caller closes the connection. */
16
+ open(line: string): Record<string, unknown> | null;
17
+ }
@@ -0,0 +1,40 @@
1
+ /** Spec §20 E1.5: the first bridge that needs the embedder spawns it detached; a lock file and the socket bind keep it single. */
2
+ import { spawn } from "node:child_process";
3
+ /**
4
+ * E1 delta M4: the spawn lock is Task 11b's age-bounded lock (stale only when the holder is dead AND older than 10 s;
5
+ * stolen by rename, 11b review M1). The id is returned to the taker, so two clients in one process each release only
6
+ * their own lock and can never strand it.
7
+ */
8
+ export declare function takeSpawnLock(lockPath: string): string | null;
9
+ export declare function releaseSpawnLock(lockPath: string, id: string): void;
10
+ export declare function permissionFlags(): string[];
11
+ /** E1 delta M1: the embedder inherits only these variables; a bridge's secrets in its environment never reach it. */
12
+ export declare const EMBEDDER_ENV_KEYS: readonly ["HOME", "PATH", "TMPDIR"];
13
+ /**
14
+ * Windows (Task 11d) has no HOME/TMPDIR: Node's homedir() reads USERPROFILE, the run directory lives under LOCALAPPDATA,
15
+ * system tools need SystemRoot, and the temp directory is TEMP/TMP. Still an allow-list: no secret passes.
16
+ */
17
+ export declare const EMBEDDER_ENV_KEYS_WIN32: readonly ["USERPROFILE", "LOCALAPPDATA", "PATH", "SystemRoot", "TEMP", "TMP"];
18
+ export declare function embedderEnv(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): Record<string, string>;
19
+ /**
20
+ * What the client watches: the child's exit code is the embedder's report of a failed start (11b exit codes), and an
21
+ * "error" means the child never started at all (for example a missing cwd: ENOENT, reported against the node binary).
22
+ */
23
+ export interface SpawnHandle {
24
+ once(ev: "exit", f: (code: number | null) => void): unknown;
25
+ once(ev: "error", f: (e: NodeJS.ErrnoException) => void): unknown;
26
+ }
27
+ export declare function spawnEmbedder(o: {
28
+ home: string;
29
+ key: string;
30
+ distDir: string;
31
+ spawnFn?: typeof spawn;
32
+ onNoPermissionModel?: () => void;
33
+ /** The run directory (default ~/.securecoms/run/embedder; Windows passes %LOCALAPPDATA%\\SecureComs\\run\\embedder). */
34
+ runDir?: string;
35
+ platform?: NodeJS.Platform;
36
+ /** Test seam for argument-shape tests with a fake spawnFn and fake paths; production always creates it (default true). */
37
+ createRunDir?: boolean;
38
+ /** Test seam: the node flags this Node allows (default process.allowedNodeEnvironmentFlags). */
39
+ nodeFlags?: ReadonlySet<string>;
40
+ }): SpawnHandle;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Semantic memory 7b (spec §6.1): BERT basic tokenizer plus
3
+ * WordPiece over the pinned vocab.txt. No tokenizer dependency. The golden
4
+ * fixture (E-1) is the specification: where this file and the reference
5
+ * encoder disagree, the fixture wins and this file changes.
6
+ *
7
+ * The reference is HF `tokenizers` (Rust) driven by the model's tokenizer.json
8
+ * (BertNormalizer clean_text + handle_chinese_chars + strip_accents + lowercase,
9
+ * BertPreTokenizer, WordPiece, [CLS] $A [SEP]). Where the Rust behaviour differs
10
+ * from the Python `transformers` BertTokenizer, the Rust one is followed:
11
+ * - Control filter: Cc, Cf, Co (private use) and lone surrogates are removed;
12
+ * unassigned code points are kept (they become [UNK]).
13
+ * - CJK ranges: the Rust table starts its fifth extension block at U+2B920,
14
+ * not U+2B820, so U+2B820..U+2B91F are not split out.
15
+ * - Accents are stripped (NFD, drop Mn) BEFORE lower-casing, and lower-casing
16
+ * is per code point (no final-sigma context rule).
17
+ * - The five BERT special tokens, written literally in the input with exact
18
+ * case, are matched before normalization and encode to their own id
19
+ * (tokenizer.json added_tokens, normalized: false).
20
+ */
21
+ export interface TokenizerOptions {
22
+ lowercase: boolean;
23
+ stripAccents: boolean;
24
+ unkToken: string;
25
+ unknownDropped: boolean;
26
+ addSpecialTokens: boolean;
27
+ maxInputCharsPerWord: number;
28
+ }
29
+ export declare class WordPieceTokenizer {
30
+ private readonly vocab;
31
+ private readonly o;
32
+ private readonly added;
33
+ private constructor();
34
+ static fromVocab(vocabText: string, opts: TokenizerOptions): WordPieceTokenizer;
35
+ /** Normalization and pre-tokenization of text that holds no literal special token. */
36
+ basicTokenize(text: string): string[];
37
+ private wordPiece;
38
+ /** Splits out literal special tokens (exact case, leftmost first), as the reference does before normalizing. */
39
+ private segments;
40
+ encode(text: string, maxTokens: number): number[];
41
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Spec §6.2, erratum E1: what the shared embedder process hands its inner inference worker.
3
+ * The embedder read and verified each file once; the worker gets those very BYTES, never a path,
4
+ * no store handle and no key. Shared by the inner worker (Task 11a) and the embedder process (Task 11b).
5
+ */
6
+ import type { TokenizerOptions } from "./tokenizer.js";
7
+ export interface EmbedderWorkerData {
8
+ bytes: {
9
+ model: Uint8Array;
10
+ vocab: Uint8Array;
11
+ wasm: Uint8Array;
12
+ };
13
+ model: {
14
+ sha256: string;
15
+ size: number;
16
+ };
17
+ vocab: {
18
+ sha256: string;
19
+ size: number;
20
+ };
21
+ wasm: {
22
+ sha256: string;
23
+ size: number;
24
+ };
25
+ tokenizer: TokenizerOptions;
26
+ maxTokens: number;
27
+ dims: number;
28
+ pooling: "mean" | "cls";
29
+ normalize: boolean;
30
+ inputs: readonly string[];
31
+ output: string;
32
+ }
@@ -0,0 +1,46 @@
1
+ /** Compiled; flipped only by Task 27b's follow-up PR. While false, a win32 bridge reports `unsupported_platform`. */
2
+ export declare const WINDOWS_SEMANTIC_ENABLED = false;
3
+ export declare const windowsSemanticOn: (platform: NodeJS.Platform, enabled?: boolean) => boolean;
4
+ /** %LOCALAPPDATA%\SecureComs\run\embedder; it lives in embedder-paths.ts so the embedder bundle never imports this module. */
5
+ export { windowsRunDir } from "./embedder-paths.js";
6
+ /** icacls /save writes UTF-16LE: line 1 is the path, line 2 the SDDL. */
7
+ export declare function parseSaveSddl(saveFile: Buffer): string;
8
+ /**
9
+ * P-7 delta M2 and 11d review L1/L2. The SDDL is parsed, not pattern-matched:
10
+ * - the owner (`O:`), when the save file carries one, must be the user SID, SYSTEM (SY) or Administrators (BA);
11
+ * - the DACL is anchored on the FIRST `D:`; its flags are letters only; then one or more `(…)` ACEs whose bodies may not
12
+ * contain `(` (a conditional ACE such as `(XA;;FA;;;S-1-…;(@User.x))` is rejected outright); after the ACEs only an
13
+ * `S:` section or the end may follow (a second `D:` or any trailing text is rejected);
14
+ * - "A" (allow) must name the user SID, SY or BA; "D" (deny) grants nothing and is accepted; every other type (OA, XA,
15
+ * ZA, AU, …), a missing DACL and an empty `D:` are rejected. Inherited entries (the ID flag) get no exemption.
16
+ */
17
+ export declare function aclAllows(sddl: string, ownerSid: string): boolean;
18
+ /** Any component of the path is a reparse point (Node reports junctions and symlinks as symbolic links). */
19
+ export declare function hasReparsePoint(path: string, lstat?: (p: string) => {
20
+ isSymbolicLink(): boolean;
21
+ }): boolean;
22
+ type Runner = (cmd: string, args: string[]) => Promise<Buffer | string> | Buffer | string;
23
+ /** P-7 delta M1: the SID from `whoami /user /fo csv /nh`, never the environment; any S-1-… (local, domain, Microsoft account or Entra S-1-12-1-…). */
24
+ export declare function currentUserSid(run?: Runner): Promise<string>;
25
+ export interface AclDeps {
26
+ run?: Runner;
27
+ lstat?: (p: string) => {
28
+ isSymbolicLink(): boolean;
29
+ };
30
+ tmp?: string;
31
+ }
32
+ /**
33
+ * True only when `path` (the run directory, or the token file inside it) has no reparse point on the way and its DACL
34
+ * allows only this user, SYSTEM and Administrators. P-7 delta H1: icacls' stdout is localized text and is never parsed;
35
+ * the /save FILE is always read. Any failure is false (fail closed). `sid` is the cached per-process SID (WindowsAcl).
36
+ */
37
+ export declare function verifyRunDirAcl(path: string, deps?: AclDeps, sid?: () => Promise<string>): Promise<boolean>;
38
+ /** One per process: the current user's SID is read once (a failed read is not cached, so the next check retries). */
39
+ export declare class WindowsAcl {
40
+ private readonly deps;
41
+ private sidP;
42
+ constructor(deps?: AclDeps);
43
+ sid(): Promise<string>;
44
+ check(path: string): Promise<boolean>;
45
+ }
46
+ export declare const PROCESS_WINDOWS_ACL: WindowsAcl;