@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,283 @@
1
+ import { z } from "zod/v4";
2
+ import { validateGateCommand } from "../engine/gate-command-validator.js";
3
+ import { validateTemplate } from "../engine/handlebars.js";
4
+ // ─── Leaf Schemas ───
5
+ export const FlowDefinitionSchema = z.object({
6
+ name: z.string().min(1),
7
+ description: z.string().optional(),
8
+ entitySchema: z.record(z.string(), z.unknown()).optional(),
9
+ initialState: z.string().min(1),
10
+ maxConcurrent: z.number().int().min(0).optional().default(0),
11
+ maxConcurrentPerRepo: z.number().int().min(0).optional().default(0),
12
+ affinityWindowMs: z.number().int().min(0).optional().default(300000),
13
+ gateTimeoutMs: z.number().int().min(1).optional(),
14
+ version: z.number().int().min(1).optional().default(1),
15
+ createdBy: z.string().optional(),
16
+ discipline: z.string().min(1),
17
+ defaultModelTier: z.string().min(1).optional(),
18
+ timeoutPrompt: z
19
+ .string()
20
+ .min(1)
21
+ .refine((val) => validateTemplate(val), {
22
+ message: "timeoutPrompt contains disallowed Handlebars expressions",
23
+ })
24
+ .optional(),
25
+ });
26
+ export const OnEnterSchema = z.object({
27
+ command: z
28
+ .string()
29
+ .min(1)
30
+ .refine((val) => validateTemplate(val), {
31
+ message: "onEnter command contains disallowed Handlebars expressions",
32
+ }),
33
+ artifacts: z.array(z.string().min(1)).min(1),
34
+ timeout_ms: z.number().int().min(0).optional().default(30000),
35
+ });
36
+ export const StateDefinitionSchema = z.object({
37
+ name: z.string().min(1),
38
+ flowName: z.string().min(1),
39
+ modelTier: z.string().optional(),
40
+ mode: z.enum(["passive", "active"]).optional().default("passive"),
41
+ promptTemplate: z
42
+ .string()
43
+ .min(1)
44
+ .refine((val) => validateTemplate(val), {
45
+ message: "promptTemplate contains disallowed Handlebars expressions",
46
+ })
47
+ .optional(),
48
+ constraints: z.record(z.string(), z.unknown()).optional(),
49
+ onEnter: OnEnterSchema.optional(),
50
+ });
51
+ // Gate: discriminated union on `type`
52
+ const BaseGateSchema = z.object({
53
+ name: z.string().min(1),
54
+ timeoutMs: z.number().int().min(1).optional(),
55
+ failurePrompt: z.string().optional(),
56
+ timeoutPrompt: z.string().min(1).optional(),
57
+ });
58
+ export const CommandGateSchema = BaseGateSchema.extend({
59
+ type: z.literal("command"),
60
+ command: z
61
+ .string()
62
+ .min(1)
63
+ .superRefine((cmd, ctx) => {
64
+ const result = validateGateCommand(cmd);
65
+ if (!result.valid) {
66
+ ctx.addIssue({ code: "custom", message: result.error ?? "Gate command not allowed" });
67
+ }
68
+ }),
69
+ });
70
+ export const FunctionGateSchema = BaseGateSchema.extend({
71
+ type: z.literal("function"),
72
+ functionRef: z.string().regex(/^[^:]+:[^:]+$/, "functionRef must be in 'path:exportName' format"),
73
+ });
74
+ export const ApiGateSchema = BaseGateSchema.extend({
75
+ type: z.literal("api"),
76
+ apiConfig: z.record(z.string(), z.unknown()),
77
+ });
78
+ export const GateDefinitionSchema = z.discriminatedUnion("type", [
79
+ CommandGateSchema,
80
+ FunctionGateSchema,
81
+ ApiGateSchema,
82
+ ]);
83
+ export const TransitionRuleSchema = z.object({
84
+ flowName: z.string().min(1),
85
+ fromState: z.string().min(1),
86
+ toState: z.string().min(1),
87
+ trigger: z.string().min(1),
88
+ gateName: z.string().optional(),
89
+ condition: z
90
+ .string()
91
+ .refine((val) => validateTemplate(val), {
92
+ message: "condition contains disallowed Handlebars expressions",
93
+ })
94
+ .optional(),
95
+ priority: z.number().int().min(0).optional().default(0),
96
+ spawnFlow: z.string().optional(),
97
+ spawnTemplate: z
98
+ .string()
99
+ .refine((val) => validateTemplate(val), {
100
+ message: "spawnTemplate contains disallowed Handlebars expressions",
101
+ })
102
+ .optional(),
103
+ });
104
+ // ─── Seed File Schema (with cross-reference validation) ───
105
+ export const SeedFileSchema = z
106
+ .object({
107
+ flows: z.array(FlowDefinitionSchema).min(1),
108
+ states: z.array(StateDefinitionSchema).min(1),
109
+ gates: z.array(GateDefinitionSchema).optional().default([]),
110
+ transitions: z.array(TransitionRuleSchema).min(1),
111
+ })
112
+ .strict()
113
+ .superRefine((seed, ctx) => {
114
+ // Bug 2 fix: detect duplicate flow names explicitly before building the Set
115
+ const flowNames = new Set();
116
+ for (let i = 0; i < seed.flows.length; i++) {
117
+ const name = seed.flows[i].name;
118
+ if (flowNames.has(name)) {
119
+ ctx.addIssue({
120
+ code: "custom",
121
+ message: `Duplicate flow name "${name}"`,
122
+ path: ["flows", i, "name"],
123
+ });
124
+ }
125
+ else {
126
+ flowNames.add(name);
127
+ }
128
+ }
129
+ // Bug 2 fix: detect duplicate gate names explicitly before building the Set
130
+ const gateNames = new Set();
131
+ for (let i = 0; i < seed.gates.length; i++) {
132
+ const name = seed.gates[i].name;
133
+ if (gateNames.has(name)) {
134
+ ctx.addIssue({
135
+ code: "custom",
136
+ message: `Duplicate gate name "${name}"`,
137
+ path: ["gates", i, "name"],
138
+ });
139
+ }
140
+ else {
141
+ gateNames.add(name);
142
+ }
143
+ }
144
+ // Detect duplicate state names within a flow
145
+ const stateNamesByFlow = new Map();
146
+ for (let i = 0; i < seed.states.length; i++) {
147
+ const s = seed.states[i];
148
+ if (!stateNamesByFlow.has(s.flowName)) {
149
+ stateNamesByFlow.set(s.flowName, new Set());
150
+ }
151
+ const seen = stateNamesByFlow.get(s.flowName);
152
+ if (seen?.has(s.name)) {
153
+ ctx.addIssue({
154
+ code: "custom",
155
+ message: `Duplicate state name '${s.name}' in flow '${s.flowName}'`,
156
+ path: ["states", i, "name"],
157
+ });
158
+ }
159
+ else {
160
+ seen?.add(s.name);
161
+ }
162
+ }
163
+ // Bug 1 fix: only populate statesByFlow for flows that actually exist,
164
+ // so that transitions referencing unknown flows don't find stale state data.
165
+ const statesByFlow = new Map();
166
+ for (const s of seed.states) {
167
+ if (!flowNames.has(s.flowName))
168
+ continue;
169
+ if (!statesByFlow.has(s.flowName)) {
170
+ statesByFlow.set(s.flowName, new Set());
171
+ }
172
+ statesByFlow.get(s.flowName)?.add(s.name);
173
+ }
174
+ // Validate states reference existing flows
175
+ for (let i = 0; i < seed.states.length; i++) {
176
+ const s = seed.states[i];
177
+ if (!flowNames.has(s.flowName)) {
178
+ ctx.addIssue({
179
+ code: "custom",
180
+ message: `State "${s.name}" references unknown flow "${s.flowName}"`,
181
+ path: ["states", i, "flowName"],
182
+ });
183
+ }
184
+ }
185
+ // Validate each flow's initialState is a defined state
186
+ for (let i = 0; i < seed.flows.length; i++) {
187
+ const f = seed.flows[i];
188
+ const flowStates = statesByFlow.get(f.name);
189
+ if (!flowStates || !flowStates.has(f.initialState)) {
190
+ ctx.addIssue({
191
+ code: "custom",
192
+ message: `Flow "${f.name}" has initialState "${f.initialState}" which is not a defined state`,
193
+ path: ["flows", i, "initialState"],
194
+ });
195
+ }
196
+ }
197
+ // Validate transitions
198
+ for (let i = 0; i < seed.transitions.length; i++) {
199
+ const t = seed.transitions[i];
200
+ if (!flowNames.has(t.flowName)) {
201
+ ctx.addIssue({
202
+ code: "custom",
203
+ message: `Transition references unknown flow "${t.flowName}"`,
204
+ path: ["transitions", i, "flowName"],
205
+ });
206
+ }
207
+ else {
208
+ // Bug 3 fix: flow exists — check fromState/toState even if the flow has
209
+ // zero states (statesByFlow entry will be missing or empty in that case).
210
+ const flowStates = statesByFlow.get(t.flowName) ?? new Set();
211
+ if (!flowStates.has(t.fromState)) {
212
+ ctx.addIssue({
213
+ code: "custom",
214
+ message: `Transition fromState "${t.fromState}" not defined in flow "${t.flowName}"`,
215
+ path: ["transitions", i, "fromState"],
216
+ });
217
+ }
218
+ if (!flowStates.has(t.toState)) {
219
+ ctx.addIssue({
220
+ code: "custom",
221
+ message: `Transition toState "${t.toState}" not defined in flow "${t.flowName}"`,
222
+ path: ["transitions", i, "toState"],
223
+ });
224
+ }
225
+ }
226
+ if (t.gateName && !gateNames.has(t.gateName)) {
227
+ ctx.addIssue({
228
+ code: "custom",
229
+ message: `Transition references unknown gate "${t.gateName}"`,
230
+ path: ["transitions", i, "gateName"],
231
+ });
232
+ }
233
+ if (t.spawnFlow && !flowNames.has(t.spawnFlow)) {
234
+ ctx.addIssue({
235
+ code: "custom",
236
+ message: `Transition spawnFlow "${t.spawnFlow}" references unknown flow`,
237
+ path: ["transitions", i, "spawnFlow"],
238
+ });
239
+ }
240
+ }
241
+ // Detect circular spawnFlow chains via DFS
242
+ const spawnAdj = new Map();
243
+ for (const t of seed.transitions) {
244
+ if (t.spawnFlow && flowNames.has(t.flowName) && flowNames.has(t.spawnFlow)) {
245
+ if (!spawnAdj.has(t.flowName))
246
+ spawnAdj.set(t.flowName, new Set());
247
+ spawnAdj.get(t.flowName)?.add(t.spawnFlow);
248
+ }
249
+ }
250
+ const visited = new Set();
251
+ const inStack = new Set();
252
+ function dfs(node, path) {
253
+ if (inStack.has(node))
254
+ return [...path, node];
255
+ if (visited.has(node))
256
+ return null;
257
+ visited.add(node);
258
+ inStack.add(node);
259
+ for (const neighbor of spawnAdj.get(node) ?? []) {
260
+ const cycle = dfs(neighbor, [...path, node]);
261
+ if (cycle)
262
+ return cycle;
263
+ }
264
+ inStack.delete(node);
265
+ return null;
266
+ }
267
+ for (const flowName of spawnAdj.keys()) {
268
+ if (visited.has(flowName))
269
+ continue;
270
+ const cycle = dfs(flowName, []);
271
+ if (cycle) {
272
+ const cycleStart = cycle[cycle.length - 1];
273
+ const cycleStartIdx = cycle.indexOf(cycleStart);
274
+ const cyclePath = cycle.slice(cycleStartIdx);
275
+ ctx.addIssue({
276
+ code: "custom",
277
+ message: `Circular spawnFlow chain detected: ${cyclePath.join(" -> ")}`,
278
+ path: ["transitions"],
279
+ });
280
+ break;
281
+ }
282
+ }
283
+ });
package/dist/cors.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export interface CorsOriginResult {
2
+ /** Explicit allowed origin, or null meaning "loopback-only default pattern" */
3
+ origin: string | null;
4
+ }
5
+ export declare function resolveCorsOrigin(opts: {
6
+ host: string;
7
+ corsEnv: string | undefined;
8
+ }): CorsOriginResult;
package/dist/cors.js ADDED
@@ -0,0 +1,21 @@
1
+ const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
2
+ export function resolveCorsOrigin(opts) {
3
+ const corsValue = opts.corsEnv?.trim() || undefined;
4
+ const isLoopback = LOOPBACK_HOSTS.has(opts.host);
5
+ // If explicit origin provided, validate and use it
6
+ if (corsValue) {
7
+ if (!/^https?:\/\/[^/]+$/.test(corsValue)) {
8
+ throw new Error(`DEFCON_CORS_ORIGIN must be a bare origin like https://app.example.com, not ${corsValue}. ` +
9
+ "Remove any path component or trailing slash.");
10
+ }
11
+ return { origin: corsValue };
12
+ }
13
+ // Non-loopback without explicit origin — refuse to start
14
+ if (!isLoopback) {
15
+ throw new Error(`DEFCON_CORS_ORIGIN must be set when binding to non-loopback address "${opts.host}". ` +
16
+ "Without an explicit CORS origin, any website on the network can make cross-origin requests to this server. " +
17
+ 'Set DEFCON_CORS_ORIGIN to the allowed origin (e.g. "https://my-app.example.com") or use a loopback address.');
18
+ }
19
+ // Loopback without explicit origin — use default pattern
20
+ return { origin: null };
21
+ }
@@ -0,0 +1 @@
1
+ export declare const DEFAULT_TIMEOUT_PROMPT = "Your report was received. The gate is still evaluating \u2014 this is not an error. Call flow.claim to reclaim the entity, then call flow.report again with the same arguments after a short wait.";
@@ -0,0 +1 @@
1
+ export const DEFAULT_TIMEOUT_PROMPT = "Your report was received. The gate is still evaluating — this is not an error. Call flow.claim to reclaim the entity, then call flow.report again with the same arguments after a short wait.";
@@ -0,0 +1,69 @@
1
+ import type { Logger } from "../logger.js";
2
+ import type { Artifacts, Entity, IEntityRepository, IFlowRepository, IGateRepository, IInvocationRepository, ITransitionLogRepository } from "../repositories/interfaces.js";
3
+ import type { IEventBusAdapter } from "./event-types.js";
4
+ export interface ProcessSignalResult {
5
+ newState?: string;
6
+ /** Names (not IDs) of gates that evaluated and passed during this transition. */
7
+ gatesPassed: string[];
8
+ gated: boolean;
9
+ gateTimedOut?: boolean;
10
+ gateOutput?: string;
11
+ gateName?: string;
12
+ failurePrompt?: string;
13
+ timeoutPrompt?: string;
14
+ onEnterFailed?: boolean;
15
+ invocationId?: string;
16
+ spawned?: string[];
17
+ terminal: boolean;
18
+ }
19
+ export interface ClaimWorkResult {
20
+ entityId: string;
21
+ invocationId: string;
22
+ prompt: string;
23
+ context: Record<string, unknown> | null;
24
+ }
25
+ export interface EngineStatus {
26
+ flows: Record<string, Record<string, number>>;
27
+ activeInvocations: number;
28
+ pendingClaims: number;
29
+ }
30
+ export interface EngineDeps {
31
+ entityRepo: IEntityRepository;
32
+ flowRepo: IFlowRepository;
33
+ invocationRepo: IInvocationRepository;
34
+ gateRepo: IGateRepository;
35
+ transitionLogRepo: ITransitionLogRepository;
36
+ adapters: Map<string, unknown>;
37
+ eventEmitter: IEventBusAdapter;
38
+ logger?: Logger;
39
+ }
40
+ export declare class Engine {
41
+ private entityRepo;
42
+ private flowRepo;
43
+ private invocationRepo;
44
+ private gateRepo;
45
+ private transitionLogRepo;
46
+ readonly adapters: Map<string, unknown>;
47
+ private eventEmitter;
48
+ private readonly logger;
49
+ constructor(deps: EngineDeps);
50
+ processSignal(entityId: string, signal: string, artifacts?: Artifacts, triggeringInvocationId?: string): Promise<ProcessSignalResult>;
51
+ createEntity(flowName: string, refs?: Record<string, {
52
+ adapter: string;
53
+ id: string;
54
+ [key: string]: unknown;
55
+ }>): Promise<Entity>;
56
+ claimWork(role: string, flowName?: string, worker_id?: string): Promise<ClaimWorkResult | null>;
57
+ /**
58
+ * Try to claim an existing unclaimed invocation for an already-claimed entity.
59
+ * Handles the race condition where another worker claims the invocation first
60
+ * (releases the entity claim and returns null so the caller can try the next candidate).
61
+ */
62
+ private tryClaimInvocation;
63
+ private setAffinityIfNeeded;
64
+ private buildPrompt;
65
+ private emitAndReturn;
66
+ getStatus(): Promise<EngineStatus>;
67
+ startReaper(intervalMs: number, entityTtlMs?: number): () => Promise<void>;
68
+ private checkConcurrency;
69
+ }