@anchrd/intel-api 0.2.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 (114) hide show
  1. package/README.md +96 -0
  2. package/bin/intel.mjs +134 -0
  3. package/dist/adapters/cloudflare/cloudflare-flow-workflow.d.ts +21 -0
  4. package/dist/adapters/cloudflare/cloudflare-flow-workflow.js +39 -0
  5. package/dist/adapters/cloudflare/cloudflare.d.ts +8 -0
  6. package/dist/adapters/cloudflare/cloudflare.js +200 -0
  7. package/dist/adapters/cloudflare/cloudflare.types.d.ts +29 -0
  8. package/dist/adapters/cloudflare/cloudflare.types.js +1 -0
  9. package/dist/adapters/content/content.d.ts +3 -0
  10. package/dist/adapters/content/content.js +22 -0
  11. package/dist/adapters/content/content.types.d.ts +14 -0
  12. package/dist/adapters/content/content.types.js +1 -0
  13. package/dist/adapters/db/db-flows.d.ts +6 -0
  14. package/dist/adapters/db/db-flows.js +466 -0
  15. package/dist/adapters/db/db-indexing.d.ts +3 -0
  16. package/dist/adapters/db/db-indexing.js +70 -0
  17. package/dist/adapters/db/db-oauth.d.ts +3 -0
  18. package/dist/adapters/db/db-oauth.js +26 -0
  19. package/dist/adapters/db/db-tools.d.ts +6 -0
  20. package/dist/adapters/db/db-tools.js +200 -0
  21. package/dist/adapters/db/db.d.ts +6 -0
  22. package/dist/adapters/db/db.js +755 -0
  23. package/dist/adapters/db/db.types.d.ts +16 -0
  24. package/dist/adapters/db/db.types.js +1 -0
  25. package/dist/adapters/document-converter/document-converter.d.ts +2 -0
  26. package/dist/adapters/document-converter/document-converter.js +25 -0
  27. package/dist/adapters/document-converter/document-converter.types.d.ts +9 -0
  28. package/dist/adapters/document-converter/document-converter.types.js +1 -0
  29. package/dist/adapters/flow-runtime/flow-runtime.d.ts +3 -0
  30. package/dist/adapters/flow-runtime/flow-runtime.js +22 -0
  31. package/dist/adapters/flow-runtime/flow-runtime.types.d.ts +19 -0
  32. package/dist/adapters/flow-runtime/flow-runtime.types.js +1 -0
  33. package/dist/adapters/index-queue/index-queue.d.ts +10 -0
  34. package/dist/adapters/index-queue/index-queue.js +12 -0
  35. package/dist/adapters/index-queue/index-queue.types.d.ts +11 -0
  36. package/dist/adapters/index-queue/index-queue.types.js +1 -0
  37. package/dist/adapters/json-schema/json-schema.d.ts +2 -0
  38. package/dist/adapters/json-schema/json-schema.js +31 -0
  39. package/dist/adapters/openid/openid.d.ts +5 -0
  40. package/dist/adapters/openid/openid.js +159 -0
  41. package/dist/adapters/portal-tokens/portal-tokens.d.ts +8 -0
  42. package/dist/adapters/portal-tokens/portal-tokens.js +71 -0
  43. package/dist/adapters/remote-tools/remote-tools.d.ts +5 -0
  44. package/dist/adapters/remote-tools/remote-tools.js +109 -0
  45. package/dist/adapters/semantic-index/semantic-index.d.ts +2 -0
  46. package/dist/adapters/semantic-index/semantic-index.js +57 -0
  47. package/dist/adapters/semantic-index/semantic-index.types.d.ts +32 -0
  48. package/dist/adapters/semantic-index/semantic-index.types.js +1 -0
  49. package/dist/adapters/session-cookie/session-cookie.d.ts +5 -0
  50. package/dist/adapters/session-cookie/session-cookie.js +81 -0
  51. package/dist/adapters/tool-source-policy/tool-source-policy.d.ts +1 -0
  52. package/dist/adapters/tool-source-policy/tool-source-policy.js +22 -0
  53. package/dist/auth/auth.d.ts +2 -0
  54. package/dist/auth/auth.js +176 -0
  55. package/dist/auth/auth.types.d.ts +104 -0
  56. package/dist/auth/auth.types.js +1 -0
  57. package/dist/build/build.d.ts +2 -0
  58. package/dist/build/build.js +125 -0
  59. package/dist/build/build.types.d.ts +17 -0
  60. package/dist/build/build.types.js +1 -0
  61. package/dist/cli/cli.d.ts +4 -0
  62. package/dist/cli/cli.js +141 -0
  63. package/dist/cli/cli.types.d.ts +14 -0
  64. package/dist/cli/cli.types.js +1 -0
  65. package/dist/flows/flows.d.ts +4 -0
  66. package/dist/flows/flows.js +409 -0
  67. package/dist/flows/flows.types.d.ts +108 -0
  68. package/dist/flows/flows.types.js +1 -0
  69. package/dist/http/http.d.ts +8 -0
  70. package/dist/http/http.js +286 -0
  71. package/dist/http/http.types.d.ts +14 -0
  72. package/dist/http/http.types.js +1 -0
  73. package/dist/indexing/indexing.d.ts +6 -0
  74. package/dist/indexing/indexing.js +48 -0
  75. package/dist/indexing/indexing.types.d.ts +10 -0
  76. package/dist/indexing/indexing.types.js +1 -0
  77. package/dist/intel/intel.d.ts +3 -0
  78. package/dist/intel/intel.js +77 -0
  79. package/dist/intel/intel.types.d.ts +14 -0
  80. package/dist/intel/intel.types.js +1 -0
  81. package/dist/knowledge/knowledge.d.ts +2 -0
  82. package/dist/knowledge/knowledge.js +479 -0
  83. package/dist/knowledge/knowledge.types.d.ts +162 -0
  84. package/dist/knowledge/knowledge.types.js +1 -0
  85. package/dist/mcp/mcp.d.ts +2 -0
  86. package/dist/mcp/mcp.js +484 -0
  87. package/dist/mcp/mcp.types.d.ts +10 -0
  88. package/dist/mcp/mcp.types.js +1 -0
  89. package/dist/prepare/prepare.d.ts +4 -0
  90. package/dist/prepare/prepare.js +16 -0
  91. package/dist/prepare/prepare.types.d.ts +9 -0
  92. package/dist/prepare/prepare.types.js +1 -0
  93. package/dist/shared/gate-authorization/gate-authorization.d.ts +4 -0
  94. package/dist/shared/gate-authorization/gate-authorization.js +19 -0
  95. package/dist/shared/intel-error/intel-error.d.ts +5 -0
  96. package/dist/shared/intel-error/intel-error.js +10 -0
  97. package/dist/shared/problem-details/problem-details.d.ts +8 -0
  98. package/dist/shared/problem-details/problem-details.js +4 -0
  99. package/dist/shared/safe-return-path/safe-return-path.d.ts +2 -0
  100. package/dist/shared/safe-return-path/safe-return-path.js +11 -0
  101. package/dist/shared/sha256/sha256.d.ts +1 -0
  102. package/dist/shared/sha256/sha256.js +9 -0
  103. package/dist/tools/tools.d.ts +2 -0
  104. package/dist/tools/tools.js +107 -0
  105. package/dist/tools/tools.types.d.ts +56 -0
  106. package/dist/tools/tools.types.js +1 -0
  107. package/examples/branding/favicon.svg +5 -0
  108. package/examples/branding/logo.svg +5 -0
  109. package/examples/branding/theme.css +16 -0
  110. package/examples/dev.vars.example +7 -0
  111. package/examples/intel.json +9 -0
  112. package/migrations/0000_intel_foundation.sql +203 -0
  113. package/migrations/0001_portal_native_tools.sql +21 -0
  114. package/package.json +62 -0
@@ -0,0 +1,466 @@
1
+ import { Flow, FlowGraph, FlowRun, FlowVersion, } from "@anchrd/intel-contract";
2
+ const flowColumns = `id, title, description, owner_id, current_version_id,
3
+ published_version_id, created_at, updated_at, archived_at`;
4
+ const runColumnNames = [
5
+ "id",
6
+ "flow_id",
7
+ "version_id",
8
+ "status",
9
+ "current_node_id",
10
+ "input_json",
11
+ "output_json",
12
+ "error",
13
+ "initiated_by",
14
+ "created_at",
15
+ "updated_at",
16
+ "completed_at",
17
+ ];
18
+ const runColumns = runColumnNames.join(", ");
19
+ // Joined queries need the columns qualified. Deriving them from the array keeps both forms in step;
20
+ // splitting the rendered string would break the moment it is reformatted.
21
+ const qualifiedRunColumns = (alias) => runColumnNames.map((column) => `${alias}.${column}`).join(", ");
22
+ function accessPredicate(roles = []) {
23
+ const role = roles.length ? `AND grant_row.role IN (${roles.map(() => "?").join(", ")})` : "";
24
+ return `(flow.owner_id = ? OR EXISTS (
25
+ SELECT 1 FROM resource_grants grant_row
26
+ WHERE grant_row.resource_type = 'flow' AND grant_row.resource_id = flow.id
27
+ AND (
28
+ (grant_row.principal_type = 'user' AND grant_row.principal_id = ?)
29
+ OR (grant_row.principal_type = 'email' AND lower(grant_row.principal_id) = lower(?))
30
+ OR (grant_row.principal_type = 'organization' AND grant_row.principal_id = '*')
31
+ )
32
+ ${role}
33
+ AND (grant_row.expires_at IS NULL OR grant_row.expires_at > ?)
34
+ ))`;
35
+ }
36
+ function accessBindings(actor, now, roles = []) {
37
+ return [actor.id, actor.id, actor.email, ...roles, now];
38
+ }
39
+ function mapFlow(row) {
40
+ return Flow.parse({
41
+ id: row.id,
42
+ title: row.title,
43
+ description: row.description,
44
+ ownerId: row.owner_id,
45
+ currentVersionId: row.current_version_id,
46
+ publishedVersionId: row.published_version_id,
47
+ createdAt: row.created_at,
48
+ updatedAt: row.updated_at,
49
+ archivedAt: row.archived_at,
50
+ });
51
+ }
52
+ function mapVersion(row) {
53
+ return FlowVersion.parse({
54
+ id: row.id,
55
+ flowId: row.flow_id,
56
+ sequence: row.sequence,
57
+ graph: FlowGraph.parse(JSON.parse(row.graph_json)),
58
+ createdBy: row.created_by,
59
+ createdAt: row.created_at,
60
+ });
61
+ }
62
+ function mapRun(row) {
63
+ return FlowRun.parse({
64
+ id: row.id,
65
+ flowId: row.flow_id,
66
+ versionId: row.version_id,
67
+ status: row.status,
68
+ currentNodeId: row.current_node_id,
69
+ input: JSON.parse(row.input_json),
70
+ output: row.output_json === null ? null : JSON.parse(row.output_json),
71
+ error: row.error,
72
+ initiatedBy: row.initiated_by,
73
+ createdAt: row.created_at,
74
+ updatedAt: row.updated_at,
75
+ completedAt: row.completed_at,
76
+ });
77
+ }
78
+ function mapGrant(row) {
79
+ let principal;
80
+ switch (row.principal_type) {
81
+ case "user":
82
+ principal = { type: "user", id: row.principal_id };
83
+ break;
84
+ case "email":
85
+ principal = { type: "email", email: row.principal_id };
86
+ break;
87
+ case "organization":
88
+ principal = { type: "organization" };
89
+ break;
90
+ default:
91
+ throw new Error("Unsupported flow grant principal type");
92
+ }
93
+ return {
94
+ id: row.id,
95
+ resourceId: row.resource_id,
96
+ principal,
97
+ role: row.role,
98
+ expiresAt: row.expires_at,
99
+ createdBy: row.created_by,
100
+ createdAt: row.created_at,
101
+ };
102
+ }
103
+ export function createFlowRepository(deps) {
104
+ const visible = accessPredicate();
105
+ return {
106
+ async listVisible(actor) {
107
+ const result = await deps.db
108
+ .prepare(`SELECT ${flowColumns} FROM flows flow
109
+ WHERE ${visible} AND flow.archived_at IS NULL
110
+ ORDER BY lower(flow.title), flow.id`)
111
+ .bind(...accessBindings(actor, deps.now().toISOString()))
112
+ .all();
113
+ return (result.results ?? []).map(mapFlow);
114
+ },
115
+ async getVisible(actor, flowId) {
116
+ const row = await deps.db
117
+ .prepare(`SELECT ${flowColumns} FROM flows flow WHERE flow.id = ? AND ${visible}`)
118
+ .bind(flowId, ...accessBindings(actor, deps.now().toISOString()))
119
+ .first();
120
+ return row ? mapFlow(row) : null;
121
+ },
122
+ async canEdit(actor, flowId) {
123
+ const row = await deps.db
124
+ .prepare(`SELECT 1 AS allowed FROM flows flow
125
+ WHERE flow.id = ? AND ${accessPredicate(["editor", "manager"])}`)
126
+ .bind(flowId, ...accessBindings(actor, deps.now().toISOString(), ["editor", "manager"]))
127
+ .first();
128
+ return row?.allowed === 1;
129
+ },
130
+ async canManage(actor, flowId) {
131
+ const row = await deps.db
132
+ .prepare(`SELECT 1 AS allowed FROM flows flow
133
+ WHERE flow.id = ? AND ${accessPredicate(["manager"])}`)
134
+ .bind(flowId, ...accessBindings(actor, deps.now().toISOString(), ["manager"]))
135
+ .first();
136
+ return row?.allowed === 1;
137
+ },
138
+ async findIdempotent(actorId, operation, key) {
139
+ const row = await deps.db
140
+ .prepare(`SELECT resource_id FROM idempotency_keys
141
+ WHERE actor_id = ? AND operation = ? AND idempotency_key = ?`)
142
+ .bind(actorId, operation, key)
143
+ .first();
144
+ return row?.resource_id ?? null;
145
+ },
146
+ async findIdempotentRevocation(actorId, key) {
147
+ const row = await deps.db
148
+ .prepare(`SELECT resource_id FROM idempotency_keys
149
+ WHERE actor_id = ? AND operation = 'flows.revoke' AND idempotency_key = ?`)
150
+ .bind(actorId, key)
151
+ .first();
152
+ if (!row)
153
+ return null;
154
+ return row.resource_id.startsWith("1:");
155
+ },
156
+ async insertFlow(input) {
157
+ const flow = input.flow;
158
+ try {
159
+ await deps.db.batch([
160
+ deps.db
161
+ .prepare(`INSERT INTO flows (
162
+ id, title, description, owner_id, current_version_id, published_version_id,
163
+ created_at, updated_at, archived_at
164
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
165
+ .bind(flow.id, flow.title, flow.description, flow.ownerId, flow.currentVersionId, flow.publishedVersionId, flow.createdAt, flow.updatedAt, flow.archivedAt),
166
+ deps.db
167
+ .prepare(`INSERT INTO idempotency_keys (
168
+ actor_id, operation, idempotency_key, resource_id, created_at
169
+ ) VALUES (?, 'flows.create', ?, ?, ?)`)
170
+ .bind(input.actorId, input.idempotencyKey, flow.id, flow.createdAt),
171
+ deps.db
172
+ .prepare(`INSERT INTO audit_events (
173
+ id, actor_id, action, resource_type, resource_id, metadata_json, occurred_at
174
+ ) VALUES (?, ?, 'flows.create', 'flow', ?, '{}', ?)`)
175
+ .bind(input.auditId, input.actorId, flow.id, flow.createdAt),
176
+ ]);
177
+ return flow;
178
+ }
179
+ catch (error) {
180
+ const replayedId = await this.findIdempotent(input.actorId, "flows.create", input.idempotencyKey);
181
+ const replayed = replayedId
182
+ ? await deps.db
183
+ .prepare(`SELECT ${flowColumns} FROM flows WHERE id = ?`)
184
+ .bind(replayedId)
185
+ .first()
186
+ : null;
187
+ if (replayed)
188
+ return mapFlow(replayed);
189
+ throw error;
190
+ }
191
+ },
192
+ async getVersion(versionId) {
193
+ const row = await deps.db
194
+ .prepare(`SELECT id, flow_id, sequence, graph_json, created_by, created_at
195
+ FROM flow_versions WHERE id = ?`)
196
+ .bind(versionId)
197
+ .first();
198
+ return row ? mapVersion(row) : null;
199
+ },
200
+ async insertVersion(input) {
201
+ const version = input.version;
202
+ try {
203
+ await deps.db.batch([
204
+ deps.db
205
+ .prepare(`INSERT INTO flow_versions (
206
+ id, flow_id, sequence, graph_json, created_by, created_at
207
+ ) SELECT ?, ?, ?, ?, ?, ?
208
+ WHERE EXISTS (
209
+ SELECT 1 FROM flows WHERE id = ? AND current_version_id IS ?
210
+ )`)
211
+ .bind(version.id, version.flowId, version.sequence, JSON.stringify(version.graph), version.createdBy, version.createdAt, version.flowId, input.baseVersionId),
212
+ deps.db
213
+ .prepare(`UPDATE flows SET current_version_id = ?, updated_at = ?
214
+ WHERE id = ? AND current_version_id IS ?
215
+ AND EXISTS (SELECT 1 FROM flow_versions WHERE id = ?)`)
216
+ .bind(version.id, version.createdAt, version.flowId, input.baseVersionId, version.id),
217
+ deps.db
218
+ .prepare(`INSERT INTO idempotency_keys (
219
+ actor_id, operation, idempotency_key, resource_id, created_at
220
+ ) SELECT ?, 'flows.save', ?, ?, ?
221
+ WHERE EXISTS (SELECT 1 FROM flow_versions WHERE id = ?)`)
222
+ .bind(input.actorId, input.idempotencyKey, version.id, version.createdAt, version.id),
223
+ deps.db
224
+ .prepare(`INSERT INTO audit_events (
225
+ id, actor_id, action, resource_type, resource_id, metadata_json, occurred_at
226
+ ) SELECT ?, ?, 'flows.save', 'flow', ?, ?, ?
227
+ WHERE EXISTS (SELECT 1 FROM flow_versions WHERE id = ?)`)
228
+ .bind(input.auditId, input.actorId, version.flowId, JSON.stringify({ versionId: version.id, sequence: version.sequence }), version.createdAt, version.id),
229
+ ]);
230
+ }
231
+ catch (error) {
232
+ const replayed = await this.findIdempotent(input.actorId, "flows.save", input.idempotencyKey);
233
+ if (!replayed)
234
+ throw error;
235
+ }
236
+ const stored = await deps.db
237
+ .prepare("SELECT 1 AS saved FROM flow_versions WHERE id = ?")
238
+ .bind(version.id)
239
+ .first();
240
+ return stored ? "saved" : "conflict";
241
+ },
242
+ async publish(input) {
243
+ try {
244
+ await deps.db.batch([
245
+ deps.db
246
+ .prepare(`UPDATE flows SET published_version_id = ?, updated_at = ?
247
+ WHERE id = ? AND EXISTS (
248
+ SELECT 1 FROM flow_versions WHERE id = ? AND flow_id = ?
249
+ )`)
250
+ .bind(input.versionId, input.occurredAt, input.flowId, input.versionId, input.flowId),
251
+ deps.db
252
+ .prepare(`INSERT INTO idempotency_keys (
253
+ actor_id, operation, idempotency_key, resource_id, created_at
254
+ ) SELECT ?, 'flows.publish', ?, ?, ?
255
+ WHERE EXISTS (
256
+ SELECT 1 FROM flows WHERE id = ? AND published_version_id = ?
257
+ )`)
258
+ .bind(input.actorId, input.idempotencyKey, input.versionId, input.occurredAt, input.flowId, input.versionId),
259
+ deps.db
260
+ .prepare(`INSERT INTO audit_events (
261
+ id, actor_id, action, resource_type, resource_id, metadata_json, occurred_at
262
+ ) SELECT ?, ?, 'flows.publish', 'flow', ?, ?, ?
263
+ WHERE EXISTS (
264
+ SELECT 1 FROM idempotency_keys
265
+ WHERE actor_id = ? AND operation = 'flows.publish' AND idempotency_key = ?
266
+ )`)
267
+ .bind(input.auditId, input.actorId, input.flowId, JSON.stringify({ versionId: input.versionId }), input.occurredAt, input.actorId, input.idempotencyKey),
268
+ ]);
269
+ }
270
+ catch (error) {
271
+ const replayed = await this.findIdempotent(input.actorId, "flows.publish", input.idempotencyKey);
272
+ if (!replayed)
273
+ throw error;
274
+ }
275
+ return ((await this.findIdempotent(input.actorId, "flows.publish", input.idempotencyKey)) !== null);
276
+ },
277
+ async listGrants(flowId) {
278
+ const result = await deps.db
279
+ .prepare(`SELECT id, resource_id, principal_type, principal_id, role, expires_at,
280
+ created_by, created_at FROM resource_grants
281
+ WHERE resource_type = 'flow' AND resource_id = ?
282
+ ORDER BY created_at, id`)
283
+ .bind(flowId)
284
+ .all();
285
+ return (result.results ?? []).map(mapGrant);
286
+ },
287
+ async setGrant(input) {
288
+ const grant = input.grant;
289
+ const principalType = grant.principal.type;
290
+ const principalId = grant.principal.type === "user"
291
+ ? grant.principal.id
292
+ : grant.principal.type === "email"
293
+ ? grant.principal.email.toLowerCase()
294
+ : "*";
295
+ try {
296
+ await deps.db.batch([
297
+ deps.db
298
+ .prepare(`INSERT INTO resource_grants (
299
+ id, resource_type, resource_id, principal_type, principal_id, role,
300
+ expires_at, created_by, created_at
301
+ ) VALUES (?, 'flow', ?, ?, ?, ?, ?, ?, ?)
302
+ ON CONFLICT (resource_type, resource_id, principal_type, principal_id)
303
+ DO UPDATE SET role = excluded.role, expires_at = excluded.expires_at`)
304
+ .bind(grant.id, grant.resourceId, principalType, principalId, grant.role, grant.expiresAt, grant.createdBy, grant.createdAt),
305
+ deps.db
306
+ .prepare(`INSERT INTO idempotency_keys (
307
+ actor_id, operation, idempotency_key, resource_id, created_at
308
+ ) SELECT ?, 'flows.share', ?, id, ? FROM resource_grants
309
+ WHERE resource_type = 'flow' AND resource_id = ?
310
+ AND principal_type = ? AND principal_id = ?`)
311
+ .bind(input.actorId, input.idempotencyKey, grant.createdAt, grant.resourceId, principalType, principalId),
312
+ deps.db
313
+ .prepare(`INSERT INTO audit_events (
314
+ id, actor_id, action, resource_type, resource_id, metadata_json, occurred_at
315
+ ) VALUES (?, ?, 'flows.share', 'flow', ?, ?, ?)`)
316
+ .bind(input.auditId, input.actorId, grant.resourceId, JSON.stringify({ principalType, role: grant.role }), grant.createdAt),
317
+ ]);
318
+ const stored = await deps.db
319
+ .prepare(`SELECT id, resource_id, principal_type, principal_id, role, expires_at,
320
+ created_by, created_at FROM resource_grants
321
+ WHERE resource_type = 'flow' AND resource_id = ?
322
+ AND principal_type = ? AND principal_id = ?`)
323
+ .bind(grant.resourceId, principalType, principalId)
324
+ .first();
325
+ if (!stored)
326
+ throw new Error("Flow grant disappeared after upsert");
327
+ return mapGrant(stored);
328
+ }
329
+ catch (error) {
330
+ const replayed = await this.findIdempotent(input.actorId, "flows.share", input.idempotencyKey);
331
+ const row = replayed
332
+ ? await deps.db
333
+ .prepare(`SELECT id, resource_id, principal_type, principal_id, role, expires_at,
334
+ created_by, created_at FROM resource_grants WHERE id = ?`)
335
+ .bind(replayed)
336
+ .first()
337
+ : null;
338
+ if (row)
339
+ return mapGrant(row);
340
+ throw error;
341
+ }
342
+ },
343
+ async revokeGrant(input) {
344
+ try {
345
+ await deps.db.batch([
346
+ deps.db
347
+ .prepare(`INSERT INTO idempotency_keys (
348
+ actor_id, operation, idempotency_key, resource_id, created_at
349
+ ) SELECT ?, 'flows.revoke', ?,
350
+ (CASE WHEN EXISTS (
351
+ SELECT 1 FROM resource_grants
352
+ WHERE id = ? AND resource_type = 'flow' AND resource_id = ?
353
+ ) THEN '1:' ELSE '0:' END) || ?, ?`)
354
+ .bind(input.actorId, input.idempotencyKey, input.grantId, input.flowId, input.grantId, input.occurredAt),
355
+ deps.db
356
+ .prepare(`DELETE FROM resource_grants
357
+ WHERE id = ? AND resource_type = 'flow' AND resource_id = ?`)
358
+ .bind(input.grantId, input.flowId),
359
+ deps.db
360
+ .prepare(`INSERT INTO audit_events (
361
+ id, actor_id, action, resource_type, resource_id, metadata_json, occurred_at
362
+ ) SELECT ?, ?, 'flows.revoke', 'flow', ?,
363
+ json_object(
364
+ 'grantId', ?,
365
+ 'revoked', json(CASE WHEN substr(resource_id, 1, 2) = '1:' THEN 'true' ELSE 'false' END)
366
+ ), ?
367
+ FROM idempotency_keys
368
+ WHERE actor_id = ? AND operation = 'flows.revoke' AND idempotency_key = ?`)
369
+ .bind(input.auditId, input.actorId, input.flowId, input.grantId, input.occurredAt, input.actorId, input.idempotencyKey),
370
+ ]);
371
+ }
372
+ catch (error) {
373
+ const replayed = await this.findIdempotentRevocation(input.actorId, input.idempotencyKey);
374
+ if (replayed === null)
375
+ throw error;
376
+ return replayed;
377
+ }
378
+ return (await this.findIdempotentRevocation(input.actorId, input.idempotencyKey)) ?? false;
379
+ },
380
+ async insertRun(input) {
381
+ const run = input.run;
382
+ try {
383
+ await deps.db.batch([
384
+ deps.db
385
+ .prepare(`INSERT INTO flow_runs (
386
+ id, flow_id, version_id, status, current_node_id, input_json, output_json,
387
+ error, initiated_by, created_at, updated_at, completed_at
388
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
389
+ .bind(run.id, run.flowId, run.versionId, run.status, run.currentNodeId, JSON.stringify(run.input), null, run.error, run.initiatedBy, run.createdAt, run.updatedAt, run.completedAt),
390
+ deps.db
391
+ .prepare(`INSERT INTO idempotency_keys (
392
+ actor_id, operation, idempotency_key, resource_id, created_at
393
+ ) VALUES (?, 'flows.run', ?, ?, ?)`)
394
+ .bind(input.actorId, input.idempotencyKey, run.id, run.createdAt),
395
+ deps.db
396
+ .prepare(`INSERT INTO audit_events (
397
+ id, actor_id, action, resource_type, resource_id, metadata_json, occurred_at
398
+ ) VALUES (?, ?, 'flows.run', 'flow-run', ?, ?, ?)`)
399
+ .bind(input.auditId, input.actorId, run.id, JSON.stringify({ flowId: run.flowId, versionId: run.versionId }), run.createdAt),
400
+ ]);
401
+ return run;
402
+ }
403
+ catch (error) {
404
+ const replayedId = await this.findIdempotent(input.actorId, "flows.run", input.idempotencyKey);
405
+ const replayed = replayedId
406
+ ? await deps.db
407
+ .prepare(`SELECT ${runColumns} FROM flow_runs WHERE id = ?`)
408
+ .bind(replayedId)
409
+ .first()
410
+ : null;
411
+ if (replayed)
412
+ return mapRun(replayed);
413
+ throw error;
414
+ }
415
+ },
416
+ async getRunVisible(actor, runId) {
417
+ const row = await deps.db
418
+ .prepare(`SELECT ${qualifiedRunColumns("run")} FROM flow_runs run
419
+ JOIN flows flow ON flow.id = run.flow_id
420
+ WHERE run.id = ? AND ${visible}`)
421
+ .bind(runId, ...accessBindings(actor, deps.now().toISOString()))
422
+ .first();
423
+ return row ? mapRun(row) : null;
424
+ },
425
+ async advanceRun(input) {
426
+ const run = input.run;
427
+ try {
428
+ await deps.db.batch([
429
+ deps.db
430
+ .prepare(`UPDATE flow_runs SET
431
+ status = ?, current_node_id = ?, output_json = ?, error = ?,
432
+ updated_at = ?, completed_at = ?, transition_id = ?
433
+ WHERE id = ? AND current_node_id = ? AND status IN ('running', 'waiting')`)
434
+ .bind(run.status, run.currentNodeId, run.output === null ? null : JSON.stringify(run.output), run.error, run.updatedAt, run.completedAt, input.stepId, run.id, input.expectedNodeId),
435
+ deps.db
436
+ .prepare(`INSERT INTO flow_run_steps (
437
+ id, run_id, node_id, outcome, branch, output_json, error, completed_by, completed_at
438
+ ) SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?
439
+ WHERE EXISTS (
440
+ SELECT 1 FROM flow_runs WHERE id = ? AND transition_id = ?
441
+ )`)
442
+ .bind(input.stepId, run.id, input.expectedNodeId, input.outcome, input.branch, JSON.stringify(input.output), input.error, input.actorId, run.updatedAt, run.id, input.stepId),
443
+ deps.db
444
+ .prepare(`INSERT INTO idempotency_keys (
445
+ actor_id, operation, idempotency_key, resource_id, created_at
446
+ ) SELECT ?, 'flows.complete', ?, ?, ?
447
+ WHERE EXISTS (SELECT 1 FROM flow_run_steps WHERE id = ?)`)
448
+ .bind(input.actorId, input.idempotencyKey, run.id, run.updatedAt, input.stepId),
449
+ deps.db
450
+ .prepare(`INSERT INTO audit_events (
451
+ id, actor_id, action, resource_type, resource_id, metadata_json, occurred_at
452
+ ) SELECT ?, ?, 'flows.complete', 'flow-run', ?, ?, ?
453
+ WHERE EXISTS (SELECT 1 FROM flow_run_steps WHERE id = ?)`)
454
+ .bind(input.auditId, input.actorId, run.id, JSON.stringify({ nodeId: input.expectedNodeId, outcome: input.outcome }), run.updatedAt, input.stepId),
455
+ ]);
456
+ }
457
+ catch (error) {
458
+ const replayed = await this.findIdempotent(input.actorId, "flows.complete", input.idempotencyKey);
459
+ if (!replayed)
460
+ throw error;
461
+ }
462
+ const replayed = await this.findIdempotent(input.actorId, "flows.complete", input.idempotencyKey);
463
+ return replayed === run.id ? "advanced" : "conflict";
464
+ },
465
+ };
466
+ }
@@ -0,0 +1,3 @@
1
+ import type { KnowledgeIndexRepository } from "../../knowledge/knowledge.types.js";
2
+ import type { D1Database } from "./db.types.js";
3
+ export declare function createKnowledgeIndexRepository(db: D1Database): KnowledgeIndexRepository;
@@ -0,0 +1,70 @@
1
+ function mapTarget(row) {
2
+ return {
3
+ nodeId: row.node_id,
4
+ versionId: row.version_id,
5
+ title: row.title,
6
+ contentKey: row.content_key,
7
+ mediaType: row.media_type,
8
+ kind: row.kind,
9
+ updatedAt: row.updated_at,
10
+ };
11
+ }
12
+ export function createKnowledgeIndexRepository(db) {
13
+ return {
14
+ async getTarget(versionId) {
15
+ const row = await db
16
+ .prepare(`SELECT n.id AS node_id, v.id AS version_id, n.title, v.content_key,
17
+ v.media_type, n.kind, n.updated_at
18
+ FROM knowledge_versions v
19
+ JOIN knowledge_nodes n ON n.id = v.node_id
20
+ WHERE v.id = ? AND n.current_version_id = v.id AND n.archived_at IS NULL`)
21
+ .bind(versionId)
22
+ .first();
23
+ return row ? mapTarget(row) : null;
24
+ },
25
+ async replace(target, content) {
26
+ await db.batch([
27
+ db
28
+ .prepare(`DELETE FROM knowledge_fts WHERE node_id = ? AND EXISTS (
29
+ SELECT 1 FROM knowledge_nodes
30
+ WHERE id = ? AND current_version_id = ? AND archived_at IS NULL
31
+ )`)
32
+ .bind(target.nodeId, target.nodeId, target.versionId),
33
+ db
34
+ .prepare(`INSERT INTO knowledge_fts (version_id, node_id, title, content)
35
+ SELECT ?, ?, ?, ? WHERE EXISTS (
36
+ SELECT 1 FROM knowledge_nodes
37
+ WHERE id = ? AND current_version_id = ? AND archived_at IS NULL
38
+ )`)
39
+ .bind(target.versionId, target.nodeId, target.title, content, target.nodeId, target.versionId),
40
+ ]);
41
+ },
42
+ async markIndexed(versionId, occurredAt) {
43
+ await db
44
+ .prepare(`UPDATE knowledge_index_state
45
+ SET status = 'indexed', attempt_count = attempt_count + 1,
46
+ last_error = NULL, updated_at = ?
47
+ WHERE version_id = ? AND EXISTS (
48
+ SELECT 1 FROM knowledge_versions version
49
+ JOIN knowledge_nodes node ON node.id = version.node_id
50
+ WHERE version.id = ? AND node.current_version_id = version.id
51
+ AND node.archived_at IS NULL
52
+ )`)
53
+ .bind(occurredAt, versionId, versionId)
54
+ .run();
55
+ },
56
+ async markError(versionId, message, occurredAt) {
57
+ await db
58
+ .prepare(`UPDATE knowledge_index_state
59
+ SET status = 'error', attempt_count = attempt_count + 1,
60
+ last_error = ?, updated_at = ? WHERE version_id = ? AND EXISTS (
61
+ SELECT 1 FROM knowledge_versions version
62
+ JOIN knowledge_nodes node ON node.id = version.node_id
63
+ WHERE version.id = ? AND node.current_version_id = version.id
64
+ AND node.archived_at IS NULL
65
+ )`)
66
+ .bind(message.slice(0, 500), occurredAt, versionId, versionId)
67
+ .run();
68
+ },
69
+ };
70
+ }
@@ -0,0 +1,3 @@
1
+ import type { OAuthClientStore } from "../../auth/auth.types.js";
2
+ import type { D1Database } from "./db.types.js";
3
+ export declare function createOAuthClientStore(db: D1Database): OAuthClientStore;
@@ -0,0 +1,26 @@
1
+ export function createOAuthClientStore(db) {
2
+ return {
3
+ async get(issuer, redirectUri) {
4
+ const row = await db
5
+ .prepare("SELECT client_id FROM oauth_clients WHERE issuer = ? AND redirect_uri = ?")
6
+ .bind(issuer, redirectUri)
7
+ .first();
8
+ return row?.client_id ?? null;
9
+ },
10
+ async put(input) {
11
+ await db
12
+ .prepare(`INSERT OR IGNORE INTO oauth_clients (
13
+ issuer, redirect_uri, client_id, created_at
14
+ ) VALUES (?, ?, ?, ?)`)
15
+ .bind(input.issuer, input.redirectUri, input.clientId, input.createdAt)
16
+ .run();
17
+ const stored = await db
18
+ .prepare("SELECT client_id FROM oauth_clients WHERE issuer = ? AND redirect_uri = ?")
19
+ .bind(input.issuer, input.redirectUri)
20
+ .first();
21
+ if (!stored)
22
+ throw new Error("OAuth client registration was not stored");
23
+ return stored.client_id;
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,6 @@
1
+ import type { ToolRepository } from "../../tools/tools.types.js";
2
+ import type { D1Database } from "./db.types.js";
3
+ export declare function createToolRepository(deps: {
4
+ db: D1Database;
5
+ now(): Date;
6
+ }): ToolRepository;