@wopr-network/defcon 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 (243) hide show
  1. package/README.md +274 -0
  2. package/dist/api/router.d.ts +24 -0
  3. package/dist/api/router.js +44 -0
  4. package/dist/api/server.d.ts +13 -0
  5. package/dist/api/server.js +280 -0
  6. package/dist/api/wire-types.d.ts +46 -0
  7. package/dist/api/wire-types.js +5 -0
  8. package/dist/config/db-path.d.ts +1 -0
  9. package/dist/config/db-path.js +1 -0
  10. package/dist/config/exporter.d.ts +3 -0
  11. package/dist/config/exporter.js +87 -0
  12. package/dist/config/index.d.ts +4 -0
  13. package/dist/config/index.js +4 -0
  14. package/dist/config/seed-loader.d.ts +10 -0
  15. package/dist/config/seed-loader.js +108 -0
  16. package/dist/config/zod-schemas.d.ts +165 -0
  17. package/dist/config/zod-schemas.js +283 -0
  18. package/dist/cors.d.ts +8 -0
  19. package/dist/cors.js +21 -0
  20. package/dist/engine/constants.d.ts +1 -0
  21. package/dist/engine/constants.js +1 -0
  22. package/dist/engine/engine.d.ts +69 -0
  23. package/dist/engine/engine.js +485 -0
  24. package/dist/engine/event-emitter.d.ts +9 -0
  25. package/dist/engine/event-emitter.js +19 -0
  26. package/dist/engine/event-types.d.ts +105 -0
  27. package/dist/engine/event-types.js +1 -0
  28. package/dist/engine/flow-spawner.d.ts +8 -0
  29. package/dist/engine/flow-spawner.js +28 -0
  30. package/dist/engine/gate-command-validator.d.ts +6 -0
  31. package/dist/engine/gate-command-validator.js +46 -0
  32. package/dist/engine/gate-evaluator.d.ts +12 -0
  33. package/dist/engine/gate-evaluator.js +233 -0
  34. package/dist/engine/handlebars.d.ts +9 -0
  35. package/dist/engine/handlebars.js +51 -0
  36. package/dist/engine/index.d.ts +12 -0
  37. package/dist/engine/index.js +7 -0
  38. package/dist/engine/invocation-builder.d.ts +18 -0
  39. package/dist/engine/invocation-builder.js +58 -0
  40. package/dist/engine/on-enter.d.ts +8 -0
  41. package/dist/engine/on-enter.js +102 -0
  42. package/dist/engine/ssrf-guard.d.ts +22 -0
  43. package/dist/engine/ssrf-guard.js +159 -0
  44. package/dist/engine/state-machine.d.ts +12 -0
  45. package/dist/engine/state-machine.js +74 -0
  46. package/dist/execution/active-runner.d.ts +45 -0
  47. package/dist/execution/active-runner.js +165 -0
  48. package/dist/execution/admin-schemas.d.ts +116 -0
  49. package/dist/execution/admin-schemas.js +125 -0
  50. package/dist/execution/cli.d.ts +57 -0
  51. package/dist/execution/cli.js +498 -0
  52. package/dist/execution/handlers/admin.d.ts +67 -0
  53. package/dist/execution/handlers/admin.js +200 -0
  54. package/dist/execution/handlers/flow.d.ts +25 -0
  55. package/dist/execution/handlers/flow.js +289 -0
  56. package/dist/execution/handlers/query.d.ts +31 -0
  57. package/dist/execution/handlers/query.js +64 -0
  58. package/dist/execution/index.d.ts +4 -0
  59. package/dist/execution/index.js +3 -0
  60. package/dist/execution/mcp-helpers.d.ts +42 -0
  61. package/dist/execution/mcp-helpers.js +23 -0
  62. package/dist/execution/mcp-server.d.ts +33 -0
  63. package/dist/execution/mcp-server.js +1020 -0
  64. package/dist/execution/provision-worktree.d.ts +16 -0
  65. package/dist/execution/provision-worktree.js +123 -0
  66. package/dist/execution/tool-schemas.d.ts +40 -0
  67. package/dist/execution/tool-schemas.js +44 -0
  68. package/dist/gates/blocking-graph.d.ts +26 -0
  69. package/dist/gates/blocking-graph.js +102 -0
  70. package/dist/gates/test/bad-return-gate.d.ts +1 -0
  71. package/dist/gates/test/bad-return-gate.js +4 -0
  72. package/dist/gates/test/passing-gate.d.ts +2 -0
  73. package/dist/gates/test/passing-gate.js +3 -0
  74. package/dist/gates/test/slow-gate.d.ts +2 -0
  75. package/dist/gates/test/slow-gate.js +5 -0
  76. package/dist/gates/test/throwing-gate.d.ts +1 -0
  77. package/dist/gates/test/throwing-gate.js +3 -0
  78. package/dist/logger.d.ts +8 -0
  79. package/dist/logger.js +12 -0
  80. package/dist/main.d.ts +14 -0
  81. package/dist/main.js +28 -0
  82. package/dist/repositories/drizzle/entity.repo.d.ts +27 -0
  83. package/dist/repositories/drizzle/entity.repo.js +190 -0
  84. package/dist/repositories/drizzle/event.repo.d.ts +12 -0
  85. package/dist/repositories/drizzle/event.repo.js +24 -0
  86. package/dist/repositories/drizzle/flow.repo.d.ts +22 -0
  87. package/dist/repositories/drizzle/flow.repo.js +364 -0
  88. package/dist/repositories/drizzle/gate.repo.d.ts +16 -0
  89. package/dist/repositories/drizzle/gate.repo.js +98 -0
  90. package/dist/repositories/drizzle/index.d.ts +6 -0
  91. package/dist/repositories/drizzle/index.js +7 -0
  92. package/dist/repositories/drizzle/invocation.repo.d.ts +23 -0
  93. package/dist/repositories/drizzle/invocation.repo.js +199 -0
  94. package/dist/repositories/drizzle/schema.d.ts +1932 -0
  95. package/dist/repositories/drizzle/schema.js +155 -0
  96. package/dist/repositories/drizzle/transition-log.repo.d.ts +11 -0
  97. package/dist/repositories/drizzle/transition-log.repo.js +42 -0
  98. package/dist/repositories/interfaces.d.ts +321 -0
  99. package/dist/repositories/interfaces.js +2 -0
  100. package/dist/src/api/router.d.ts +24 -0
  101. package/dist/src/api/router.js +44 -0
  102. package/dist/src/api/server.d.ts +13 -0
  103. package/dist/src/api/server.js +280 -0
  104. package/dist/src/api/wire-types.d.ts +46 -0
  105. package/dist/src/api/wire-types.js +5 -0
  106. package/dist/src/config/db-path.d.ts +1 -0
  107. package/dist/src/config/db-path.js +1 -0
  108. package/dist/src/config/exporter.d.ts +3 -0
  109. package/dist/src/config/exporter.js +87 -0
  110. package/dist/src/config/index.d.ts +4 -0
  111. package/dist/src/config/index.js +4 -0
  112. package/dist/src/config/seed-loader.d.ts +14 -0
  113. package/dist/src/config/seed-loader.js +131 -0
  114. package/dist/src/config/zod-schemas.d.ts +165 -0
  115. package/dist/src/config/zod-schemas.js +283 -0
  116. package/dist/src/cors.d.ts +8 -0
  117. package/dist/src/cors.js +21 -0
  118. package/dist/src/engine/constants.d.ts +1 -0
  119. package/dist/src/engine/constants.js +1 -0
  120. package/dist/src/engine/engine.d.ts +69 -0
  121. package/dist/src/engine/engine.js +485 -0
  122. package/dist/src/engine/event-emitter.d.ts +9 -0
  123. package/dist/src/engine/event-emitter.js +19 -0
  124. package/dist/src/engine/event-types.d.ts +105 -0
  125. package/dist/src/engine/event-types.js +1 -0
  126. package/dist/src/engine/flow-spawner.d.ts +8 -0
  127. package/dist/src/engine/flow-spawner.js +28 -0
  128. package/dist/src/engine/gate-command-validator.d.ts +6 -0
  129. package/dist/src/engine/gate-command-validator.js +46 -0
  130. package/dist/src/engine/gate-evaluator.d.ts +12 -0
  131. package/dist/src/engine/gate-evaluator.js +233 -0
  132. package/dist/src/engine/handlebars.d.ts +9 -0
  133. package/dist/src/engine/handlebars.js +51 -0
  134. package/dist/src/engine/index.d.ts +12 -0
  135. package/dist/src/engine/index.js +7 -0
  136. package/dist/src/engine/invocation-builder.d.ts +18 -0
  137. package/dist/src/engine/invocation-builder.js +58 -0
  138. package/dist/src/engine/on-enter.d.ts +8 -0
  139. package/dist/src/engine/on-enter.js +102 -0
  140. package/dist/src/engine/ssrf-guard.d.ts +22 -0
  141. package/dist/src/engine/ssrf-guard.js +159 -0
  142. package/dist/src/engine/state-machine.d.ts +12 -0
  143. package/dist/src/engine/state-machine.js +74 -0
  144. package/dist/src/execution/active-runner.d.ts +45 -0
  145. package/dist/src/execution/active-runner.js +165 -0
  146. package/dist/src/execution/admin-schemas.d.ts +116 -0
  147. package/dist/src/execution/admin-schemas.js +125 -0
  148. package/dist/src/execution/cli.d.ts +57 -0
  149. package/dist/src/execution/cli.js +501 -0
  150. package/dist/src/execution/handlers/admin.d.ts +67 -0
  151. package/dist/src/execution/handlers/admin.js +200 -0
  152. package/dist/src/execution/handlers/flow.d.ts +25 -0
  153. package/dist/src/execution/handlers/flow.js +289 -0
  154. package/dist/src/execution/handlers/query.d.ts +31 -0
  155. package/dist/src/execution/handlers/query.js +64 -0
  156. package/dist/src/execution/index.d.ts +4 -0
  157. package/dist/src/execution/index.js +3 -0
  158. package/dist/src/execution/mcp-helpers.d.ts +42 -0
  159. package/dist/src/execution/mcp-helpers.js +23 -0
  160. package/dist/src/execution/mcp-server.d.ts +33 -0
  161. package/dist/src/execution/mcp-server.js +1020 -0
  162. package/dist/src/execution/provision-worktree.d.ts +16 -0
  163. package/dist/src/execution/provision-worktree.js +123 -0
  164. package/dist/src/execution/tool-schemas.d.ts +40 -0
  165. package/dist/src/execution/tool-schemas.js +44 -0
  166. package/dist/src/logger.d.ts +8 -0
  167. package/dist/src/logger.js +12 -0
  168. package/dist/src/main.d.ts +14 -0
  169. package/dist/src/main.js +28 -0
  170. package/dist/src/repositories/drizzle/entity.repo.d.ts +27 -0
  171. package/dist/src/repositories/drizzle/entity.repo.js +190 -0
  172. package/dist/src/repositories/drizzle/event.repo.d.ts +12 -0
  173. package/dist/src/repositories/drizzle/event.repo.js +24 -0
  174. package/dist/src/repositories/drizzle/flow.repo.d.ts +22 -0
  175. package/dist/src/repositories/drizzle/flow.repo.js +364 -0
  176. package/dist/src/repositories/drizzle/gate.repo.d.ts +16 -0
  177. package/dist/src/repositories/drizzle/gate.repo.js +98 -0
  178. package/dist/src/repositories/drizzle/index.d.ts +6 -0
  179. package/dist/src/repositories/drizzle/index.js +7 -0
  180. package/dist/src/repositories/drizzle/invocation.repo.d.ts +23 -0
  181. package/dist/src/repositories/drizzle/invocation.repo.js +199 -0
  182. package/dist/src/repositories/drizzle/schema.d.ts +1932 -0
  183. package/dist/src/repositories/drizzle/schema.js +155 -0
  184. package/dist/src/repositories/drizzle/transition-log.repo.d.ts +11 -0
  185. package/dist/src/repositories/drizzle/transition-log.repo.js +42 -0
  186. package/dist/src/repositories/interfaces.d.ts +321 -0
  187. package/dist/src/repositories/interfaces.js +2 -0
  188. package/dist/src/utils/redact.d.ts +2 -0
  189. package/dist/src/utils/redact.js +62 -0
  190. package/dist/utils/redact.d.ts +2 -0
  191. package/dist/utils/redact.js +62 -0
  192. package/drizzle/.gitkeep +0 -0
  193. package/drizzle/0000_simple_surge.sql +144 -0
  194. package/drizzle/0001_peaceful_marvel_apes.sql +18 -0
  195. package/drizzle/0002_add_invocations_created_at.sql +1 -0
  196. package/drizzle/0003_drop_integration_config.sql +1 -0
  197. package/drizzle/0004_add_flow_discipline.sql +2 -0
  198. package/drizzle/0004_lucky_silverclaw.sql +5 -0
  199. package/drizzle/0005_old_blue_shield.sql +2 -0
  200. package/drizzle/0006_solid_magik.sql +2 -0
  201. package/drizzle/0007_fancy_luke_cage.sql +1 -0
  202. package/drizzle/0008_thick_dark_beast.sql +1 -0
  203. package/drizzle/0009_brief_midnight.sql +1 -0
  204. package/drizzle/0010_amusing_bastion.sql +1 -0
  205. package/drizzle/meta/0000_snapshot.json +996 -0
  206. package/drizzle/meta/0004_snapshot.json +1008 -0
  207. package/drizzle/meta/0005_snapshot.json +1023 -0
  208. package/drizzle/meta/0006_snapshot.json +1037 -0
  209. package/drizzle/meta/0007_snapshot.json +1044 -0
  210. package/drizzle/meta/0008_snapshot.json +1051 -0
  211. package/drizzle/meta/0009_snapshot.json +1058 -0
  212. package/drizzle/meta/0010_snapshot.json +1065 -0
  213. package/drizzle/meta/_journal.json +83 -0
  214. package/gates/.gitkeep +0 -0
  215. package/gates/blocking-graph.d.ts +26 -0
  216. package/gates/blocking-graph.js +102 -0
  217. package/gates/blocking-graph.ts +121 -0
  218. package/gates/check-design-posted.sh +39 -0
  219. package/gates/check-merge.sh +51 -0
  220. package/gates/check-pr-capacity.sh +17 -0
  221. package/gates/check-review-ready.sh +47 -0
  222. package/gates/check-spec-posted.sh +34 -0
  223. package/gates/check-unblocked.sh +56 -0
  224. package/gates/ci-green.sh +9 -0
  225. package/gates/merge-queue.sh +14 -0
  226. package/gates/review-bots-ready.sh +9 -0
  227. package/gates/spec-posted.sh +31 -0
  228. package/gates/test/bad-return-gate.d.ts +1 -0
  229. package/gates/test/bad-return-gate.js +4 -0
  230. package/gates/test/bad-return-gate.ts +4 -0
  231. package/gates/test/passing-gate.d.ts +2 -0
  232. package/gates/test/passing-gate.js +3 -0
  233. package/gates/test/passing-gate.ts +5 -0
  234. package/gates/test/slow-gate.d.ts +2 -0
  235. package/gates/test/slow-gate.js +5 -0
  236. package/gates/test/slow-gate.ts +7 -0
  237. package/gates/test/throwing-gate.d.ts +1 -0
  238. package/gates/test/throwing-gate.js +3 -0
  239. package/gates/test/throwing-gate.ts +3 -0
  240. package/gates/test-fail.sh +2 -0
  241. package/gates/test-pass.sh +2 -0
  242. package/gates/timeout-gate-script.sh +3 -0
  243. package/package.json +64 -0
@@ -0,0 +1,155 @@
1
+ import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
2
+ // ─── Definition Tables ───
3
+ export const flowDefinitions = sqliteTable("flow_definitions", {
4
+ id: text("id").primaryKey(),
5
+ name: text("name").notNull().unique(),
6
+ description: text("description"),
7
+ entitySchema: text("entity_schema", { mode: "json" }),
8
+ initialState: text("initial_state").notNull(),
9
+ maxConcurrent: integer("max_concurrent").default(0),
10
+ maxConcurrentPerRepo: integer("max_concurrent_per_repo").default(0),
11
+ affinityWindowMs: integer("affinity_window_ms").default(300000),
12
+ gateTimeoutMs: integer("gate_timeout_ms"),
13
+ version: integer("version").default(1),
14
+ createdBy: text("created_by"),
15
+ discipline: text("discipline"),
16
+ defaultModelTier: text("default_model_tier"),
17
+ timeoutPrompt: text("timeout_prompt"),
18
+ createdAt: integer("created_at"),
19
+ updatedAt: integer("updated_at"),
20
+ });
21
+ export const stateDefinitions = sqliteTable("state_definitions", {
22
+ id: text("id").primaryKey(),
23
+ flowId: text("flow_id")
24
+ .notNull()
25
+ .references(() => flowDefinitions.id),
26
+ name: text("name").notNull(),
27
+ agentRole: text("agent_role"),
28
+ modelTier: text("model_tier"),
29
+ mode: text("mode").default("passive"),
30
+ promptTemplate: text("prompt_template"),
31
+ constraints: text("constraints", { mode: "json" }),
32
+ onEnter: text("on_enter", { mode: "json" }),
33
+ }, (table) => ({
34
+ flowNameUnique: uniqueIndex("state_definitions_flow_name_unique").on(table.flowId, table.name),
35
+ }));
36
+ export const gateDefinitions = sqliteTable("gate_definitions", {
37
+ id: text("id").primaryKey(),
38
+ name: text("name").notNull().unique(),
39
+ type: text("type").notNull(),
40
+ command: text("command"),
41
+ functionRef: text("function_ref"),
42
+ apiConfig: text("api_config", { mode: "json" }),
43
+ timeoutMs: integer("timeout_ms"),
44
+ failurePrompt: text("failure_prompt"),
45
+ timeoutPrompt: text("timeout_prompt"),
46
+ });
47
+ export const transitionRules = sqliteTable("transition_rules", {
48
+ id: text("id").primaryKey(),
49
+ flowId: text("flow_id")
50
+ .notNull()
51
+ .references(() => flowDefinitions.id),
52
+ fromState: text("from_state").notNull(),
53
+ toState: text("to_state").notNull(),
54
+ trigger: text("trigger").notNull(),
55
+ gateId: text("gate_id").references(() => gateDefinitions.id),
56
+ condition: text("condition"),
57
+ priority: integer("priority").default(0),
58
+ spawnFlow: text("spawn_flow"),
59
+ spawnTemplate: text("spawn_template"),
60
+ createdAt: integer("created_at"),
61
+ });
62
+ export const flowVersions = sqliteTable("flow_versions", {
63
+ id: text("id").primaryKey(),
64
+ flowId: text("flow_id")
65
+ .notNull()
66
+ .references(() => flowDefinitions.id),
67
+ version: integer("version").notNull(),
68
+ snapshot: text("snapshot", { mode: "json" }),
69
+ changedBy: text("changed_by"),
70
+ changeReason: text("change_reason"),
71
+ createdAt: integer("created_at"),
72
+ }, (table) => ({
73
+ flowVersionUnique: uniqueIndex("flow_versions_flow_version_unique").on(table.flowId, table.version),
74
+ }));
75
+ // ─── Runtime Tables ───
76
+ export const entities = sqliteTable("entities", {
77
+ id: text("id").primaryKey(),
78
+ flowId: text("flow_id")
79
+ .notNull()
80
+ .references(() => flowDefinitions.id),
81
+ state: text("state").notNull(),
82
+ refs: text("refs", { mode: "json" }),
83
+ artifacts: text("artifacts", { mode: "json" }),
84
+ claimedBy: text("claimed_by"),
85
+ claimedAt: integer("claimed_at"),
86
+ flowVersion: integer("flow_version"),
87
+ priority: integer("priority").default(0),
88
+ createdAt: integer("created_at"),
89
+ updatedAt: integer("updated_at"),
90
+ affinityWorkerId: text("affinity_worker_id"),
91
+ affinityRole: text("affinity_role"),
92
+ affinityExpiresAt: integer("affinity_expires_at"),
93
+ }, (table) => ({
94
+ flowStateIdx: index("entities_flow_state_idx").on(table.flowId, table.state),
95
+ claimIdx: index("entities_claim_idx").on(table.flowId, table.state, table.claimedBy),
96
+ affinityIdx: index("entities_affinity_idx").on(table.affinityWorkerId, table.affinityRole, table.affinityExpiresAt),
97
+ }));
98
+ export const invocations = sqliteTable("invocations", {
99
+ id: text("id").primaryKey(),
100
+ entityId: text("entity_id")
101
+ .notNull()
102
+ .references(() => entities.id),
103
+ stage: text("stage").notNull(),
104
+ agentRole: text("agent_role"),
105
+ mode: text("mode").notNull(),
106
+ prompt: text("prompt").notNull(),
107
+ context: text("context", { mode: "json" }),
108
+ claimedBy: text("claimed_by"),
109
+ claimedAt: integer("claimed_at"),
110
+ startedAt: integer("started_at"),
111
+ completedAt: integer("completed_at"),
112
+ failedAt: integer("failed_at"),
113
+ signal: text("signal"),
114
+ artifacts: text("artifacts", { mode: "json" }),
115
+ error: text("error"),
116
+ ttlMs: integer("ttl_ms").default(1800000),
117
+ createdAt: integer("created_at"),
118
+ }, (table) => ({
119
+ entityIdx: index("invocations_entity_idx").on(table.entityId),
120
+ }));
121
+ export const gateResults = sqliteTable("gate_results", {
122
+ id: text("id").primaryKey(),
123
+ entityId: text("entity_id")
124
+ .notNull()
125
+ .references(() => entities.id),
126
+ gateId: text("gate_id")
127
+ .notNull()
128
+ .references(() => gateDefinitions.id),
129
+ passed: integer("passed").notNull(),
130
+ output: text("output"),
131
+ evaluatedAt: integer("evaluated_at"),
132
+ });
133
+ export const entityHistory = sqliteTable("entity_history", {
134
+ id: text("id").primaryKey(),
135
+ entityId: text("entity_id")
136
+ .notNull()
137
+ .references(() => entities.id),
138
+ fromState: text("from_state"),
139
+ toState: text("to_state").notNull(),
140
+ trigger: text("trigger"),
141
+ invocationId: text("invocation_id").references(() => invocations.id),
142
+ timestamp: integer("timestamp").notNull(),
143
+ }, (table) => ({
144
+ entityTimestampIdx: index("entity_history_entity_ts_idx").on(table.entityId, table.timestamp),
145
+ }));
146
+ export const events = sqliteTable("events", {
147
+ id: text("id").primaryKey(),
148
+ type: text("type").notNull(),
149
+ entityId: text("entity_id"),
150
+ flowId: text("flow_id"),
151
+ payload: text("payload", { mode: "json" }),
152
+ emittedAt: integer("emitted_at").notNull(),
153
+ }, (table) => ({
154
+ typeEmittedIdx: index("events_type_emitted_idx").on(table.type, table.emittedAt),
155
+ }));
@@ -0,0 +1,11 @@
1
+ import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
2
+ import type { ITransitionLogRepository, TransitionLog } from "../interfaces.js";
3
+ import type * as schema from "./schema.js";
4
+ type Db = BetterSQLite3Database<typeof schema>;
5
+ export declare class DrizzleTransitionLogRepository implements ITransitionLogRepository {
6
+ private db;
7
+ constructor(db: Db);
8
+ record(log: Omit<TransitionLog, "id">): Promise<TransitionLog>;
9
+ historyFor(entityId: string): Promise<TransitionLog[]>;
10
+ }
11
+ export {};
@@ -0,0 +1,42 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { asc, eq, sql } from "drizzle-orm";
3
+ import { entityHistory } from "./schema.js";
4
+ export class DrizzleTransitionLogRepository {
5
+ db;
6
+ constructor(db) {
7
+ this.db = db;
8
+ }
9
+ async record(log) {
10
+ const id = randomUUID();
11
+ this.db
12
+ .insert(entityHistory)
13
+ .values({
14
+ id,
15
+ entityId: log.entityId,
16
+ fromState: log.fromState ?? null,
17
+ toState: log.toState,
18
+ trigger: log.trigger ?? null,
19
+ invocationId: log.invocationId ?? null,
20
+ timestamp: log.timestamp.getTime(),
21
+ })
22
+ .run();
23
+ return { id, ...log };
24
+ }
25
+ async historyFor(entityId) {
26
+ const rows = this.db
27
+ .select()
28
+ .from(entityHistory)
29
+ .where(eq(entityHistory.entityId, entityId))
30
+ .orderBy(asc(entityHistory.timestamp), sql `rowid`)
31
+ .all();
32
+ return rows.map((r) => ({
33
+ id: r.id,
34
+ entityId: r.entityId,
35
+ fromState: r.fromState,
36
+ toState: r.toState,
37
+ trigger: r.trigger,
38
+ invocationId: r.invocationId,
39
+ timestamp: new Date(r.timestamp),
40
+ }));
41
+ }
42
+ }
@@ -0,0 +1,321 @@
1
+ /** External-system reference map keyed by adapter name */
2
+ export type Refs = Record<string, {
3
+ adapter: string;
4
+ id: string;
5
+ [key: string]: unknown;
6
+ }>;
7
+ /** Freeform key-value artifact bag */
8
+ export type Artifacts = Record<string, unknown>;
9
+ /** Configuration for running a command when an entity enters a state */
10
+ export interface OnEnterConfig {
11
+ command: string;
12
+ artifacts: string[];
13
+ timeout_ms?: number;
14
+ }
15
+ /** Invocation execution mode */
16
+ export type Mode = "active" | "passive";
17
+ /** Runtime entity tracked through a flow */
18
+ export interface Entity {
19
+ id: string;
20
+ flowId: string;
21
+ state: string;
22
+ refs: Refs | null;
23
+ artifacts: Artifacts | null;
24
+ claimedBy: string | null;
25
+ claimedAt: Date | null;
26
+ flowVersion: number;
27
+ priority: number;
28
+ createdAt: Date;
29
+ updatedAt: Date;
30
+ affinityWorkerId: string | null;
31
+ affinityRole: string | null;
32
+ affinityExpiresAt: Date | null;
33
+ }
34
+ /** A single agent invocation tied to an entity */
35
+ export interface Invocation {
36
+ id: string;
37
+ entityId: string;
38
+ stage: string;
39
+ mode: Mode;
40
+ prompt: string;
41
+ context: Record<string, unknown> | null;
42
+ claimedBy: string | null;
43
+ claimedAt: Date | null;
44
+ startedAt: Date | null;
45
+ completedAt: Date | null;
46
+ failedAt: Date | null;
47
+ signal: string | null;
48
+ artifacts: Artifacts | null;
49
+ error: string | null;
50
+ ttlMs: number;
51
+ }
52
+ /** Result of evaluating a gate against an entity */
53
+ export interface GateResult {
54
+ id: string;
55
+ entityId: string;
56
+ gateId: string;
57
+ passed: boolean;
58
+ output: string | null;
59
+ evaluatedAt: Date | null;
60
+ }
61
+ /**
62
+ * Entity with pre-fetched related data for use in Handlebars template helpers.
63
+ * The `invocations` and `gateResults` fields are populated by the engine before
64
+ * rendering prompt templates; they are never present on raw DB rows.
65
+ */
66
+ export interface EnrichedEntity extends Entity {
67
+ invocations?: Invocation[];
68
+ gateResults?: GateResult[];
69
+ }
70
+ /** Audit-log entry for an entity state transition */
71
+ export interface TransitionLog {
72
+ id: string;
73
+ entityId: string;
74
+ fromState: string | null;
75
+ toState: string;
76
+ trigger: string | null;
77
+ invocationId: string | null;
78
+ timestamp: Date;
79
+ }
80
+ /** A state within a flow definition */
81
+ export interface State {
82
+ id: string;
83
+ flowId: string;
84
+ name: string;
85
+ modelTier: string | null;
86
+ mode: Mode;
87
+ promptTemplate: string | null;
88
+ constraints: Record<string, unknown> | null;
89
+ onEnter: OnEnterConfig | null;
90
+ }
91
+ /** A transition rule between two states */
92
+ export interface Transition {
93
+ id: string;
94
+ flowId: string;
95
+ fromState: string;
96
+ toState: string;
97
+ trigger: string;
98
+ gateId: string | null;
99
+ condition: string | null;
100
+ priority: number;
101
+ spawnFlow: string | null;
102
+ spawnTemplate: string | null;
103
+ createdAt: Date | null;
104
+ }
105
+ /** A gate definition (quality check) */
106
+ export interface Gate {
107
+ id: string;
108
+ name: string;
109
+ type: string;
110
+ command: string | null;
111
+ functionRef: string | null;
112
+ apiConfig: Record<string, unknown> | null;
113
+ timeoutMs: number | null;
114
+ failurePrompt: string | null;
115
+ timeoutPrompt: string | null;
116
+ }
117
+ /** A complete flow definition with its states and transitions */
118
+ export interface Flow {
119
+ id: string;
120
+ name: string;
121
+ description: string | null;
122
+ entitySchema: Record<string, unknown> | null;
123
+ initialState: string;
124
+ maxConcurrent: number;
125
+ maxConcurrentPerRepo: number;
126
+ affinityWindowMs: number;
127
+ gateTimeoutMs: number | null;
128
+ version: number;
129
+ createdBy: string | null;
130
+ discipline: string | null;
131
+ defaultModelTier: string | null;
132
+ timeoutPrompt: string | null;
133
+ createdAt: Date | null;
134
+ updatedAt: Date | null;
135
+ states: State[];
136
+ transitions: Transition[];
137
+ }
138
+ /** A versioned snapshot of a flow */
139
+ export interface FlowVersion {
140
+ id: string;
141
+ flowId: string;
142
+ version: number;
143
+ snapshot: Record<string, unknown>;
144
+ changedBy: string | null;
145
+ changeReason: string | null;
146
+ createdAt: Date | null;
147
+ }
148
+ /** Input for creating a new flow */
149
+ export interface CreateFlowInput {
150
+ name: string;
151
+ description?: string;
152
+ entitySchema?: Record<string, unknown>;
153
+ initialState: string;
154
+ maxConcurrent?: number;
155
+ maxConcurrentPerRepo?: number;
156
+ affinityWindowMs?: number;
157
+ gateTimeoutMs?: number;
158
+ createdBy?: string;
159
+ discipline?: string;
160
+ defaultModelTier?: string;
161
+ timeoutPrompt?: string;
162
+ }
163
+ /** Input for adding a state to a flow */
164
+ export interface CreateStateInput {
165
+ name: string;
166
+ modelTier?: string;
167
+ mode?: Mode;
168
+ promptTemplate?: string;
169
+ constraints?: Record<string, unknown>;
170
+ onEnter?: OnEnterConfig;
171
+ }
172
+ /** Input for adding a transition rule */
173
+ export interface CreateTransitionInput {
174
+ fromState: string;
175
+ toState: string;
176
+ trigger: string;
177
+ gateId?: string;
178
+ condition?: string;
179
+ priority?: number;
180
+ spawnFlow?: string;
181
+ spawnTemplate?: string;
182
+ }
183
+ /** Input for creating a gate definition */
184
+ export interface CreateGateInput {
185
+ name: string;
186
+ type: string;
187
+ command?: string;
188
+ functionRef?: string;
189
+ apiConfig?: Record<string, unknown>;
190
+ timeoutMs?: number;
191
+ failurePrompt?: string;
192
+ timeoutPrompt?: string;
193
+ }
194
+ /** Data-access contract for entity lifecycle operations. */
195
+ export interface IEntityRepository {
196
+ /** Create a new entity in the given flow's initial state. */
197
+ create(flowId: string, initialState: string, refs?: Refs): Promise<Entity>;
198
+ /** Get an entity by ID, or null if not found. */
199
+ get(id: string): Promise<Entity | null>;
200
+ /** Find entities in a given flow and state, up to an optional limit. */
201
+ findByFlowAndState(flowId: string, state: string, limit?: number): Promise<Entity[]>;
202
+ /** Return true if at least one entity exists in the given flow across any of the given states. */
203
+ hasAnyInFlowAndState(flowId: string, stateNames: string[]): Promise<boolean>;
204
+ /** Transition an entity to a new state, recording the trigger and optional artifacts. */
205
+ transition(id: string, toState: string, trigger: string, artifacts?: Partial<Artifacts>): Promise<Entity>;
206
+ /** Merge partial artifacts into an entity's existing artifact bag. Performs a shallow merge
207
+ * ({ ...existing, ...artifacts }) — only the specified keys are updated; unspecified keys are preserved. */
208
+ updateArtifacts(id: string, artifacts: Partial<Artifacts>): Promise<void>;
209
+ /** Atomically claim one unclaimed entity in the given flow+state for the specified agent. Returns null if none available. Uses compare-and-swap (UPDATE WHERE claimedBy IS NULL). */
210
+ claim(flowId: string, state: string, agentId: string): Promise<Entity | null>;
211
+ /** Atomically claim a specific entity by ID for the specified agent. Returns null if already claimed. Uses compare-and-swap (UPDATE WHERE claimedBy IS NULL). */
212
+ claimById(entityId: string, agentId: string): Promise<Entity | null>;
213
+ /** Release a claimed entity, clearing claimedBy and claimedAt. */
214
+ release(entityId: string, agentId: string): Promise<void>;
215
+ /** Find entities whose claim has expired beyond ttlMs and release them. Returns the IDs of released entities. */
216
+ reapExpired(ttlMs: number): Promise<string[]>;
217
+ /** Set affinity metadata on an entity, recording the last worker that touched it. */
218
+ setAffinity(entityId: string, workerId: string, role: string, expiresAt: Date): Promise<void>;
219
+ /** Clear expired affinity records. Returns the IDs of entities whose affinity was cleared. */
220
+ clearExpiredAffinity(): Promise<string[]>;
221
+ /** Atomically append a spawned child entry to the parent entity's artifacts.spawnedChildren array.
222
+ * Reads the current array and writes back in a single transaction to prevent TOCTOU races. */
223
+ appendSpawnedChild(parentId: string, entry: {
224
+ childId: string;
225
+ childFlow: string;
226
+ spawnedAt: string;
227
+ }): Promise<void>;
228
+ }
229
+ /** Fields that can be updated on a flow's top-level definition */
230
+ export type UpdateFlowInput = Partial<Omit<Flow, "id" | "states" | "transitions" | "createdAt" | "updatedAt">>;
231
+ /** Fields that can be updated on a state definition */
232
+ export type UpdateStateInput = Partial<Omit<State, "id" | "flowId">>;
233
+ /** Fields that can be updated on a transition rule */
234
+ export type UpdateTransitionInput = Partial<Omit<Transition, "id" | "flowId">>;
235
+ /** Data-access contract for flow definition CRUD and versioning. */
236
+ export interface IFlowRepository {
237
+ /** Create a new flow definition. */
238
+ create(flow: CreateFlowInput): Promise<Flow>;
239
+ /** List all flow definitions. */
240
+ list(): Promise<Flow[]>;
241
+ /** Get a flow by ID, including its states and transitions. Returns null if not found. */
242
+ get(id: string): Promise<Flow | null>;
243
+ /** Get a flow by unique name. Returns null if not found. */
244
+ getByName(name: string): Promise<Flow | null>;
245
+ /** Update a flow's top-level fields. */
246
+ update(id: string, changes: UpdateFlowInput): Promise<Flow>;
247
+ /** Add a state definition to a flow. */
248
+ addState(flowId: string, state: CreateStateInput): Promise<State>;
249
+ /** Update an existing state definition. */
250
+ updateState(stateId: string, changes: UpdateStateInput): Promise<State>;
251
+ /** Add a transition rule to a flow. */
252
+ addTransition(flowId: string, transition: CreateTransitionInput): Promise<Transition>;
253
+ /** Update an existing transition rule. */
254
+ updateTransition(transitionId: string, changes: UpdateTransitionInput): Promise<Transition>;
255
+ /** Create a versioned snapshot of the current flow definition. */
256
+ snapshot(flowId: string): Promise<FlowVersion>;
257
+ /** Restore a flow definition to a previous version. */
258
+ restore(flowId: string, version: number): Promise<void>;
259
+ /** List all flow definitions. */
260
+ listAll(): Promise<Flow[]>;
261
+ }
262
+ /** Data-access contract for invocation lifecycle and claiming. */
263
+ export interface IInvocationRepository {
264
+ /** Create a new invocation for an entity at a given stage. */
265
+ create(entityId: string, stage: string, prompt: string, mode: Mode, ttlMs?: number, context?: Record<string, unknown>): Promise<Invocation>;
266
+ /** Get an invocation by ID, or null if not found. */
267
+ get(id: string): Promise<Invocation | null>;
268
+ /** Atomically claim an unclaimed invocation for the specified agent. Uses compare-and-swap (UPDATE WHERE claimedBy IS NULL). Returns null if already claimed. */
269
+ claim(invocationId: string, agentId: string): Promise<Invocation | null>;
270
+ /** Mark an invocation as completed with a signal and optional artifacts. */
271
+ complete(id: string, signal: string, artifacts?: Artifacts): Promise<Invocation>;
272
+ /** Mark an invocation as failed with an error message. */
273
+ fail(id: string, error: string): Promise<Invocation>;
274
+ /** Release a claim on an invocation, making it available for another worker to claim. */
275
+ releaseClaim(id: string): Promise<void>;
276
+ /** Find all invocations for a given entity. */
277
+ findByEntity(entityId: string): Promise<Invocation[]>;
278
+ /** Find unclaimed invocations where the entity has unexpired affinity for the given worker and role. */
279
+ findUnclaimedWithAffinity(flowId: string, role: string, workerId: string): Promise<Invocation[]>;
280
+ /** Find all unclaimed invocations in a flow, regardless of agentRole. For discipline-based claiming. */
281
+ findUnclaimedByFlow(flowId: string): Promise<Invocation[]>;
282
+ /** Find all invocations for a given flow (across all entities). */
283
+ findByFlow(flowId: string): Promise<Invocation[]>;
284
+ /** Find and mark expired invocations (where now - claimedAt > row's ttlMs). Returns the expired invocations. */
285
+ reapExpired(): Promise<Invocation[]>;
286
+ /** Find unclaimed active-mode invocations, optionally filtered by flow. */
287
+ findUnclaimedActive(flowId?: string): Promise<Invocation[]>;
288
+ /** Count active invocations (claimed, not completed/failed) for a flow. */
289
+ countActiveByFlow(flowId: string): Promise<number>;
290
+ /** Count pending invocations (unclaimed, not completed/failed) for a flow. */
291
+ countPendingByFlow(flowId: string): Promise<number>;
292
+ }
293
+ /** Data-access contract for entity state-transition audit trails. */
294
+ export interface ITransitionLogRepository {
295
+ /** Record a state transition for an entity. */
296
+ record(log: Omit<TransitionLog, "id">): Promise<TransitionLog>;
297
+ /** Get full transition history for an entity, ordered by timestamp. */
298
+ historyFor(entityId: string): Promise<TransitionLog[]>;
299
+ }
300
+ /** Data-access contract for emitting definition-change events. */
301
+ export interface IEventRepository {
302
+ /** Emit a definition change event for a tool action. */
303
+ emitDefinitionChanged(flowId: string | null, tool: string, payload: Record<string, unknown>): Promise<void>;
304
+ }
305
+ /** Data-access contract for gate definitions and result recording. */
306
+ export interface IGateRepository {
307
+ /** Create a new gate definition. */
308
+ create(gate: CreateGateInput): Promise<Gate>;
309
+ /** Get a gate by ID, or null if not found. */
310
+ get(id: string): Promise<Gate | null>;
311
+ /** Get a gate by unique name, or null if not found. */
312
+ getByName(name: string): Promise<Gate | null>;
313
+ /** List all gate definitions. */
314
+ listAll(): Promise<Gate[]>;
315
+ /** Record the result of evaluating a gate against an entity. */
316
+ record(entityId: string, gateId: string, passed: boolean, output: string): Promise<GateResult>;
317
+ /** Get all gate results for a given entity. */
318
+ resultsFor(entityId: string): Promise<GateResult[]>;
319
+ /** Update mutable fields on a gate definition. */
320
+ update(id: string, changes: Partial<Pick<Gate, "command" | "functionRef" | "apiConfig" | "timeoutMs" | "failurePrompt" | "timeoutPrompt">>): Promise<Gate>;
321
+ }
@@ -0,0 +1,2 @@
1
+ // Repository interfaces — I*Repository contracts
2
+ export {};
@@ -0,0 +1,24 @@
1
+ export interface ParsedRequest {
2
+ params: Record<string, string>;
3
+ query: URLSearchParams;
4
+ body: Record<string, unknown> | null;
5
+ authorization?: string;
6
+ }
7
+ export interface ApiResponse {
8
+ status: number;
9
+ body: unknown;
10
+ }
11
+ type Handler = (req: ParsedRequest) => Promise<ApiResponse>;
12
+ interface MatchResult {
13
+ params: Record<string, string>;
14
+ handler: Handler;
15
+ longRunning?: boolean;
16
+ }
17
+ export declare class Router {
18
+ private routes;
19
+ add(method: string, path: string, handler: Handler, options?: {
20
+ longRunning?: boolean;
21
+ }): void;
22
+ match(method: string, pathname: string): MatchResult | null;
23
+ }
24
+ export {};
@@ -0,0 +1,44 @@
1
+ export class Router {
2
+ routes = [];
3
+ add(method, path, handler, options) {
4
+ const paramNames = [];
5
+ // Split on param segments, escape literal segments, then reassemble
6
+ const patternStr = path
7
+ .split(/(:([^/]+))/g)
8
+ .map((segment, i) => {
9
+ // Every 3rd token (i % 3 === 1) is the full ":name" match — replace with capture group
10
+ if (i % 3 === 1) {
11
+ paramNames.push(segment.slice(1));
12
+ return "([^/]+)";
13
+ }
14
+ // Every 3rd+1 token (i % 3 === 2) is the captured name — skip (already handled above)
15
+ if (i % 3 === 2)
16
+ return "";
17
+ // Literal segment — escape regex metacharacters
18
+ return segment.replace(/[.+*?^${}()|[\]\\]/g, "\\$&");
19
+ })
20
+ .join("");
21
+ this.routes.push({
22
+ method,
23
+ pattern: new RegExp(`^${patternStr}$`),
24
+ paramNames,
25
+ handler,
26
+ longRunning: options?.longRunning,
27
+ });
28
+ }
29
+ match(method, pathname) {
30
+ for (const route of this.routes) {
31
+ if (route.method !== method)
32
+ continue;
33
+ const m = pathname.match(route.pattern);
34
+ if (m) {
35
+ const params = {};
36
+ route.paramNames.forEach((name, i) => {
37
+ params[name] = m[i + 1];
38
+ });
39
+ return { params, handler: route.handler, longRunning: route.longRunning };
40
+ }
41
+ }
42
+ return null;
43
+ }
44
+ }
@@ -0,0 +1,13 @@
1
+ import http from "node:http";
2
+ import type { Engine } from "../engine/engine.js";
3
+ import type { McpServerDeps } from "../execution/mcp-server.js";
4
+ import type { Logger } from "../logger.js";
5
+ export interface HttpServerDeps {
6
+ engine: Engine;
7
+ mcpDeps: McpServerDeps;
8
+ adminToken?: string;
9
+ workerToken?: string;
10
+ corsOrigin?: string;
11
+ logger?: Logger;
12
+ }
13
+ export declare function createHttpServer(deps: HttpServerDeps): http.Server;