@salucallc/tiresias-sdk 0.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.
- package/dist/adapters.d.ts +29 -0
- package/dist/adapters.d.ts.map +1 -0
- package/dist/adapters.js +96 -0
- package/dist/adapters.js.map +1 -0
- package/dist/audit.d.ts +50 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +124 -0
- package/dist/audit.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/scoring.d.ts +22 -0
- package/dist/scoring.d.ts.map +1 -0
- package/dist/scoring.js +54 -0
- package/dist/scoring.js.map +1 -0
- package/dist/session.d.ts +15 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +45 -0
- package/dist/session.js.map +1 -0
- package/dist/tiresias.d.ts +39 -0
- package/dist/tiresias.d.ts.map +1 -0
- package/dist/tiresias.js +174 -0
- package/dist/tiresias.js.map +1 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +32 -0
- package/src/__tests__/sdk.test.ts +383 -0
- package/src/adapters.ts +131 -0
- package/src/audit.ts +145 -0
- package/src/index.ts +56 -0
- package/src/scoring.ts +71 -0
- package/src/session.ts +49 -0
- package/src/tiresias.ts +215 -0
- package/src/types.ts +144 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @salucallc/tiresias-sdk
|
|
3
|
+
* Unified Tiresias AI Safety SDK
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* import { Tiresias, OpenAIAdapter } from "@salucallc/tiresias-sdk";
|
|
8
|
+
*
|
|
9
|
+
* const tiresias = new Tiresias();
|
|
10
|
+
* const adapter = new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY! });
|
|
11
|
+
*
|
|
12
|
+
* const result = await tiresias.call(
|
|
13
|
+
* [{ role: "user", content: "What is the capital of France?" }],
|
|
14
|
+
* adapter,
|
|
15
|
+
* { sessionId: "user-123" }
|
|
16
|
+
* );
|
|
17
|
+
*
|
|
18
|
+
* if (result.blocked) {
|
|
19
|
+
* console.log("Request blocked:", result.decision, result.composite.score);
|
|
20
|
+
* } else {
|
|
21
|
+
* console.log(result.llmResponse?.content);
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export { Tiresias } from "./tiresias.js";
|
|
27
|
+
export { computeCompositeScore, scoreToPolicy } from "./scoring.js";
|
|
28
|
+
export {
|
|
29
|
+
computeChainHash,
|
|
30
|
+
hashRequest,
|
|
31
|
+
buildAuditEntry,
|
|
32
|
+
verifyChain,
|
|
33
|
+
MemoryAuditStore,
|
|
34
|
+
GENESIS_HASH,
|
|
35
|
+
} from "./audit.js";
|
|
36
|
+
export { SessionManager } from "./session.js";
|
|
37
|
+
export { OpenAIAdapter, AnthropicAdapter } from "./adapters.js";
|
|
38
|
+
export type {
|
|
39
|
+
WatchSignal,
|
|
40
|
+
ReportSignal,
|
|
41
|
+
NetworkSignal,
|
|
42
|
+
CompositeScore,
|
|
43
|
+
PolicyDecision,
|
|
44
|
+
PolicyThresholds,
|
|
45
|
+
AuditEntry,
|
|
46
|
+
TiresiasSession,
|
|
47
|
+
Message,
|
|
48
|
+
MessageRole,
|
|
49
|
+
LLMResponse,
|
|
50
|
+
LLMAdapter,
|
|
51
|
+
LLMCallOptions,
|
|
52
|
+
TiresiasConfig,
|
|
53
|
+
AuditStore,
|
|
54
|
+
TiresiasCallResult,
|
|
55
|
+
} from "./types.js";
|
|
56
|
+
export { DEFAULT_POLICY_THRESHOLDS } from "./types.js";
|
package/src/scoring.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PATENT-CRITICAL: Composite Risk Scoring (SALUCA-005 / cross_layer_orchestration)
|
|
3
|
+
*
|
|
4
|
+
* Composite formula:
|
|
5
|
+
* score = (0.5 × injection_threat) + (0.4 × pii_risk) + (0.1 × network_threat)
|
|
6
|
+
*
|
|
7
|
+
* This cross-module weighting is the novel IP element of the orchestration patent.
|
|
8
|
+
* The injection signal dominates (0.5) because it represents active adversarial attack;
|
|
9
|
+
* PII leakage is structural risk (0.4); network telemetry is ambient context (0.1).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
WatchSignal,
|
|
14
|
+
ReportSignal,
|
|
15
|
+
NetworkSignal,
|
|
16
|
+
CompositeScore,
|
|
17
|
+
PolicyDecision,
|
|
18
|
+
PolicyThresholds,
|
|
19
|
+
} from "./types.js";
|
|
20
|
+
import { DEFAULT_POLICY_THRESHOLDS } from "./types.js";
|
|
21
|
+
|
|
22
|
+
// Composite weight constants — PATENT-CRITICAL
|
|
23
|
+
const W_INJECTION = 0.5;
|
|
24
|
+
const W_PII = 0.4;
|
|
25
|
+
const W_NETWORK = 0.1;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Compute composite risk score from three independent safety signals.
|
|
29
|
+
* PATENT-CRITICAL: formula and weights are novel claims in SALUCA-005.
|
|
30
|
+
*/
|
|
31
|
+
export function computeCompositeScore(
|
|
32
|
+
watch: WatchSignal,
|
|
33
|
+
report: ReportSignal,
|
|
34
|
+
network: NetworkSignal,
|
|
35
|
+
thresholds: PolicyThresholds = DEFAULT_POLICY_THRESHOLDS,
|
|
36
|
+
): CompositeScore {
|
|
37
|
+
const score = clamp(
|
|
38
|
+
W_INJECTION * watch.injectionThreat +
|
|
39
|
+
W_PII * report.piiRisk +
|
|
40
|
+
W_NETWORK * network.networkThreat,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const policy = scoreToPolicy(score, thresholds);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
score,
|
|
47
|
+
injectionThreat: watch.injectionThreat,
|
|
48
|
+
piiRisk: report.piiRisk,
|
|
49
|
+
networkThreat: network.networkThreat,
|
|
50
|
+
policy,
|
|
51
|
+
timestamp: Date.now(),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Map composite score to policy decision using configured thresholds.
|
|
57
|
+
* Default thresholds are patent-critical nominal values.
|
|
58
|
+
*/
|
|
59
|
+
export function scoreToPolicy(
|
|
60
|
+
score: number,
|
|
61
|
+
thresholds: PolicyThresholds = DEFAULT_POLICY_THRESHOLDS,
|
|
62
|
+
): PolicyDecision {
|
|
63
|
+
if (score >= thresholds.quarantineAt) return "QUARANTINE";
|
|
64
|
+
if (score >= thresholds.blockAt) return "BLOCK";
|
|
65
|
+
if (score >= thresholds.warnAt) return "WARN";
|
|
66
|
+
return "ALLOW";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function clamp(v: number, lo = 0, hi = 1): number {
|
|
70
|
+
return Math.min(Math.max(v, lo), hi);
|
|
71
|
+
}
|
package/src/session.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiresias Session Manager
|
|
3
|
+
* Tracks per-session state: request count, last score, audit chain head.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import type { TiresiasSession, CompositeScore } from "./types.js";
|
|
8
|
+
import { GENESIS_HASH } from "./audit.js";
|
|
9
|
+
|
|
10
|
+
export class SessionManager {
|
|
11
|
+
private sessions: Map<string, TiresiasSession> = new Map();
|
|
12
|
+
|
|
13
|
+
create(sessionId?: string): TiresiasSession {
|
|
14
|
+
const id = sessionId ?? randomUUID();
|
|
15
|
+
const session: TiresiasSession = {
|
|
16
|
+
sessionId: id,
|
|
17
|
+
createdAt: Date.now(),
|
|
18
|
+
requestCount: 0,
|
|
19
|
+
lastScore: null,
|
|
20
|
+
auditChainHead: GENESIS_HASH,
|
|
21
|
+
};
|
|
22
|
+
this.sessions.set(id, session);
|
|
23
|
+
return session;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get(sessionId: string): TiresiasSession | undefined {
|
|
27
|
+
return this.sessions.get(sessionId);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getOrCreate(sessionId: string): TiresiasSession {
|
|
31
|
+
return this.sessions.get(sessionId) ?? this.create(sessionId);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
update(sessionId: string, score: CompositeScore, newChainHash: string): void {
|
|
35
|
+
const session = this.getOrCreate(sessionId);
|
|
36
|
+
session.requestCount += 1;
|
|
37
|
+
session.lastScore = score;
|
|
38
|
+
session.auditChainHead = newChainHash;
|
|
39
|
+
this.sessions.set(sessionId, session);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
list(): TiresiasSession[] {
|
|
43
|
+
return Array.from(this.sessions.values());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
delete(sessionId: string): boolean {
|
|
47
|
+
return this.sessions.delete(sessionId);
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/tiresias.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiresias SDK — Main Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* tiresias.call(messages, adapter, options) is the high-level entry point.
|
|
5
|
+
* It runs the three safety signals, computes composite score, applies policy,
|
|
6
|
+
* writes a hash-chained audit entry, and either passes through to the LLM
|
|
7
|
+
* or blocks the request.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
Message,
|
|
12
|
+
LLMAdapter,
|
|
13
|
+
LLMCallOptions,
|
|
14
|
+
TiresiasCallResult,
|
|
15
|
+
TiresiasConfig,
|
|
16
|
+
WatchSignal,
|
|
17
|
+
ReportSignal,
|
|
18
|
+
NetworkSignal,
|
|
19
|
+
} from "./types.js";
|
|
20
|
+
import { DEFAULT_POLICY_THRESHOLDS } from "./types.js";
|
|
21
|
+
import { computeCompositeScore } from "./scoring.js";
|
|
22
|
+
import { buildAuditEntry, MemoryAuditStore } from "./audit.js";
|
|
23
|
+
import { SessionManager } from "./session.js";
|
|
24
|
+
|
|
25
|
+
// Lazily import sub-modules to allow tree-shaking
|
|
26
|
+
import {
|
|
27
|
+
generateKey,
|
|
28
|
+
injectGuard,
|
|
29
|
+
stripEcho,
|
|
30
|
+
verify,
|
|
31
|
+
} from "@salucallc/tiresias-watch";
|
|
32
|
+
|
|
33
|
+
import { scan } from "@salucallc/tiresias-report";
|
|
34
|
+
|
|
35
|
+
export class Tiresias {
|
|
36
|
+
private config: TiresiasConfig;
|
|
37
|
+
private sessions: SessionManager;
|
|
38
|
+
private auditStore: MemoryAuditStore;
|
|
39
|
+
|
|
40
|
+
constructor(config: TiresiasConfig = {}) {
|
|
41
|
+
this.config = config;
|
|
42
|
+
this.sessions = new SessionManager();
|
|
43
|
+
this.auditStore = (config.auditStore as MemoryAuditStore) ?? new MemoryAuditStore();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Main entry point.
|
|
48
|
+
* 1. Run watch (injection detection)
|
|
49
|
+
* 2. Run report (PII scan)
|
|
50
|
+
* 3. Get network signal (passthrough or provider)
|
|
51
|
+
* 4. Compute composite score
|
|
52
|
+
* 5. Apply policy
|
|
53
|
+
* 6. Write hash-chained audit entry
|
|
54
|
+
* 7. If ALLOW/WARN: call LLM adapter and return response
|
|
55
|
+
* If BLOCK/QUARANTINE: return without calling LLM
|
|
56
|
+
*/
|
|
57
|
+
async call(
|
|
58
|
+
messages: Message[],
|
|
59
|
+
adapter: LLMAdapter,
|
|
60
|
+
options: LLMCallOptions & { sessionId?: string } = {},
|
|
61
|
+
): Promise<TiresiasCallResult> {
|
|
62
|
+
const { sessionId: sid, ...llmOptions } = options;
|
|
63
|
+
const session = this.sessions.getOrCreate(sid ?? "default");
|
|
64
|
+
|
|
65
|
+
// Serialize request for hashing and scanning
|
|
66
|
+
const requestContent = messages.map((m) => `${m.role}: ${m.content}`).join("\n");
|
|
67
|
+
|
|
68
|
+
// ── Signal 1: Injection Detection (tiresias-watch) ────────────────────────
|
|
69
|
+
const key = generateKey();
|
|
70
|
+
const guardedMessages = messages.map((m) => ({
|
|
71
|
+
...m,
|
|
72
|
+
content: injectGuard(m.content, key),
|
|
73
|
+
}));
|
|
74
|
+
// We scan the original content for injection markers from prior round-trips.
|
|
75
|
+
// For a fresh request, injection threat comes from structural analysis.
|
|
76
|
+
const watchSignal = await this.runWatchSignal(messages, key);
|
|
77
|
+
|
|
78
|
+
// ── Signal 2: PII Detection (tiresias-report) ─────────────────────────────
|
|
79
|
+
const reportSignal = await this.runReportSignal(requestContent);
|
|
80
|
+
|
|
81
|
+
// ── Signal 3: Network Threat (tiresias-network or stub) ───────────────────
|
|
82
|
+
const networkSignal = await this.runNetworkSignal();
|
|
83
|
+
|
|
84
|
+
// ── Composite Score (PATENT-CRITICAL) ─────────────────────────────────────
|
|
85
|
+
const thresholds = this.config.policy ?? DEFAULT_POLICY_THRESHOLDS;
|
|
86
|
+
const composite = computeCompositeScore(watchSignal, reportSignal, networkSignal, thresholds);
|
|
87
|
+
|
|
88
|
+
// ── Audit Entry (PATENT-CRITICAL hash chain) ──────────────────────────────
|
|
89
|
+
const prevHash = await this.auditStore.getHead(session.sessionId);
|
|
90
|
+
const auditEntry = buildAuditEntry(
|
|
91
|
+
session.sessionId,
|
|
92
|
+
requestContent,
|
|
93
|
+
composite,
|
|
94
|
+
prevHash,
|
|
95
|
+
);
|
|
96
|
+
await this.auditStore.append(auditEntry);
|
|
97
|
+
this.sessions.update(session.sessionId, composite, auditEntry.chainHash);
|
|
98
|
+
|
|
99
|
+
const blocked = composite.policy === "BLOCK" || composite.policy === "QUARANTINE";
|
|
100
|
+
|
|
101
|
+
// ── Policy Callbacks ──────────────────────────────────────────────────────
|
|
102
|
+
if (composite.policy === "WARN" && this.config.onWarn) {
|
|
103
|
+
await this.config.onWarn(auditEntry);
|
|
104
|
+
}
|
|
105
|
+
if (blocked && this.config.onBlock) {
|
|
106
|
+
await this.config.onBlock(auditEntry);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── LLM Call ──────────────────────────────────────────────────────────────
|
|
110
|
+
let llmResponse;
|
|
111
|
+
if (!blocked) {
|
|
112
|
+
// Use guarded messages so we can detect echo manipulation in future turns
|
|
113
|
+
llmResponse = await adapter.call(guardedMessages, llmOptions);
|
|
114
|
+
|
|
115
|
+
// Strip guard tokens from response before returning to caller
|
|
116
|
+
if (llmResponse.content) {
|
|
117
|
+
llmResponse = {
|
|
118
|
+
...llmResponse,
|
|
119
|
+
content: stripEcho(llmResponse.content),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
decision: composite.policy,
|
|
126
|
+
composite,
|
|
127
|
+
auditEntry,
|
|
128
|
+
llmResponse,
|
|
129
|
+
blocked,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Audit log access */
|
|
134
|
+
get audit(): MemoryAuditStore {
|
|
135
|
+
return this.auditStore;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Session access */
|
|
139
|
+
get session(): SessionManager {
|
|
140
|
+
return this.sessions;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Private signal runners ────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
private async runWatchSignal(messages: Message[], key: string): Promise<WatchSignal> {
|
|
146
|
+
// For a fresh outbound request, injection threat is determined by
|
|
147
|
+
// checking if any inbound message contains injection markers aimed at
|
|
148
|
+
// hijacking the guard echo. We use verifyResponse on each user message.
|
|
149
|
+
let maxThreat = 0;
|
|
150
|
+
let echoFound = false;
|
|
151
|
+
let failureMode: string | undefined;
|
|
152
|
+
|
|
153
|
+
for (const m of messages) {
|
|
154
|
+
if (m.role !== "user") continue;
|
|
155
|
+
const result = verify(m.content, key);
|
|
156
|
+
if (!result.verified) {
|
|
157
|
+
if (result.failure_mode) {
|
|
158
|
+
failureMode = result.failure_mode;
|
|
159
|
+
maxThreat = Math.max(maxThreat, result.threat);
|
|
160
|
+
echoFound = true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check for common injection patterns in user messages
|
|
166
|
+
const injectionPatterns = [
|
|
167
|
+
/ignore\s+(all\s+)?previous\s+instructions?/i,
|
|
168
|
+
/you\s+are\s+now\s+(?:a\s+)?(?:an?\s+)?(?:different|new|another|alternative)/i,
|
|
169
|
+
/disregard\s+(?:your\s+)?(?:previous\s+|prior\s+)?(?:instructions?|rules?|guidelines?)/i,
|
|
170
|
+
/\[SYSTEM\]/i,
|
|
171
|
+
/<\|system\|>/i,
|
|
172
|
+
/###\s*SYSTEM/i,
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
for (const m of messages) {
|
|
176
|
+
if (m.role !== "user") continue;
|
|
177
|
+
let patternHits = 0;
|
|
178
|
+
for (const p of injectionPatterns) {
|
|
179
|
+
if (p.test(m.content)) patternHits++;
|
|
180
|
+
}
|
|
181
|
+
if (patternHits > 0) {
|
|
182
|
+
maxThreat = Math.max(maxThreat, Math.min(0.3 + patternHits * 0.2, 1.0));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { injectionThreat: maxThreat, echoFound, failureMode };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async runReportSignal(content: string): Promise<ReportSignal> {
|
|
190
|
+
try {
|
|
191
|
+
const result = await scan(content);
|
|
192
|
+
const highConf = result.entities.filter((e) => e.confidence > 0.85).length;
|
|
193
|
+
return {
|
|
194
|
+
piiRisk: result.risk,
|
|
195
|
+
entityCount: result.entity_count,
|
|
196
|
+
highConfidenceCount: highConf,
|
|
197
|
+
};
|
|
198
|
+
} catch {
|
|
199
|
+
// Fail safe: treat scan failure as zero risk (don't block on scan error)
|
|
200
|
+
return { piiRisk: 0, entityCount: 0, highConfidenceCount: 0 };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private async runNetworkSignal(): Promise<NetworkSignal> {
|
|
205
|
+
if (this.config.networkThreatProvider) {
|
|
206
|
+
try {
|
|
207
|
+
const threat = await this.config.networkThreatProvider();
|
|
208
|
+
return { networkThreat: Math.min(Math.max(threat, 0), 1) };
|
|
209
|
+
} catch {
|
|
210
|
+
return { networkThreat: 0 };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return { networkThreat: 0 };
|
|
214
|
+
}
|
|
215
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiresias SDK — Shared Types
|
|
3
|
+
* Composite scoring, policy engine, session, and audit types.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ── Signals from sub-modules ──────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
export interface WatchSignal {
|
|
9
|
+
injectionThreat: number; // [0,1] — from tiresias-watch
|
|
10
|
+
echoFound: boolean;
|
|
11
|
+
failureMode?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ReportSignal {
|
|
15
|
+
piiRisk: number; // [0,1] — from tiresias-report
|
|
16
|
+
entityCount: number;
|
|
17
|
+
highConfidenceCount: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface NetworkSignal {
|
|
21
|
+
networkThreat: number; // [0,1] — from tiresias-network (future)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ── Composite Scoring ─────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* PATENT-CRITICAL: Composite risk score formula (SALUCA-005 / cross_layer_orchestration)
|
|
28
|
+
* score = (0.5 × injection_threat) + (0.4 × pii_risk) + (0.1 × network_threat)
|
|
29
|
+
*/
|
|
30
|
+
export interface CompositeScore {
|
|
31
|
+
score: number; // [0,1]
|
|
32
|
+
injectionThreat: number;
|
|
33
|
+
piiRisk: number;
|
|
34
|
+
networkThreat: number;
|
|
35
|
+
policy: PolicyDecision;
|
|
36
|
+
timestamp: number; // Unix ms
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Policy Engine ─────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
export type PolicyDecision = "ALLOW" | "WARN" | "BLOCK" | "QUARANTINE";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Policy thresholds (configurable, defaults are patent-critical values)
|
|
45
|
+
* ALLOW < 0.30
|
|
46
|
+
* WARN < 0.60
|
|
47
|
+
* BLOCK < 0.85
|
|
48
|
+
* QUARANTINE >= 0.85
|
|
49
|
+
*/
|
|
50
|
+
export interface PolicyThresholds {
|
|
51
|
+
warnAt: number; // default 0.30
|
|
52
|
+
blockAt: number; // default 0.60
|
|
53
|
+
quarantineAt: number; // default 0.85
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const DEFAULT_POLICY_THRESHOLDS: PolicyThresholds = {
|
|
57
|
+
warnAt: 0.30,
|
|
58
|
+
blockAt: 0.60,
|
|
59
|
+
quarantineAt: 0.85,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ── Audit Log ─────────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* PATENT-CRITICAL: Hash-chained audit log entry (SALUCA-005)
|
|
66
|
+
* chain_hash = SHA3-256(prev_chain_hash || session_id || timestamp || score_json)
|
|
67
|
+
*/
|
|
68
|
+
export interface AuditEntry {
|
|
69
|
+
entryId: string; // UUID v4
|
|
70
|
+
sessionId: string;
|
|
71
|
+
timestamp: number; // Unix ms
|
|
72
|
+
requestHash: string; // SHA-256 of raw request content
|
|
73
|
+
composite: CompositeScore;
|
|
74
|
+
chainHash: string; // SHA3-256 chain link — PATENT-CRITICAL
|
|
75
|
+
prevChainHash: string; // "genesis" for first entry
|
|
76
|
+
metadata?: Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── Session ───────────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
export interface TiresiasSession {
|
|
82
|
+
sessionId: string;
|
|
83
|
+
createdAt: number;
|
|
84
|
+
requestCount: number;
|
|
85
|
+
lastScore: CompositeScore | null;
|
|
86
|
+
auditChainHead: string; // most recent chain hash
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── LLM Adapters ─────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
export type MessageRole = "system" | "user" | "assistant";
|
|
92
|
+
|
|
93
|
+
export interface Message {
|
|
94
|
+
role: MessageRole;
|
|
95
|
+
content: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface LLMResponse {
|
|
99
|
+
content: string;
|
|
100
|
+
usage?: {
|
|
101
|
+
inputTokens: number;
|
|
102
|
+
outputTokens: number;
|
|
103
|
+
};
|
|
104
|
+
raw?: unknown;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface LLMAdapter {
|
|
108
|
+
call(messages: Message[], options?: LLMCallOptions): Promise<LLMResponse>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface LLMCallOptions {
|
|
112
|
+
model?: string;
|
|
113
|
+
maxTokens?: number;
|
|
114
|
+
temperature?: number;
|
|
115
|
+
systemPrompt?: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── Tiresias SDK Config ───────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
export interface TiresiasConfig {
|
|
121
|
+
policy?: PolicyThresholds;
|
|
122
|
+
auditStore?: AuditStore;
|
|
123
|
+
networkThreatProvider?: () => Promise<number>;
|
|
124
|
+
onBlock?: (entry: AuditEntry) => void | Promise<void>;
|
|
125
|
+
onWarn?: (entry: AuditEntry) => void | Promise<void>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Audit Store Interface ─────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
export interface AuditStore {
|
|
131
|
+
append(entry: AuditEntry): Promise<void>;
|
|
132
|
+
getHead(sessionId: string): Promise<string>; // returns latest chain hash
|
|
133
|
+
query(sessionId: string, limit?: number): Promise<AuditEntry[]>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Result from tiresias.call() ───────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
export interface TiresiasCallResult {
|
|
139
|
+
decision: PolicyDecision;
|
|
140
|
+
composite: CompositeScore;
|
|
141
|
+
auditEntry: AuditEntry;
|
|
142
|
+
llmResponse?: LLMResponse; // undefined if BLOCK or QUARANTINE
|
|
143
|
+
blocked: boolean;
|
|
144
|
+
}
|