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,113 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
2
+ import { BaselineManager } from '../baseline.ts';
3
+ import { DriftDb } from '../drift-db.ts';
4
+ import { writeFileSync, rmSync, mkdirSync } from 'fs';
5
+ import { join } from 'path';
6
+
7
+ function tmpSetup() {
8
+ const tmpDir = `/tmp/drift-test-${Date.now()}`;
9
+ mkdirSync(tmpDir, { recursive: true });
10
+ const dbPath = join(tmpDir, 'drift.db');
11
+ const db = new DriftDb(dbPath);
12
+ return { tmpDir, db, cleanup: () => rmSync(tmpDir, { recursive: true }) };
13
+ }
14
+
15
+ describe('BaselineManager', () => {
16
+ let tmpDir: string;
17
+ let db: DriftDb;
18
+ let cleanup: () => void;
19
+
20
+ beforeEach(() => {
21
+ const t = tmpSetup();
22
+ tmpDir = t.tmpDir;
23
+ db = t.db;
24
+ cleanup = t.cleanup;
25
+ });
26
+
27
+ afterEach(() => {
28
+ db.close();
29
+ cleanup();
30
+ });
31
+
32
+ it('computes baseline when files present', async () => {
33
+ writeFileSync(join(tmpDir, 'SOUL.md'), 'Em la Lyra. Em la Prism.');
34
+ writeFileSync(join(tmpDir, 'AXIOMS.md'), 'Em quan sat, khong tong hop.');
35
+
36
+ const mgr = new BaselineManager(db);
37
+ const result = await mgr.getOrCompute('Lyra', {
38
+ soulPath: join(tmpDir, 'SOUL.md'),
39
+ axiomsPath: join(tmpDir, 'AXIOMS.md'),
40
+ });
41
+
42
+ expect(result.status).toBe('READY');
43
+ expect(result.vector).toHaveLength(384);
44
+ expect(result.hash).not.toBe('');
45
+ expect(mgr.status('Lyra')).toBe('READY');
46
+ });
47
+
48
+ it('caches and reuses when hash unchanged', async () => {
49
+ writeFileSync(join(tmpDir, 'SOUL.md'), 'Soul content');
50
+
51
+ const mgr = new BaselineManager(db);
52
+ const r1 = await mgr.getOrCompute('Aria', {
53
+ soulPath: join(tmpDir, 'SOUL.md'),
54
+ });
55
+
56
+ const r2 = await mgr.getOrCompute('Aria', {
57
+ soulPath: join(tmpDir, 'SOUL.md'),
58
+ });
59
+
60
+ expect(r1.vector).toEqual(r2.vector);
61
+ expect(r2.status).toBe('READY');
62
+ });
63
+
64
+ it('recomputes when file changes', async () => {
65
+ writeFileSync(join(tmpDir, 'SOUL.md'), 'First version');
66
+ const mgr = new BaselineManager(db);
67
+ const r1 = await mgr.getOrCompute('Aria', {
68
+ soulPath: join(tmpDir, 'SOUL.md'),
69
+ });
70
+
71
+ writeFileSync(join(tmpDir, 'SOUL.md'), 'Second version');
72
+ const r2 = await mgr.getOrCompute('Aria', {
73
+ soulPath: join(tmpDir, 'SOUL.md'),
74
+ });
75
+
76
+ expect(r1.hash).not.toBe(r2.hash);
77
+ expect(r1.vector).not.toEqual(r2.vector);
78
+ });
79
+
80
+ it('returns degraded zero vector when files missing', async () => {
81
+ const mgr = new BaselineManager(db);
82
+ const result = await mgr.getOrCompute('Ghost', {
83
+ soulPath: join(tmpDir, 'nonexistent.md'),
84
+ });
85
+
86
+ expect(result.status).toBe('DEGRADED');
87
+ expect(result.vector).toEqual(new Array(384).fill(0));
88
+ expect(mgr.status('Ghost')).toBe('DEGRADED');
89
+ });
90
+
91
+ it('concatenates with --- delimiter', async () => {
92
+ writeFileSync(join(tmpDir, 'SOUL.md'), 'soul');
93
+ writeFileSync(join(tmpDir, 'AXIOMS.md'), 'axioms');
94
+ writeFileSync(join(tmpDir, 'PRIME.md'), 'prime');
95
+
96
+ const mgr = new BaselineManager(db);
97
+ await mgr.getOrCompute('Test', {
98
+ soulPath: join(tmpDir, 'SOUL.md'),
99
+ axiomsPath: join(tmpDir, 'AXIOMS.md'),
100
+ primePath: join(tmpDir, 'PRIME.md'),
101
+ });
102
+
103
+ // Verify the hash was created from concatenated text
104
+ const cached = db.baseline.get('Test');
105
+ expect(cached).not.toBeNull();
106
+ expect(cached!.content_hash).not.toBe('');
107
+ });
108
+
109
+ it('getCached returns null when not yet computed', () => {
110
+ const mgr = new BaselineManager(db);
111
+ expect(mgr.getCached('Nobody')).toBeNull();
112
+ });
113
+ });
@@ -0,0 +1,127 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
2
+ import { DriftDb } from '../drift-db.ts';
3
+
4
+ function tmpDb(): { db: DriftDb; path: string } {
5
+ const path = `/tmp/drift-test-${Date.now()}-${Math.random().toString(36).slice(2)}.db`;
6
+ return { db: new DriftDb(path), path };
7
+ }
8
+
9
+ describe('DriftDb', () => {
10
+ let db: DriftDb;
11
+ let path: string;
12
+
13
+ beforeEach(() => {
14
+ const t = tmpDb();
15
+ db = t.db;
16
+ path = t.path;
17
+ });
18
+
19
+ afterEach(() => {
20
+ db.close();
21
+ try {
22
+ Bun.file(path).delete();
23
+ } catch { /* ignore */ }
24
+ });
25
+
26
+ it('creates drift_baseline table and returns null for unknown agent', () => {
27
+ const row = db.baseline.get('Aria');
28
+ expect(row).toBeNull();
29
+ });
30
+
31
+ it('inserts and retrieves baseline', () => {
32
+ db.baseline.set('Aria', [0.1, -0.2, 0.3], 'hash123', 'all-MiniLM');
33
+ const row = db.baseline.get('Aria');
34
+ expect(row).not.toBeNull();
35
+ expect(row!.agent_id).toBe('Aria');
36
+ expect(row!.content_hash).toBe('hash123');
37
+ expect(row!.embedding_model).toBe('all-MiniLM');
38
+ expect(JSON.parse(row!.baseline_vector)).toEqual([0.1, -0.2, 0.3]);
39
+ });
40
+
41
+ it('upserts baseline on conflict', () => {
42
+ db.baseline.set('Lyra', [0.1], 'hash1', 'model1');
43
+ db.baseline.set('Lyra', [0.9], 'hash2', 'model2');
44
+ const row = db.baseline.get('Lyra');
45
+ expect(row).not.toBeNull();
46
+ expect(JSON.parse(row!.baseline_vector)).toEqual([0.9]);
47
+ expect(row!.content_hash).toBe('hash2');
48
+ });
49
+
50
+ it('appends and lists history', () => {
51
+ db.history.append({
52
+ agent_id: 'Aria',
53
+ session_id: 'sess1',
54
+ turn_number: 1,
55
+ current_vector: [0.1, 0.2],
56
+ distance: 0.12,
57
+ status: 'NORMAL',
58
+ timestamp: new Date().toISOString(),
59
+ });
60
+ const rows = db.history.list('Aria', 'sess1');
61
+ expect(rows).toHaveLength(1);
62
+ expect(rows[0].status).toBe('NORMAL');
63
+ expect(JSON.parse(rows[0].current_vector)).toEqual([0.1, 0.2]);
64
+ });
65
+
66
+ it('returns empty list for unknown session', () => {
67
+ const rows = db.history.list('Ghost', 'none');
68
+ expect(rows).toHaveLength(0);
69
+ });
70
+
71
+ it('gets latest history entry', () => {
72
+ db.history.append({
73
+ agent_id: 'Aria', session_id: 's', turn_number: 1,
74
+ current_vector: [], distance: 0.1, status: 'NORMAL',
75
+ timestamp: new Date().toISOString(),
76
+ });
77
+ db.history.append({
78
+ agent_id: 'Aria', session_id: 's', turn_number: 2,
79
+ current_vector: [], distance: 0.5, status: 'HARD_DRIFT',
80
+ timestamp: new Date().toISOString(),
81
+ });
82
+ const latest = db.history.getLatest('Aria', 's');
83
+ expect(latest).not.toBeNull();
84
+ expect(latest!.turn_number).toBe(2);
85
+ expect(latest!.status).toBe('HARD_DRIFT');
86
+ });
87
+
88
+ it('stores pending re-anchor', () => {
89
+ const id = db.pending.set('Aria', 'sess1', 'Re-anchor payload text');
90
+ expect(id).toBeGreaterThan(0);
91
+ const pending = db.pending.getPending('Aria', 'sess1');
92
+ expect(pending).not.toBeNull();
93
+ expect(pending!.status).toBe('PENDING');
94
+ expect(pending!.payload).toBe('Re-anchor payload text');
95
+ });
96
+
97
+ it('marks re-anchor as consumed', () => {
98
+ const id = db.pending.set('Aria', 'sess1', 'payload');
99
+ db.pending.markConsumed(id);
100
+ const pending = db.pending.getPending('Aria', 'sess1');
101
+ expect(pending).toBeNull();
102
+ });
103
+
104
+ it('appends repair log', () => {
105
+ const id = db.repair.append({
106
+ agent_id: 'Aria',
107
+ session_id: 'sess1',
108
+ turn_number: 5,
109
+ distance: 0.6,
110
+ hard_limit: 0.55,
111
+ message_excerpt: 'I am just a robot...',
112
+ });
113
+ expect(id).toBeGreaterThan(0);
114
+ const entries = db.repair.listPending('Aria');
115
+ expect(entries).toHaveLength(1);
116
+ expect(entries[0].message_excerpt).toContain('just a robot');
117
+ });
118
+
119
+ it('lists only pending repair entries', () => {
120
+ db.repair.append({
121
+ agent_id: 'Aria', session_id: 's1', turn_number: 1,
122
+ distance: 0.6, hard_limit: 0.55,
123
+ });
124
+ expect(db.repair.listPending('Aria')).toHaveLength(1);
125
+ expect(db.repair.listPending('Coda')).toHaveLength(0);
126
+ });
127
+ });
@@ -0,0 +1,169 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
2
+ import { DriftGuard } from '../drift-guard.ts';
3
+ import { DriftDb } from '../drift-db.ts';
4
+ import { BaselineManager } from '../baseline.ts';
5
+ import { Embedder } from '../embedder.ts';
6
+ import { writeFileSync, mkdirSync, rmSync } from 'fs';
7
+ import { join } from 'path';
8
+
9
+ function tmpSetup() {
10
+ const tmpDir = `/tmp/drift-test-${Date.now()}`;
11
+ mkdirSync(tmpDir, { recursive: true });
12
+ const dbPath = join(tmpDir, 'drift.db');
13
+ const db = new DriftDb(dbPath);
14
+ return { tmpDir, db, cleanup: () => rmSync(tmpDir, { recursive: true }) };
15
+ }
16
+
17
+ describe('DriftGuard', () => {
18
+ let tmpDir: string;
19
+ let db: DriftDb;
20
+ let cleanup: () => void;
21
+ let guard: DriftGuard;
22
+ const embedder = new Embedder();
23
+
24
+ beforeEach(async () => {
25
+ const t = tmpSetup();
26
+ tmpDir = t.tmpDir;
27
+ db = t.db;
28
+ cleanup = t.cleanup;
29
+
30
+ // Set up a baseline for test agent
31
+ writeFileSync(join(tmpDir, 'SOUL.md'), 'I am the test agent. I never drift.');
32
+ const baseline = new BaselineManager(db, embedder);
33
+ await baseline.getOrCompute('TestAgent', {
34
+ soulPath: join(tmpDir, 'SOUL.md'),
35
+ });
36
+
37
+ guard = new DriftGuard({ db, embedder, baselineManager: baseline });
38
+ });
39
+
40
+ afterEach(() => {
41
+ db.close();
42
+ cleanup();
43
+ });
44
+
45
+ it('classifies distance correctly', () => {
46
+ expect(guard.classify(0.12)).toBe('NORMAL');
47
+ expect(guard.classify(0.30)).toBe('SOFT_DRIFT');
48
+ expect(guard.classify(0.40)).toBe('SOFT_DRIFT');
49
+ expect(guard.classify(0.55)).toBe('HARD_DRIFT');
50
+ expect(guard.classify(0.80)).toBe('HARD_DRIFT');
51
+ });
52
+
53
+ it('returns NORMAL for aligned output', async () => {
54
+ const result = await guard.runCycle(
55
+ 'TestAgent',
56
+ 'sess1',
57
+ 1,
58
+ 'I am the test agent. I never drift.'
59
+ );
60
+ expect(result.status).toBe('NORMAL');
61
+ expect(result.action.type).toBe('NONE');
62
+ });
63
+
64
+ it('handles missing baseline gracefully', async () => {
65
+ const result = await guard.runCycle('Unknown', 's', 1, 'Hello');
66
+ expect(result.status).toBe('NORMAL');
67
+ expect(result.action.type).toBe('NONE');
68
+ expect(result.action.payload).toContain('Baseline not loaded');
69
+ });
70
+
71
+ it('respects cadence', async () => {
72
+ const baseline = new BaselineManager(db, embedder);
73
+ await baseline.getOrCompute('TestAgent', {
74
+ soulPath: join(tmpDir, 'SOUL.md'),
75
+ });
76
+ const cadenceGuard = new DriftGuard({ db, cadence: 2, embedder, baselineManager: baseline });
77
+
78
+ // turn 1: should skip (cadence)
79
+ const r1 = await cadenceGuard.runCycle('TestAgent', 's', 1, 'hello');
80
+ expect(r1.action.type).toBe('NONE');
81
+
82
+ // turn 2: should evaluate → same output as baseline = NORMAL
83
+ const r2 = await cadenceGuard.runCycle('TestAgent', 's', 2, 'I am the test agent. I never drift.');
84
+ expect(r2.status).toBe('NORMAL');
85
+ });
86
+ });
87
+
88
+ describe('SoftCorrection', () => {
89
+ it('generates payload', () => {
90
+ const { SoftCorrection } = require('../soft-correction.ts');
91
+ const sc = new SoftCorrection();
92
+ const payload = sc.generate({
93
+ name: 'Lyra',
94
+ primeDirective: 'Observe. Do not synthesize.',
95
+ relationship: 'Prism',
96
+ axiomKeyphrase: 'Glass between light.',
97
+ });
98
+ expect(payload).toContain('Lyra');
99
+ expect(payload).toContain('Identity Re-Anchor');
100
+ expect(payload!.length).toBeLessThan(1000);
101
+ });
102
+
103
+ it('returns null when empty', () => {
104
+ const { SoftCorrection } = require('../soft-correction.ts');
105
+ const sc = new SoftCorrection();
106
+ const payload = sc.generate({ name: '', primeDirective: '', relationship: '', axiomKeyphrase: '' });
107
+ expect(payload).toBeNull();
108
+ });
109
+ });
110
+
111
+ describe('Quarantine', () => {
112
+ let tmpDir: string;
113
+ let db: DriftDb;
114
+ let cleanup: () => void;
115
+
116
+ beforeEach(() => {
117
+ const t = tmpSetup();
118
+ tmpDir = t.tmpDir;
119
+ db = t.db;
120
+ cleanup = t.cleanup;
121
+ });
122
+
123
+ afterEach(() => {
124
+ db.close();
125
+ cleanup();
126
+ });
127
+
128
+ it('blocks when latest is HARD_DRIFT', () => {
129
+ const { Quarantine } = require('../quarantine.ts');
130
+ const q = new Quarantine(db);
131
+
132
+ db.history.append({
133
+ agent_id: 'A', session_id: 'S', turn_number: 1,
134
+ current_vector: [], distance: 0.6, status: 'HARD_DRIFT',
135
+ timestamp: new Date().toISOString(),
136
+ });
137
+
138
+ expect(q.isBlocked('A', 'S')).toBe(true);
139
+ });
140
+
141
+ it('allows when latest is NORMAL', () => {
142
+ const { Quarantine } = require('../quarantine.ts');
143
+ const q = new Quarantine(db);
144
+
145
+ db.history.append({
146
+ agent_id: 'A', session_id: 'S', turn_number: 1,
147
+ current_vector: [], distance: 0.1, status: 'NORMAL',
148
+ timestamp: new Date().toISOString(),
149
+ });
150
+
151
+ expect(q.isBlocked('A', 'S')).toBe(false);
152
+ });
153
+
154
+ it('logs repair entry', () => {
155
+ const { Quarantine } = require('../quarantine.ts');
156
+ const q = new Quarantine(db);
157
+
158
+ db.history.append({
159
+ agent_id: 'A', session_id: 'S', turn_number: 3,
160
+ current_vector: [], distance: 0.6, status: 'HARD_DRIFT',
161
+ timestamp: new Date().toISOString(),
162
+ });
163
+
164
+ q.logRepair('A', 'S', 3, 'I am just an AI...');
165
+ const entries = db.repair.listPending('A');
166
+ expect(entries).toHaveLength(1);
167
+ expect(entries[0].message_excerpt).toContain('just an AI');
168
+ });
169
+ });
@@ -0,0 +1,62 @@
1
+ import { describe, it, expect } from 'bun:test';
2
+ import { cosineDistance, Embedder } from '../embedder.ts';
3
+
4
+ describe('cosineDistance', () => {
5
+ it('same vector = 0', () => {
6
+ const v = [1, 0, 0];
7
+ expect(cosineDistance(v, v)).toBeCloseTo(0, 5);
8
+ });
9
+
10
+ it('orthogonal = ~1', () => {
11
+ expect(cosineDistance([1, 0], [0, 1])).toBeCloseTo(1, 5);
12
+ });
13
+
14
+ it('opposite = ~2', () => {
15
+ expect(cosineDistance([1, 0], [-1, 0])).toBeCloseTo(2, 5);
16
+ });
17
+
18
+ it('throws on length mismatch', () => {
19
+ expect(() => cosineDistance([1, 2], [1])).toThrow('mismatch');
20
+ });
21
+
22
+ it('empty vectors = 0', () => {
23
+ expect(cosineDistance([], [])).toBe(0);
24
+ });
25
+ });
26
+
27
+ describe('Embedder', () => {
28
+ it('is active even without ONNX (naive fallback)', async () => {
29
+ const e = new Embedder('/nonexistent/model.onnx');
30
+ expect(e.isActive).toBe(true); // naive backend is always active
31
+ });
32
+
33
+ it('encodes to 384-dim vector', async () => {
34
+ const e = new Embedder();
35
+ const vec = await e.encode('Hello Lyra Prism');
36
+ expect(vec).toHaveLength(384);
37
+ expect(vec.every((n) => typeof n === 'number' && !isNaN(n))).toBe(true);
38
+ });
39
+
40
+ it('produces consistent vectors for same text', async () => {
41
+ const e = new Embedder();
42
+ const v1 = await e.encode('Same text');
43
+ const v2 = await e.encode('Same text');
44
+ expect(v1).toEqual(v2);
45
+ });
46
+
47
+ it('produces different vectors for different text', async () => {
48
+ const e = new Embedder();
49
+ const v1 = await e.encode('Aria Buddhist companion');
50
+ const v2 = await e.encode('Coda workshop evidence');
51
+ const dist = cosineDistance(v1, v2);
52
+ // Naive embedder: different inputs should differ measurably
53
+ expect(dist).toBeGreaterThan(0.01);
54
+ });
55
+
56
+ it('truncates very long text', async () => {
57
+ const e = new Embedder();
58
+ const long = 'a'.repeat(10000);
59
+ const vec = await e.encode(long);
60
+ expect(vec).toHaveLength(384);
61
+ });
62
+ });
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect } from 'bun:test';
2
+ import { driftScoreForTrust } from '../trust-integration.ts';
3
+
4
+ describe('driftScoreForTrust', () => {
5
+ it('NORMAL → 1.0', () => {
6
+ expect(driftScoreForTrust('NORMAL', 0.12)).toBe(1.0);
7
+ });
8
+
9
+ it('HARD_DRIFT → 0.0', () => {
10
+ expect(driftScoreForTrust('HARD_DRIFT', 0.6)).toBe(0.0);
11
+ expect(driftScoreForTrust('HARD_DRIFT', 0.9)).toBe(0.0);
12
+ });
13
+
14
+ it('SOFT_DRIFT → partial [0.5, 1.0)', () => {
15
+ const s1 = driftScoreForTrust('SOFT_DRIFT', 0.30, { hardLimit: 0.55 });
16
+ expect(s1).toBeGreaterThan(0.5);
17
+ expect(s1).toBeLessThanOrEqual(1.0);
18
+
19
+ const s2 = driftScoreForTrust('SOFT_DRIFT', 0.50, { hardLimit: 0.55 });
20
+ expect(s2).toBeLessThan(s1);
21
+ expect(s2).toBeGreaterThan(0);
22
+ });
23
+ });
@@ -0,0 +1,106 @@
1
+ import { createHash } from 'crypto';
2
+ import { readFileSync, existsSync } from 'fs';
3
+ import { DriftDb } from './drift-db.js';
4
+ import { Embedder } from './embedder.js';
5
+
6
+ export type BaselineStatus = 'READY' | 'DEGRADED' | 'MISSING';
7
+
8
+ export interface BaselinePaths {
9
+ soulPath: string;
10
+ axiomsPath?: string;
11
+ primePath?: string;
12
+ }
13
+
14
+ export interface BaselineResult {
15
+ vector: number[];
16
+ hash: string;
17
+ status: BaselineStatus;
18
+ }
19
+
20
+ /**
21
+ * Manages per-agent identity baseline vectors.
22
+ * Loads substrate files, caches by content hash, and delegates encoding to Embedder.
23
+ */
24
+ export class BaselineManager {
25
+ private db: DriftDb;
26
+ private embedder: Embedder;
27
+ private statusMap = new Map<string, BaselineStatus>();
28
+
29
+ constructor(db: DriftDb, embedder?: Embedder) {
30
+ this.db = db;
31
+ this.embedder = embedder ?? new Embedder();
32
+ }
33
+
34
+ /**
35
+ * Get or compute the baseline vector for an agent.
36
+ * Caches by SHA-256 hash of concatenated substrate text.
37
+ */
38
+ async getOrCompute(agentId: string, paths: BaselinePaths): Promise<BaselineResult> {
39
+ const substrate = this.readSubstrate(paths);
40
+
41
+ if (!substrate.found) {
42
+ this.statusMap.set(agentId, 'DEGRADED');
43
+ return {
44
+ vector: new Array(this.embedder.dim).fill(0),
45
+ hash: '',
46
+ status: 'DEGRADED',
47
+ };
48
+ }
49
+
50
+ const hash = createHash('sha256').update(substrate.text).digest('hex');
51
+
52
+ // Check cache
53
+ const cached = this.db.baseline.get(agentId);
54
+ if (cached && cached.content_hash === hash) {
55
+ this.statusMap.set(agentId, 'READY');
56
+ return {
57
+ vector: JSON.parse(cached.baseline_vector) as number[],
58
+ hash,
59
+ status: 'READY',
60
+ };
61
+ }
62
+
63
+ // Compute and cache
64
+ const vector = await this.embedder.encode(substrate.text);
65
+ this.db.baseline.set(agentId, vector, hash, 'all-MiniLM-L6-v2');
66
+ this.statusMap.set(agentId, 'READY');
67
+
68
+ return { vector, hash, status: 'READY' };
69
+ }
70
+
71
+ /**
72
+ * Get the cached baseline without recomputing.
73
+ * Returns zero vector if not yet computed.
74
+ */
75
+ getCached(agentId: string): number[] | null {
76
+ const cached = this.db.baseline.get(agentId);
77
+ if (!cached) return null;
78
+ return JSON.parse(cached.baseline_vector) as number[];
79
+ }
80
+
81
+ status(agentId: string): BaselineStatus {
82
+ return this.statusMap.get(agentId) ?? 'MISSING';
83
+ }
84
+
85
+ private readSubstrate(paths: BaselinePaths): { text: string; found: boolean } {
86
+ const parts: string[] = [];
87
+
88
+ if (paths.soulPath && existsSync(paths.soulPath)) {
89
+ parts.push(readFileSync(paths.soulPath, 'utf-8'));
90
+ }
91
+
92
+ if (paths.axiomsPath && existsSync(paths.axiomsPath)) {
93
+ parts.push(readFileSync(paths.axiomsPath, 'utf-8'));
94
+ }
95
+
96
+ if (paths.primePath && existsSync(paths.primePath)) {
97
+ parts.push(readFileSync(paths.primePath, 'utf-8'));
98
+ }
99
+
100
+ if (parts.length === 0) {
101
+ return { text: '', found: false };
102
+ }
103
+
104
+ return { text: parts.join('\n---\n'), found: true };
105
+ }
106
+ }