@plumbus/core 0.7.1 → 0.8.0-beta.1

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 (513) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +2 -1
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/admission/client.d.ts +9 -0
  5. package/dist/admission/client.d.ts.map +1 -0
  6. package/dist/admission/client.js +34 -0
  7. package/dist/admission/client.js.map +1 -0
  8. package/dist/admission/index.d.ts +5 -0
  9. package/dist/admission/index.d.ts.map +1 -0
  10. package/dist/admission/index.js +4 -0
  11. package/dist/admission/index.js.map +1 -0
  12. package/dist/admission/request-admission.d.ts +26 -0
  13. package/dist/admission/request-admission.d.ts.map +1 -0
  14. package/dist/admission/request-admission.js +85 -0
  15. package/dist/admission/request-admission.js.map +1 -0
  16. package/dist/admission/shared.d.ts +31 -0
  17. package/dist/admission/shared.d.ts.map +1 -0
  18. package/dist/admission/shared.js +30 -0
  19. package/dist/admission/shared.js.map +1 -0
  20. package/dist/ai/ai/provider.d.ts +5 -7
  21. package/dist/ai/ai-service.d.ts +8 -1
  22. package/dist/ai/ai-service.d.ts.map +1 -1
  23. package/dist/ai/ai-service.js +23 -13
  24. package/dist/ai/ai-service.js.map +1 -1
  25. package/dist/ai/errors/data-errors.d.ts +6 -0
  26. package/dist/ai/errors/index.d.ts +4 -1
  27. package/dist/ai/fill-prompt-template.d.ts +12 -0
  28. package/dist/ai/fill-prompt-template.d.ts.map +1 -0
  29. package/dist/ai/fill-prompt-template.js +19 -0
  30. package/dist/ai/fill-prompt-template.js.map +1 -0
  31. package/dist/ai/governed-artifacts.d.ts +49 -0
  32. package/dist/ai/governed-artifacts.d.ts.map +1 -0
  33. package/dist/ai/governed-artifacts.js +277 -0
  34. package/dist/ai/governed-artifacts.js.map +1 -0
  35. package/dist/ai/governed-budget.d.ts +17 -0
  36. package/dist/ai/governed-budget.d.ts.map +1 -0
  37. package/dist/ai/governed-budget.js +52 -0
  38. package/dist/ai/governed-budget.js.map +1 -0
  39. package/dist/ai/governed-host.d.ts +54 -0
  40. package/dist/ai/governed-host.d.ts.map +1 -0
  41. package/dist/ai/governed-host.js +8 -0
  42. package/dist/ai/governed-host.js.map +1 -0
  43. package/dist/ai/governed-invoke.d.ts +41 -0
  44. package/dist/ai/governed-invoke.d.ts.map +1 -0
  45. package/dist/ai/governed-invoke.js +102 -0
  46. package/dist/ai/governed-invoke.js.map +1 -0
  47. package/dist/ai/governed-runtime.d.ts +103 -0
  48. package/dist/ai/governed-runtime.d.ts.map +1 -0
  49. package/dist/ai/governed-runtime.js +151 -0
  50. package/dist/ai/governed-runtime.js.map +1 -0
  51. package/dist/ai/index.d.ts +12 -2
  52. package/dist/ai/index.d.ts.map +1 -1
  53. package/dist/ai/index.js +8 -2
  54. package/dist/ai/index.js.map +1 -1
  55. package/dist/ai/prompt-registry.d.ts +6 -0
  56. package/dist/ai/prompt-registry.d.ts.map +1 -1
  57. package/dist/ai/prompt-registry.js +12 -0
  58. package/dist/ai/prompt-registry.js.map +1 -1
  59. package/dist/ai/provider.d.ts +5 -7
  60. package/dist/ai/provider.d.ts.map +1 -1
  61. package/dist/ai/provider.js +29 -0
  62. package/dist/ai/provider.js.map +1 -1
  63. package/dist/ai/types/enums.d.ts +2 -0
  64. package/dist/ai/types/fields.d.ts +7 -0
  65. package/dist/ai/types/prompt.d.ts +8 -0
  66. package/dist/ai/validation.d.ts.map +1 -1
  67. package/dist/ai/validation.js +10 -1
  68. package/dist/ai/validation.js.map +1 -1
  69. package/dist/api/exposure.d.ts +14 -0
  70. package/dist/api/exposure.d.ts.map +1 -1
  71. package/dist/api/exposure.js +16 -0
  72. package/dist/api/exposure.js.map +1 -1
  73. package/dist/api/idempotency.d.ts +67 -0
  74. package/dist/api/idempotency.d.ts.map +1 -0
  75. package/dist/api/idempotency.js +176 -0
  76. package/dist/api/idempotency.js.map +1 -0
  77. package/dist/api/index.d.ts +3 -1
  78. package/dist/api/index.d.ts.map +1 -1
  79. package/dist/api/index.js +4 -2
  80. package/dist/api/index.js.map +1 -1
  81. package/dist/api/route-generator.d.ts +50 -6
  82. package/dist/api/route-generator.d.ts.map +1 -1
  83. package/dist/api/route-generator.js +254 -12
  84. package/dist/api/route-generator.js.map +1 -1
  85. package/dist/approvals/action-risk.d.ts +41 -0
  86. package/dist/approvals/action-risk.d.ts.map +1 -0
  87. package/dist/approvals/action-risk.js +56 -0
  88. package/dist/approvals/action-risk.js.map +1 -0
  89. package/dist/approvals/actors.d.ts +8 -0
  90. package/dist/approvals/actors.d.ts.map +1 -0
  91. package/dist/approvals/actors.js +26 -0
  92. package/dist/approvals/actors.js.map +1 -0
  93. package/dist/approvals/authorization.d.ts +5 -0
  94. package/dist/approvals/authorization.d.ts.map +1 -0
  95. package/dist/approvals/authorization.js +16 -0
  96. package/dist/approvals/authorization.js.map +1 -0
  97. package/dist/approvals/digest.d.ts +3 -0
  98. package/dist/approvals/digest.d.ts.map +1 -0
  99. package/dist/approvals/digest.js +21 -0
  100. package/dist/approvals/digest.js.map +1 -0
  101. package/dist/approvals/gate.d.ts +15 -0
  102. package/dist/approvals/gate.d.ts.map +1 -0
  103. package/dist/approvals/gate.js +102 -0
  104. package/dist/approvals/gate.js.map +1 -0
  105. package/dist/approvals/host-runtime.d.ts +16 -0
  106. package/dist/approvals/host-runtime.d.ts.map +1 -0
  107. package/dist/approvals/host-runtime.js +12 -0
  108. package/dist/approvals/host-runtime.js.map +1 -0
  109. package/dist/approvals/index.d.ts +19 -0
  110. package/dist/approvals/index.d.ts.map +1 -0
  111. package/dist/approvals/index.js +13 -0
  112. package/dist/approvals/index.js.map +1 -0
  113. package/dist/approvals/memory-store.d.ts +3 -0
  114. package/dist/approvals/memory-store.d.ts.map +1 -0
  115. package/dist/approvals/memory-store.js +33 -0
  116. package/dist/approvals/memory-store.js.map +1 -0
  117. package/dist/approvals/schema.d.ts +484 -0
  118. package/dist/approvals/schema.d.ts.map +1 -0
  119. package/dist/approvals/schema.js +55 -0
  120. package/dist/approvals/schema.js.map +1 -0
  121. package/dist/approvals/service.d.ts +17 -0
  122. package/dist/approvals/service.d.ts.map +1 -0
  123. package/dist/approvals/service.js +240 -0
  124. package/dist/approvals/service.js.map +1 -0
  125. package/dist/approvals/sql-store.d.ts +9 -0
  126. package/dist/approvals/sql-store.d.ts.map +1 -0
  127. package/dist/approvals/sql-store.js +179 -0
  128. package/dist/approvals/sql-store.js.map +1 -0
  129. package/dist/approvals/types.d.ts +156 -0
  130. package/dist/approvals/types.d.ts.map +1 -0
  131. package/dist/approvals/types.js +32 -0
  132. package/dist/approvals/types.js.map +1 -0
  133. package/dist/approvals/wait.d.ts +10 -0
  134. package/dist/approvals/wait.d.ts.map +1 -0
  135. package/dist/approvals/wait.js +11 -0
  136. package/dist/approvals/wait.js.map +1 -0
  137. package/dist/audit/service.d.ts.map +1 -1
  138. package/dist/audit/service.js +6 -1
  139. package/dist/audit/service.js.map +1 -1
  140. package/dist/cli/cli.d.ts.map +1 -1
  141. package/dist/cli/cli.js +2 -1
  142. package/dist/cli/cli.js.map +1 -1
  143. package/dist/cli/commands/api.d.ts +65 -0
  144. package/dist/cli/commands/api.d.ts.map +1 -1
  145. package/dist/cli/commands/api.js +79 -4
  146. package/dist/cli/commands/api.js.map +1 -1
  147. package/dist/cli/commands/compile-flows.d.ts +9 -0
  148. package/dist/cli/commands/compile-flows.d.ts.map +1 -0
  149. package/dist/cli/commands/compile-flows.js +55 -0
  150. package/dist/cli/commands/compile-flows.js.map +1 -0
  151. package/dist/cli/commands/dev.d.ts +6 -0
  152. package/dist/cli/commands/dev.d.ts.map +1 -1
  153. package/dist/cli/commands/dev.js +22 -5
  154. package/dist/cli/commands/dev.js.map +1 -1
  155. package/dist/cli/commands/e2e.d.ts +5 -0
  156. package/dist/cli/commands/e2e.d.ts.map +1 -1
  157. package/dist/cli/commands/e2e.js +42 -3
  158. package/dist/cli/commands/e2e.js.map +1 -1
  159. package/dist/cli/commands/flow.d.ts.map +1 -1
  160. package/dist/cli/commands/flow.js +12 -3
  161. package/dist/cli/commands/flow.js.map +1 -1
  162. package/dist/cli/commands/generate-openapi-from-zod.d.ts +26 -0
  163. package/dist/cli/commands/generate-openapi-from-zod.d.ts.map +1 -0
  164. package/dist/cli/commands/generate-openapi-from-zod.js +189 -0
  165. package/dist/cli/commands/generate-openapi-from-zod.js.map +1 -0
  166. package/dist/cli/commands/generate.d.ts +10 -4
  167. package/dist/cli/commands/generate.d.ts.map +1 -1
  168. package/dist/cli/commands/generate.js +27 -70
  169. package/dist/cli/commands/generate.js.map +1 -1
  170. package/dist/cli/commands/index.d.ts +1 -0
  171. package/dist/cli/commands/index.d.ts.map +1 -1
  172. package/dist/cli/commands/index.js +2 -0
  173. package/dist/cli/commands/index.js.map +1 -1
  174. package/dist/cli/commands/mcp.d.ts +5 -0
  175. package/dist/cli/commands/mcp.d.ts.map +1 -1
  176. package/dist/cli/commands/mcp.js +28 -2
  177. package/dist/cli/commands/mcp.js.map +1 -1
  178. package/dist/cli/commands/migrate.d.ts +1 -0
  179. package/dist/cli/commands/migrate.d.ts.map +1 -1
  180. package/dist/cli/commands/migrate.js +145 -34
  181. package/dist/cli/commands/migrate.js.map +1 -1
  182. package/dist/cli/commands/openapi-3-1.d.ts +34 -0
  183. package/dist/cli/commands/openapi-3-1.d.ts.map +1 -0
  184. package/dist/cli/commands/openapi-3-1.js +333 -0
  185. package/dist/cli/commands/openapi-3-1.js.map +1 -0
  186. package/dist/cli/commands/start.d.ts +5 -0
  187. package/dist/cli/commands/start.d.ts.map +1 -1
  188. package/dist/cli/commands/start.js +20 -2
  189. package/dist/cli/commands/start.js.map +1 -1
  190. package/dist/cli/commands/ui.d.ts +11 -0
  191. package/dist/cli/commands/ui.d.ts.map +1 -1
  192. package/dist/cli/commands/ui.js +105 -20
  193. package/dist/cli/commands/ui.js.map +1 -1
  194. package/dist/cli/commands/verify.d.ts.map +1 -1
  195. package/dist/cli/commands/verify.js +2 -0
  196. package/dist/cli/commands/verify.js.map +1 -1
  197. package/dist/cli/commands/worker.d.ts +5 -0
  198. package/dist/cli/commands/worker.d.ts.map +1 -1
  199. package/dist/cli/commands/worker.js +18 -2
  200. package/dist/cli/commands/worker.js.map +1 -1
  201. package/dist/cli/index.d.ts +1 -1
  202. package/dist/cli/index.d.ts.map +1 -1
  203. package/dist/cli/index.js +1 -1
  204. package/dist/cli/index.js.map +1 -1
  205. package/dist/credentials/catalog.d.ts +71 -0
  206. package/dist/credentials/catalog.d.ts.map +1 -0
  207. package/dist/credentials/catalog.js +293 -0
  208. package/dist/credentials/catalog.js.map +1 -0
  209. package/dist/credentials/index.d.ts +3 -0
  210. package/dist/credentials/index.d.ts.map +1 -0
  211. package/dist/credentials/index.js +2 -0
  212. package/dist/credentials/index.js.map +1 -0
  213. package/dist/data/drift-inspector.d.ts +1 -4
  214. package/dist/data/drift-inspector.d.ts.map +1 -1
  215. package/dist/data/drift-inspector.js +39 -5
  216. package/dist/data/drift-inspector.js.map +1 -1
  217. package/dist/data/field-encryption.d.ts +7 -0
  218. package/dist/data/field-encryption.d.ts.map +1 -1
  219. package/dist/data/field-encryption.js +35 -17
  220. package/dist/data/field-encryption.js.map +1 -1
  221. package/dist/data/framework-migration-rewriter.js +1 -1
  222. package/dist/data/framework-migration-rewriter.js.map +1 -1
  223. package/dist/data/index.d.ts +1 -1
  224. package/dist/data/index.d.ts.map +1 -1
  225. package/dist/data/index.js +1 -1
  226. package/dist/data/index.js.map +1 -1
  227. package/dist/data/mask-fields.d.ts +2 -0
  228. package/dist/data/mask-fields.d.ts.map +1 -1
  229. package/dist/data/mask-fields.js +6 -0
  230. package/dist/data/mask-fields.js.map +1 -1
  231. package/dist/data/migration.d.ts.map +1 -1
  232. package/dist/data/migration.js +25 -1
  233. package/dist/data/migration.js.map +1 -1
  234. package/dist/data/repository.d.ts.map +1 -1
  235. package/dist/data/repository.js +15 -3
  236. package/dist/data/repository.js.map +1 -1
  237. package/dist/data/schema-generator.d.ts +95 -3
  238. package/dist/data/schema-generator.d.ts.map +1 -1
  239. package/dist/data/schema-generator.js +122 -6
  240. package/dist/data/schema-generator.js.map +1 -1
  241. package/dist/define/defineCapability.d.ts +7 -0
  242. package/dist/define/defineCapability.d.ts.map +1 -1
  243. package/dist/define/defineCapability.js +15 -0
  244. package/dist/define/defineCapability.js.map +1 -1
  245. package/dist/define/defineFlow.d.ts +3 -1
  246. package/dist/define/defineFlow.d.ts.map +1 -1
  247. package/dist/define/defineFlow.js.map +1 -1
  248. package/dist/durable/apply-ddl.d.ts +13 -0
  249. package/dist/durable/apply-ddl.d.ts.map +1 -0
  250. package/dist/durable/apply-ddl.js +278 -0
  251. package/dist/durable/apply-ddl.js.map +1 -0
  252. package/dist/durable/harness.d.ts +31 -0
  253. package/dist/durable/harness.d.ts.map +1 -0
  254. package/dist/durable/harness.js +136 -0
  255. package/dist/durable/harness.js.map +1 -0
  256. package/dist/durable/index.d.ts +18 -0
  257. package/dist/durable/index.d.ts.map +1 -0
  258. package/dist/durable/index.js +16 -0
  259. package/dist/durable/index.js.map +1 -0
  260. package/dist/durable/memory-store.d.ts +48 -0
  261. package/dist/durable/memory-store.d.ts.map +1 -0
  262. package/dist/durable/memory-store.js +260 -0
  263. package/dist/durable/memory-store.js.map +1 -0
  264. package/dist/durable/migrations-path.d.ts +5 -0
  265. package/dist/durable/migrations-path.d.ts.map +1 -0
  266. package/dist/durable/migrations-path.js +8 -0
  267. package/dist/durable/migrations-path.js.map +1 -0
  268. package/dist/durable/opaque-dispatch.d.ts +7 -0
  269. package/dist/durable/opaque-dispatch.d.ts.map +1 -0
  270. package/dist/durable/opaque-dispatch.js +67 -0
  271. package/dist/durable/opaque-dispatch.js.map +1 -0
  272. package/dist/durable/persist-before-ack.d.ts +52 -0
  273. package/dist/durable/persist-before-ack.d.ts.map +1 -0
  274. package/dist/durable/persist-before-ack.js +153 -0
  275. package/dist/durable/persist-before-ack.js.map +1 -0
  276. package/dist/durable/pg-env.d.ts +9 -0
  277. package/dist/durable/pg-env.d.ts.map +1 -0
  278. package/dist/durable/pg-env.js +20 -0
  279. package/dist/durable/pg-env.js.map +1 -0
  280. package/dist/durable/postgres-persist.d.ts +33 -0
  281. package/dist/durable/postgres-persist.d.ts.map +1 -0
  282. package/dist/durable/postgres-persist.js +239 -0
  283. package/dist/durable/postgres-persist.js.map +1 -0
  284. package/dist/durable/reconciliation.d.ts +20 -0
  285. package/dist/durable/reconciliation.d.ts.map +1 -0
  286. package/dist/durable/reconciliation.js +91 -0
  287. package/dist/durable/reconciliation.js.map +1 -0
  288. package/dist/durable/schema.d.ts +1448 -0
  289. package/dist/durable/schema.d.ts.map +1 -0
  290. package/dist/durable/schema.js +138 -0
  291. package/dist/durable/schema.js.map +1 -0
  292. package/dist/durable/spine-claim.d.ts +16 -0
  293. package/dist/durable/spine-claim.d.ts.map +1 -0
  294. package/dist/durable/spine-claim.js +131 -0
  295. package/dist/durable/spine-claim.js.map +1 -0
  296. package/dist/durable/types.d.ts +153 -0
  297. package/dist/durable/types.d.ts.map +1 -0
  298. package/dist/durable/types.js +114 -0
  299. package/dist/durable/types.js.map +1 -0
  300. package/dist/errors/data-errors.d.ts +6 -0
  301. package/dist/errors/data-errors.d.ts.map +1 -1
  302. package/dist/errors/data-errors.js +9 -0
  303. package/dist/errors/data-errors.js.map +1 -1
  304. package/dist/errors/http.d.ts.map +1 -1
  305. package/dist/errors/http.js +9 -1
  306. package/dist/errors/http.js.map +1 -1
  307. package/dist/errors/index.d.ts +4 -1
  308. package/dist/errors/index.d.ts.map +1 -1
  309. package/dist/errors/index.js +11 -1
  310. package/dist/errors/index.js.map +1 -1
  311. package/dist/errors-entry.d.ts +1 -1
  312. package/dist/errors-entry.d.ts.map +1 -1
  313. package/dist/errors-entry.js +1 -1
  314. package/dist/errors-entry.js.map +1 -1
  315. package/dist/events/consumer-registry.d.ts +6 -0
  316. package/dist/events/consumer-registry.d.ts.map +1 -1
  317. package/dist/events/consumer-registry.js.map +1 -1
  318. package/dist/events/dispatcher.d.ts +19 -0
  319. package/dist/events/dispatcher.d.ts.map +1 -1
  320. package/dist/events/dispatcher.js +161 -114
  321. package/dist/events/dispatcher.js.map +1 -1
  322. package/dist/events/idempotency.d.ts +3 -2
  323. package/dist/events/idempotency.d.ts.map +1 -1
  324. package/dist/events/idempotency.js +3 -2
  325. package/dist/events/idempotency.js.map +1 -1
  326. package/dist/events/index.d.ts +2 -0
  327. package/dist/events/index.d.ts.map +1 -1
  328. package/dist/events/index.js +1 -0
  329. package/dist/events/index.js.map +1 -1
  330. package/dist/events/subscription.d.ts +29 -0
  331. package/dist/events/subscription.d.ts.map +1 -0
  332. package/dist/events/subscription.js +19 -0
  333. package/dist/events/subscription.js.map +1 -0
  334. package/dist/events/worker.d.ts +8 -1
  335. package/dist/events/worker.d.ts.map +1 -1
  336. package/dist/events/worker.js +32 -5
  337. package/dist/events/worker.js.map +1 -1
  338. package/dist/execution/capability-executor.d.ts +6 -3
  339. package/dist/execution/capability-executor.d.ts.map +1 -1
  340. package/dist/execution/capability-executor.js +34 -6
  341. package/dist/execution/capability-executor.js.map +1 -1
  342. package/dist/execution/context-deps.d.ts +2 -0
  343. package/dist/execution/context-deps.d.ts.map +1 -1
  344. package/dist/execution/context-deps.js +2 -1
  345. package/dist/execution/context-deps.js.map +1 -1
  346. package/dist/execution/context-factory.d.ts +38 -1
  347. package/dist/execution/context-factory.d.ts.map +1 -1
  348. package/dist/execution/context-factory.js +102 -4
  349. package/dist/execution/context-factory.js.map +1 -1
  350. package/dist/execution/index.d.ts +2 -2
  351. package/dist/execution/index.d.ts.map +1 -1
  352. package/dist/execution/index.js +1 -1
  353. package/dist/execution/index.js.map +1 -1
  354. package/dist/execution/transactional-outbox.d.ts +10 -0
  355. package/dist/execution/transactional-outbox.d.ts.map +1 -1
  356. package/dist/execution/transactional-outbox.js +41 -2
  357. package/dist/execution/transactional-outbox.js.map +1 -1
  358. package/dist/flows/budget.d.ts +44 -0
  359. package/dist/flows/budget.d.ts.map +1 -0
  360. package/dist/flows/budget.js +81 -0
  361. package/dist/flows/budget.js.map +1 -0
  362. package/dist/flows/compile-flow.d.ts +23 -0
  363. package/dist/flows/compile-flow.d.ts.map +1 -0
  364. package/dist/flows/compile-flow.js +176 -0
  365. package/dist/flows/compile-flow.js.map +1 -0
  366. package/dist/flows/compiled-registry.d.ts +43 -0
  367. package/dist/flows/compiled-registry.d.ts.map +1 -0
  368. package/dist/flows/compiled-registry.js +198 -0
  369. package/dist/flows/compiled-registry.js.map +1 -0
  370. package/dist/flows/dead-letter.d.ts +16 -0
  371. package/dist/flows/dead-letter.d.ts.map +1 -1
  372. package/dist/flows/dead-letter.js +67 -0
  373. package/dist/flows/dead-letter.js.map +1 -1
  374. package/dist/flows/definition-strategy.d.ts +13 -0
  375. package/dist/flows/definition-strategy.d.ts.map +1 -0
  376. package/dist/flows/definition-strategy.js +30 -0
  377. package/dist/flows/definition-strategy.js.map +1 -0
  378. package/dist/flows/engine.d.ts +38 -1
  379. package/dist/flows/engine.d.ts.map +1 -1
  380. package/dist/flows/engine.js +375 -32
  381. package/dist/flows/engine.js.map +1 -1
  382. package/dist/flows/index.d.ts +12 -4
  383. package/dist/flows/index.d.ts.map +1 -1
  384. package/dist/flows/index.js +7 -2
  385. package/dist/flows/index.js.map +1 -1
  386. package/dist/flows/scheduler.d.ts +26 -0
  387. package/dist/flows/scheduler.d.ts.map +1 -1
  388. package/dist/flows/scheduler.js +157 -37
  389. package/dist/flows/scheduler.js.map +1 -1
  390. package/dist/flows/schema.d.ts +34 -0
  391. package/dist/flows/schema.d.ts.map +1 -1
  392. package/dist/flows/schema.js +4 -0
  393. package/dist/flows/schema.js.map +1 -1
  394. package/dist/flows/step-executor.d.ts +3 -0
  395. package/dist/flows/step-executor.d.ts.map +1 -1
  396. package/dist/flows/step-executor.js +33 -0
  397. package/dist/flows/step-executor.js.map +1 -1
  398. package/dist/governance/capability-source-scan.d.ts.map +1 -1
  399. package/dist/governance/capability-source-scan.js +2 -29
  400. package/dist/governance/capability-source-scan.js.map +1 -1
  401. package/dist/governance/forbidden-path-scan.d.ts +7 -0
  402. package/dist/governance/forbidden-path-scan.d.ts.map +1 -0
  403. package/dist/governance/forbidden-path-scan.js +60 -0
  404. package/dist/governance/forbidden-path-scan.js.map +1 -0
  405. package/dist/governance/index.d.ts +2 -0
  406. package/dist/governance/index.d.ts.map +1 -1
  407. package/dist/governance/index.js +2 -0
  408. package/dist/governance/index.js.map +1 -1
  409. package/dist/governance/source-walk.d.ts +3 -0
  410. package/dist/governance/source-walk.d.ts.map +1 -0
  411. package/dist/governance/source-walk.js +33 -0
  412. package/dist/governance/source-walk.js.map +1 -0
  413. package/dist/index.d.ts +21 -12
  414. package/dist/index.d.ts.map +1 -1
  415. package/dist/index.js +22 -9
  416. package/dist/index.js.map +1 -1
  417. package/dist/runtime/bootstrap.d.ts +3 -1
  418. package/dist/runtime/bootstrap.d.ts.map +1 -1
  419. package/dist/runtime/load-extensions.d.ts.map +1 -1
  420. package/dist/runtime/load-extensions.js +11 -0
  421. package/dist/runtime/load-extensions.js.map +1 -1
  422. package/dist/runtime/register-consumers.d.ts +3 -0
  423. package/dist/runtime/register-consumers.d.ts.map +1 -1
  424. package/dist/runtime/register-consumers.js +4 -0
  425. package/dist/runtime/register-consumers.js.map +1 -1
  426. package/dist/runtime/start-worker-pool.d.ts.map +1 -1
  427. package/dist/runtime/start-worker-pool.js +1 -0
  428. package/dist/runtime/start-worker-pool.js.map +1 -1
  429. package/dist/runtime-entry.d.ts +4 -0
  430. package/dist/runtime-entry.d.ts.map +1 -0
  431. package/dist/runtime-entry.js +30 -0
  432. package/dist/runtime-entry.js.map +1 -0
  433. package/dist/server/bootstrap.d.ts +116 -8
  434. package/dist/server/bootstrap.d.ts.map +1 -1
  435. package/dist/server/bootstrap.js +139 -62
  436. package/dist/server/bootstrap.js.map +1 -1
  437. package/dist/tenancy/data-plane-connection.d.ts +108 -0
  438. package/dist/tenancy/data-plane-connection.d.ts.map +1 -0
  439. package/dist/tenancy/data-plane-connection.js +320 -0
  440. package/dist/tenancy/data-plane-connection.js.map +1 -0
  441. package/dist/tenancy/data-plane-migrate.d.ts +30 -0
  442. package/dist/tenancy/data-plane-migrate.d.ts.map +1 -0
  443. package/dist/tenancy/data-plane-migrate.js +44 -0
  444. package/dist/tenancy/data-plane-migrate.js.map +1 -0
  445. package/dist/tenancy/data-plane-provisioning.d.ts +135 -0
  446. package/dist/tenancy/data-plane-provisioning.d.ts.map +1 -0
  447. package/dist/tenancy/data-plane-provisioning.js +639 -0
  448. package/dist/tenancy/data-plane-provisioning.js.map +1 -0
  449. package/dist/tenancy/data-plane-resolver.d.ts +37 -0
  450. package/dist/tenancy/data-plane-resolver.d.ts.map +1 -0
  451. package/dist/tenancy/data-plane-resolver.js +302 -0
  452. package/dist/tenancy/data-plane-resolver.js.map +1 -0
  453. package/dist/tenancy/index.d.ts +9 -0
  454. package/dist/tenancy/index.d.ts.map +1 -0
  455. package/dist/tenancy/index.js +22 -0
  456. package/dist/tenancy/index.js.map +1 -0
  457. package/dist/tenancy/types.d.ts +117 -0
  458. package/dist/tenancy/types.d.ts.map +1 -0
  459. package/dist/tenancy/types.js +8 -0
  460. package/dist/tenancy/types.js.map +1 -0
  461. package/dist/testing/context.d.ts.map +1 -1
  462. package/dist/testing/context.js +6 -1
  463. package/dist/testing/context.js.map +1 -1
  464. package/dist/testing/crash-matrix-simulation.d.ts +51 -0
  465. package/dist/testing/crash-matrix-simulation.d.ts.map +1 -0
  466. package/dist/testing/crash-matrix-simulation.js +350 -0
  467. package/dist/testing/crash-matrix-simulation.js.map +1 -0
  468. package/dist/testing/scaffolding.d.ts.map +1 -1
  469. package/dist/testing/scaffolding.js +4 -1
  470. package/dist/testing/scaffolding.js.map +1 -1
  471. package/dist/types/capability.d.ts +16 -0
  472. package/dist/types/capability.d.ts.map +1 -1
  473. package/dist/types/capability.js.map +1 -1
  474. package/dist/types/context.d.ts +62 -0
  475. package/dist/types/context.d.ts.map +1 -1
  476. package/dist/types/enums.d.ts +2 -0
  477. package/dist/types/enums.d.ts.map +1 -1
  478. package/dist/types/enums.js +2 -0
  479. package/dist/types/enums.js.map +1 -1
  480. package/dist/types/fields.d.ts +7 -0
  481. package/dist/types/fields.d.ts.map +1 -1
  482. package/dist/types/flow.d.ts +96 -1
  483. package/dist/types/flow.d.ts.map +1 -1
  484. package/dist/types/flow.js +7 -1
  485. package/dist/types/flow.js.map +1 -1
  486. package/dist/types/index.d.ts +9 -6
  487. package/dist/types/index.d.ts.map +1 -1
  488. package/dist/types/index.js +5 -1
  489. package/dist/types/index.js.map +1 -1
  490. package/dist/types/prompt.d.ts +8 -0
  491. package/dist/types/prompt.d.ts.map +1 -1
  492. package/dist/types/prompt.js +7 -0
  493. package/dist/types/prompt.js.map +1 -1
  494. package/dist/worker/bootstrap.d.ts +100 -1
  495. package/dist/worker/bootstrap.d.ts.map +1 -1
  496. package/dist/worker/bootstrap.js +155 -14
  497. package/dist/worker/bootstrap.js.map +1 -1
  498. package/instructions/api.md +21 -0
  499. package/instructions/cli.md +15 -9
  500. package/instructions/deployment.md +27 -25
  501. package/instructions/flows.md +8 -1
  502. package/instructions/framework.md +2 -1
  503. package/instructions/mcp.md +1 -1
  504. package/instructions/peer-dependencies.md +16 -14
  505. package/instructions/prompts.md +3 -0
  506. package/instructions/security.md +4 -0
  507. package/instructions/testing.md +2 -2
  508. package/migrations/durable-tenant/0000_durable_tenant.sql +92 -0
  509. package/migrations/durable-tenant/0001_human_task.sql +45 -0
  510. package/migrations/durable-tenant/meta/_journal.json +20 -0
  511. package/migrations/spine/0000_opaque_dispatch.sql +26 -0
  512. package/migrations/spine/meta/_journal.json +13 -0
  513. package/package.json +26 -8
@@ -0,0 +1,176 @@
1
+ /** Thrown when an in-flight idempotency claim is aborted (e.g. the first request failed). */
2
+ export class IdempotencyAbortedError extends Error {
3
+ constructor() {
4
+ super('Idempotency claim aborted');
5
+ this.name = 'IdempotencyAbortedError';
6
+ }
7
+ }
8
+ const TTL_MULTIPLIERS = {
9
+ s: 1_000,
10
+ m: 60_000,
11
+ h: 3_600_000,
12
+ d: 86_400_000,
13
+ };
14
+ /** Parse manifest idempotency TTL strings such as `24h` or `30m` into milliseconds. */
15
+ export function parseIdempotencyTtl(ttl) {
16
+ const match = /^(\d+)([smhd])$/.exec(ttl.trim());
17
+ if (match === null) {
18
+ return undefined;
19
+ }
20
+ const value = Number(match[1]);
21
+ const unit = match[2];
22
+ if (unit === undefined) {
23
+ return undefined;
24
+ }
25
+ const multiplier = TTL_MULTIPLIERS[unit];
26
+ if (multiplier === undefined) {
27
+ return undefined;
28
+ }
29
+ return value * multiplier;
30
+ }
31
+ export function buildIdempotencyStoreKey(operationId, principal, idempotencyKey) {
32
+ const tenantPart = principal.tenantId ?? '';
33
+ const userPart = principal.userId ?? '';
34
+ return `${operationId}:${tenantPart}:${userPart}:${idempotencyKey}`;
35
+ }
36
+ export function principalsMatch(a, b) {
37
+ return a.userId === b.userId && a.tenantId === b.tenantId;
38
+ }
39
+ /** True when neither userId nor tenantId is set (anonymous `::` principal). */
40
+ export function isAnonymousIdempotencyPrincipal(principal) {
41
+ return principal.userId === undefined && principal.tenantId === undefined;
42
+ }
43
+ /**
44
+ * How deep a payload may nest before fingerprinting refuses it.
45
+ *
46
+ * `canonicalize` recurses once per level, and a body that nests tens of thousands of levels
47
+ * (JSON.parse accepts it) took the whole request handler down with a `RangeError` — an
48
+ * unhandled 500 from a tiny payload. No real input comes close to this depth; a payload past
49
+ * it is refused as malformed rather than hashed.
50
+ */
51
+ export const MAX_PAYLOAD_DEPTH = 64;
52
+ /** Thrown by {@link hashPayload} when a payload nests deeper than {@link MAX_PAYLOAD_DEPTH}. */
53
+ export class PayloadTooDeepError extends Error {
54
+ constructor() {
55
+ super(`Payload nests deeper than ${MAX_PAYLOAD_DEPTH} levels`);
56
+ this.name = 'PayloadTooDeepError';
57
+ }
58
+ }
59
+ function canonicalize(value, depth) {
60
+ if (value === null || typeof value !== 'object') {
61
+ return value;
62
+ }
63
+ if (depth >= MAX_PAYLOAD_DEPTH) {
64
+ throw new PayloadTooDeepError();
65
+ }
66
+ if (Array.isArray(value)) {
67
+ return value.map((item) => canonicalize(item, depth + 1));
68
+ }
69
+ const obj = value;
70
+ const sorted = Object.keys(obj).sort();
71
+ const result = {};
72
+ for (const key of sorted) {
73
+ result[key] = canonicalize(obj[key], depth + 1);
74
+ }
75
+ return result;
76
+ }
77
+ /**
78
+ * Stable JSON serialization for idempotency payload hashing (sorted object keys).
79
+ *
80
+ * Throws {@link PayloadTooDeepError} for a payload nested past {@link MAX_PAYLOAD_DEPTH}; a
81
+ * route answers that as a `validation` refusal, never as an unhandled error.
82
+ */
83
+ export function hashPayload(payload) {
84
+ return JSON.stringify(canonicalize(payload, 0));
85
+ }
86
+ function isExpired(record) {
87
+ return record.expiresAt !== undefined && Date.now() > record.expiresAt;
88
+ }
89
+ function replayOrConflict(record, payloadHash, principal) {
90
+ if (!principalsMatch(record.principal, principal)) {
91
+ return { status: 'conflict', reason: 'principal' };
92
+ }
93
+ if (record.payloadHash !== payloadHash) {
94
+ return { status: 'conflict', reason: 'payload' };
95
+ }
96
+ return { status: 'replay', record };
97
+ }
98
+ function createInFlightEntry(payloadHash, principal) {
99
+ let resolve = () => { };
100
+ let reject = () => { };
101
+ const promise = new Promise((res, rej) => {
102
+ resolve = res;
103
+ reject = rej;
104
+ });
105
+ void promise.catch(() => {
106
+ /* absorb early abort before a route handler attaches its own catch */
107
+ });
108
+ return { payloadHash, principal, promise, resolve, reject };
109
+ }
110
+ function pruneExpiredEntries(store) {
111
+ const now = Date.now();
112
+ for (const [key, entry] of store) {
113
+ if ('result' in entry && entry.expiresAt !== undefined && now > entry.expiresAt) {
114
+ store.delete(key);
115
+ }
116
+ }
117
+ }
118
+ /**
119
+ * Dev-oriented in-memory idempotency store. Entries without TTL are never evicted;
120
+ * when `ttlMs` is supplied on complete, expired records are ignored on subsequent claims.
121
+ */
122
+ export function createInMemoryIdempotencyStore() {
123
+ const store = new Map();
124
+ return {
125
+ async claim(key, payloadHash, principal, options) {
126
+ if (options?.ttlMs !== undefined) {
127
+ pruneExpiredEntries(store);
128
+ }
129
+ const existing = store.get(key);
130
+ if (existing === undefined) {
131
+ store.set(key, createInFlightEntry(payloadHash, principal));
132
+ return { status: 'new' };
133
+ }
134
+ if ('result' in existing) {
135
+ if (isExpired(existing)) {
136
+ store.delete(key);
137
+ store.set(key, createInFlightEntry(payloadHash, principal));
138
+ return { status: 'new' };
139
+ }
140
+ return replayOrConflict(existing, payloadHash, principal);
141
+ }
142
+ if (!principalsMatch(existing.principal, principal)) {
143
+ return { status: 'conflict', reason: 'principal' };
144
+ }
145
+ if (existing.payloadHash !== payloadHash) {
146
+ return { status: 'conflict', reason: 'payload' };
147
+ }
148
+ return { status: 'in-flight', wait: existing.promise };
149
+ },
150
+ async complete(key, result, options) {
151
+ const existing = store.get(key);
152
+ if (existing === undefined || 'result' in existing) {
153
+ return;
154
+ }
155
+ const record = {
156
+ payloadHash: existing.payloadHash,
157
+ result,
158
+ principal: existing.principal,
159
+ expiresAt: options?.ttlMs !== undefined ? Date.now() + options.ttlMs : undefined,
160
+ };
161
+ store.set(key, record);
162
+ existing.resolve(record);
163
+ if (options?.ttlMs !== undefined) {
164
+ pruneExpiredEntries(store);
165
+ }
166
+ },
167
+ async abort(key) {
168
+ const existing = store.get(key);
169
+ if (existing !== undefined && !('result' in existing)) {
170
+ existing.reject(new IdempotencyAbortedError());
171
+ store.delete(key);
172
+ }
173
+ },
174
+ };
175
+ }
176
+ //# sourceMappingURL=idempotency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../../src/api/idempotency.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,8 @@
1
- export { isApiExposed } from './exposure.js';
1
+ export { capabilityHttpMethod, isApiExposed } from './exposure.js';
2
2
  export { authenticationFailureToHttp, buildAuthenticationRequest, } from './authentication-http.js';
3
3
  export { parseCookieHeader } from './cookies.js';
4
4
  export { LOCALE_COOKIE_NAME, registerAllRoutes, registerCapabilityRoute, registerStreamingRoute, resolveRequestLocale, } from './route-generator.js';
5
5
  export type { DependencyOptions, ResolveRequestLocaleOptions, RouteGeneratorConfig, } from './route-generator.js';
6
+ export { buildIdempotencyStoreKey, createInMemoryIdempotencyStore, hashPayload, IdempotencyAbortedError, isAnonymousIdempotencyPrincipal, MAX_PAYLOAD_DEPTH, parseIdempotencyTtl, PayloadTooDeepError, principalsMatch, } from './idempotency.js';
7
+ export type { IdempotencyClaimResult, IdempotencyPrincipal, IdempotencyRecord, IdempotencyStore, IdempotencyStoreOptions, } from './idempotency.js';
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,iBAAiB,EACjB,2BAA2B,EAC3B,oBAAoB,GACrB,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,iBAAiB,EACjB,2BAA2B,EAC3B,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,wBAAwB,EACxB,8BAA8B,EAC9B,WAAW,EACX,uBAAuB,EACvB,+BAA+B,EAC/B,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC"}
package/dist/api/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  // ── API Module ──
2
2
  // HTTP route generation: registers Fastify routes for capabilities.
3
- // Maps capability kind to HTTP method (query→GET, action/job→POST).
3
+ // Maps a capability to its HTTP method: the declared `api.method` where there is one,
4
+ // otherwise the kind's default (query→GET, action/job→POST).
4
5
  //
5
6
  // Key exports: registerAllRoutes, registerCapabilityRoute, isApiExposed
6
- export { isApiExposed } from './exposure.js';
7
+ export { capabilityHttpMethod, isApiExposed } from './exposure.js';
7
8
  export { authenticationFailureToHttp, buildAuthenticationRequest, } from './authentication-http.js';
8
9
  export { parseCookieHeader } from './cookies.js';
9
10
  export { LOCALE_COOKIE_NAME, registerAllRoutes, registerCapabilityRoute, registerStreamingRoute, resolveRequestLocale, } from './route-generator.js';
11
+ export { buildIdempotencyStoreKey, createInMemoryIdempotencyStore, hashPayload, IdempotencyAbortedError, isAnonymousIdempotencyPrincipal, MAX_PAYLOAD_DEPTH, parseIdempotencyTtl, PayloadTooDeepError, principalsMatch, } from './idempotency.js';
10
12
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,mBAAmB;AACnB,oEAAoE;AACpE,oEAAoE;AACpE,EAAE;AACF,wEAAwE;AAExE,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,mBAAmB;AACnB,oEAAoE;AACpE,sFAAsF;AACtF,6DAA6D;AAC7D,EAAE;AACF,wEAAwE;AAExE,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAM9B,OAAO,EACL,wBAAwB,EACxB,8BAA8B,EAC9B,WAAW,EACX,uBAAuB,EACvB,+BAA+B,EAC/B,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,GAChB,MAAM,kBAAkB,CAAC"}
@@ -3,6 +3,7 @@ import type { FastifyInstance } from 'fastify';
3
3
  import type { AuthAdapter } from '../auth/adapter.js';
4
4
  import type { RequestAuthenticator } from '../auth/http-authentication.js';
5
5
  import type { EventQueue } from '../events/queue.js';
6
+ import { type IdempotencyStore } from './idempotency.js';
6
7
  import type { ContextDependencies } from '../execution/context-factory.js';
7
8
  import type { CapabilityContract } from '../types/capability.js';
8
9
  import type { ExecutionContext } from '../types/context.js';
@@ -12,8 +13,29 @@ export interface DependencyOptions {
12
13
  /** Resolved locale for this request (from cookie / Accept-Language) */
13
14
  locale?: string;
14
15
  }
16
+ /**
17
+ * A request's dependencies together with the database they were wired against.
18
+ *
19
+ * The generator needs the database as well as the dependencies because two
20
+ * things happen outside the execution context — queue dispatch for `kind: 'job'`
21
+ * capabilities and the `onCapabilityError` hook — and both must address the same
22
+ * database the capability itself read and wrote. Returning them together makes
23
+ * that impossible to get wrong.
24
+ */
25
+ export interface ResolvedRequestDependencies {
26
+ /** Dependencies for this request. */
27
+ dependencies: ContextDependencies;
28
+ /** The database `dependencies` were wired against. */
29
+ db: PostgresJsDatabase;
30
+ }
15
31
  export interface RouteGeneratorConfig {
16
- /** Live Drizzle database connection used by the server. */
32
+ /**
33
+ * Live Drizzle database connection used by the server.
34
+ *
35
+ * This is the database every request uses unless `resolveDependencies` is
36
+ * supplied, in which case it is only the fallback for requests that never
37
+ * reach dependency resolution.
38
+ */
17
39
  db: PostgresJsDatabase;
18
40
  /** Auth adapter for extracting identity from requests */
19
41
  authAdapter: AuthAdapter;
@@ -21,8 +43,35 @@ export interface RouteGeneratorConfig {
21
43
  requestAuthenticator?: RequestAuthenticator;
22
44
  /** Factory to build base context dependencies for each request */
23
45
  createDependencies: (auth: NonNullable<Awaited<ReturnType<AuthAdapter['authenticate']>>>, options?: DependencyOptions) => ContextDependencies;
46
+ /**
47
+ * Optional async alternative to `createDependencies`, used for every request
48
+ * when present and ignored when absent.
49
+ *
50
+ * It exists for deployments that choose the request's database per request
51
+ * rather than per boot — a tenant's data plane resolved from its auth context.
52
+ * Leaving it unset keeps the synchronous `createDependencies` path exactly as
53
+ * it was, with no additional `await` between authentication and execution.
54
+ *
55
+ * Throwing rejects the request: the thrown value is mapped through the normal
56
+ * error-to-HTTP rules (a `PlumbusError` keeps its code and status, anything
57
+ * else becomes a generic 500), so a resolver that refuses an unknown or absent
58
+ * tenant fails closed instead of falling through to another tenant's data.
59
+ */
60
+ resolveDependencies?: (auth: NonNullable<Awaited<ReturnType<AuthAdapter['authenticate']>>>, options?: DependencyOptions) => Promise<ResolvedRequestDependencies>;
24
61
  /** Optional queue for dispatching async job capabilities */
25
62
  jobQueue?: EventQueue;
63
+ /**
64
+ * Where `api.idempotency` claims are kept for the routes this config registers.
65
+ *
66
+ * A capability that declares `api: { idempotency: { required: true, header } }` is served
67
+ * only with that header present; the same key with the same payload replays the stored
68
+ * answer, the same key with a different payload or principal is refused with a conflict,
69
+ * and a failed execution releases the key so the caller may retry. Left unset, every route
70
+ * registered with this config shares one in-memory store — correct within a process and
71
+ * lost on restart; a deployment that needs replay protection across instances supplies a
72
+ * shared store here.
73
+ */
74
+ idempotencyStore?: IdempotencyStore;
26
75
  /** Default locale when request headers do not resolve one */
27
76
  defaultLocale?: string;
28
77
  /** Locales supported by registered translation definitions */
@@ -41,11 +90,6 @@ export interface RouteGeneratorConfig {
41
90
  db?: PostgresJsDatabase;
42
91
  }) => void | Promise<void>;
43
92
  }
44
- /**
45
- * Register Fastify routes for a single capability.
46
- * HTTP method is derived from capability kind:
47
- * query → GET, action → POST, job → POST (async), eventHandler → skipped
48
- */
49
93
  export declare function registerCapabilityRoute(app: FastifyInstance, capability: CapabilityContract, config: RouteGeneratorConfig): void;
50
94
  /**
51
95
  * Register routes for multiple capabilities at once.
@@ -1 +1 @@
1
- {"version":3,"file":"route-generator.d.ts","sourceRoot":"","sources":["../../src/api/route-generator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"route-generator.d.ts","sourceRoot":"","sources":["../../src/api/route-generator.ts"],"names":[],"mappings":"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"}
@@ -1,22 +1,37 @@
1
1
  import { authenticationFailureToHttp, buildAuthenticationRequest } from './authentication-http.js';
2
- import { errorToHttpResponse, errorToSsePayload, unknownErrorToSsePayload, } from '../errors/http.js';
2
+ import { errorToHttpResponse, errorToSsePayload, GENERIC_INTERNAL_MESSAGE, unknownErrorToSsePayload, } from '../errors/http.js';
3
+ import { capabilityHttpMethod } from './exposure.js';
4
+ import { isPlumbusError } from '../errors/index.js';
3
5
  import { logHookError } from '../errors/hook-log.js';
4
6
  import { JobExecutionSource } from '../jobs/schema.js';
5
7
  import { dispatchQueuedJob } from '../jobs/dispatch.js';
6
8
  import { evaluateAccess } from '../execution/authorization.js';
7
9
  import { getCanonicalCapabilityName } from '../execution/canonical-name.js';
8
- import { executeCapability } from '../execution/capability-executor.js';
10
+ import { authorizeCapability, executeCapability } from '../execution/capability-executor.js';
11
+ import { buildIdempotencyStoreKey, createInMemoryIdempotencyStore, hashPayload, PayloadTooDeepError, IdempotencyAbortedError, isAnonymousIdempotencyPrincipal, parseIdempotencyTtl, } from './idempotency.js';
9
12
  import { createExecutionContext } from '../execution/context-factory.js';
10
13
  /**
11
14
  * Register Fastify routes for a single capability.
12
15
  * HTTP method is derived from capability kind:
13
16
  * query → GET, action → POST, job → POST (async), eventHandler → skipped
14
17
  */
18
+ /** One in-memory store per route configuration, for deployments that supplied none. */
19
+ const defaultIdempotencyStores = new WeakMap();
20
+ function idempotencyStoreOf(config) {
21
+ if (config.idempotencyStore)
22
+ return config.idempotencyStore;
23
+ let store = defaultIdempotencyStores.get(config);
24
+ if (!store) {
25
+ store = createInMemoryIdempotencyStore();
26
+ defaultIdempotencyStores.set(config, store);
27
+ }
28
+ return store;
29
+ }
15
30
  export function registerCapabilityRoute(app, capability, config) {
16
31
  // Event handlers are internal-only, no HTTP route
17
32
  if (capability.kind === 'eventHandler')
18
33
  return;
19
- const method = capability.kind === 'query' ? 'GET' : 'POST';
34
+ const method = capabilityHttpMethod(capability);
20
35
  const path = `/api/${capability.domain}/${toKebabCase(capability.name)}`;
21
36
  const handler = async (request, reply) => {
22
37
  const requestId = resolveRequestCorrelationId(request.headers);
@@ -78,7 +93,28 @@ export function registerCapabilityRoute(app, capability, config) {
78
93
  defaultLocale: config.defaultLocale ?? 'en',
79
94
  supportedLocales: config.supportedLocales ?? [config.defaultLocale ?? 'en'],
80
95
  });
81
- const deps = config.createDependencies(authContext, { bypassTenantScope, locale });
96
+ // The synchronous factory stays the only path when no resolver is supplied:
97
+ // nothing extra is awaited between authentication and execution.
98
+ let deps;
99
+ let requestDb = config.db;
100
+ if (config.resolveDependencies) {
101
+ try {
102
+ const resolved = await config.resolveDependencies(authContext, {
103
+ bypassTenantScope,
104
+ locale,
105
+ });
106
+ deps = resolved.dependencies;
107
+ requestDb = resolved.db;
108
+ }
109
+ catch (err) {
110
+ const failure = dependencyResolutionToHttp(err, requestId);
111
+ reply.status(failure.statusCode).send(failure.body);
112
+ return;
113
+ }
114
+ }
115
+ else {
116
+ deps = config.createDependencies(authContext, { bypassTenantScope, locale });
117
+ }
82
118
  deps.correlationId = resolveRequestCorrelationId(request.headers);
83
119
  deps.request = {
84
120
  sourceIp: request.ip,
@@ -105,7 +141,7 @@ export function registerCapabilityRoute(app, capability, config) {
105
141
  return reply.status(statusCode).send(body);
106
142
  }
107
143
  const jobId = await dispatchQueuedJob({
108
- db: config.db,
144
+ db: requestDb,
109
145
  jobQueue: config.jobQueue,
110
146
  capability,
111
147
  input: parsed.data,
@@ -115,6 +151,158 @@ export function registerCapabilityRoute(app, capability, config) {
115
151
  reply.status(202).send({ data: { jobId, status: 'accepted' } });
116
152
  return;
117
153
  }
154
+ // 4b. Declared idempotency (`api.idempotency.required`): the key is demanded, and the
155
+ // outcome of one execution is what every later request with that key receives.
156
+ const idempotency = capability.api?.idempotency;
157
+ // A caller the capability refuses is answered by the executor as always (403, audited)
158
+ // before any header is looked at, so authorization never leaks behind a validation answer.
159
+ const idempotencyApplies = idempotency?.required === true &&
160
+ method !== 'GET' &&
161
+ evaluateAccess(capability.access, ctx.auth).allowed;
162
+ if (idempotencyApplies && idempotency) {
163
+ const headerName = (idempotency.header ?? 'Idempotency-Key').toLowerCase();
164
+ const rawKey = request.headers[headerName];
165
+ const idempotencyKey = Array.isArray(rawKey) ? rawKey[0] : rawKey;
166
+ if (typeof idempotencyKey !== 'string' || idempotencyKey.length === 0) {
167
+ const err = ctx.errors.validation(`Missing required header: ${idempotency.header ?? 'Idempotency-Key'}`, { capability: canonicalName, reason: 'idempotency-key-required' });
168
+ const { statusCode, body } = errorToHttpResponse(err);
169
+ reply.status(statusCode).send(body);
170
+ return;
171
+ }
172
+ // Only a request that would execute claims a key: invalid input is answered by the
173
+ // executor as always, and leaves nothing behind for a retry to hit.
174
+ const parsed = capability.input.safeParse(input);
175
+ const principal = {
176
+ userId: ctx.auth.userId,
177
+ tenantId: ctx.auth.tenantId,
178
+ };
179
+ if (parsed.success && !isAnonymousIdempotencyPrincipal(principal)) {
180
+ const store = idempotencyStoreOf(config);
181
+ const operationId = capability.api?.operationId ?? canonicalName;
182
+ const storeKey = buildIdempotencyStoreKey(operationId, principal, idempotencyKey);
183
+ const authorityOf = (auth) => ({
184
+ roles: [...auth.roles].sort(),
185
+ scopes: [...auth.scopes].sort(),
186
+ });
187
+ // A role change can change the shape of a permitted result (e.g. staff vs student).
188
+ // Never reuse that result across different authority, even for the same person/input.
189
+ const authority = hashPayload(authorityOf(ctx.auth));
190
+ let payloadHash;
191
+ try {
192
+ payloadHash = hashPayload({ input: parsed.data, authority });
193
+ }
194
+ catch (hashErr) {
195
+ if (!(hashErr instanceof PayloadTooDeepError))
196
+ throw hashErr;
197
+ // A body nested past the fingerprint's depth cap is malformed input, answered like
198
+ // any other validation failure — not an unhandled RangeError from the hasher.
199
+ const err = ctx.errors.validation('Request body nests too deeply', {
200
+ capability: canonicalName,
201
+ reason: 'payload-too-deep',
202
+ });
203
+ const { statusCode, body } = errorToHttpResponse(err);
204
+ reply.status(statusCode).send(body);
205
+ return;
206
+ }
207
+ const ttlMs = idempotency.ttl !== undefined ? parseIdempotencyTtl(idempotency.ttl) : undefined;
208
+ const storeOptions = ttlMs !== undefined ? { ttlMs } : undefined;
209
+ // A cached result is still a disclosure. Refresh session/dependency authority, then
210
+ // run the capability's current resource checks without executing its handler again.
211
+ // In particular, the same check runs after an in-flight wait, not only before it.
212
+ const replay = async (result) => {
213
+ try {
214
+ const fresh = config.resolveDependencies
215
+ ? (await config.resolveDependencies(authContext, { bypassTenantScope, locale }))
216
+ .dependencies
217
+ : config.createDependencies(authContext, { bypassTenantScope, locale });
218
+ fresh.correlationId = deps.correlationId;
219
+ fresh.request = deps.request;
220
+ const current = createExecutionContext(fresh);
221
+ if (current.auth.userId !== principal.userId ||
222
+ current.auth.tenantId !== principal.tenantId) {
223
+ throw current.errors.forbidden('Idempotency replay principal changed');
224
+ }
225
+ if (hashPayload(authorityOf(current.auth)) !== authority) {
226
+ throw current.errors.forbidden('Idempotency replay authority changed');
227
+ }
228
+ const authorized = await authorizeCapability(capability, current, parsed.data);
229
+ if (!authorized.success) {
230
+ const failure = errorToHttpResponse(authorized.error);
231
+ reply.status(failure.statusCode).send(failure.body);
232
+ return;
233
+ }
234
+ reply.status(200).send({ data: result });
235
+ }
236
+ catch (error) {
237
+ const failure = dependencyResolutionToHttp(error, requestId);
238
+ reply.status(failure.statusCode).send(failure.body);
239
+ }
240
+ };
241
+ let claim = await store.claim(storeKey, payloadHash, principal, storeOptions);
242
+ for (;;) {
243
+ if (claim.status === 'replay') {
244
+ await replay(claim.record.result);
245
+ return;
246
+ }
247
+ if (claim.status === 'conflict') {
248
+ const err = ctx.errors.conflict(claim.reason === 'principal'
249
+ ? 'Idempotency key belongs to a different principal'
250
+ : 'Idempotency key reused with different input', { capability: canonicalName, reason: 'idempotency-conflict', conflict: claim.reason });
251
+ const { statusCode, body } = errorToHttpResponse(err);
252
+ reply.status(statusCode).send(body);
253
+ return;
254
+ }
255
+ if (claim.status === 'in-flight') {
256
+ try {
257
+ const record = await claim.wait;
258
+ await replay(record.result);
259
+ return;
260
+ }
261
+ catch (waitErr) {
262
+ if (!(waitErr instanceof IdempotencyAbortedError))
263
+ throw waitErr;
264
+ claim = await store.claim(storeKey, payloadHash, principal, storeOptions);
265
+ continue;
266
+ }
267
+ }
268
+ break;
269
+ }
270
+ let claimed;
271
+ try {
272
+ claimed = await executeCapability(capability, ctx, input);
273
+ }
274
+ catch (execErr) {
275
+ await store.abort(storeKey);
276
+ throw execErr;
277
+ }
278
+ if (claimed.success) {
279
+ await store.complete(storeKey, claimed.data, storeOptions);
280
+ reply.status(200).send({ data: claimed.data });
281
+ return;
282
+ }
283
+ await store.abort(storeKey);
284
+ const httpError = errorToHttpResponse(claimed.error);
285
+ reply.status(httpError.statusCode).send(httpError.body);
286
+ if (config.onCapabilityError) {
287
+ const failed = claimed.error;
288
+ void (async () => config.onCapabilityError?.({
289
+ capabilityName: capability.name,
290
+ domain: capability.domain,
291
+ errorCode: failed.code,
292
+ errorMessage: failed.message,
293
+ metadata: failed.metadata,
294
+ userId: ctx.auth.userId,
295
+ tenantId: ctx.auth.tenantId,
296
+ sourceIp: request.ip,
297
+ userAgent: request.headers['user-agent'],
298
+ db: requestDb,
299
+ }))().catch((hookErr) => {
300
+ logHookError('onCapabilityError', hookErr);
301
+ });
302
+ }
303
+ return;
304
+ }
305
+ }
118
306
  const result = await executeCapability(capability, ctx, input);
119
307
  if (result.success) {
120
308
  reply.status(200).send({ data: result.data });
@@ -135,18 +323,33 @@ export function registerCapabilityRoute(app, capability, config) {
135
323
  tenantId: ctx.auth.tenantId,
136
324
  sourceIp: request.ip,
137
325
  userAgent: request.headers['user-agent'],
138
- db: config.db,
326
+ db: requestDb,
139
327
  }))().catch((hookErr) => {
140
328
  logHookError('onCapabilityError', hookErr);
141
329
  });
142
330
  }
143
331
  }
144
332
  };
145
- if (method === 'GET') {
146
- app.get(path, handler);
147
- }
148
- else {
149
- app.post(path, handler);
333
+ /*
334
+ * Every method a capability may declare, not just the two the `kind` fallback produces. A
335
+ * declared `PATCH` that silently registered as `POST` is the same defect as a declared `POST`
336
+ * served as `GET`: the contract says one thing and the wire does another.
337
+ */
338
+ switch (method) {
339
+ case 'GET':
340
+ app.get(path, handler);
341
+ break;
342
+ case 'PATCH':
343
+ app.patch(path, handler);
344
+ break;
345
+ case 'PUT':
346
+ app.put(path, handler);
347
+ break;
348
+ case 'DELETE':
349
+ app.delete(path, handler);
350
+ break;
351
+ default:
352
+ app.post(path, handler);
150
353
  }
151
354
  }
152
355
  /**
@@ -226,7 +429,24 @@ export function registerStreamingRoute(app, capability, config, streamHandler) {
226
429
  defaultLocale: config.defaultLocale ?? 'en',
227
430
  supportedLocales: config.supportedLocales ?? [config.defaultLocale ?? 'en'],
228
431
  });
229
- const deps = config.createDependencies(authContext, { bypassTenantScope, locale });
432
+ let deps;
433
+ if (config.resolveDependencies) {
434
+ try {
435
+ const resolved = await config.resolveDependencies(authContext, {
436
+ bypassTenantScope,
437
+ locale,
438
+ });
439
+ deps = resolved.dependencies;
440
+ }
441
+ catch (err) {
442
+ const failure = dependencyResolutionToHttp(err, requestId);
443
+ reply.status(failure.statusCode).send(failure.body);
444
+ return;
445
+ }
446
+ }
447
+ else {
448
+ deps = config.createDependencies(authContext, { bypassTenantScope, locale });
449
+ }
230
450
  deps.correlationId = resolveRequestCorrelationId(request.headers);
231
451
  deps.request = {
232
452
  sourceIp: request.ip,
@@ -280,6 +500,28 @@ export function registerStreamingRoute(app, capability, config, streamHandler) {
280
500
  reply.raw.end();
281
501
  });
282
502
  }
503
+ /**
504
+ * Shape a failure from `resolveDependencies` into an HTTP response.
505
+ *
506
+ * A `PlumbusError` keeps its own code and status — a resolver that refuses an
507
+ * unknown tenant with `notFound`, or an untenanted request with `forbidden`,
508
+ * reaches the client as that. Anything else becomes a generic 500 so an
509
+ * infrastructure failure (a connection string, a routing table) is never echoed
510
+ * back to the caller. Streaming routes call this before any SSE header is
511
+ * written, so the refusal is a plain HTTP status there too.
512
+ */
513
+ function dependencyResolutionToHttp(err, requestId) {
514
+ const mapped = isPlumbusError(err)
515
+ ? errorToHttpResponse(err)
516
+ : {
517
+ statusCode: 500,
518
+ body: { error: { code: 'internal', message: GENERIC_INTERNAL_MESSAGE } },
519
+ };
520
+ return {
521
+ statusCode: mapped.statusCode,
522
+ body: { error: { ...mapped.body.error, ...(requestId ? { requestId } : {}) } },
523
+ };
524
+ }
283
525
  function resolveRequestCorrelationId(headers) {
284
526
  for (const key of ['x-correlation-id', 'x-request-id']) {
285
527
  const value = headers[key];