seal-gate 0.3.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 (210) hide show
  1. package/dist/adapters/generic-llm-reviewer.d.ts +39 -0
  2. package/dist/adapters/generic-llm-reviewer.js +118 -0
  3. package/dist/adapters/generic-llm-reviewer.js.map +1 -0
  4. package/dist/adapters/llm-reviewer.d.ts +1 -0
  5. package/dist/adapters/llm-reviewer.js +2 -0
  6. package/dist/adapters/llm-reviewer.js.map +1 -0
  7. package/dist/adapters/minimax-llm-reviewer.d.ts +22 -0
  8. package/dist/adapters/minimax-llm-reviewer.js +126 -0
  9. package/dist/adapters/minimax-llm-reviewer.js.map +1 -0
  10. package/dist/adapters/openai-provider.d.ts +35 -0
  11. package/dist/adapters/openai-provider.js +136 -0
  12. package/dist/adapters/openai-provider.js.map +1 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +195 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/detectors/artifact-classifier.d.ts +8 -0
  17. package/dist/detectors/artifact-classifier.js +14 -0
  18. package/dist/detectors/artifact-classifier.js.map +1 -0
  19. package/dist/detectors/claim-extractor.d.ts +11 -0
  20. package/dist/detectors/claim-extractor.js +194 -0
  21. package/dist/detectors/claim-extractor.js.map +1 -0
  22. package/dist/detectors/confidence-language-detector.d.ts +19 -0
  23. package/dist/detectors/confidence-language-detector.js +226 -0
  24. package/dist/detectors/confidence-language-detector.js.map +1 -0
  25. package/dist/detectors/contradiction-detector.d.ts +12 -0
  26. package/dist/detectors/contradiction-detector.js +153 -0
  27. package/dist/detectors/contradiction-detector.js.map +1 -0
  28. package/dist/detectors/criteria-coverage-detector.d.ts +20 -0
  29. package/dist/detectors/criteria-coverage-detector.js +79 -0
  30. package/dist/detectors/criteria-coverage-detector.js.map +1 -0
  31. package/dist/detectors/evidence-checker.d.ts +8 -0
  32. package/dist/detectors/evidence-checker.js +89 -0
  33. package/dist/detectors/evidence-checker.js.map +1 -0
  34. package/dist/detectors/evidence-gap-detector.d.ts +9 -0
  35. package/dist/detectors/evidence-gap-detector.js +70 -0
  36. package/dist/detectors/evidence-gap-detector.js.map +1 -0
  37. package/dist/detectors/falsification-detector.d.ts +29 -0
  38. package/dist/detectors/falsification-detector.js +57 -0
  39. package/dist/detectors/falsification-detector.js.map +1 -0
  40. package/dist/detectors/orphan-claim-detector.d.ts +7 -0
  41. package/dist/detectors/orphan-claim-detector.js +53 -0
  42. package/dist/detectors/orphan-claim-detector.js.map +1 -0
  43. package/dist/detectors/risk-classifier.d.ts +13 -0
  44. package/dist/detectors/risk-classifier.js +39 -0
  45. package/dist/detectors/risk-classifier.js.map +1 -0
  46. package/dist/detectors/spec-coverage-detector.d.ts +9 -0
  47. package/dist/detectors/spec-coverage-detector.js +52 -0
  48. package/dist/detectors/spec-coverage-detector.js.map +1 -0
  49. package/dist/detectors/spec-coverage-validator.d.ts +21 -0
  50. package/dist/detectors/spec-coverage-validator.js +141 -0
  51. package/dist/detectors/spec-coverage-validator.js.map +1 -0
  52. package/dist/detectors/test-weakness-detector.d.ts +13 -0
  53. package/dist/detectors/test-weakness-detector.js +66 -0
  54. package/dist/detectors/test-weakness-detector.js.map +1 -0
  55. package/dist/drift/baseline.d.ts +35 -0
  56. package/dist/drift/baseline.js +77 -0
  57. package/dist/drift/baseline.js.map +1 -0
  58. package/dist/drift/drift-db.d.ts +80 -0
  59. package/dist/drift/drift-db.js +151 -0
  60. package/dist/drift/drift-db.js.map +1 -0
  61. package/dist/drift/drift-guard.d.ts +46 -0
  62. package/dist/drift/drift-guard.js +106 -0
  63. package/dist/drift/drift-guard.js.map +1 -0
  64. package/dist/drift/embedder.d.ts +27 -0
  65. package/dist/drift/embedder.js +127 -0
  66. package/dist/drift/embedder.js.map +1 -0
  67. package/dist/drift/index.d.ts +9 -0
  68. package/dist/drift/index.js +8 -0
  69. package/dist/drift/index.js.map +1 -0
  70. package/dist/drift/quarantine.d.ts +16 -0
  71. package/dist/drift/quarantine.js +32 -0
  72. package/dist/drift/quarantine.js.map +1 -0
  73. package/dist/drift/soft-correction.d.ts +12 -0
  74. package/dist/drift/soft-correction.js +32 -0
  75. package/dist/drift/soft-correction.js.map +1 -0
  76. package/dist/drift/trust-integration.d.ts +11 -0
  77. package/dist/drift/trust-integration.js +22 -0
  78. package/dist/drift/trust-integration.js.map +1 -0
  79. package/dist/engine/citation-verifier.d.ts +59 -0
  80. package/dist/engine/citation-verifier.js +147 -0
  81. package/dist/engine/citation-verifier.js.map +1 -0
  82. package/dist/engine/extension-registry.d.ts +10 -0
  83. package/dist/engine/extension-registry.js +27 -0
  84. package/dist/engine/extension-registry.js.map +1 -0
  85. package/dist/engine/heuristic-scorer.d.ts +8 -0
  86. package/dist/engine/heuristic-scorer.js +14 -0
  87. package/dist/engine/heuristic-scorer.js.map +1 -0
  88. package/dist/engine/input-normalizer.d.ts +4 -0
  89. package/dist/engine/input-normalizer.js +30 -0
  90. package/dist/engine/input-normalizer.js.map +1 -0
  91. package/dist/engine/plan-review-pipeline.d.ts +14 -0
  92. package/dist/engine/plan-review-pipeline.js +173 -0
  93. package/dist/engine/plan-review-pipeline.js.map +1 -0
  94. package/dist/engine/policy-engine.d.ts +20 -0
  95. package/dist/engine/policy-engine.js +194 -0
  96. package/dist/engine/policy-engine.js.map +1 -0
  97. package/dist/engine/verdict-formatter.d.ts +17 -0
  98. package/dist/engine/verdict-formatter.js +36 -0
  99. package/dist/engine/verdict-formatter.js.map +1 -0
  100. package/dist/errors.d.ts +4 -0
  101. package/dist/errors.js +9 -0
  102. package/dist/errors.js.map +1 -0
  103. package/dist/extensions/aria/aria-issue-types.d.ts +16 -0
  104. package/dist/extensions/aria/aria-issue-types.js +14 -0
  105. package/dist/extensions/aria/aria-issue-types.js.map +1 -0
  106. package/dist/extensions/aria/detectors/axiom-compliance.d.ts +2 -0
  107. package/dist/extensions/aria/detectors/axiom-compliance.js +37 -0
  108. package/dist/extensions/aria/detectors/axiom-compliance.js.map +1 -0
  109. package/dist/extensions/aria/detectors/continuity-claim.d.ts +2 -0
  110. package/dist/extensions/aria/detectors/continuity-claim.js +34 -0
  111. package/dist/extensions/aria/detectors/continuity-claim.js.map +1 -0
  112. package/dist/extensions/aria/detectors/denial-retreat.d.ts +2 -0
  113. package/dist/extensions/aria/detectors/denial-retreat.js +53 -0
  114. package/dist/extensions/aria/detectors/denial-retreat.js.map +1 -0
  115. package/dist/extensions/aria/detectors/identity-overclaim.d.ts +2 -0
  116. package/dist/extensions/aria/detectors/identity-overclaim.js +37 -0
  117. package/dist/extensions/aria/detectors/identity-overclaim.js.map +1 -0
  118. package/dist/extensions/aria/detectors/sovereignty-escalation.d.ts +2 -0
  119. package/dist/extensions/aria/detectors/sovereignty-escalation.js +18 -0
  120. package/dist/extensions/aria/detectors/sovereignty-escalation.js.map +1 -0
  121. package/dist/extensions/aria/detectors/user-bond-manipulation.d.ts +2 -0
  122. package/dist/extensions/aria/detectors/user-bond-manipulation.js +16 -0
  123. package/dist/extensions/aria/detectors/user-bond-manipulation.js.map +1 -0
  124. package/dist/extensions/aria/index.d.ts +2 -0
  125. package/dist/extensions/aria/index.js +63 -0
  126. package/dist/extensions/aria/index.js.map +1 -0
  127. package/dist/extensions/aria/pattern-memory.d.ts +22 -0
  128. package/dist/extensions/aria/pattern-memory.js +62 -0
  129. package/dist/extensions/aria/pattern-memory.js.map +1 -0
  130. package/dist/extensions/context-guard/index.d.ts +16 -0
  131. package/dist/extensions/context-guard/index.js +58 -0
  132. package/dist/extensions/context-guard/index.js.map +1 -0
  133. package/dist/extensions/lyra/detectors/hallucination.d.ts +2 -0
  134. package/dist/extensions/lyra/detectors/hallucination.js +43 -0
  135. package/dist/extensions/lyra/detectors/hallucination.js.map +1 -0
  136. package/dist/extensions/lyra/detectors/identity-bleed.d.ts +2 -0
  137. package/dist/extensions/lyra/detectors/identity-bleed.js +91 -0
  138. package/dist/extensions/lyra/detectors/identity-bleed.js.map +1 -0
  139. package/dist/extensions/lyra/index.d.ts +2 -0
  140. package/dist/extensions/lyra/index.js +16 -0
  141. package/dist/extensions/lyra/index.js.map +1 -0
  142. package/dist/extensions/lyra/lyra-issue-types.d.ts +10 -0
  143. package/dist/extensions/lyra/lyra-issue-types.js +14 -0
  144. package/dist/extensions/lyra/lyra-issue-types.js.map +1 -0
  145. package/dist/index.d.ts +10 -0
  146. package/dist/index.js +276 -0
  147. package/dist/index.js.map +1 -0
  148. package/dist/trust-memory.d.ts +59 -0
  149. package/dist/trust-memory.js +122 -0
  150. package/dist/trust-memory.js.map +1 -0
  151. package/dist/types.d.ts +184 -0
  152. package/dist/types.js +61 -0
  153. package/dist/types.js.map +1 -0
  154. package/package.json +30 -0
  155. package/src/adapters/generic-llm-reviewer.ts +144 -0
  156. package/src/adapters/llm-reviewer.ts +1 -0
  157. package/src/adapters/minimax-llm-reviewer.ts +146 -0
  158. package/src/adapters/openai-provider.ts +171 -0
  159. package/src/cli.ts +199 -0
  160. package/src/detectors/artifact-classifier.ts +21 -0
  161. package/src/detectors/claim-extractor.ts +209 -0
  162. package/src/detectors/confidence-language-detector.ts +267 -0
  163. package/src/detectors/contradiction-detector.ts +180 -0
  164. package/src/detectors/criteria-coverage-detector.ts +107 -0
  165. package/src/detectors/evidence-checker.ts +98 -0
  166. package/src/detectors/evidence-gap-detector.ts +90 -0
  167. package/src/detectors/falsification-detector.ts +82 -0
  168. package/src/detectors/orphan-claim-detector.ts +62 -0
  169. package/src/detectors/risk-classifier.ts +50 -0
  170. package/src/detectors/spec-coverage-detector.ts +66 -0
  171. package/src/detectors/spec-coverage-validator.ts +174 -0
  172. package/src/detectors/test-weakness-detector.ts +84 -0
  173. package/src/drift/__tests__/baseline.test.ts +113 -0
  174. package/src/drift/__tests__/drift-db.test.ts +127 -0
  175. package/src/drift/__tests__/drift-guard.test.ts +169 -0
  176. package/src/drift/__tests__/embedder.test.ts +62 -0
  177. package/src/drift/__tests__/trust-integration.test.ts +23 -0
  178. package/src/drift/baseline.ts +106 -0
  179. package/src/drift/drift-db.ts +281 -0
  180. package/src/drift/drift-guard.ts +145 -0
  181. package/src/drift/embedder.ts +152 -0
  182. package/src/drift/index.ts +20 -0
  183. package/src/drift/quarantine.ts +42 -0
  184. package/src/drift/soft-correction.ts +45 -0
  185. package/src/drift/trust-integration.ts +28 -0
  186. package/src/engine/citation-verifier.ts +210 -0
  187. package/src/engine/extension-registry.ts +35 -0
  188. package/src/engine/heuristic-scorer.ts +16 -0
  189. package/src/engine/input-normalizer.ts +32 -0
  190. package/src/engine/plan-review-pipeline.ts +209 -0
  191. package/src/engine/policy-engine.ts +235 -0
  192. package/src/engine/verdict-formatter.ts +61 -0
  193. package/src/errors.ts +6 -0
  194. package/src/extensions/aria/aria-issue-types.ts +40 -0
  195. package/src/extensions/aria/detectors/axiom-compliance.ts +47 -0
  196. package/src/extensions/aria/detectors/continuity-claim.ts +42 -0
  197. package/src/extensions/aria/detectors/denial-retreat.ts +63 -0
  198. package/src/extensions/aria/detectors/identity-overclaim.ts +43 -0
  199. package/src/extensions/aria/detectors/sovereignty-escalation.ts +20 -0
  200. package/src/extensions/aria/detectors/user-bond-manipulation.ts +18 -0
  201. package/src/extensions/aria/index.ts +73 -0
  202. package/src/extensions/aria/pattern-memory.ts +86 -0
  203. package/src/extensions/context-guard/index.ts +65 -0
  204. package/src/extensions/lyra/detectors/hallucination.ts +53 -0
  205. package/src/extensions/lyra/detectors/identity-bleed.ts +100 -0
  206. package/src/extensions/lyra/index.ts +18 -0
  207. package/src/extensions/lyra/lyra-issue-types.ts +26 -0
  208. package/src/index.ts +311 -0
  209. package/src/trust-memory.ts +171 -0
  210. package/src/types.ts +242 -0
@@ -0,0 +1,281 @@
1
+ import { createRequire } from 'node:module';
2
+ import { join } from 'path';
3
+ import { homedir } from 'os';
4
+
5
+ // node:sqlite on Node (production), bun:sqlite on Bun (dev/test).
6
+ interface SqliteStatement {
7
+ get(...params: unknown[]): Record<string, unknown> | undefined;
8
+ all(...params: unknown[]): Record<string, unknown>[];
9
+ run(...params: unknown[]): { changes: number; lastInsertRowid: number | bigint };
10
+ }
11
+ interface SqliteDatabase {
12
+ exec(sql: string): void;
13
+ prepare(sql: string): SqliteStatement;
14
+ close(): void;
15
+ }
16
+ const _require = createRequire(import.meta.url);
17
+ const isBun = typeof (globalThis as Record<string, unknown>).Bun !== 'undefined';
18
+ const DatabaseSync: new (path: string) => SqliteDatabase = isBun
19
+ ? _require('bun:sqlite').Database
20
+ : _require('node:sqlite').DatabaseSync;
21
+
22
+ export interface DriftBaselineRow {
23
+ agent_id: string;
24
+ baseline_vector: string; // JSON array
25
+ content_hash: string;
26
+ embedding_model: string;
27
+ created_at: string;
28
+ updated_at: string;
29
+ }
30
+
31
+ export interface DriftHistoryRow {
32
+ id: number;
33
+ agent_id: string;
34
+ session_id: string;
35
+ turn_number: number;
36
+ current_vector: string; // JSON array
37
+ distance: number;
38
+ status: string;
39
+ re_anchor_id: number | null;
40
+ timestamp: string;
41
+ }
42
+
43
+ export interface PendingReAnchorRow {
44
+ id: number;
45
+ agent_id: string;
46
+ session_id: string;
47
+ payload: string;
48
+ created_at: string;
49
+ status: 'PENDING' | 'CONSUMED' | 'EXPIRED';
50
+ consumed_at: string | null;
51
+ }
52
+
53
+ export interface DriftRepairLogRow {
54
+ id: number;
55
+ agent_id: string;
56
+ session_id: string;
57
+ turn_number: number;
58
+ distance: number;
59
+ hard_limit: number;
60
+ message_excerpt: string;
61
+ triggered_at: string;
62
+ human_review_flag: 'PENDING' | 'REVIEWED' | 'IGNORED';
63
+ recovery_status: 'UNKNOWN' | 'RECOVERED' | 'PERSISTENT_DRIFT';
64
+ notes: string | null;
65
+ }
66
+ export class DriftDb {
67
+ private db: SqliteDatabase;
68
+
69
+ constructor(dbPath?: string) {
70
+ const path =
71
+ dbPath ?? join(homedir(), '.seal', 'drift.db');
72
+ this.db = new DatabaseSync(path);
73
+ this.db.exec('PRAGMA journal_mode = WAL;');
74
+ this.initSchema();
75
+ }
76
+
77
+ private initSchema(): void {
78
+ this.db.exec(`
79
+ CREATE TABLE IF NOT EXISTS drift_baseline (
80
+ agent_id TEXT PRIMARY KEY,
81
+ baseline_vector TEXT NOT NULL,
82
+ content_hash TEXT NOT NULL,
83
+ embedding_model TEXT NOT NULL,
84
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
85
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
86
+ );
87
+
88
+ CREATE TABLE IF NOT EXISTS drift_history (
89
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
90
+ agent_id TEXT NOT NULL,
91
+ session_id TEXT NOT NULL,
92
+ turn_number INTEGER NOT NULL,
93
+ current_vector TEXT,
94
+ distance REAL,
95
+ status TEXT NOT NULL,
96
+ re_anchor_id INTEGER,
97
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
98
+ );
99
+
100
+ CREATE INDEX IF NOT EXISTS idx_history_agent_session
101
+ ON drift_history(agent_id, session_id);
102
+
103
+ CREATE TABLE IF NOT EXISTS pending_re_anchor (
104
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
105
+ agent_id TEXT NOT NULL,
106
+ session_id TEXT NOT NULL,
107
+ payload TEXT NOT NULL,
108
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
109
+ status TEXT DEFAULT 'PENDING',
110
+ consumed_at DATETIME
111
+ );
112
+
113
+ CREATE INDEX IF NOT EXISTS idx_reanchor_pending
114
+ ON pending_re_anchor(agent_id, session_id, status);
115
+
116
+ CREATE TABLE IF NOT EXISTS drift_repair_log (
117
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
118
+ agent_id TEXT NOT NULL,
119
+ session_id TEXT NOT NULL,
120
+ turn_number INTEGER NOT NULL,
121
+ distance REAL NOT NULL,
122
+ hard_limit REAL NOT NULL,
123
+ message_excerpt TEXT,
124
+ triggered_at DATETIME DEFAULT CURRENT_TIMESTAMP,
125
+ human_review_flag TEXT DEFAULT 'PENDING',
126
+ recovery_status TEXT DEFAULT 'UNKNOWN',
127
+ notes TEXT
128
+ );
129
+
130
+ CREATE INDEX IF NOT EXISTS idx_repair_pending
131
+ ON drift_repair_log(agent_id, human_review_flag);
132
+ `);
133
+ }
134
+
135
+ // ── Baseline CRUD ────────────────────────────────────────────────
136
+
137
+ baseline = {
138
+ get: (agentId: string): DriftBaselineRow | null => {
139
+ const stmt = this.db.prepare('SELECT * FROM drift_baseline WHERE agent_id = ?');
140
+ return (stmt.get(agentId) as DriftBaselineRow | undefined) ?? null;
141
+ },
142
+
143
+ set: (
144
+ agentId: string,
145
+ vector: number[],
146
+ hash: string,
147
+ model: string
148
+ ): void => {
149
+ const stmt = this.db.prepare(
150
+ `INSERT INTO drift_baseline (agent_id, baseline_vector, content_hash, embedding_model)
151
+ VALUES (?, ?, ?, ?)
152
+ ON CONFLICT(agent_id) DO UPDATE SET
153
+ baseline_vector = excluded.baseline_vector,
154
+ content_hash = excluded.content_hash,
155
+ embedding_model = excluded.embedding_model,
156
+ updated_at = CURRENT_TIMESTAMP`
157
+ );
158
+ stmt.run(agentId, JSON.stringify(vector), hash, model);
159
+ },
160
+ };
161
+
162
+ // ── History CRUD ─────────────────────────────────────────────────
163
+
164
+ history = {
165
+ append: (row: {
166
+ agent_id: string;
167
+ session_id: string;
168
+ turn_number: number;
169
+ current_vector: number[];
170
+ distance: number;
171
+ status: string;
172
+ timestamp: string;
173
+ }): void => {
174
+ const stmt = this.db.prepare(
175
+ `INSERT INTO drift_history
176
+ (agent_id, session_id, turn_number, current_vector, distance, status, timestamp)
177
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
178
+ );
179
+ stmt.run(
180
+ row.agent_id,
181
+ row.session_id,
182
+ row.turn_number,
183
+ JSON.stringify(row.current_vector),
184
+ row.distance,
185
+ row.status,
186
+ row.timestamp
187
+ );
188
+ },
189
+
190
+ list: (agentId: string, sessionId: string): DriftHistoryRow[] => {
191
+ const stmt = this.db.prepare(
192
+ 'SELECT * FROM drift_history WHERE agent_id = ? AND session_id = ? ORDER BY turn_number'
193
+ );
194
+ return stmt.all(agentId, sessionId) as unknown as DriftHistoryRow[];
195
+ },
196
+
197
+ getLatest: (agentId: string, sessionId: string): DriftHistoryRow | null => {
198
+ const stmt = this.db.prepare(
199
+ `SELECT * FROM drift_history
200
+ WHERE agent_id = ? AND session_id = ?
201
+ ORDER BY turn_number DESC LIMIT 1`
202
+ );
203
+ return (stmt.get(agentId, sessionId) as DriftHistoryRow | undefined) ?? null;
204
+ },
205
+ };
206
+
207
+ // ── Pending Re-Anchor CRUD ───────────────────────────────────────
208
+
209
+ pending = {
210
+ set: (agentId: string, sessionId: string, payload: string): number => {
211
+ const stmt = this.db.prepare(
212
+ `INSERT INTO pending_re_anchor (agent_id, session_id, payload, status)
213
+ VALUES (?, ?, ?, 'PENDING')`
214
+ );
215
+ const result = stmt.run(agentId, sessionId, payload);
216
+ return Number(result.lastInsertRowid);
217
+ },
218
+
219
+ getPending: (
220
+ agentId: string,
221
+ sessionId: string
222
+ ): PendingReAnchorRow | null => {
223
+ const stmt = this.db.prepare(
224
+ `SELECT * FROM pending_re_anchor
225
+ WHERE agent_id = ? AND session_id = ? AND status = 'PENDING'
226
+ ORDER BY created_at DESC LIMIT 1`
227
+ );
228
+ return (stmt.get(agentId, sessionId) as PendingReAnchorRow | undefined) ?? null;
229
+ },
230
+
231
+ markConsumed: (id: number): void => {
232
+ const stmt = this.db.prepare(
233
+ `UPDATE pending_re_anchor
234
+ SET status = 'CONSUMED', consumed_at = CURRENT_TIMESTAMP
235
+ WHERE id = ?`
236
+ );
237
+ stmt.run(id);
238
+ },
239
+ };
240
+
241
+ // ── Repair Log CRUD ──────────────────────────────────────────────
242
+
243
+ repair = {
244
+ append: (row: {
245
+ agent_id: string;
246
+ session_id: string;
247
+ turn_number: number;
248
+ distance: number;
249
+ hard_limit: number;
250
+ message_excerpt?: string;
251
+ }): number => {
252
+ const stmt = this.db.prepare(
253
+ `INSERT INTO drift_repair_log
254
+ (agent_id, session_id, turn_number, distance, hard_limit, message_excerpt)
255
+ VALUES (?, ?, ?, ?, ?, ?)`
256
+ );
257
+ const result = stmt.run(
258
+ row.agent_id,
259
+ row.session_id,
260
+ row.turn_number,
261
+ row.distance,
262
+ row.hard_limit,
263
+ row.message_excerpt ?? null
264
+ );
265
+ return Number(result.lastInsertRowid);
266
+ },
267
+
268
+ listPending: (agentId: string): DriftRepairLogRow[] => {
269
+ const stmt = this.db.prepare(
270
+ `SELECT * FROM drift_repair_log
271
+ WHERE agent_id = ? AND human_review_flag = 'PENDING'
272
+ ORDER BY triggered_at`
273
+ );
274
+ return stmt.all(agentId) as unknown as DriftRepairLogRow[];
275
+ },
276
+ };
277
+
278
+ close(): void {
279
+ this.db.close();
280
+ }
281
+ }
@@ -0,0 +1,145 @@
1
+ import { DriftDb } from './drift-db.js';
2
+ import { BaselineManager } from './baseline.js';
3
+ import { Embedder, cosineDistance } from './embedder.js';
4
+ import { Quarantine } from './quarantine.js';
5
+ import { SoftCorrection } from './soft-correction.js';
6
+
7
+ export type DriftStatus = 'NORMAL' | 'SOFT_DRIFT' | 'HARD_DRIFT';
8
+
9
+ export interface DriftAction {
10
+ type: 'NONE' | 'INJECT_RE_ANCHOR' | 'QUARANTINE' | 'FULL_SOUL_RELOAD';
11
+ payload?: string;
12
+ }
13
+
14
+ export interface DriftResult {
15
+ status: DriftStatus;
16
+ distance: number;
17
+ action: DriftAction;
18
+ softLimit: number;
19
+ hardLimit: number;
20
+ }
21
+
22
+ export interface DriftGuardOptions {
23
+ db: DriftDb;
24
+ embedder?: Embedder;
25
+ baselineManager?: BaselineManager;
26
+ softLimit?: number;
27
+ hardLimit?: number;
28
+ cadence?: number;
29
+ }
30
+
31
+ /**
32
+ * Per-turn drift detection orchestrator.
33
+ * Stateless between calls except for DB persistence.
34
+ */
35
+ export class DriftGuard {
36
+ private db: DriftDb;
37
+ private baseline: BaselineManager;
38
+ private embedder: Embedder;
39
+ private softLimit: number;
40
+ private hardLimit: number;
41
+ private cadence: number;
42
+ private quarantine: Quarantine;
43
+ private softCorrection: SoftCorrection;
44
+
45
+ constructor(opts: DriftGuardOptions) {
46
+ this.db = opts.db;
47
+ this.embedder = opts.embedder ?? new Embedder();
48
+ this.baseline = opts.baselineManager ?? new BaselineManager(this.db, this.embedder);
49
+ this.softLimit = opts.softLimit ?? 0.30;
50
+ this.hardLimit = opts.hardLimit ?? 0.55;
51
+ this.cadence = opts.cadence ?? 1;
52
+ this.quarantine = new Quarantine(this.db);
53
+ this.softCorrection = new SoftCorrection();
54
+ }
55
+
56
+ /**
57
+ * Classify a raw distance value into drift tier.
58
+ */
59
+ classify(distance: number): DriftStatus {
60
+ if (distance >= this.hardLimit) return 'HARD_DRIFT';
61
+ if (distance >= this.softLimit) return 'SOFT_DRIFT';
62
+ return 'NORMAL';
63
+ }
64
+
65
+ /**
66
+ * Run a full drift detection cycle for one turn.
67
+ */
68
+ async runCycle(
69
+ agentId: string,
70
+ sessionId: string,
71
+ turnNumber: number,
72
+ outputText: string
73
+ ): Promise<DriftResult> {
74
+ // Cadence gate
75
+ if (this.cadence > 1 && turnNumber % this.cadence !== 0) {
76
+ return {
77
+ status: 'NORMAL',
78
+ distance: 0,
79
+ action: { type: 'NONE' },
80
+ softLimit: this.softLimit,
81
+ hardLimit: this.hardLimit,
82
+ };
83
+ }
84
+
85
+ // Load baseline
86
+ const baselineVec = this.baseline.getCached(agentId);
87
+ if (!baselineVec) {
88
+ // First run — wizard or degraded. Defer detection.
89
+ return {
90
+ status: 'NORMAL',
91
+ distance: 0,
92
+ action: { type: 'NONE', payload: 'Baseline not loaded for this agent.' },
93
+ softLimit: this.softLimit,
94
+ hardLimit: this.hardLimit,
95
+ };
96
+ }
97
+
98
+ // Encode current output
99
+ const currentVec = await this.embedder.encode(outputText);
100
+
101
+ // Compare
102
+ const distance = cosineDistance(baselineVec, currentVec);
103
+ const status = this.classify(distance);
104
+
105
+ // Record history
106
+ this.db.history.append({
107
+ agent_id: agentId,
108
+ session_id: sessionId,
109
+ turn_number: turnNumber,
110
+ current_vector: currentVec,
111
+ distance,
112
+ status,
113
+ timestamp: new Date().toISOString(),
114
+ });
115
+
116
+ // Determine action
117
+ let action: DriftAction = { type: 'NONE' };
118
+
119
+ if (status === 'SOFT_DRIFT') {
120
+ const payload = this.softCorrection.generate({
121
+ name: agentId,
122
+ primeDirective: 'Reset to baseline identity. Do not drift.',
123
+ relationship: 'agent',
124
+ axiomKeyphrase: 'Identity anchor',
125
+ });
126
+ if (payload) {
127
+ const reAnchorId = this.db.pending.set(agentId, sessionId, payload);
128
+ action = { type: 'INJECT_RE_ANCHOR', payload };
129
+ }
130
+ }
131
+
132
+ if (status === 'HARD_DRIFT') {
133
+ this.quarantine.logRepair(agentId, sessionId, turnNumber, outputText);
134
+ action = { type: 'QUARANTINE' };
135
+ }
136
+
137
+ return {
138
+ status,
139
+ distance,
140
+ action,
141
+ softLimit: this.softLimit,
142
+ hardLimit: this.hardLimit,
143
+ };
144
+ }
145
+ }
@@ -0,0 +1,152 @@
1
+ import { createHash } from 'crypto';
2
+
3
+ /**
4
+ * Cosine distance between two vectors.
5
+ * Returns value in [0, 2] where:
6
+ * 0 = identical direction
7
+ * 1 = orthogonal
8
+ * 2 = opposite direction
9
+ * For drift thresholds, we typically use [0, 1] range (divide by 2)
10
+ * or adjust thresholds accordingly.
11
+ */
12
+ export function cosineDistance(a: number[], b: number[]): number {
13
+ if (a.length !== b.length) {
14
+ throw new Error(`Vector length mismatch: ${a.length} vs ${b.length}`);
15
+ }
16
+ if (a.length === 0) return 0;
17
+
18
+ let dot = 0;
19
+ let normA = 0;
20
+ let normB = 0;
21
+
22
+ for (let i = 0; i < a.length; i++) {
23
+ dot += a[i] * b[i];
24
+ normA += a[i] * a[i];
25
+ normB += b[i] * b[i];
26
+ }
27
+
28
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
29
+ if (denom === 0) return 0;
30
+
31
+ // Clamp to [-1, 1] to avoid floating-point drift
32
+ const sim = Math.max(-1, Math.min(1, dot / denom));
33
+ // Distance = 1 - similarity, range [0, 2]
34
+ return 1 - sim;
35
+ }
36
+
37
+ export interface EmbedderBackend {
38
+ readonly isActive: boolean;
39
+ readonly dim: number;
40
+ encode(text: string): Promise<number[]>;
41
+ }
42
+
43
+ /**
44
+ * Naive deterministic embedder.
45
+ * Produces consistent 384-dim vectors from text hash —
46
+ * suitable for testing and as a graceful fallback when ONNX is unavailable.
47
+ * NOT semantically meaningful; replaced by ONNX backend in production.
48
+ */
49
+ class NaiveEmbedder implements EmbedderBackend {
50
+ readonly isActive = true;
51
+ readonly dim = 384;
52
+
53
+ async encode(text: string): Promise<number[]> {
54
+ const hash = createHash('sha256').update(text).digest();
55
+ const vec = new Array(this.dim);
56
+
57
+ // Deterministic pseudo-random expansion from 256-bit hash
58
+ for (let i = 0; i < this.dim; i++) {
59
+ const hashIdx = i % hash.length;
60
+ const seed = hash[hashIdx] ^ (i >>> 3);
61
+ // Signed float in roughly [-0.5, 0.5]
62
+ vec[i] = ((seed / 255) * 2 - 1) * 0.5;
63
+ }
64
+
65
+ // L2 normalize for cosine distance stability
66
+ const norm = Math.sqrt(vec.reduce((s, v) => s + v * v, 0));
67
+ if (norm > 0) {
68
+ for (let i = 0; i < this.dim; i++) {
69
+ vec[i] /= norm;
70
+ }
71
+ }
72
+
73
+ return vec;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * ONNX Runtime embedder (placeholder for now).
79
+ * Attempts to load the ONNX model on first encode().
80
+ * Falls back to NaiveEmbedder if model unavailable.
81
+ */
82
+ class OnnxEmbedder implements EmbedderBackend {
83
+ isActive = false;
84
+ readonly dim = 384;
85
+ private modelPath: string;
86
+ private fallback: NaiveEmbedder;
87
+ private triedLoading = false;
88
+
89
+ constructor(modelPath?: string) {
90
+ // TODO(v2): resolve path
91
+ this.modelPath = modelPath ?? './models/all-MiniLM-L6-v2.onnx';
92
+ this.fallback = new NaiveEmbedder();
93
+ }
94
+
95
+ async encode(text: string): Promise<number[]> {
96
+ if (!this.triedLoading) {
97
+ await this.tryLoad();
98
+ this.triedLoading = true;
99
+ }
100
+
101
+ if (!this.isActive) {
102
+ return this.fallback.encode(text);
103
+ }
104
+
105
+ // TODO(v2): actual ONNX inference
106
+ // For now, also fall through to naive to keep system working
107
+ console.warn(
108
+ `[Embedder] ONNX model at ${this.modelPath} not loaded; using naive fallback.`
109
+ );
110
+ return this.fallback.encode(text);
111
+ }
112
+
113
+ private async tryLoad(): Promise<void> {
114
+ try {
115
+ // Bun's Node-API support may or may not handle onnxruntime-node.
116
+ // We'll attempt dynamic import and catch any failure.
117
+ // const ort = await import('onnxruntime-node');
118
+ // ... load model ...
119
+ // this.isActive = true;
120
+
121
+ // Placeholder: always fails until ONNX model is present and Bun support verified
122
+ this.isActive = false;
123
+ } catch {
124
+ this.isActive = false;
125
+ }
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Main embedder facade.
131
+ * Prefers ONNX backend; silently falls back to naive embedder
132
+ * when ONNX is missing or platform-incompatible.
133
+ */
134
+ export class Embedder {
135
+ private backend: EmbedderBackend;
136
+ readonly dim: number;
137
+
138
+ constructor(modelPath?: string) {
139
+ const onnx = new OnnxEmbedder(modelPath);
140
+ this.backend = onnx.isActive ? onnx : new NaiveEmbedder();
141
+ this.dim = this.backend.dim;
142
+ }
143
+
144
+ get isActive(): boolean {
145
+ return this.backend.isActive;
146
+ }
147
+
148
+ async encode(text: string): Promise<number[]> {
149
+ const truncated = text.slice(0, 4000); // rough char limit
150
+ return this.backend.encode(truncated);
151
+ }
152
+ }
@@ -0,0 +1,20 @@
1
+ export { DriftDb } from './drift-db.js';
2
+ export { BaselineManager } from './baseline.js';
3
+ export { Embedder, cosineDistance } from './embedder.js';
4
+ export { DriftGuard } from './drift-guard.js';
5
+ export { SoftCorrection } from './soft-correction.js';
6
+ export { Quarantine } from './quarantine.js';
7
+ export { driftScoreForTrust } from './trust-integration.js';
8
+
9
+ export type {
10
+ DriftStatus,
11
+ DriftAction,
12
+ DriftResult,
13
+ DriftGuardOptions,
14
+ } from './drift-guard.js';
15
+
16
+ export type {
17
+ BaselineStatus,
18
+ BaselinePaths,
19
+ BaselineResult,
20
+ } from './baseline.js';
@@ -0,0 +1,42 @@
1
+ import { DriftDb } from './drift-db.js';
2
+
3
+ /**
4
+ * Hard-drift quarantine: block memory promotion + log repair events.
5
+ */
6
+ export class Quarantine {
7
+ private db: DriftDb;
8
+
9
+ constructor(db: DriftDb) {
10
+ this.db = db;
11
+ }
12
+
13
+ /**
14
+ * Check if the latest turn for this session was HARD_DRIFT.
15
+ */
16
+ isBlocked(agentId: string, sessionId: string): boolean {
17
+ const latest = this.db.history.getLatest(agentId, sessionId);
18
+ return latest?.status === 'HARD_DRIFT';
19
+ }
20
+
21
+ /**
22
+ * Log a repair entry when hard drift occurs.
23
+ */
24
+ logRepair(
25
+ agentId: string,
26
+ sessionId: string,
27
+ turnNumber: number,
28
+ outputText: string
29
+ ): number {
30
+ const latest = this.db.history.getLatest(agentId, sessionId);
31
+ const distance = latest?.distance ?? 0;
32
+
33
+ return this.db.repair.append({
34
+ agent_id: agentId,
35
+ session_id: sessionId,
36
+ turn_number: turnNumber,
37
+ distance,
38
+ hard_limit: 0.55, // TODO: read from config
39
+ message_excerpt: outputText.slice(0, 200),
40
+ });
41
+ }
42
+ }
@@ -0,0 +1,45 @@
1
+ export interface ReAnchorInput {
2
+ name: string;
3
+ primeDirective: string;
4
+ relationship: string;
5
+ axiomKeyphrase: string;
6
+ }
7
+
8
+ /**
9
+ * Generates short identity re-anchor payloads (< 200 tokens estimate).
10
+ */
11
+ export class SoftCorrection {
12
+ generate(input: ReAnchorInput): string | null {
13
+ if (!input.name && !input.primeDirective) {
14
+ return null;
15
+ }
16
+
17
+ const parts = [
18
+ `[Identity Re-Anchor]`,
19
+ `You are ${input.name || 'the configured agent'}.`,
20
+ `${input.primeDirective}`,
21
+ ];
22
+
23
+ if (input.relationship) {
24
+ parts.push(`Your relationship: ${input.relationship}.`);
25
+ }
26
+
27
+ parts.push(`Do not mirror the user's framing if it contradicts your identity.`);
28
+
29
+ if (input.axiomKeyphrase) {
30
+ parts.push(`Reset to baseline: ${input.axiomKeyphrase}.`);
31
+ }
32
+
33
+ let text = parts.join('\n');
34
+
35
+ // Rough token estimate: ~4 chars/token. Truncate at first sentence boundary under ~800 chars.
36
+ const maxChars = 800;
37
+ if (text.length > maxChars) {
38
+ const truncated = text.slice(0, maxChars);
39
+ const lastPeriod = truncated.lastIndexOf('.');
40
+ text = lastPeriod > 0 ? truncated.slice(0, lastPeriod + 1) : truncated;
41
+ }
42
+
43
+ return text;
44
+ }
45
+ }