@opentrust/db 7.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/client.d.ts +3 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +51 -0
- package/dist/dialect.d.ts +3 -0
- package/dist/dialect.d.ts.map +1 -0
- package/dist/dialect.js +12 -0
- package/dist/generate.d.ts +2 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +20 -0
- package/dist/helpers.d.ts +11 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +32 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/migrate.d.ts +2 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +61 -0
- package/dist/queries/agents.d.ts +25 -0
- package/dist/queries/agents.d.ts.map +1 -0
- package/dist/queries/agents.js +46 -0
- package/dist/queries/auth.d.ts +18 -0
- package/dist/queries/auth.d.ts.map +1 -0
- package/dist/queries/auth.js +77 -0
- package/dist/queries/detection-results.d.ts +24 -0
- package/dist/queries/detection-results.d.ts.map +1 -0
- package/dist/queries/detection-results.js +43 -0
- package/dist/queries/observations.d.ts +58 -0
- package/dist/queries/observations.d.ts.map +1 -0
- package/dist/queries/observations.js +212 -0
- package/dist/queries/policies.d.ts +25 -0
- package/dist/queries/policies.d.ts.map +1 -0
- package/dist/queries/policies.js +38 -0
- package/dist/queries/scanners.d.ts +25 -0
- package/dist/queries/scanners.d.ts.map +1 -0
- package/dist/queries/scanners.js +56 -0
- package/dist/queries/settings.d.ts +8 -0
- package/dist/queries/settings.d.ts.map +1 -0
- package/dist/queries/settings.js +30 -0
- package/dist/queries/usage.d.ts +18 -0
- package/dist/queries/usage.d.ts.map +1 -0
- package/dist/queries/usage.js +54 -0
- package/dist/schema/index.d.ts +4415 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +19 -0
- package/dist/schema/mysql.d.ts +1479 -0
- package/dist/schema/mysql.d.ts.map +1 -0
- package/dist/schema/mysql.js +151 -0
- package/dist/schema/pg.d.ts +1479 -0
- package/dist/schema/pg.d.ts.map +1 -0
- package/dist/schema/pg.js +151 -0
- package/dist/schema/sqlite.d.ts +1479 -0
- package/dist/schema/sqlite.d.ts.map +1 -0
- package/dist/schema/sqlite.js +153 -0
- package/dist/seed.d.ts +2 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +49 -0
- package/drizzle/sqlite/0000_serious_martin_li.sql +143 -0
- package/drizzle/sqlite/meta/0000_snapshot.json +945 -0
- package/drizzle/sqlite/meta/_journal.json +13 -0
- package/drizzle.config.mysql.ts +10 -0
- package/drizzle.config.pg.ts +10 -0
- package/drizzle.config.sqlite.ts +10 -0
- package/package.json +55 -0
- package/src/client.ts +66 -0
- package/src/dialect.ts +13 -0
- package/src/generate.ts +26 -0
- package/src/helpers.ts +47 -0
- package/src/index.ts +12 -0
- package/src/migrate.ts +74 -0
- package/src/queries/agents.ts +68 -0
- package/src/queries/auth.ts +94 -0
- package/src/queries/detection-results.ts +58 -0
- package/src/queries/observations.ts +275 -0
- package/src/queries/policies.ts +59 -0
- package/src/queries/scanners.ts +74 -0
- package/src/queries/settings.ts +34 -0
- package/src/queries/usage.ts +69 -0
- package/src/schema/index.ts +22 -0
- package/src/schema/mysql.ts +207 -0
- package/src/schema/pg.ts +208 -0
- package/src/schema/sqlite.ts +199 -0
- package/src/seed.ts +56 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { eq, and, desc, count, sql } from "drizzle-orm";
|
|
2
|
+
import type { Database } from "../client.js";
|
|
3
|
+
import { toolCallObservations, agentPermissions } from "../schema/index.js";
|
|
4
|
+
import { DEFAULT_TENANT_ID } from "@opentrust/shared";
|
|
5
|
+
|
|
6
|
+
// ─── Tool name → category / access pattern inference ────────────
|
|
7
|
+
|
|
8
|
+
const ACCESS_READ_PREFIXES = ["list", "get", "search", "read", "fetch", "find", "query", "check", "view"];
|
|
9
|
+
const ACCESS_WRITE_PREFIXES = ["create", "send", "write", "update", "edit", "post", "put", "add", "set"];
|
|
10
|
+
const ACCESS_ADMIN_PREFIXES = ["delete", "remove", "execute", "run", "admin", "destroy", "revoke", "drop"];
|
|
11
|
+
|
|
12
|
+
export function inferCategory(toolName: string): string {
|
|
13
|
+
// "github_create_issue" → "github"
|
|
14
|
+
// "slack_send_message" → "slack"
|
|
15
|
+
// "read_file" → "filesystem"
|
|
16
|
+
const lower = toolName.toLowerCase();
|
|
17
|
+
|
|
18
|
+
if (lower.startsWith("read_file") || lower.startsWith("write_file") || lower.startsWith("list_dir")) {
|
|
19
|
+
return "filesystem";
|
|
20
|
+
}
|
|
21
|
+
if (lower.startsWith("execute_command") || lower.startsWith("run_command") || lower === "bash") {
|
|
22
|
+
return "shell";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const underscoreIdx = lower.indexOf("_");
|
|
26
|
+
if (underscoreIdx > 0) {
|
|
27
|
+
return lower.slice(0, underscoreIdx);
|
|
28
|
+
}
|
|
29
|
+
return lower;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function inferAccessPattern(toolName: string): "read" | "write" | "admin" | "unknown" {
|
|
33
|
+
const lower = toolName.toLowerCase();
|
|
34
|
+
|
|
35
|
+
// Strip category prefix: "github_create_issue" → "create_issue"
|
|
36
|
+
const underscoreIdx = lower.indexOf("_");
|
|
37
|
+
const action = underscoreIdx > 0 ? lower.slice(underscoreIdx + 1) : lower;
|
|
38
|
+
|
|
39
|
+
for (const prefix of ACCESS_ADMIN_PREFIXES) {
|
|
40
|
+
if (action.startsWith(prefix)) return "admin";
|
|
41
|
+
}
|
|
42
|
+
for (const prefix of ACCESS_WRITE_PREFIXES) {
|
|
43
|
+
if (action.startsWith(prefix)) return "write";
|
|
44
|
+
}
|
|
45
|
+
for (const prefix of ACCESS_READ_PREFIXES) {
|
|
46
|
+
if (action.startsWith(prefix)) return "read";
|
|
47
|
+
}
|
|
48
|
+
return "unknown";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ─── Query Functions ────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
export function observationQueries(db: Database) {
|
|
54
|
+
return {
|
|
55
|
+
/**
|
|
56
|
+
* Record a tool call observation.
|
|
57
|
+
*/
|
|
58
|
+
async record(data: {
|
|
59
|
+
agentId: string;
|
|
60
|
+
sessionKey?: string;
|
|
61
|
+
toolName: string;
|
|
62
|
+
params?: Record<string, unknown>;
|
|
63
|
+
phase: "before" | "after";
|
|
64
|
+
result?: unknown;
|
|
65
|
+
error?: string;
|
|
66
|
+
durationMs?: number;
|
|
67
|
+
blocked?: boolean;
|
|
68
|
+
blockReason?: string;
|
|
69
|
+
tenantId?: string;
|
|
70
|
+
}) {
|
|
71
|
+
const category = inferCategory(data.toolName);
|
|
72
|
+
const accessPattern = inferAccessPattern(data.toolName);
|
|
73
|
+
const tenantId = data.tenantId ?? DEFAULT_TENANT_ID;
|
|
74
|
+
|
|
75
|
+
await db.insert(toolCallObservations).values({
|
|
76
|
+
agentId: data.agentId,
|
|
77
|
+
sessionKey: data.sessionKey ?? null,
|
|
78
|
+
toolName: data.toolName,
|
|
79
|
+
category,
|
|
80
|
+
accessPattern,
|
|
81
|
+
paramsJson: data.params ?? null,
|
|
82
|
+
phase: data.phase,
|
|
83
|
+
resultJson: data.result ?? null,
|
|
84
|
+
error: data.error ?? null,
|
|
85
|
+
durationMs: data.durationMs ?? null,
|
|
86
|
+
blocked: data.blocked ?? false,
|
|
87
|
+
blockReason: data.blockReason ?? null,
|
|
88
|
+
tenantId,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Upsert permission on "after" phase (or "before" if blocked)
|
|
92
|
+
if (data.phase === "after" || data.blocked) {
|
|
93
|
+
await this.upsertPermission({
|
|
94
|
+
agentId: data.agentId,
|
|
95
|
+
toolName: data.toolName,
|
|
96
|
+
category,
|
|
97
|
+
accessPattern,
|
|
98
|
+
params: data.params,
|
|
99
|
+
hasError: !!data.error,
|
|
100
|
+
tenantId,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Upsert an agent permission entry based on observed tool call.
|
|
107
|
+
*/
|
|
108
|
+
async upsertPermission(data: {
|
|
109
|
+
agentId: string;
|
|
110
|
+
toolName: string;
|
|
111
|
+
category: string;
|
|
112
|
+
accessPattern: string;
|
|
113
|
+
params?: Record<string, unknown>;
|
|
114
|
+
hasError: boolean;
|
|
115
|
+
tenantId: string;
|
|
116
|
+
}) {
|
|
117
|
+
const now = new Date().toISOString();
|
|
118
|
+
|
|
119
|
+
// Check if permission already exists
|
|
120
|
+
const existing = await db
|
|
121
|
+
.select()
|
|
122
|
+
.from(agentPermissions)
|
|
123
|
+
.where(
|
|
124
|
+
and(
|
|
125
|
+
eq(agentPermissions.tenantId, data.tenantId),
|
|
126
|
+
eq(agentPermissions.agentId, data.agentId),
|
|
127
|
+
eq(agentPermissions.toolName, data.toolName),
|
|
128
|
+
),
|
|
129
|
+
)
|
|
130
|
+
.limit(1);
|
|
131
|
+
|
|
132
|
+
if (existing.length > 0) {
|
|
133
|
+
const perm = existing[0]!;
|
|
134
|
+
const targets = (perm.targetsJson as string[]) || [];
|
|
135
|
+
const newTargets = extractTargets(data.params);
|
|
136
|
+
const mergedTargets = mergeTargets(targets, newTargets);
|
|
137
|
+
|
|
138
|
+
await db
|
|
139
|
+
.update(agentPermissions)
|
|
140
|
+
.set({
|
|
141
|
+
callCount: (perm.callCount ?? 0) + 1,
|
|
142
|
+
errorCount: (perm.errorCount ?? 0) + (data.hasError ? 1 : 0),
|
|
143
|
+
lastSeen: now,
|
|
144
|
+
targetsJson: mergedTargets,
|
|
145
|
+
})
|
|
146
|
+
.where(eq(agentPermissions.id, perm.id));
|
|
147
|
+
} else {
|
|
148
|
+
const targets = extractTargets(data.params);
|
|
149
|
+
await db.insert(agentPermissions).values({
|
|
150
|
+
tenantId: data.tenantId,
|
|
151
|
+
agentId: data.agentId,
|
|
152
|
+
toolName: data.toolName,
|
|
153
|
+
category: data.category,
|
|
154
|
+
accessPattern: data.accessPattern,
|
|
155
|
+
targetsJson: targets,
|
|
156
|
+
callCount: 1,
|
|
157
|
+
errorCount: data.hasError ? 1 : 0,
|
|
158
|
+
firstSeen: now,
|
|
159
|
+
lastSeen: now,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get recent observations, optionally filtered by agentId.
|
|
166
|
+
*/
|
|
167
|
+
async findRecent(opts: {
|
|
168
|
+
agentId?: string;
|
|
169
|
+
limit?: number;
|
|
170
|
+
tenantId?: string;
|
|
171
|
+
} = {}) {
|
|
172
|
+
const tenantId = opts.tenantId ?? DEFAULT_TENANT_ID;
|
|
173
|
+
const limit = opts.limit ?? 50;
|
|
174
|
+
|
|
175
|
+
const conditions = [eq(toolCallObservations.tenantId, tenantId)];
|
|
176
|
+
if (opts.agentId) {
|
|
177
|
+
conditions.push(eq(toolCallObservations.agentId, opts.agentId));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return db
|
|
181
|
+
.select()
|
|
182
|
+
.from(toolCallObservations)
|
|
183
|
+
.where(and(...conditions))
|
|
184
|
+
.orderBy(desc(toolCallObservations.timestamp))
|
|
185
|
+
.limit(limit);
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get the aggregated permission profile for an agent.
|
|
190
|
+
*/
|
|
191
|
+
async getPermissions(agentId: string, tenantId: string = DEFAULT_TENANT_ID) {
|
|
192
|
+
return db
|
|
193
|
+
.select()
|
|
194
|
+
.from(agentPermissions)
|
|
195
|
+
.where(
|
|
196
|
+
and(
|
|
197
|
+
eq(agentPermissions.tenantId, tenantId),
|
|
198
|
+
eq(agentPermissions.agentId, agentId),
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
.orderBy(desc(agentPermissions.callCount));
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get permissions for all agents (overview).
|
|
206
|
+
*/
|
|
207
|
+
async getAllPermissions(tenantId: string = DEFAULT_TENANT_ID) {
|
|
208
|
+
return db
|
|
209
|
+
.select()
|
|
210
|
+
.from(agentPermissions)
|
|
211
|
+
.where(eq(agentPermissions.tenantId, tenantId))
|
|
212
|
+
.orderBy(agentPermissions.agentId, desc(agentPermissions.callCount));
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Find first-seen tool calls (anomalies) — permissions with callCount = 1.
|
|
217
|
+
*/
|
|
218
|
+
async findAnomalies(tenantId: string = DEFAULT_TENANT_ID, limit: number = 20) {
|
|
219
|
+
return db
|
|
220
|
+
.select()
|
|
221
|
+
.from(agentPermissions)
|
|
222
|
+
.where(
|
|
223
|
+
and(
|
|
224
|
+
eq(agentPermissions.tenantId, tenantId),
|
|
225
|
+
eq(agentPermissions.callCount, 1),
|
|
226
|
+
),
|
|
227
|
+
)
|
|
228
|
+
.orderBy(desc(agentPermissions.firstSeen))
|
|
229
|
+
.limit(limit);
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get observation count summary per agent.
|
|
234
|
+
*/
|
|
235
|
+
async summary(tenantId: string = DEFAULT_TENANT_ID) {
|
|
236
|
+
return db
|
|
237
|
+
.select({
|
|
238
|
+
agentId: toolCallObservations.agentId,
|
|
239
|
+
totalCalls: count(),
|
|
240
|
+
blockedCalls: sql<number>`sum(case when ${toolCallObservations.blocked} = true then 1 else 0 end)`,
|
|
241
|
+
uniqueTools: sql<number>`count(distinct ${toolCallObservations.toolName})`,
|
|
242
|
+
})
|
|
243
|
+
.from(toolCallObservations)
|
|
244
|
+
.where(eq(toolCallObservations.tenantId, tenantId))
|
|
245
|
+
.groupBy(toolCallObservations.agentId);
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ─── Helpers ────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
/** Extract likely target identifiers from tool call params. */
|
|
253
|
+
function extractTargets(params?: Record<string, unknown>): string[] {
|
|
254
|
+
if (!params) return [];
|
|
255
|
+
const targets: string[] = [];
|
|
256
|
+
|
|
257
|
+
const targetKeys = ["repo", "repository", "channel", "to", "email", "path", "file", "url", "owner", "user", "org"];
|
|
258
|
+
for (const key of targetKeys) {
|
|
259
|
+
const val = params[key];
|
|
260
|
+
if (typeof val === "string" && val.length > 0 && val.length < 200) {
|
|
261
|
+
targets.push(val);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return targets;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Merge new targets into existing list, capped at 50 entries. */
|
|
268
|
+
function mergeTargets(existing: string[], incoming: string[]): string[] {
|
|
269
|
+
const set = new Set(existing);
|
|
270
|
+
for (const t of incoming) {
|
|
271
|
+
set.add(t);
|
|
272
|
+
}
|
|
273
|
+
const merged = [...set];
|
|
274
|
+
return merged.length > 50 ? merged.slice(-50) : merged;
|
|
275
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { eq, and } from "drizzle-orm";
|
|
2
|
+
import type { Database } from "../client.js";
|
|
3
|
+
import { policies } from "../schema/index.js";
|
|
4
|
+
import { insertReturning, updateReturning } from "../helpers.js";
|
|
5
|
+
import { DEFAULT_TENANT_ID } from "@opentrust/shared";
|
|
6
|
+
|
|
7
|
+
export function policyQueries(db: Database) {
|
|
8
|
+
return {
|
|
9
|
+
async findAll(tenantId: string = DEFAULT_TENANT_ID) {
|
|
10
|
+
return db.select().from(policies).where(eq(policies.tenantId, tenantId)).orderBy(policies.createdAt);
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
async findById(id: string, tenantId: string = DEFAULT_TENANT_ID) {
|
|
14
|
+
const result = await db.select().from(policies).where(and(eq(policies.id, id), eq(policies.tenantId, tenantId))).limit(1);
|
|
15
|
+
return result[0] ?? null;
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
async create(data: {
|
|
19
|
+
name: string;
|
|
20
|
+
description?: string | null;
|
|
21
|
+
scannerIds: string[];
|
|
22
|
+
action: string;
|
|
23
|
+
sensitivityThreshold?: number;
|
|
24
|
+
tenantId?: string;
|
|
25
|
+
}) {
|
|
26
|
+
return insertReturning(db, policies, {
|
|
27
|
+
...data,
|
|
28
|
+
sensitivityThreshold: data.sensitivityThreshold ?? 0.5,
|
|
29
|
+
tenantId: data.tenantId ?? DEFAULT_TENANT_ID,
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async update(id: string, data: Partial<{
|
|
34
|
+
name: string;
|
|
35
|
+
description: string | null;
|
|
36
|
+
scannerIds: string[];
|
|
37
|
+
action: string;
|
|
38
|
+
sensitivityThreshold: number;
|
|
39
|
+
isEnabled: boolean;
|
|
40
|
+
}>, tenantId: string = DEFAULT_TENANT_ID) {
|
|
41
|
+
return updateReturning(db, policies, and(eq(policies.id, id), eq(policies.tenantId, tenantId)), {
|
|
42
|
+
...data,
|
|
43
|
+
updatedAt: new Date().toISOString(),
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
async delete(id: string, tenantId: string = DEFAULT_TENANT_ID) {
|
|
48
|
+
await db.delete(policies).where(and(eq(policies.id, id), eq(policies.tenantId, tenantId)));
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
/** Get all enabled policies for detection flow */
|
|
52
|
+
async getEnabled(tenantId: string = DEFAULT_TENANT_ID) {
|
|
53
|
+
return db
|
|
54
|
+
.select()
|
|
55
|
+
.from(policies)
|
|
56
|
+
.where(and(eq(policies.isEnabled, true), eq(policies.tenantId, tenantId)));
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { eq, and } from "drizzle-orm";
|
|
2
|
+
import type { Database } from "../client.js";
|
|
3
|
+
import { scannerDefinitions } from "../schema/index.js";
|
|
4
|
+
import { insertReturning } from "../helpers.js";
|
|
5
|
+
import { DEFAULT_TENANT_ID } from "@opentrust/shared";
|
|
6
|
+
|
|
7
|
+
export function scannerQueries(db: Database) {
|
|
8
|
+
return {
|
|
9
|
+
/** Get all scanners (defaults + overrides) */
|
|
10
|
+
async getAll(tenantId: string = DEFAULT_TENANT_ID) {
|
|
11
|
+
return db
|
|
12
|
+
.select()
|
|
13
|
+
.from(scannerDefinitions)
|
|
14
|
+
.where(eq(scannerDefinitions.tenantId, tenantId))
|
|
15
|
+
.orderBy(scannerDefinitions.scannerId);
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
/** Get all system default scanners */
|
|
19
|
+
async getDefaults(tenantId: string = DEFAULT_TENANT_ID) {
|
|
20
|
+
return db
|
|
21
|
+
.select()
|
|
22
|
+
.from(scannerDefinitions)
|
|
23
|
+
.where(and(eq(scannerDefinitions.isDefault, true), eq(scannerDefinitions.tenantId, tenantId)))
|
|
24
|
+
.orderBy(scannerDefinitions.scannerId);
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
/** Get enabled scanners for detection */
|
|
28
|
+
async getEnabled(tenantId: string = DEFAULT_TENANT_ID) {
|
|
29
|
+
return db
|
|
30
|
+
.select()
|
|
31
|
+
.from(scannerDefinitions)
|
|
32
|
+
.where(and(eq(scannerDefinitions.isEnabled, true), eq(scannerDefinitions.tenantId, tenantId)))
|
|
33
|
+
.orderBy(scannerDefinitions.scannerId);
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
/** Upsert a scanner override */
|
|
37
|
+
async upsert(data: {
|
|
38
|
+
scannerId: string;
|
|
39
|
+
name: string;
|
|
40
|
+
description: string;
|
|
41
|
+
isEnabled: boolean;
|
|
42
|
+
tenantId?: string;
|
|
43
|
+
}) {
|
|
44
|
+
const tid = data.tenantId ?? DEFAULT_TENANT_ID;
|
|
45
|
+
// Delete existing non-default with same scannerId for this tenant
|
|
46
|
+
await db
|
|
47
|
+
.delete(scannerDefinitions)
|
|
48
|
+
.where(
|
|
49
|
+
and(
|
|
50
|
+
eq(scannerDefinitions.scannerId, data.scannerId),
|
|
51
|
+
eq(scannerDefinitions.isDefault, false),
|
|
52
|
+
eq(scannerDefinitions.tenantId, tid)
|
|
53
|
+
)
|
|
54
|
+
);
|
|
55
|
+
return insertReturning(db, scannerDefinitions, {
|
|
56
|
+
scannerId: data.scannerId,
|
|
57
|
+
name: data.name,
|
|
58
|
+
description: data.description,
|
|
59
|
+
isEnabled: data.isEnabled,
|
|
60
|
+
isDefault: false,
|
|
61
|
+
tenantId: tid,
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
/** Create a system default scanner */
|
|
66
|
+
async createDefault(data: { scannerId: string; name: string; description: string; tenantId?: string }) {
|
|
67
|
+
return insertReturning(db, scannerDefinitions, {
|
|
68
|
+
...data,
|
|
69
|
+
isDefault: true,
|
|
70
|
+
tenantId: data.tenantId ?? DEFAULT_TENANT_ID,
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { eq } from "drizzle-orm";
|
|
2
|
+
import type { Database } from "../client.js";
|
|
3
|
+
import { settings } from "../schema/index.js";
|
|
4
|
+
|
|
5
|
+
export function settingsQueries(db: Database) {
|
|
6
|
+
return {
|
|
7
|
+
async get(key: string): Promise<string | null> {
|
|
8
|
+
const result = await db.select().from(settings).where(eq(settings.key, key)).limit(1);
|
|
9
|
+
return result[0]?.value ?? null;
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
async set(key: string, value: string) {
|
|
13
|
+
const existing = await this.get(key);
|
|
14
|
+
if (existing !== null) {
|
|
15
|
+
await db.update(settings).set({ value, updatedAt: new Date().toISOString() }).where(eq(settings.key, key));
|
|
16
|
+
} else {
|
|
17
|
+
await db.insert(settings).values({ key, value });
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
async getAll(): Promise<Record<string, string>> {
|
|
22
|
+
const rows = await db.select().from(settings);
|
|
23
|
+
const result: Record<string, string> = {};
|
|
24
|
+
for (const row of rows) {
|
|
25
|
+
result[row.key] = row.value;
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
async delete(key: string) {
|
|
31
|
+
await db.delete(settings).where(eq(settings.key, key));
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { eq, gte, lte, sql, count, and } from "drizzle-orm";
|
|
2
|
+
import type { Database } from "../client.js";
|
|
3
|
+
import { usageLogs } from "../schema/index.js";
|
|
4
|
+
import { DEFAULT_TENANT_ID } from "@opentrust/shared";
|
|
5
|
+
|
|
6
|
+
export function usageQueries(db: Database) {
|
|
7
|
+
return {
|
|
8
|
+
async log(data: {
|
|
9
|
+
agentId?: string | null;
|
|
10
|
+
endpoint: string;
|
|
11
|
+
statusCode: number;
|
|
12
|
+
responseSafe: boolean | null;
|
|
13
|
+
categories?: string[];
|
|
14
|
+
latencyMs: number;
|
|
15
|
+
requestId: string;
|
|
16
|
+
tenantId?: string;
|
|
17
|
+
}) {
|
|
18
|
+
await db.insert(usageLogs).values({
|
|
19
|
+
...data,
|
|
20
|
+
categories: data.categories ?? [],
|
|
21
|
+
tenantId: data.tenantId ?? DEFAULT_TENANT_ID,
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
async countInPeriod(start: Date | string, end: Date | string, tenantId: string = DEFAULT_TENANT_ID) {
|
|
26
|
+
const result = await db
|
|
27
|
+
.select({ count: count() })
|
|
28
|
+
.from(usageLogs)
|
|
29
|
+
.where(and(gte(usageLogs.createdAt, start), lte(usageLogs.createdAt, end), eq(usageLogs.tenantId, tenantId)));
|
|
30
|
+
return result[0]?.count ?? 0;
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async summary(start: Date | string, end: Date | string, tenantId: string = DEFAULT_TENANT_ID) {
|
|
34
|
+
const result = await db
|
|
35
|
+
.select({
|
|
36
|
+
totalCalls: count(),
|
|
37
|
+
safeCount: sql<number>`sum(case when ${usageLogs.responseSafe} = true then 1 else 0 end)`,
|
|
38
|
+
unsafeCount: sql<number>`sum(case when ${usageLogs.responseSafe} = false then 1 else 0 end)`,
|
|
39
|
+
})
|
|
40
|
+
.from(usageLogs)
|
|
41
|
+
.where(and(gte(usageLogs.createdAt, start), lte(usageLogs.createdAt, end), eq(usageLogs.tenantId, tenantId)));
|
|
42
|
+
return result[0] ?? { totalCalls: 0, safeCount: 0, unsafeCount: 0 };
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
async daily(start: Date | string, end: Date | string, tenantId: string = DEFAULT_TENANT_ID) {
|
|
46
|
+
const result = await db
|
|
47
|
+
.select({
|
|
48
|
+
date: sql<string>`date(${usageLogs.createdAt})`,
|
|
49
|
+
count: count(),
|
|
50
|
+
safeCount: sql<number>`sum(case when ${usageLogs.responseSafe} = true then 1 else 0 end)`,
|
|
51
|
+
unsafeCount: sql<number>`sum(case when ${usageLogs.responseSafe} = false then 1 else 0 end)`,
|
|
52
|
+
})
|
|
53
|
+
.from(usageLogs)
|
|
54
|
+
.where(and(gte(usageLogs.createdAt, start), lte(usageLogs.createdAt, end), eq(usageLogs.tenantId, tenantId)))
|
|
55
|
+
.groupBy(sql`date(${usageLogs.createdAt})`)
|
|
56
|
+
.orderBy(sql`date(${usageLogs.createdAt})`);
|
|
57
|
+
return result;
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
async countRecent(minutes: number = 1, tenantId: string = DEFAULT_TENANT_ID) {
|
|
61
|
+
const since = new Date(Date.now() - minutes * 60_000).toISOString();
|
|
62
|
+
const result = await db
|
|
63
|
+
.select({ count: count() })
|
|
64
|
+
.from(usageLogs)
|
|
65
|
+
.where(and(gte(usageLogs.createdAt, since), eq(usageLogs.tenantId, tenantId)));
|
|
66
|
+
return result[0]?.count ?? 0;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getDialect } from "../dialect.js";
|
|
2
|
+
|
|
3
|
+
const dialect = getDialect();
|
|
4
|
+
|
|
5
|
+
// Dynamic schema loading based on dialect
|
|
6
|
+
// Using top-level await (Node 22+)
|
|
7
|
+
const mod = dialect === "sqlite"
|
|
8
|
+
? await import("./sqlite.js")
|
|
9
|
+
: dialect === "mysql"
|
|
10
|
+
? await import("./mysql.js")
|
|
11
|
+
: await import("./pg.js");
|
|
12
|
+
|
|
13
|
+
export const settings = mod.settings;
|
|
14
|
+
export const agents = mod.agents;
|
|
15
|
+
export const scannerDefinitions = mod.scannerDefinitions;
|
|
16
|
+
export const policies = mod.policies;
|
|
17
|
+
export const usageLogs = mod.usageLogs;
|
|
18
|
+
export const detectionResults = mod.detectionResults;
|
|
19
|
+
export const toolCallObservations = mod.toolCallObservations;
|
|
20
|
+
export const agentPermissions = mod.agentPermissions;
|
|
21
|
+
export const magicLinks = mod.magicLinks;
|
|
22
|
+
export const userSessions = mod.userSessions;
|