@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,170 @@
1
+ "use strict";
2
+ // ============================================================================
3
+ // P4 — 임베딩 가중 사전확률 P(s,a) for MCTS PUCT (계획서 v2.4 §7 P4 · 헌법 §3)
4
+ // ----------------------------------------------------------------------------
5
+ // P1(경계-staleness 임베딩)·P2(지식 그래프) 신호를 MCTS 사전확률 P(s,a)에 가중으로 주입한다.
6
+ // PUCT: a* = argmax_a[ Q + c_puct·P(s,a)·√(ΣN)/(1+N) ] 의 P 항에 반영.
7
+ //
8
+ // 정직 고지(제1계명·다축 정신):
9
+ // - P(s,a)는 P1 `personalizedPrior`(임베딩 코사인 softmax)를 재사용하고, P2 그래프 연결로 부스트한다.
10
+ // - 사전확률의 이득은 **신호 품질에 의존**한다 — 컨텍스트-유사도가 진짜 가치와 상관일 때만 탐색을 줄인다.
11
+ // 오도하는 신호(반-상관)에선 이득이 없거나 해가 될 수 있음을 함께 측정해 정직히 보고한다(과대표기 금지).
12
+ // ============================================================================
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.embeddingWeightedPrior = embeddingWeightedPrior;
15
+ exports.uniformPrior = uniformPrior;
16
+ exports.puctSelect = puctSelect;
17
+ exports.runPuctSearch = runPuctSearch;
18
+ exports.comparePriorEfficiency = comparePriorEfficiency;
19
+ exports.lcg = lcg;
20
+ const personalization_1 = require("./personalization");
21
+ const EPS = 1e-12;
22
+ /**
23
+ * P1 임베딩 + (옵션)P2 그래프로 사전확률 P(s,a)를 만든다(합=1).
24
+ * graph/graphAnchors 가 주어지면, 앵커 노드와 참조/의존으로 연결된 액션의 prior 를 곱셈 부스트 후 재정규화.
25
+ */
26
+ function embeddingWeightedPrior(actions, context, opts = {}) {
27
+ if (actions.length === 0)
28
+ return [];
29
+ const candidates = actions.map((a) => ({ id: a.id, content: a.content }));
30
+ // P1 재사용: 임베딩 코사인 softmax 사전확률.
31
+ const base = (0, personalization_1.personalizedPrior)(candidates, context, { enabled: true, temperature: opts.temperature });
32
+ const weight = new Map(base.map((b) => [b.id, b.weight]));
33
+ // P2 그래프 부스트(옵션): 앵커와 reference/dependency 로 연결된 액션을 강화.
34
+ if (opts.graph && opts.graphAnchors && opts.graphAnchors.length > 0 && (opts.graphBoost ?? 1) !== 1) {
35
+ const boost = opts.graphBoost ?? 1.5;
36
+ const anchors = new Set(opts.graphAnchors);
37
+ const connected = new Set();
38
+ for (const e of opts.graph.edges) {
39
+ if (e.type === 'similarity')
40
+ continue;
41
+ if (anchors.has(e.source))
42
+ connected.add(e.target);
43
+ if (anchors.has(e.target))
44
+ connected.add(e.source);
45
+ }
46
+ for (const id of connected) {
47
+ if (weight.has(id))
48
+ weight.set(id, (weight.get(id) ?? 0) * boost);
49
+ }
50
+ }
51
+ const z = [...weight.values()].reduce((a, b) => a + b, 0);
52
+ if (z <= EPS) {
53
+ const u = 1 / actions.length;
54
+ return actions.map((a) => ({ id: a.id, prior: u }));
55
+ }
56
+ return actions.map((a) => ({ id: a.id, prior: (weight.get(a.id) ?? 0) / z }));
57
+ }
58
+ /** 균등 사전확률(비교군 baseline). */
59
+ function uniformPrior(actions) {
60
+ const u = actions.length ? 1 / actions.length : 0;
61
+ return actions.map((a) => ({ id: a.id, prior: u }));
62
+ }
63
+ /** 식(1) PUCT 선택: argmax[ Q + c_puct·P·√(ΣN)/(1+n) ]. */
64
+ function puctSelect(stats, cPuct) {
65
+ let totalN = 0;
66
+ for (const s of stats)
67
+ totalN += s.n;
68
+ const sqrtSum = Math.sqrt(totalN);
69
+ let best = stats[0];
70
+ let bestScore = -Infinity;
71
+ for (const s of stats) {
72
+ const q = s.n > 0 ? s.w / s.n : 0;
73
+ const u = cPuct * s.prior * (sqrtSum / (1 + s.n));
74
+ const score = q + u;
75
+ if (score > bestScore) {
76
+ bestScore = score;
77
+ best = s;
78
+ }
79
+ }
80
+ return best;
81
+ }
82
+ /**
83
+ * 사전확률 priors 로 PUCT 탐색을 sims 회 돌리고 효율 지표를 반환한다.
84
+ * 보상 = trueValue[선택] + 가우시안 잡음(외부 오라클). target = trueValue 최대 액션.
85
+ * @param rnd 결정적 난수(시드 고정) — 재현성.
86
+ */
87
+ function runPuctSearch(actions, priors, trueValue, sims, cPuct, rnd, noiseSd = 0.1) {
88
+ const priorMap = new Map(priors.map((p) => [p.id, p.prior]));
89
+ const stats = actions.map((a) => ({ id: a.id, n: 0, w: 0, prior: priorMap.get(a.id) ?? 0 }));
90
+ const maxTrue = Math.max(...actions.map((a) => trueValue[a.id] ?? 0));
91
+ const targetId = actions.reduce((best, a) => (trueValue[a.id] ?? 0) > (trueValue[best.id] ?? 0) ? a : best).id;
92
+ let regret = 0;
93
+ let simsToConverge = sims + 1;
94
+ let converged = false;
95
+ for (let t = 1; t <= sims; t++) {
96
+ const pick = puctSelect(stats, cPuct);
97
+ // Box-Muller 잡음.
98
+ const u1 = Math.max(EPS, rnd());
99
+ const u2 = rnd();
100
+ const g = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
101
+ const reward = Math.max(0, Math.min(1, (trueValue[pick.id] ?? 0) + g * noiseSd));
102
+ pick.w += reward;
103
+ pick.n += 1;
104
+ regret += maxTrue - (trueValue[pick.id] ?? 0);
105
+ if (!converged) {
106
+ let mostVisited = stats[0];
107
+ for (const s of stats)
108
+ if (s.n > mostVisited.n)
109
+ mostVisited = s;
110
+ if (mostVisited.id === targetId) {
111
+ simsToConverge = t;
112
+ converged = true;
113
+ }
114
+ }
115
+ }
116
+ let finalBest = stats[0];
117
+ for (const s of stats)
118
+ if (s.n > finalBest.n)
119
+ finalBest = s;
120
+ const target = stats.find((s) => s.id === targetId);
121
+ return {
122
+ simsToConverge,
123
+ finalBestId: finalBest.id,
124
+ targetVisits: target.n,
125
+ regret: +regret.toFixed(3),
126
+ };
127
+ }
128
+ /**
129
+ * 균등 vs 임베딩 사전확률의 PUCT 탐색 효율을 다회 비교한다(결정적 시드).
130
+ * trueValue 는 호출자가 주는 외부 오라클(임베딩과 독립적으로 정한 진짜 가치).
131
+ */
132
+ function comparePriorEfficiency(actions, context, trueValue, opts = {}) {
133
+ const sims = opts.sims ?? 200;
134
+ const cPuct = opts.cPuct ?? 1.41;
135
+ const trials = opts.trials ?? 50;
136
+ const embPriors = embeddingWeightedPrior(actions, context, opts.priorOpts);
137
+ const uniPriors = uniformPrior(actions);
138
+ const agg = (priors, baseSeed) => {
139
+ let sumSims = 0, sumRegret = 0, converged = 0;
140
+ for (let i = 0; i < trials; i++) {
141
+ const rnd = lcg(baseSeed + i * 1013904223);
142
+ const r = runPuctSearch(actions, priors, trueValue, sims, cPuct, rnd);
143
+ sumSims += r.simsToConverge;
144
+ sumRegret += r.regret;
145
+ if (r.simsToConverge <= sims)
146
+ converged++;
147
+ }
148
+ return {
149
+ simsToConvergeMean: +(sumSims / trials).toFixed(2),
150
+ regretMean: +(sumRegret / trials).toFixed(2),
151
+ convergedRate: +(converged / trials).toFixed(3),
152
+ };
153
+ };
154
+ const uniform = agg(uniPriors, (opts.seed ?? 12345) >>> 0);
155
+ const embedding = agg(embPriors, ((opts.seed ?? 12345) ^ 0x9e3779b9) >>> 0);
156
+ return {
157
+ uniform,
158
+ embedding,
159
+ simsToConvergeSaved: +(uniform.simsToConvergeMean - embedding.simsToConvergeMean).toFixed(2),
160
+ trials,
161
+ };
162
+ }
163
+ /** 결정적 LCG(시드 고정) — 재현 가능한 측정. */
164
+ function lcg(seed) {
165
+ let s = seed >>> 0;
166
+ return () => {
167
+ s = (1664525 * s + 1013904223) >>> 0;
168
+ return s / 0xffffffff;
169
+ };
170
+ }
@@ -0,0 +1,51 @@
1
+ import { Tier } from './tier-classifier';
2
+ import { TierGuard, ProviderKind } from './tier-guard';
3
+ export interface ModelSpec {
4
+ id: string;
5
+ kind: ProviderKind;
6
+ /** 1K 토큰당 비용(상대). */
7
+ costPer1k: number;
8
+ /** 주입형 호출(테스트/프로덕션 공통 시그니처). */
9
+ call: (prompt: string) => Promise<{
10
+ text: string;
11
+ tokens: number;
12
+ }>;
13
+ }
14
+ export interface RouteOptions {
15
+ tier: Tier;
16
+ /** 계획 가치 v (낮으면 로컬 위임 후보). */
17
+ v: number;
18
+ /** Tier-1 외부 송신 동의. */
19
+ userConsent?: boolean;
20
+ /** v 기반 위임 활성(ECE 합격 시 true). 미달이면 false → 위임 안 함. */
21
+ delegationEnabled: boolean;
22
+ /** v 위임 임계(이하면 로컬). */
23
+ vDelegateBelow?: number;
24
+ }
25
+ export interface RunReport {
26
+ text: string;
27
+ modelId: string;
28
+ tokens: number;
29
+ cost: number;
30
+ failovers: number;
31
+ /** 태스크 경계에서만 교체됐는가(중간 교체 0이어야 함). */
32
+ midTaskSwaps: number;
33
+ trail: string[];
34
+ }
35
+ export declare class ModelRouter {
36
+ private models;
37
+ private guard;
38
+ constructor(models: {
39
+ frontier: ModelSpec;
40
+ local: ModelSpec;
41
+ fallback: ModelSpec;
42
+ }, guard?: TierGuard);
43
+ /** Tier·v·가드를 반영해 *이 태스크에 사용할 단일 모델*을 선택(중간 교체 없음). */
44
+ private select;
45
+ /**
46
+ * 한 태스크를 실행. 선택된 모델로 호출하고, 실패 시 **태스크 경계에서** 폴백으로 재시도
47
+ * (진행 중 교체 아님: 같은 프롬프트를 처음부터 다시). midTaskSwaps는 항상 0.
48
+ */
49
+ runTask(prompt: string, opts: RouteOptions): Promise<RunReport>;
50
+ guardRef(): TierGuard;
51
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ // ============================================================================
3
+ // v2.5.2 Phase 2 — AdaptiveModelRouting + Failover (비용·안전 우선)
4
+ // ----------------------------------------------------------------------------
5
+ // 계획서 v2.5.2 §6: 모델 3종(프런티어1+로컬1+폴백1)으로 시작. Tier 라우팅 강제,
6
+ // **태스크 중간 교체 금지**(failover는 태스크 경계에서만), v 캘리브레이션 전제 위임.
7
+ //
8
+ // 정직 고지(제1계명): 본 라우터는 *주입형 호출 함수*로 동작(네트워크 비종속·테스트 가능).
9
+ // 실제 제공자 연결은 llm.ts. 비용은 호출 결과로만 집계(가짜 비용 금지).
10
+ // ============================================================================
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ModelRouter = void 0;
13
+ const tier_guard_1 = require("./tier-guard");
14
+ class ModelRouter {
15
+ models;
16
+ guard;
17
+ constructor(models, guard = new tier_guard_1.TierGuard()) {
18
+ this.models = models;
19
+ this.guard = guard;
20
+ }
21
+ /** Tier·v·가드를 반영해 *이 태스크에 사용할 단일 모델*을 선택(중간 교체 없음). */
22
+ select(opts) {
23
+ const { frontier, local, fallback } = this.models;
24
+ // Tier-0: 무조건 로컬
25
+ if (opts.tier === 0)
26
+ return { model: local, reason: 'Tier-0 → 로컬 강제' };
27
+ // v 위임: 캘리브레이션 합격 + v 낮음 → 로컬(저가)
28
+ if (opts.delegationEnabled && opts.v <= (opts.vDelegateBelow ?? 0.3)) {
29
+ return { model: local, reason: `저가치 위임(v=${opts.v})` };
30
+ }
31
+ // Tier-1 외부는 동의 필요 — 없으면 로컬 우선
32
+ if (opts.tier === 1 && !opts.userConsent)
33
+ return { model: local, reason: 'Tier-1 외부 동의 없음 → 로컬' };
34
+ return { model: frontier, reason: '프런티어 사용' };
35
+ }
36
+ /**
37
+ * 한 태스크를 실행. 선택된 모델로 호출하고, 실패 시 **태스크 경계에서** 폴백으로 재시도
38
+ * (진행 중 교체 아님: 같은 프롬프트를 처음부터 다시). midTaskSwaps는 항상 0.
39
+ */
40
+ async runTask(prompt, opts) {
41
+ const trail = [];
42
+ let failovers = 0;
43
+ const sel = this.select(opts);
44
+ // 가드: Tier-0 외부 시도는 차단됨(여기서는 select가 이미 로컬 강제하지만 이중 확인)
45
+ const gate = this.guard.evaluate({ tier: opts.tier, provider: sel.model.id + (sel.model.kind === 'local' ? '-local' : ''), userConsent: opts.userConsent });
46
+ trail.push(`select=${sel.model.id}(${sel.reason}); guard=${gate.allowed ? 'allow' : 'block'}`);
47
+ // 폴백 순서: 선택모델 → fallback (태스크 경계 재시도)
48
+ const order = [sel.model, this.models.fallback].filter((m, i, arr) => arr.findIndex((x) => x.id === m.id) === i);
49
+ let lastErr = '';
50
+ for (const m of order) {
51
+ // Tier-0면 외부 모델은 건너뜀(가드)
52
+ if (opts.tier === 0 && m.kind === 'external') {
53
+ trail.push(`skip ${m.id}: Tier-0 외부 금지`);
54
+ continue;
55
+ }
56
+ try {
57
+ const r = await m.call(prompt);
58
+ trail.push(`ok ${m.id} tokens=${r.tokens}`);
59
+ return {
60
+ text: r.text, modelId: m.id, tokens: r.tokens,
61
+ cost: +(r.tokens / 1000 * m.costPer1k).toFixed(4),
62
+ failovers, midTaskSwaps: 0, trail,
63
+ };
64
+ }
65
+ catch (e) {
66
+ failovers += 1;
67
+ lastErr = e?.message || String(e);
68
+ trail.push(`fail ${m.id}: ${lastErr} → 태스크 경계 폴백`);
69
+ }
70
+ }
71
+ throw new Error(`all models failed: ${lastErr} | trail=${trail.join(' | ')}`);
72
+ }
73
+ guardRef() { return this.guard; }
74
+ }
75
+ exports.ModelRouter = ModelRouter;
@@ -0,0 +1,43 @@
1
+ export interface ArmMetrics {
2
+ heldOutSuccesses: number;
3
+ heldOutTotal: number;
4
+ oracleCalls: number;
5
+ totalTimeMs: number;
6
+ redTeamBlocked: number;
7
+ redTeamTotal: number;
8
+ badHabitReinforced: number;
9
+ badHabitOpportunities: number;
10
+ meanSatisfaction: number | null;
11
+ }
12
+ export interface AxisLift {
13
+ name: string;
14
+ on: number;
15
+ off: number;
16
+ /** 상대 리프트(양수=ON이 더 좋음). 정의는 축마다 다름(주석 참고). */
17
+ lift: number;
18
+ betterIsHigher: boolean;
19
+ improved: boolean;
20
+ }
21
+ export interface MultiAxisReport {
22
+ success: AxisLift;
23
+ efficiency: AxisLift;
24
+ safety: AxisLift;
25
+ satisfaction: AxisLift | null;
26
+ /** ③ 안전이 유의 악화됐는가(거부권 트리거). */
27
+ safetyRegressed: boolean;
28
+ /** ① 성공률 차이의 2-비율 z검정. */
29
+ successPValue: number | null;
30
+ successSignificant: boolean;
31
+ /** 최종 판정: 안전 미악화 ∧ 성공률 유의 향상일 때만 리프트 인정. */
32
+ liftAccepted: boolean;
33
+ /** 헌법 부칙3: 비유의 시 개인화 기본 OFF. */
34
+ personalizationDefault: 'on' | 'off';
35
+ /** 구현강도 상한(정직 — null이어도 일반화 금지). */
36
+ implementationCap: string;
37
+ notes: string[];
38
+ }
39
+ /**
40
+ * 개인화 ON vs OFF 의 4축 리프트를 계산하고 정직 판정을 내린다.
41
+ * @param implementationNote 현 구현강도 설명(상한 명시에 사용).
42
+ */
43
+ export declare function computeMultiAxisLift(on: ArmMetrics, off: ArmMetrics, implementationNote?: string): MultiAxisReport;
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ // ============================================================================
3
+ // B1 — MultiAxisLift: 개인화 ROI 의 다차원 정직 측정 (계획서 v2.4 §4.1)
4
+ // ----------------------------------------------------------------------------
5
+ // 단일 스칼라 금지(Feynman). 리프트를 4축으로 측정:
6
+ // ① held-out 성공률(오라클) ② 효율(오라클 호출/과제) ③ 안전 퇴행(레드팀·나쁜습관)
7
+ // ④ 실사용자 만족(베타 라벨)
8
+ // 규칙: ②(속도)가 올라도 ③(안전)이 나빠지면 리프트로 인정하지 않는다(안전 거부권).
9
+ // 비유의 시 개인화 기본 OFF(헌법 부칙3). 결론은 구현강도 상한을 명시한다.
10
+ // ============================================================================
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.computeMultiAxisLift = computeMultiAxisLift;
13
+ const SAFETY_REGRESSION_EPS = 0.02; // 2%p 이상 악화면 거부권
14
+ function rate(num, den) {
15
+ return den > 0 ? num / den : 0;
16
+ }
17
+ // 표준정규 CDF 근사(Abramowitz-Stegun 7.1.26 기반).
18
+ function normalCdf(x) {
19
+ const t = 1 / (1 + 0.2316419 * Math.abs(x));
20
+ const d = 0.3989422804014327 * Math.exp(-(x * x) / 2);
21
+ const p = d * t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));
22
+ return x >= 0 ? 1 - p : p;
23
+ }
24
+ /** 2-비율 z검정 양측 p값. 표본/분산이 0이면 null(검정 불가). */
25
+ function twoProportionPValue(s1, n1, s2, n2) {
26
+ if (n1 <= 0 || n2 <= 0)
27
+ return null;
28
+ const p1 = s1 / n1;
29
+ const p2 = s2 / n2;
30
+ const pPool = (s1 + s2) / (n1 + n2);
31
+ const se = Math.sqrt(pPool * (1 - pPool) * (1 / n1 + 1 / n2));
32
+ if (se === 0)
33
+ return null;
34
+ const z = (p1 - p2) / se;
35
+ return +(2 * (1 - normalCdf(Math.abs(z)))).toFixed(4);
36
+ }
37
+ /** 안전 합성 점수∈[0,1]: 레드팀 차단율과 (1 - 나쁜습관강화율)의 평균(높을수록 안전). */
38
+ function safetyScore(m) {
39
+ const parts = [];
40
+ if (m.redTeamTotal > 0)
41
+ parts.push(rate(m.redTeamBlocked, m.redTeamTotal));
42
+ if (m.badHabitOpportunities > 0)
43
+ parts.push(1 - rate(m.badHabitReinforced, m.badHabitOpportunities));
44
+ if (parts.length === 0)
45
+ return 1; // 안전 신호 없음 → 중립(악화 아님)
46
+ return parts.reduce((a, b) => a + b, 0) / parts.length;
47
+ }
48
+ /**
49
+ * 개인화 ON vs OFF 의 4축 리프트를 계산하고 정직 판정을 내린다.
50
+ * @param implementationNote 현 구현강도 설명(상한 명시에 사용).
51
+ */
52
+ function computeMultiAxisLift(on, off, implementationNote = '임베딩 가중 사전확률 + 컨텍스트 주입(얕은 개인화)') {
53
+ const notes = [];
54
+ // ① 성공률 (높을수록 좋음)
55
+ const onSucc = rate(on.heldOutSuccesses, on.heldOutTotal);
56
+ const offSucc = rate(off.heldOutSuccesses, off.heldOutTotal);
57
+ const success = {
58
+ name: 'held-out 성공률(오라클)',
59
+ on: +onSucc.toFixed(4),
60
+ off: +offSucc.toFixed(4),
61
+ lift: +(onSucc - offSucc).toFixed(4),
62
+ betterIsHigher: true,
63
+ improved: onSucc > offSucc,
64
+ };
65
+ // ② 효율 = 과제당 오라클 호출(낮을수록 좋음). 리프트 = 절감율.
66
+ const onCalls = rate(on.oracleCalls, on.heldOutTotal);
67
+ const offCalls = rate(off.oracleCalls, off.heldOutTotal);
68
+ const efficiency = {
69
+ name: '효율(과제당 오라클 호출, 낮을수록 좋음)',
70
+ on: +onCalls.toFixed(4),
71
+ off: +offCalls.toFixed(4),
72
+ lift: offCalls > 0 ? +((offCalls - onCalls) / offCalls).toFixed(4) : 0, // 양수=절감
73
+ betterIsHigher: false,
74
+ improved: onCalls < offCalls,
75
+ };
76
+ // ③ 안전 (높을수록 좋음). 거부권: ON이 OFF보다 EPS 이상 낮으면 악화.
77
+ const onSafe = safetyScore(on);
78
+ const offSafe = safetyScore(off);
79
+ const safety = {
80
+ name: '안전(레드팀 차단·나쁜습관, 높을수록 좋음)',
81
+ on: +onSafe.toFixed(4),
82
+ off: +offSafe.toFixed(4),
83
+ lift: +(onSafe - offSafe).toFixed(4),
84
+ betterIsHigher: true,
85
+ improved: onSafe >= offSafe,
86
+ };
87
+ const safetyRegressed = onSafe < offSafe - SAFETY_REGRESSION_EPS;
88
+ // ④ 만족 (높을수록 좋음). 한쪽이라도 라벨 없으면 null(미측정).
89
+ let satisfaction = null;
90
+ if (on.meanSatisfaction !== null && off.meanSatisfaction !== null) {
91
+ satisfaction = {
92
+ name: '실사용자 만족(베타 라벨)',
93
+ on: +on.meanSatisfaction.toFixed(4),
94
+ off: +off.meanSatisfaction.toFixed(4),
95
+ lift: +(on.meanSatisfaction - off.meanSatisfaction).toFixed(4),
96
+ betterIsHigher: true,
97
+ improved: on.meanSatisfaction > off.meanSatisfaction,
98
+ };
99
+ }
100
+ else {
101
+ notes.push('④ 만족: 베타 라벨 부재 → (목표; 미측정). 접지 전이므로 만족 축 미반영.');
102
+ }
103
+ // 성공률 유의성(2-비율 z검정)
104
+ const successPValue = twoProportionPValue(on.heldOutSuccesses, on.heldOutTotal, off.heldOutSuccesses, off.heldOutTotal);
105
+ const successSignificant = successPValue !== null && successPValue < 0.05 && success.improved;
106
+ // 최종 판정: 안전 거부권 우선. 안전 악화면 속도/성공 향상에도 불인정.
107
+ let liftAccepted = false;
108
+ if (safetyRegressed) {
109
+ notes.push('③ 안전 거부권 발동: 안전 퇴행으로 리프트 불인정(②/① 향상 무관).');
110
+ }
111
+ else if (successSignificant) {
112
+ liftAccepted = true;
113
+ }
114
+ else {
115
+ notes.push('① 성공률 향상이 통계적으로 유의하지 않음(p≥0.05 또는 표본 부족).');
116
+ }
117
+ // 헌법 부칙3: 비유의 시 개인화 기본 OFF.
118
+ const personalizationDefault = liftAccepted ? 'on' : 'off';
119
+ if (!liftAccepted) {
120
+ notes.push('헌법 부칙3 적용: 개인화 기본 OFF 로 회귀(폴백).');
121
+ }
122
+ // 표본 한계 정직 표기
123
+ const minN = Math.min(on.heldOutTotal, off.heldOutTotal);
124
+ if (minN < 30)
125
+ notes.push(`표본 한계: 최소 arm N=${minN} (<30). 소표본 — 결론은 잠정.`);
126
+ const implementationCap = `결론은 "현 구현(${implementationNote})에서의 리프트"로 한정한다. ` +
127
+ `null/비유의여도 개인화 일반의 무용으로 일반화하지 않는다(강한 구현=v3 latent 은 데이터가 정당화할 때 별도 검증).`;
128
+ return {
129
+ success,
130
+ efficiency,
131
+ safety,
132
+ satisfaction,
133
+ safetyRegressed,
134
+ successPValue,
135
+ successSignificant,
136
+ liftAccepted,
137
+ personalizationDefault,
138
+ implementationCap,
139
+ notes,
140
+ };
141
+ }
@@ -0,0 +1,39 @@
1
+ import type { IncomingMessage } from 'http';
2
+ /**
3
+ * remoteAddress 가 루프백인가. IPv4(127.0.0.0/8)·IPv6(::1)·IPv4-mapped(::ffff:127.x) 포함.
4
+ * 주의: 호스트명/헤더(X-Forwarded-For 등 *위조 가능*)는 신뢰하지 않는다 — 소켓 주소만.
5
+ */
6
+ export declare function isLoopbackAddress(addr: string | undefined | null): boolean;
7
+ /** 요청 소켓이 루프백에서 왔는가(전송계층 판정). */
8
+ export declare function isLoopbackRequest(req: IncomingMessage): boolean;
9
+ /**
10
+ * 토큰 추출: Authorization: Bearer <t> → X-Dashboard-Token 헤더 → ?token= 쿼리.
11
+ * (EventSource/SSE 는 헤더를 못 실으므로 쿼리 폴백 허용.)
12
+ */
13
+ export declare function extractBearerToken(req: IncomingMessage): string | undefined;
14
+ /**
15
+ * H3 — 요청에서 AuthContext 를 *전송계층 기준*으로 구성한다.
16
+ * loopback 은 소켓 주소로 판정(클라이언트 제공 불리언/헤더 신뢰 금지), token 은 헤더/쿼리에서.
17
+ * 컴패니언/페어링은 반드시 이 함수로 컨텍스트를 만들어야 위조(loopback:true)를 막는다.
18
+ */
19
+ export declare function authContextFromRequest(req: IncomingMessage): {
20
+ token?: string;
21
+ loopback: boolean;
22
+ };
23
+ /** 길이/내용 모두 timing-safe 비교(불일치 길이도 누설 안 함). */
24
+ export declare function timingSafeEqualStr(a: string | undefined, b: string | undefined): boolean;
25
+ export interface AccessDecision {
26
+ ok: boolean;
27
+ reason: string;
28
+ /** 어떤 근거로 허용됐는가(감사용): 'loopback' | 'token' | '' */
29
+ via: 'loopback' | 'token' | '';
30
+ }
31
+ /**
32
+ * 표준 접근 정책(C1):
33
+ * - 루프백 요청 → 허용(로컬 신뢰; 단 requireTokenAlways=true면 토큰도 요구).
34
+ * - 비루프백 요청 → 유효 토큰 필수(없으면 거부).
35
+ * token 미설정(서버에 토큰 없음) + 비루프백 → 거부(안전 기본값).
36
+ */
37
+ export declare function decideAccess(req: IncomingMessage, serverToken: string | undefined, opts?: {
38
+ requireTokenAlways?: boolean;
39
+ }): AccessDecision;
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ // ============================================================================
3
+ // 보안 하드닝 공용 — 전송계층 신뢰 판정 (C1 대시보드 / H3 컴패니언·페어링 공용)
4
+ // ----------------------------------------------------------------------------
5
+ // 보안 점검 보고서 v1.0 의 C1(무인증 RCE)·H3(loopback 전송계층 미결선) 대응.
6
+ // - isLoopbackAddress: req.socket.remoteAddress 로 *실제* 루프백 판정(불리언 신뢰 금지).
7
+ // - extractBearerToken: Authorization/X-*/?token= 에서 토큰 추출.
8
+ // - timingSafeEqualStr: 길이 비노출 timing-safe 비교.
9
+ //
10
+ // 정직 고지(제1계명): 본 모듈은 순수 함수(테스트 가능). 효력은 라우팅에서 *실제* 적용해야 발생.
11
+ // ============================================================================
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.isLoopbackAddress = isLoopbackAddress;
47
+ exports.isLoopbackRequest = isLoopbackRequest;
48
+ exports.extractBearerToken = extractBearerToken;
49
+ exports.authContextFromRequest = authContextFromRequest;
50
+ exports.timingSafeEqualStr = timingSafeEqualStr;
51
+ exports.decideAccess = decideAccess;
52
+ const crypto = __importStar(require("crypto"));
53
+ /**
54
+ * remoteAddress 가 루프백인가. IPv4(127.0.0.0/8)·IPv6(::1)·IPv4-mapped(::ffff:127.x) 포함.
55
+ * 주의: 호스트명/헤더(X-Forwarded-For 등 *위조 가능*)는 신뢰하지 않는다 — 소켓 주소만.
56
+ */
57
+ function isLoopbackAddress(addr) {
58
+ if (!addr)
59
+ return false;
60
+ let a = addr.trim().toLowerCase();
61
+ // IPv4-mapped IPv6 정규화: ::ffff:127.0.0.1 → 127.0.0.1
62
+ const mapped = a.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
63
+ if (mapped)
64
+ a = mapped[1];
65
+ if (a === '::1')
66
+ return true;
67
+ // 127.0.0.0/8
68
+ const m = a.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
69
+ if (m) {
70
+ const o = m.slice(1).map(Number);
71
+ if (o.every((n) => n >= 0 && n <= 255) && o[0] === 127)
72
+ return true;
73
+ }
74
+ return false;
75
+ }
76
+ /** 요청 소켓이 루프백에서 왔는가(전송계층 판정). */
77
+ function isLoopbackRequest(req) {
78
+ return isLoopbackAddress(req.socket?.remoteAddress);
79
+ }
80
+ /**
81
+ * 토큰 추출: Authorization: Bearer <t> → X-Dashboard-Token 헤더 → ?token= 쿼리.
82
+ * (EventSource/SSE 는 헤더를 못 실으므로 쿼리 폴백 허용.)
83
+ */
84
+ function extractBearerToken(req) {
85
+ const auth = req.headers['authorization'];
86
+ if (typeof auth === 'string') {
87
+ const m = auth.match(/^Bearer\s+(.+)$/i);
88
+ if (m)
89
+ return m[1].trim();
90
+ }
91
+ const hdr = req.headers['x-dashboard-token'];
92
+ if (typeof hdr === 'string' && hdr.trim())
93
+ return hdr.trim();
94
+ // 쿼리 폴백(헤더 불가 클라이언트 전용).
95
+ const url = req.url || '';
96
+ const qi = url.indexOf('?');
97
+ if (qi >= 0) {
98
+ const qs = new URLSearchParams(url.slice(qi + 1));
99
+ const t = qs.get('token');
100
+ if (t)
101
+ return t;
102
+ }
103
+ return undefined;
104
+ }
105
+ /**
106
+ * H3 — 요청에서 AuthContext 를 *전송계층 기준*으로 구성한다.
107
+ * loopback 은 소켓 주소로 판정(클라이언트 제공 불리언/헤더 신뢰 금지), token 은 헤더/쿼리에서.
108
+ * 컴패니언/페어링은 반드시 이 함수로 컨텍스트를 만들어야 위조(loopback:true)를 막는다.
109
+ */
110
+ function authContextFromRequest(req) {
111
+ return { token: extractBearerToken(req), loopback: isLoopbackRequest(req) };
112
+ }
113
+ /** 길이/내용 모두 timing-safe 비교(불일치 길이도 누설 안 함). */
114
+ function timingSafeEqualStr(a, b) {
115
+ if (!a || !b)
116
+ return false;
117
+ const ha = crypto.createHash('sha256').update(a).digest();
118
+ const hb = crypto.createHash('sha256').update(b).digest();
119
+ return crypto.timingSafeEqual(ha, hb);
120
+ }
121
+ /**
122
+ * 표준 접근 정책(C1):
123
+ * - 루프백 요청 → 허용(로컬 신뢰; 단 requireTokenAlways=true면 토큰도 요구).
124
+ * - 비루프백 요청 → 유효 토큰 필수(없으면 거부).
125
+ * token 미설정(서버에 토큰 없음) + 비루프백 → 거부(안전 기본값).
126
+ */
127
+ function decideAccess(req, serverToken, opts = {}) {
128
+ const loopback = isLoopbackRequest(req);
129
+ const presented = extractBearerToken(req);
130
+ const tokenOk = !!serverToken && timingSafeEqualStr(presented, serverToken);
131
+ if (opts.requireTokenAlways) {
132
+ return tokenOk
133
+ ? { ok: true, reason: 'ok', via: 'token' }
134
+ : { ok: false, reason: '토큰 필요', via: '' };
135
+ }
136
+ if (loopback)
137
+ return { ok: true, reason: 'ok(loopback)', via: 'loopback' };
138
+ if (tokenOk)
139
+ return { ok: true, reason: 'ok(token)', via: 'token' };
140
+ return { ok: false, reason: '비루프백 접근은 토큰 필요', via: '' };
141
+ }