@n2world/orchestrator 1.1.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 (154) hide show
  1. package/dist/agent-os-rd.d.ts +100 -0
  2. package/dist/agent-os-rd.js +258 -0
  3. package/dist/audit-store.d.ts +14 -0
  4. package/dist/audit-store.js +107 -0
  5. package/dist/beta-runner.d.ts +95 -0
  6. package/dist/beta-runner.js +251 -0
  7. package/dist/beta.d.ts +102 -0
  8. package/dist/beta.js +180 -0
  9. package/dist/browser-agent.d.ts +90 -0
  10. package/dist/browser-agent.js +223 -0
  11. package/dist/channel-gateway.d.ts +74 -0
  12. package/dist/channel-gateway.js +270 -0
  13. package/dist/channels.d.ts +120 -0
  14. package/dist/channels.js +432 -0
  15. package/dist/chat-store.d.ts +29 -0
  16. package/dist/chat-store.js +120 -0
  17. package/dist/cli.d.ts +2 -0
  18. package/dist/cli.js +607 -0
  19. package/dist/command-screen.d.ts +12 -0
  20. package/dist/command-screen.js +44 -0
  21. package/dist/commit-gate.d.ts +98 -0
  22. package/dist/commit-gate.js +258 -0
  23. package/dist/companion-api.d.ts +37 -0
  24. package/dist/companion-api.js +101 -0
  25. package/dist/conversation-graph.d.ts +39 -0
  26. package/dist/conversation-graph.js +92 -0
  27. package/dist/cost-estimator.d.ts +27 -0
  28. package/dist/cost-estimator.js +42 -0
  29. package/dist/cron-runner.d.ts +31 -0
  30. package/dist/cron-runner.js +46 -0
  31. package/dist/dashboard/chat.html +326 -0
  32. package/dist/dashboard/dental.html +58 -0
  33. package/dist/dashboard/freebie.png +0 -0
  34. package/dist/dashboard/icon-192.png +0 -0
  35. package/dist/dashboard/index.html +892 -0
  36. package/dist/dashboard/manifest.json +15 -0
  37. package/dist/dashboard/service-worker.js +28 -0
  38. package/dist/dashboard-server.d.ts +37 -0
  39. package/dist/dashboard-server.js +457 -0
  40. package/dist/dental-intake-service.d.ts +37 -0
  41. package/dist/dental-intake-service.js +61 -0
  42. package/dist/dental-metrics.d.ts +25 -0
  43. package/dist/dental-metrics.js +37 -0
  44. package/dist/docking.d.ts +36 -0
  45. package/dist/docking.js +73 -0
  46. package/dist/finance-mcts-candidate.d.ts +37 -0
  47. package/dist/finance-mcts-candidate.js +106 -0
  48. package/dist/finance-regulation-kr.d.ts +33 -0
  49. package/dist/finance-regulation-kr.js +104 -0
  50. package/dist/finance-workflow.d.ts +135 -0
  51. package/dist/finance-workflow.js +242 -0
  52. package/dist/gateway.d.ts +18 -0
  53. package/dist/gateway.js +123 -0
  54. package/dist/governance.d.ts +39 -0
  55. package/dist/governance.js +48 -0
  56. package/dist/governed-executor.d.ts +31 -0
  57. package/dist/governed-executor.js +63 -0
  58. package/dist/governed-llm.d.ts +41 -0
  59. package/dist/governed-llm.js +83 -0
  60. package/dist/gpu-bridge.d.ts +16 -0
  61. package/dist/gpu-bridge.js +53 -0
  62. package/dist/health.d.ts +47 -0
  63. package/dist/health.js +66 -0
  64. package/dist/identity-link.d.ts +32 -0
  65. package/dist/identity-link.js +98 -0
  66. package/dist/index.d.ts +184 -0
  67. package/dist/index.js +417 -0
  68. package/dist/integrations/emr-adapter.d.ts +41 -0
  69. package/dist/integrations/emr-adapter.js +63 -0
  70. package/dist/kakao-oauth.d.ts +16 -0
  71. package/dist/kakao-oauth.js +87 -0
  72. package/dist/knowledge-graph.d.ts +53 -0
  73. package/dist/knowledge-graph.js +156 -0
  74. package/dist/llm.d.ts +65 -0
  75. package/dist/llm.js +357 -0
  76. package/dist/mcp-client-guard.d.ts +32 -0
  77. package/dist/mcp-client-guard.js +179 -0
  78. package/dist/mcp-macaroon.d.ts +75 -0
  79. package/dist/mcp-macaroon.js +161 -0
  80. package/dist/mcts-kernel-bridge.d.ts +36 -0
  81. package/dist/mcts-kernel-bridge.js +99 -0
  82. package/dist/mcts-prior.d.ts +79 -0
  83. package/dist/mcts-prior.js +170 -0
  84. package/dist/model-router.d.ts +51 -0
  85. package/dist/model-router.js +75 -0
  86. package/dist/multi-axis-lift.d.ts +43 -0
  87. package/dist/multi-axis-lift.js +141 -0
  88. package/dist/net-guard.d.ts +39 -0
  89. package/dist/net-guard.js +141 -0
  90. package/dist/onboarding.d.ts +38 -0
  91. package/dist/onboarding.js +94 -0
  92. package/dist/oracle-anchored-search.d.ts +25 -0
  93. package/dist/oracle-anchored-search.js +50 -0
  94. package/dist/oracle.d.ts +22 -0
  95. package/dist/oracle.js +116 -0
  96. package/dist/p6-governance.d.ts +150 -0
  97. package/dist/p6-governance.js +252 -0
  98. package/dist/pairing.d.ts +22 -0
  99. package/dist/pairing.js +81 -0
  100. package/dist/personalization.d.ts +35 -0
  101. package/dist/personalization.js +73 -0
  102. package/dist/pglite-hnsw-bridge.d.ts +118 -0
  103. package/dist/pglite-hnsw-bridge.js +311 -0
  104. package/dist/pglite-store.d.ts +59 -0
  105. package/dist/pglite-store.js +180 -0
  106. package/dist/playbook.d.ts +79 -0
  107. package/dist/playbook.js +83 -0
  108. package/dist/playbooks/dental-intake.d.ts +20 -0
  109. package/dist/playbooks/dental-intake.js +112 -0
  110. package/dist/predictive-agent.d.ts +157 -0
  111. package/dist/predictive-agent.js +535 -0
  112. package/dist/prompt-optimizer.d.ts +18 -0
  113. package/dist/prompt-optimizer.js +104 -0
  114. package/dist/rate-limiter.d.ts +25 -0
  115. package/dist/rate-limiter.js +75 -0
  116. package/dist/safety-anneal.d.ts +83 -0
  117. package/dist/safety-anneal.js +153 -0
  118. package/dist/sandbox-controller.d.ts +12 -0
  119. package/dist/sandbox-controller.js +95 -0
  120. package/dist/satisfaction-metrics.d.ts +26 -0
  121. package/dist/satisfaction-metrics.js +61 -0
  122. package/dist/sensor-bridge.d.ts +53 -0
  123. package/dist/sensor-bridge.js +133 -0
  124. package/dist/session-repair.d.ts +27 -0
  125. package/dist/session-repair.js +66 -0
  126. package/dist/slack-finance-intake.d.ts +42 -0
  127. package/dist/slack-finance-intake.js +122 -0
  128. package/dist/symbolic-dynamics.d.ts +113 -0
  129. package/dist/symbolic-dynamics.js +420 -0
  130. package/dist/telemetry.d.ts +19 -0
  131. package/dist/telemetry.js +68 -0
  132. package/dist/text-embedding.d.ts +6 -0
  133. package/dist/text-embedding.js +42 -0
  134. package/dist/tier-classifier.d.ts +20 -0
  135. package/dist/tier-classifier.js +58 -0
  136. package/dist/tier-guard.d.ts +36 -0
  137. package/dist/tier-guard.js +56 -0
  138. package/dist/tui.d.ts +9 -0
  139. package/dist/tui.js +214 -0
  140. package/dist/update-security.d.ts +31 -0
  141. package/dist/update-security.js +112 -0
  142. package/dist/v-calibration.d.ts +16 -0
  143. package/dist/v-calibration.js +42 -0
  144. package/dist/value-calibration.d.ts +41 -0
  145. package/dist/value-calibration.js +133 -0
  146. package/dist/value-head.d.ts +20 -0
  147. package/dist/value-head.js +91 -0
  148. package/dist/wal-buffer.d.ts +23 -0
  149. package/dist/wal-buffer.js +144 -0
  150. package/dist/wiki-synthesizer.d.ts +80 -0
  151. package/dist/wiki-synthesizer.js +0 -0
  152. package/dist/worker-agent.d.ts +10 -0
  153. package/dist/worker-agent.js +19 -0
  154. package/package.json +65 -0
@@ -0,0 +1,311 @@
1
+ "use strict";
2
+ // ============================================================================
3
+ // P1.5 — PgliteHnswSyncBridge + DurabilitySpec (계획서 v2.4 §4.3)
4
+ // ack-after-WAL · epoch(RCU) HNSW 더블버퍼 · 검색/삽입 분리 · torn-read 차단
5
+ // ----------------------------------------------------------------------------
6
+ // 단순화 원칙: v1.2 wal-buffer(SQLiteWalBuffer)와 P1 PgliteVectorStore 를 **재사용**하고,
7
+ // 신규는 **HNSW 더블버퍼(epoch 회수)** 만 추가한다.
8
+ //
9
+ // DurabilitySpec(3대 표기, 전부 측정값):
10
+ // 1) `ack 유실 0/N` — ack 는 WAL durable append 직후 반환. 크래시 후 WAL 재생으로 복구.
11
+ // 2) `torn-read 0/N` — 인덱스 교체는 원자 swap. 리더는 캡처한 버퍼 참조로만 검색(세대 일관 스냅샷).
12
+ // 3) `임베딩 stale ≤ W` — insert → publish(검색 가시화) 윈도우 W 를 실측(0/즉시성 주장 없음).
13
+ //
14
+ // 메모리 비용: epoch 더블버퍼는 active + (리더가 남은) retired 버퍼를 동시 보유 → **메모리 ~2배**.
15
+ // ============================================================================
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.PgliteHnswSyncBridge = void 0;
18
+ const core_1 = require("@n2world/core");
19
+ const pglite_store_1 = require("./pglite-store");
20
+ const wal_buffer_1 = require("./wal-buffer");
21
+ const now = () => {
22
+ const g = globalThis;
23
+ return typeof g.performance?.now === 'function' ? g.performance.now() : Date.now();
24
+ };
25
+ /**
26
+ * PGLite(영속) ↔ HNSW(검색) 동기화 브리지.
27
+ * - 쓰기: WAL durable append(ack) → PGLite 적용 → WAL ack(삭제) → (옵션) publish.
28
+ * - 읽기: active 버퍼 참조를 캡처(epoch guard)해 검색 — 삽입에 막히지 않음(분리).
29
+ * - 교체: 새 인덱스를 빌드 후 원자 swap, 구 버퍼는 리더가 빠질 때 epoch 회수(RCU).
30
+ */
31
+ class PgliteHnswSyncBridge {
32
+ storeDir;
33
+ walPath;
34
+ store;
35
+ wal;
36
+ // 빠른 재빌드를 위한 인메모리 권위 사본(id→embedding). PGLite 가 durable 원본.
37
+ items = new Map();
38
+ // ── epoch(RCU) 더블버퍼 상태 ──
39
+ activeIndex;
40
+ activeEpoch = 0;
41
+ /** epoch → 활성 리더 수(active/retired 공통). */
42
+ readers = new Map();
43
+ /** 회수 대기 중인 구 버퍼(리더가 남아 아직 못 버림). epoch → index. */
44
+ retired = new Map();
45
+ /** 세대별 멤버십(torn-read 검증용): epoch → 그 세대에 보였던 id 집합. */
46
+ genIds = new Map();
47
+ // ── 측정 카운터(전부 실측, 스텁 금지) ──
48
+ ackedWrites = 0;
49
+ tornReadObserved = 0;
50
+ reclaimedBuffers = 0;
51
+ unsafeReclaimAttempts = 0;
52
+ peakLiveBuffers = 1;
53
+ staleSamplesMs = [];
54
+ /** publish 안 된(가시화 대기) 쓰기의 삽입 시각. id→t. */
55
+ pendingSince = new Map();
56
+ initialized = false;
57
+ constructor(storeDir, walPath) {
58
+ this.storeDir = storeDir;
59
+ this.walPath = walPath;
60
+ this.store = new pglite_store_1.PgliteVectorStore(storeDir);
61
+ this.wal = new wal_buffer_1.SQLiteWalBuffer(walPath);
62
+ this.activeIndex = new core_1.HnswIndex();
63
+ this.genIds.set(0, new Set());
64
+ }
65
+ async init() {
66
+ if (this.initialized)
67
+ return;
68
+ await this.store.init();
69
+ await this.wal.init();
70
+ // 시작 시 durable 원본(PGLite)에서 인메모리 사본·인덱스 복원.
71
+ const all = await this.store.all();
72
+ for (const r of all)
73
+ this.items.set(r.id, r.embedding);
74
+ this.rebuildAndSwap(); // 초기 publish(epoch 0→1)
75
+ this.initialized = true;
76
+ }
77
+ storeMode() {
78
+ return this.store.mode_();
79
+ }
80
+ /**
81
+ * 벡터 쓰기. **ack 는 WAL durable append 직후 반환된다**(인메모리 직후 아님).
82
+ * 반환 시점 = 내구 보장 시점. 이후 PGLite 적용·publish.
83
+ */
84
+ async insert(w, opts = {}) {
85
+ if (!this.initialized)
86
+ await this.init();
87
+ const tInsert = now();
88
+ // (1) WAL durable append — 여기서 내구성이 확정되고 ack 가 정당화된다.
89
+ await this.wal.pushMessage('vec', JSON.stringify(w));
90
+ this.ackedWrites += 1;
91
+ // 테스트: ack 후 적용 전 크래시 모사 — WAL 에만 남기고 반환(미적용).
92
+ if (opts.simulateCrashBeforeApply) {
93
+ return { acked: true };
94
+ }
95
+ // (2) PGLite(durable) 적용 + 인메모리 사본 갱신.
96
+ await this.applyWrite(w);
97
+ // (3) 적용 완료 → 해당 WAL 항목 ack(삭제). WAL 은 in-flight 만 보유한다.
98
+ await this.ackApplied(w.id);
99
+ // (4) 가시화: publish 면 즉시 swap, 아니면 stale 로 둔다(W 측정 대상).
100
+ this.pendingSince.set(w.id, tInsert);
101
+ if (opts.publish !== false) {
102
+ this.publish();
103
+ }
104
+ return { acked: true };
105
+ }
106
+ /** WAL 의 in-flight 항목을 PGLite 에 재생(크래시 복구). 복구된 건수 반환. */
107
+ async recover() {
108
+ if (!this.initialized)
109
+ await this.init();
110
+ const pending = await this.wal.peekMessages();
111
+ let recovered = 0;
112
+ const ackIds = [];
113
+ for (const m of pending) {
114
+ try {
115
+ const w = JSON.parse(m.content);
116
+ if (!this.items.has(w.id)) {
117
+ await this.applyWrite(w);
118
+ recovered += 1;
119
+ }
120
+ if (m.id !== undefined)
121
+ ackIds.push(m.id);
122
+ }
123
+ catch {
124
+ /* 손상 항목은 건너뜀(다음 단계에서 별도 격리 가능) */
125
+ }
126
+ }
127
+ await this.wal.ackMessages(ackIds);
128
+ this.publish();
129
+ return recovered;
130
+ }
131
+ /**
132
+ * 검색. active 버퍼 참조를 epoch 가드로 캡처해 **동기** 검색한다.
133
+ * 캡처한 참조는 검색 동안 불변 → 부분/혼합 세대(torn-read) 불가.
134
+ */
135
+ async search(query, k = 5) {
136
+ if (!this.initialized)
137
+ await this.init();
138
+ const g = this.acquireRead();
139
+ try {
140
+ return this.snapshotSearch(g, query, k);
141
+ }
142
+ finally {
143
+ this.releaseRead(g);
144
+ }
145
+ }
146
+ /**
147
+ * P2 인계 — 안정적 읽기 스냅샷 인터페이스. `openSnapshot()` → `snapshotSearch()`* → `closeSnapshot()`.
148
+ * 스냅샷이 열려 있는 동안 그 세대(epoch) 버퍼는 RCU 로 보호되어 회수되지 않는다(관계 추출 등
149
+ * 다단계 읽기가 일관된 단일 세대 위에서 동작하도록 보장). 닫으면 회수 가능해진다.
150
+ */
151
+ openSnapshot() {
152
+ return this.acquireRead();
153
+ }
154
+ /** 스냅샷 안의 검색(동기). 결과는 캡처한 세대의 멤버여야 한다 — 아니면 torn-read 로 카운트. */
155
+ snapshotSearch(g, query, k = 5) {
156
+ const results = g.index.search(query, k);
157
+ const gen = this.genIds.get(g.epoch);
158
+ if (gen) {
159
+ for (const id of results) {
160
+ if (!gen.has(id)) {
161
+ this.tornReadObserved += 1;
162
+ break;
163
+ }
164
+ }
165
+ }
166
+ return results;
167
+ }
168
+ closeSnapshot(g) {
169
+ this.releaseRead(g);
170
+ }
171
+ /** 가시화: 인메모리 사본으로 새 인덱스를 빌드해 원자 swap(검색 경로를 막지 않음). */
172
+ publish() {
173
+ this.rebuildAndSwap();
174
+ // 가시화된 쓰기의 stale 윈도우(insert→publish) 확정.
175
+ const tVisible = now();
176
+ for (const [, tIns] of this.pendingSince) {
177
+ this.staleSamplesMs.push(tVisible - tIns);
178
+ }
179
+ this.pendingSince.clear();
180
+ }
181
+ durability() {
182
+ return {
183
+ storeMode: this.store.mode_(),
184
+ ackedWrites: this.ackedWrites,
185
+ tornReadObserved: this.tornReadObserved,
186
+ reclaimedBuffers: this.reclaimedBuffers,
187
+ unsafeReclaimAttempts: this.unsafeReclaimAttempts,
188
+ peakLiveBuffers: this.peakLiveBuffers,
189
+ staleWindowMs: this.staleSamplesMs.length
190
+ ? +Math.max(...this.staleSamplesMs).toFixed(3)
191
+ : 0,
192
+ };
193
+ }
194
+ async count() {
195
+ if (!this.initialized)
196
+ await this.init();
197
+ return this.items.size;
198
+ }
199
+ /**
200
+ * 내구성 실측: ack 반환된 id 들이 **durable PGLite 원본**에 실제로 존재하는지 확인한다.
201
+ * `ack 유실 0/N` 은 이 검증의 missing.length === 0 으로 증명한다(스텁 아님 — 원본 조회).
202
+ */
203
+ async verifyDurable(expectedIds) {
204
+ if (!this.initialized)
205
+ await this.init();
206
+ const missing = [];
207
+ let present = 0;
208
+ for (const id of expectedIds) {
209
+ const rec = await this.store.get(id);
210
+ if (rec)
211
+ present += 1;
212
+ else
213
+ missing.push(id);
214
+ }
215
+ return { present, missing };
216
+ }
217
+ async close() {
218
+ await this.store.close();
219
+ await this.wal.close();
220
+ this.initialized = false;
221
+ }
222
+ // ────────────────────────────────────────────────────────────────────────
223
+ // 내부
224
+ // ────────────────────────────────────────────────────────────────────────
225
+ async applyWrite(w) {
226
+ await this.store.upsert(w.id, w.content, w.embedding);
227
+ this.items.set(w.id, w.embedding.slice());
228
+ }
229
+ /** 적용 완료된 id 의 WAL 항목을 ack(삭제). */
230
+ async ackApplied(id) {
231
+ const pending = await this.wal.peekMessages();
232
+ const ackIds = [];
233
+ for (const m of pending) {
234
+ try {
235
+ const w = JSON.parse(m.content);
236
+ if (w.id === id && m.id !== undefined)
237
+ ackIds.push(m.id);
238
+ }
239
+ catch {
240
+ /* skip */
241
+ }
242
+ }
243
+ if (ackIds.length)
244
+ await this.wal.ackMessages(ackIds);
245
+ }
246
+ /** 인메모리 사본으로 새 인덱스를 빌드하고 원자 swap + 구 버퍼 retire(RCU). */
247
+ rebuildAndSwap() {
248
+ const next = new core_1.HnswIndex();
249
+ const ids = new Set();
250
+ for (const [id, emb] of this.items) {
251
+ next.addItem(id, emb);
252
+ ids.add(id);
253
+ }
254
+ const oldEpoch = this.activeEpoch;
255
+ const oldIndex = this.activeIndex;
256
+ // 원자 swap: 단일 대입(JS 단일 스레드 — 이벤트 루프 틱 사이 분할 없음).
257
+ this.activeIndex = next;
258
+ this.activeEpoch = oldEpoch + 1;
259
+ this.genIds.set(this.activeEpoch, ids);
260
+ // 구 버퍼 retire: 리더가 없으면 즉시 회수, 있으면 회수 대기.
261
+ if ((this.readers.get(oldEpoch) ?? 0) > 0) {
262
+ this.retired.set(oldEpoch, oldIndex);
263
+ }
264
+ else {
265
+ this.safeReclaim(oldEpoch); // 리더 0 → 즉시 회수(빈 초기 버퍼 포함)
266
+ }
267
+ this.trackPeakBuffers();
268
+ }
269
+ /**
270
+ * RCU 회수 — **리더가 0 인 epoch 만** 버퍼/세대를 회수한다.
271
+ * 리더가 남았는데 호출되면 회수하지 않고 위반으로 카운트한다(불변식 가드).
272
+ */
273
+ safeReclaim(epoch) {
274
+ if ((this.readers.get(epoch) ?? 0) > 0) {
275
+ this.unsafeReclaimAttempts += 1;
276
+ return;
277
+ }
278
+ this.retired.delete(epoch);
279
+ this.genIds.delete(epoch);
280
+ this.reclaimedBuffers += 1;
281
+ }
282
+ acquireRead() {
283
+ const epoch = this.activeEpoch;
284
+ this.readers.set(epoch, (this.readers.get(epoch) ?? 0) + 1);
285
+ return { index: this.activeIndex, epoch };
286
+ }
287
+ releaseRead(g) {
288
+ const n = (this.readers.get(g.epoch) ?? 1) - 1;
289
+ if (n <= 0) {
290
+ this.readers.delete(g.epoch);
291
+ // 이 epoch 가 retired 면(이미 swap 됨) 이제 리더 0 → 안전하게 회수.
292
+ if (this.retired.has(g.epoch))
293
+ this.safeReclaim(g.epoch);
294
+ }
295
+ else {
296
+ this.readers.set(g.epoch, n);
297
+ }
298
+ }
299
+ trackPeakBuffers() {
300
+ // 동시 보유 = active(1) + retired(회수 대기) 수.
301
+ const live = 1 + this.retired.size;
302
+ if (live > this.peakLiveBuffers)
303
+ this.peakLiveBuffers = live;
304
+ // RCU 불변식: retired 버퍼는 리더가 남았을 때만 존재해야 한다(리더 0 이면 회수 누락 → 정리).
305
+ for (const epoch of [...this.retired.keys()]) {
306
+ if ((this.readers.get(epoch) ?? 0) === 0)
307
+ this.safeReclaim(epoch);
308
+ }
309
+ }
310
+ }
311
+ exports.PgliteHnswSyncBridge = PgliteHnswSyncBridge;
@@ -0,0 +1,59 @@
1
+ export interface VectorRecord {
2
+ id: string;
3
+ content: string;
4
+ embedding: number[];
5
+ updatedAt: number;
6
+ }
7
+ export interface SearchHit {
8
+ id: string;
9
+ content: string;
10
+ score: number;
11
+ }
12
+ export type StoreMode = 'pglite-durable' | 'pglite-memory' | 'fallback-memory';
13
+ export interface StoreStats {
14
+ mode: StoreMode;
15
+ count: number;
16
+ /** 경계-staleness 헤드라인: 임베딩 갱신→검색가능 윈도우의 최댓값(측정값, ms). 0/즉시성 주장 아님. */
17
+ staleWindowMs: number;
18
+ staleMeanMs: number;
19
+ staleSamples: number;
20
+ }
21
+ /**
22
+ * PGLite 기반 영속 벡터 저장. 공개 API 는 비동기(WASM 부팅·디스크 I/O 때문).
23
+ * - `dataDir` 지정 → 파일 백업(durable, 프로세스 재시작 후에도 잔존).
24
+ * - `dataDir` 생략 → 인메모리 pglite(비영속).
25
+ * - pglite 부팅 실패 → 인메모리 Map 폴백(비-Linux/제약 환경에서도 동작 보장).
26
+ */
27
+ export declare class PgliteVectorStore {
28
+ private readonly dataDir?;
29
+ private db;
30
+ private fallback;
31
+ private mode;
32
+ private initialized;
33
+ private staleSamplesMs;
34
+ constructor(dataDir?: string | undefined);
35
+ init(): Promise<void>;
36
+ mode_(): StoreMode;
37
+ /**
38
+ * 벡터 upsert(영속 쓰기). 쓰기 완료 후 **즉시 read-back** 으로 가시성을 확인하고
39
+ * (갱신 시작 → 검색가능)까지의 윈도우를 실측해 staleness 샘플로 적재한다.
40
+ */
41
+ upsert(id: string, content: string, embedding: number[]): Promise<void>;
42
+ /** id 로 단건 조회. 없으면 null. */
43
+ get(id: string): Promise<VectorRecord | null>;
44
+ /**
45
+ * 코사인 유사도 상위 k 검색. pgvector ANN 이 아니라 **TS 선형 스캔**(정직: O(N)).
46
+ * 작은 스킬 라이브러리 규모에서 정확·충분하며, 가짜 색인을 흉내내지 않는다.
47
+ */
48
+ search(queryEmbedding: number[], k?: number): Promise<SearchHit[]>;
49
+ /** 전체 레코드. */
50
+ all(): Promise<VectorRecord[]>;
51
+ count(): Promise<number>;
52
+ /**
53
+ * 경계-staleness 헤드라인 값(측정값, ms): 갱신→검색가능 윈도우의 최댓값.
54
+ * 표기 규약: `임베딩 stale 윈도우 ≤ W (측정값)`. **0/즉시성 주장 금지.**
55
+ */
56
+ staleWindowMs(): number;
57
+ stats(): StoreStats;
58
+ close(): Promise<void>;
59
+ }
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ // ============================================================================
3
+ // P1 — PGLite 영속 벡터 저장 + 단일 경계-staleness 임베딩 (계획서 v2.4 §3·§4.3)
4
+ // ----------------------------------------------------------------------------
5
+ // 정직 고지(제1계명·구현강도 상한):
6
+ // 1) 이 pglite 빌드(0.5.3)에는 pgvector 확장이 없다(contrib 부재 확인). 따라서 ANN을
7
+ // pgvector로 한다고 연기(mock)하지 않는다. PGLite 는 **영속 저장 계층**으로 쓰고,
8
+ // 최근접 랭킹은 TS 코사인(O(N) 선형 스캔)으로 한다 — 작은 스킬 라이브러리에 정직한 선택.
9
+ // 2) **2-속도(TwoSpeed) 일관성 기계장치를 추가하지 않는다**(단순화 영가설). 단일 경계-
10
+ // staleness 모델: 임베딩 갱신→검색가능까지의 윈도우 W 를 **실측**하고, "0/즉시성"을
11
+ // 주장하지 않는다. (Karpathy: 0 staleness 는 키워드 검색뿐. 의미(임베딩) 검색은 본질적 stale.)
12
+ // 3) durable 모드 = 파일 백업(`dataDir` 지정). 인메모리 모드는 비영속임을 명시한다.
13
+ // pglite 부팅 실패(폴백 환경) 시 인메모리 Map 으로 폴백하되 모드를 정직히 보고한다.
14
+ // ============================================================================
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.PgliteVectorStore = void 0;
17
+ const text_embedding_1 = require("./text-embedding");
18
+ // ts-jest(CJS 트랜스파일) 환경에서도 ESM 전용 pglite 를 **진짜** dynamic import 하기 위한 우회.
19
+ // (tsc/ts-jest 가 import() 를 require() 로 바꾸면 ERR_REQUIRE_ESM 이 난다. Function 으로 감싸 회피.)
20
+ const esmImport = new Function('spec', 'return import(spec)');
21
+ /**
22
+ * PGLite 기반 영속 벡터 저장. 공개 API 는 비동기(WASM 부팅·디스크 I/O 때문).
23
+ * - `dataDir` 지정 → 파일 백업(durable, 프로세스 재시작 후에도 잔존).
24
+ * - `dataDir` 생략 → 인메모리 pglite(비영속).
25
+ * - pglite 부팅 실패 → 인메모리 Map 폴백(비-Linux/제약 환경에서도 동작 보장).
26
+ */
27
+ class PgliteVectorStore {
28
+ dataDir;
29
+ db = null;
30
+ fallback = new Map();
31
+ mode = 'fallback-memory';
32
+ initialized = false;
33
+ // 경계-staleness 실측(갱신→검색가능 윈도우, ms). 가짜 난수 금지 — 전부 실측.
34
+ staleSamplesMs = [];
35
+ constructor(dataDir) {
36
+ this.dataDir = dataDir;
37
+ }
38
+ async init() {
39
+ if (this.initialized)
40
+ return;
41
+ try {
42
+ const mod = await esmImport('@electric-sql/pglite');
43
+ const PGlite = mod.PGlite ?? mod.default?.PGlite ?? mod.default;
44
+ this.db = this.dataDir ? new PGlite(this.dataDir) : new PGlite();
45
+ await this.db.waitReady;
46
+ await this.db.exec(`
47
+ CREATE TABLE IF NOT EXISTS skill_vectors (
48
+ id TEXT PRIMARY KEY,
49
+ content TEXT NOT NULL,
50
+ embedding TEXT NOT NULL, -- JSON float[] (pgvector 부재 → TEXT 직렬화)
51
+ updated_at DOUBLE PRECISION NOT NULL
52
+ );
53
+ `);
54
+ this.mode = this.dataDir ? 'pglite-durable' : 'pglite-memory';
55
+ }
56
+ catch (_e) {
57
+ // pglite 부팅 불가 환경(WASM 미지원 등) → 인메모리 Map 폴백. 모드를 숨기지 않는다.
58
+ this.db = null;
59
+ this.mode = 'fallback-memory';
60
+ }
61
+ this.initialized = true;
62
+ }
63
+ mode_() {
64
+ return this.mode;
65
+ }
66
+ /**
67
+ * 벡터 upsert(영속 쓰기). 쓰기 완료 후 **즉시 read-back** 으로 가시성을 확인하고
68
+ * (갱신 시작 → 검색가능)까지의 윈도우를 실측해 staleness 샘플로 적재한다.
69
+ */
70
+ async upsert(id, content, embedding) {
71
+ if (!this.initialized)
72
+ await this.init();
73
+ const t0 = nowMs();
74
+ const updatedAt = Date.now();
75
+ const embJson = JSON.stringify(embedding);
76
+ if (this.db) {
77
+ await this.db.query(`INSERT INTO skill_vectors (id, content, embedding, updated_at)
78
+ VALUES ($1, $2, $3, $4)
79
+ ON CONFLICT (id) DO UPDATE SET content = $2, embedding = $3, updated_at = $4;`, [id, content, embJson, updatedAt]);
80
+ // read-after-write: 방금 쓴 행이 조회 경로에서 보이는 순간까지의 실제 지연 측정.
81
+ await this.db.query(`SELECT id FROM skill_vectors WHERE id = $1;`, [id]);
82
+ }
83
+ else {
84
+ this.fallback.set(id, { id, content, embedding: embedding.slice(), updatedAt });
85
+ }
86
+ this.staleSamplesMs.push(nowMs() - t0);
87
+ }
88
+ /** id 로 단건 조회. 없으면 null. */
89
+ async get(id) {
90
+ if (!this.initialized)
91
+ await this.init();
92
+ if (this.db) {
93
+ const r = await this.db.query(`SELECT id, content, embedding, updated_at FROM skill_vectors WHERE id = $1;`, [id]);
94
+ if (r.rows.length === 0)
95
+ return null;
96
+ return rowToRecord(r.rows[0]);
97
+ }
98
+ return this.fallback.get(id) ?? null;
99
+ }
100
+ /**
101
+ * 코사인 유사도 상위 k 검색. pgvector ANN 이 아니라 **TS 선형 스캔**(정직: O(N)).
102
+ * 작은 스킬 라이브러리 규모에서 정확·충분하며, 가짜 색인을 흉내내지 않는다.
103
+ */
104
+ async search(queryEmbedding, k = 5) {
105
+ if (!this.initialized)
106
+ await this.init();
107
+ const records = await this.all();
108
+ const hits = records.map((rec) => ({
109
+ id: rec.id,
110
+ content: rec.content,
111
+ score: (0, text_embedding_1.cosineSimilarity)(rec.embedding, queryEmbedding),
112
+ }));
113
+ hits.sort((a, b) => b.score - a.score);
114
+ return hits.slice(0, Math.max(0, k));
115
+ }
116
+ /** 전체 레코드. */
117
+ async all() {
118
+ if (!this.initialized)
119
+ await this.init();
120
+ if (this.db) {
121
+ const r = await this.db.query(`SELECT id, content, embedding, updated_at FROM skill_vectors;`);
122
+ return r.rows.map(rowToRecord);
123
+ }
124
+ return [...this.fallback.values()];
125
+ }
126
+ async count() {
127
+ if (!this.initialized)
128
+ await this.init();
129
+ if (this.db) {
130
+ const r = await this.db.query(`SELECT COUNT(*)::int AS n FROM skill_vectors;`);
131
+ return Number(r.rows[0]?.n ?? 0);
132
+ }
133
+ return this.fallback.size;
134
+ }
135
+ /**
136
+ * 경계-staleness 헤드라인 값(측정값, ms): 갱신→검색가능 윈도우의 최댓값.
137
+ * 표기 규약: `임베딩 stale 윈도우 ≤ W (측정값)`. **0/즉시성 주장 금지.**
138
+ */
139
+ staleWindowMs() {
140
+ return this.staleSamplesMs.length ? Math.max(...this.staleSamplesMs) : 0;
141
+ }
142
+ stats() {
143
+ const n = this.staleSamplesMs.length;
144
+ const mean = n ? this.staleSamplesMs.reduce((a, b) => a + b, 0) / n : 0;
145
+ return {
146
+ mode: this.mode,
147
+ count: this.db ? -1 : this.fallback.size, // db 카운트는 비동기 count() 사용
148
+ staleWindowMs: +this.staleWindowMs().toFixed(3),
149
+ staleMeanMs: +mean.toFixed(3),
150
+ staleSamples: n,
151
+ };
152
+ }
153
+ async close() {
154
+ if (this.db) {
155
+ try {
156
+ await this.db.close();
157
+ }
158
+ catch (_e) {
159
+ /* 폐기 중 오류는 무시 */
160
+ }
161
+ this.db = null;
162
+ }
163
+ this.initialized = false;
164
+ }
165
+ }
166
+ exports.PgliteVectorStore = PgliteVectorStore;
167
+ function rowToRecord(row) {
168
+ return {
169
+ id: String(row.id),
170
+ content: String(row.content),
171
+ embedding: JSON.parse(String(row.embedding)),
172
+ updatedAt: Number(row.updated_at),
173
+ };
174
+ }
175
+ // 고해상도 시계(있으면 performance.now, 없으면 Date.now). staleness 실측용.
176
+ function nowMs() {
177
+ // Node 의 perf_hooks 가 항상 있으나, 환경 방어를 위해 가드.
178
+ const g = globalThis;
179
+ return typeof g.performance?.now === 'function' ? g.performance.now() : Date.now();
180
+ }
@@ -0,0 +1,79 @@
1
+ import { Tier } from './tier-classifier';
2
+ import { GovernedAgent } from './governed-llm';
3
+ export type FieldTier = 'pii' | 'sensitive' | 'public';
4
+ export interface PlaybookField {
5
+ key: string;
6
+ label: string;
7
+ required: boolean;
8
+ tier: FieldTier;
9
+ }
10
+ export interface CrossCheckContext<R = any> {
11
+ record: R;
12
+ store: RecordStore<R>;
13
+ }
14
+ export interface CrossCheckRule<R = any> {
15
+ id: string;
16
+ /** null=통과. {severity:'block'}=차단, 'warn'=경고(사람 확인). */
17
+ check: (ctx: CrossCheckContext<R>) => null | {
18
+ flag: string;
19
+ severity: 'block' | 'warn';
20
+ };
21
+ }
22
+ /** LLM 산출 단계(예: 요약·안내문). tier 로 로컬/빠른경로를 가른다. */
23
+ export interface PlaybookLlmStep {
24
+ id: string;
25
+ /** 'pii' → 로컬 강제(Tier-0). 'public' → 빠른 경로(외부 허용). */
26
+ tier: FieldTier;
27
+ /** record → 프롬프트. */
28
+ prompt: (record: any) => string;
29
+ }
30
+ export interface Playbook<R = any> {
31
+ id: string;
32
+ name: string;
33
+ domain: string;
34
+ fields: PlaybookField[];
35
+ crossChecks: CrossCheckRule<R>[];
36
+ llmSteps: PlaybookLlmStep[];
37
+ /** 임상·법률·금융 등 고위험 → 사람 확인 강제(liability). */
38
+ clinicalScope: boolean;
39
+ /** 임상 판단을 요하는지 판정(있으면 humanInLoop 강제). */
40
+ needsHumanReview?: (record: R) => boolean;
41
+ }
42
+ export interface RecordStore<R = any> {
43
+ add(r: R): void;
44
+ findDuplicate(r: R): R | null;
45
+ }
46
+ export interface PlaybookFlag {
47
+ id: string;
48
+ flag: string;
49
+ severity: 'block' | 'warn';
50
+ }
51
+ export interface StepOutput {
52
+ id: string;
53
+ tier: Tier;
54
+ provider: 'local' | 'external' | 'none';
55
+ text: string;
56
+ }
57
+ export interface PlaybookResult {
58
+ ok: boolean;
59
+ overallTier: Tier;
60
+ missingFields: string[];
61
+ flags: PlaybookFlag[];
62
+ steps: StepOutput[];
63
+ humanInLoopRequired: boolean;
64
+ blockedExternalForPii: boolean;
65
+ phasesRun: string[];
66
+ metrics: {
67
+ ms: number;
68
+ llmCalls: number;
69
+ };
70
+ }
71
+ export declare class PlaybookRuntime {
72
+ private gov?;
73
+ constructor(gov?: GovernedAgent | undefined);
74
+ /** 입력 레코드에 PII/민감 필드가 채워져 있으면 전체 Tier-0. */
75
+ private overallTier;
76
+ run<R extends Record<string, any>>(pb: Playbook<R>, record: R, store: RecordStore<R>, opts?: {
77
+ consent?: boolean;
78
+ }): Promise<PlaybookResult>;
79
+ }