pipework 0.252.0 → 0.253.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 (695) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/infra/support/handle.d.ts +8 -1
  3. package/dist/infra/support/handle.d.ts.map +1 -1
  4. package/dist/infra/support/handle.js +9 -2
  5. package/dist/infra/support/handle.js.map +1 -1
  6. package/dist/infra/support/namespace.d.ts.map +1 -1
  7. package/package.json +4 -3
  8. package/src/async/jobs/abortable-delay.ts +18 -0
  9. package/src/async/jobs/claims.ts +52 -0
  10. package/src/async/jobs/cron.ts +179 -0
  11. package/src/async/jobs/errors.ts +56 -0
  12. package/src/async/jobs/exclusion.ts +117 -0
  13. package/src/async/jobs/execute.ts +219 -0
  14. package/src/async/jobs/index.ts +10 -0
  15. package/src/async/jobs/listener.ts +135 -0
  16. package/src/async/jobs/namespace.ts +20 -0
  17. package/src/async/jobs/queue-door.ts +44 -0
  18. package/src/async/jobs/queue.ts +1012 -0
  19. package/src/async/jobs/run-script.ts +67 -0
  20. package/src/async/jobs/scheduler.ts +218 -0
  21. package/src/async/jobs/schedules-table.ts +37 -0
  22. package/src/async/jobs/serialize-error.ts +48 -0
  23. package/src/async/jobs/table.ts +191 -0
  24. package/src/async/pipeline/define.ts +53 -0
  25. package/src/async/pipeline/errors.ts +35 -0
  26. package/src/async/pipeline/execute.ts +53 -0
  27. package/src/async/pipeline/index.ts +13 -0
  28. package/src/async/pipeline/namespace.ts +12 -0
  29. package/src/async/pipeline/resume.ts +67 -0
  30. package/src/async/pipeline/run-steps.ts +98 -0
  31. package/src/async/pipeline/types.ts +57 -0
  32. package/src/async/state-machine/define.ts +64 -0
  33. package/src/async/state-machine/errors.ts +29 -0
  34. package/src/async/state-machine/index.ts +3 -0
  35. package/src/async/state-machine/namespace.ts +12 -0
  36. package/src/async/state-machine/transition.ts +90 -0
  37. package/src/async/state-machine/types.ts +37 -0
  38. package/src/async/steps/errors.ts +31 -0
  39. package/src/async/steps/graph.ts +1214 -0
  40. package/src/async/steps/hash.ts +40 -0
  41. package/src/async/steps/index.ts +7 -0
  42. package/src/async/steps/namespace.ts +7 -0
  43. package/src/async/steps/table.ts +97 -0
  44. package/src/async/steps/types.ts +318 -0
  45. package/src/audit/chain/index.ts +9 -0
  46. package/src/audit/chain/install.ts +113 -0
  47. package/src/audit/chain/retire.ts +52 -0
  48. package/src/audit/chain/verify.ts +257 -0
  49. package/src/audit/query/cursor.ts +37 -0
  50. package/src/audit/query/index.ts +9 -0
  51. package/src/audit/query/query.ts +223 -0
  52. package/src/audit/schema/audit-record-fields.ts +68 -0
  53. package/src/audit/schema/audit-record.ts +23 -0
  54. package/src/auth/auth/audit.ts +31 -0
  55. package/src/auth/auth/chain.ts +84 -0
  56. package/src/auth/auth/cookie.ts +136 -0
  57. package/src/auth/auth/email-links.ts +311 -0
  58. package/src/auth/auth/guard.ts +128 -0
  59. package/src/auth/auth/index.ts +18 -0
  60. package/src/auth/auth/multi-org.ts +337 -0
  61. package/src/auth/auth/namespace.ts +34 -0
  62. package/src/auth/auth/parse-expiry.ts +13 -0
  63. package/src/auth/auth/passwords.ts +110 -0
  64. package/src/auth/auth/schema.ts +110 -0
  65. package/src/auth/auth/sessions.ts +565 -0
  66. package/src/auth/auth/sign-in.ts +156 -0
  67. package/src/auth/auth/signing-keys.ts +69 -0
  68. package/src/auth/auth/types.ts +26 -0
  69. package/src/auth/auth/uuid.ts +10 -0
  70. package/src/auth/rbac/cache.ts +55 -0
  71. package/src/auth/rbac/check.ts +30 -0
  72. package/src/auth/rbac/create.ts +35 -0
  73. package/src/auth/rbac/index.ts +5 -0
  74. package/src/auth/rbac/middleware.ts +20 -0
  75. package/src/auth/rbac/namespace.ts +15 -0
  76. package/src/auth/rbac/resolve.ts +31 -0
  77. package/src/auth/rbac/schema.sql +31 -0
  78. package/src/auth/rbac/seed.ts +67 -0
  79. package/src/auth/rbac/tables.ts +32 -0
  80. package/src/auth/rbac/types.ts +65 -0
  81. package/src/auth/tenant/extract.ts +33 -0
  82. package/src/auth/tenant/in-tenant.ts +49 -0
  83. package/src/auth/tenant/index.ts +7 -0
  84. package/src/auth/tenant/namespace.ts +23 -0
  85. package/src/auth/tenant/propagate.ts +20 -0
  86. package/src/auth/tenant/rls.ts +120 -0
  87. package/src/auth/tenant/scope.ts +33 -0
  88. package/src/auth/tenant/scoped-db.ts +581 -0
  89. package/src/auth/tenant/types.ts +14 -0
  90. package/src/auth/tenant/validate.ts +27 -0
  91. package/src/cli/commands/add.ts +158 -0
  92. package/src/cli/commands/alerts.ts +98 -0
  93. package/src/cli/commands/check.ts +1418 -0
  94. package/src/cli/commands/codemod.ts +78 -0
  95. package/src/cli/commands/db.ts +356 -0
  96. package/src/cli/commands/dev.ts +101 -0
  97. package/src/cli/commands/generate.ts +75 -0
  98. package/src/cli/commands/init.ts +36 -0
  99. package/src/cli/commands/install.ts +28 -0
  100. package/src/cli/commands/lint.ts +62 -0
  101. package/src/cli/commands/migrate.ts +132 -0
  102. package/src/cli/commands/openapi.ts +63 -0
  103. package/src/cli/commands/remove.ts +72 -0
  104. package/src/cli/commands/run.ts +50 -0
  105. package/src/cli/commands/scope.ts +54 -0
  106. package/src/cli/commands/serve.ts +68 -0
  107. package/src/cli/commands/shard.ts +31 -0
  108. package/src/cli/commands/shared/config-pipework-version.ts +28 -0
  109. package/src/cli/commands/shared/find-config.ts +22 -0
  110. package/src/cli/commands/shared/load-config.ts +35 -0
  111. package/src/cli/commands/shared/pnpm-install.ts +95 -0
  112. package/src/cli/commands/shared/report-removed.ts +8 -0
  113. package/src/cli/commands/sync.ts +32 -0
  114. package/src/cli/commands/test.ts +307 -0
  115. package/src/cli/commands/upgrade.ts +48 -0
  116. package/src/cli/dev-bootstrap.ts +24 -0
  117. package/src/cli/errors.ts +10 -0
  118. package/src/cli/index.ts +303 -0
  119. package/src/cli/output.ts +17 -0
  120. package/src/cli/port.ts +15 -0
  121. package/src/cli/surface-kinds.ts +20 -0
  122. package/src/client/index.ts +4 -0
  123. package/src/client/poll.ts +130 -0
  124. package/src/client/report-fault.ts +119 -0
  125. package/src/core/config/app-pool.ts +19 -0
  126. package/src/core/config/aws-env.ts +29 -0
  127. package/src/core/config/base-dir.ts +24 -0
  128. package/src/core/config/discover.ts +64 -0
  129. package/src/core/config/env-resolve.ts +87 -0
  130. package/src/core/config/env-types.ts +116 -0
  131. package/src/core/config/env.ts +55 -0
  132. package/src/core/config/errors.ts +9 -0
  133. package/src/core/config/import-config.ts +40 -0
  134. package/src/core/config/index.ts +8 -0
  135. package/src/core/config/load.ts +417 -0
  136. package/src/core/config/namespace.ts +14 -0
  137. package/src/core/config/resolve.ts +195 -0
  138. package/src/core/config/schema.ts +488 -0
  139. package/src/core/config/statement-observer.ts +48 -0
  140. package/src/core/config/ts-inline-hooks.ts +75 -0
  141. package/src/core/config/ts-register.ts +4 -0
  142. package/src/core/config/ts-resolve-hooks.ts +101 -0
  143. package/src/core/errors/base.ts +49 -0
  144. package/src/core/errors/http.ts +146 -0
  145. package/src/core/errors/index.ts +2 -0
  146. package/src/core/errors/namespace.ts +31 -0
  147. package/src/core/lifecycle/drain.ts +33 -0
  148. package/src/core/lifecycle/health.ts +72 -0
  149. package/src/core/lifecycle/index.ts +4 -0
  150. package/src/core/lifecycle/namespace.ts +7 -0
  151. package/src/core/lifecycle/startup.ts +102 -0
  152. package/src/core/lifecycle/types.ts +36 -0
  153. package/src/core/logging/context.ts +60 -0
  154. package/src/core/logging/error-chain.ts +68 -0
  155. package/src/core/logging/index.ts +5 -0
  156. package/src/core/logging/logger.ts +65 -0
  157. package/src/core/logging/namespace.ts +16 -0
  158. package/src/core/logging/proxy.ts +22 -0
  159. package/src/core/logging/redact-match.ts +24 -0
  160. package/src/core/logging/types.ts +47 -0
  161. package/src/core/pipework.ts +566 -0
  162. package/src/core/runtime/broker.ts +69 -0
  163. package/src/core/surface/http.ts +133 -0
  164. package/src/core/surface/index.ts +11 -0
  165. package/src/core/surface/job-timeout.ts +40 -0
  166. package/src/core/surface/namespace.ts +13 -0
  167. package/src/core/surface/script.ts +33 -0
  168. package/src/core/surface/start.ts +142 -0
  169. package/src/core/surface/types.ts +225 -0
  170. package/src/core/surface/validate.ts +88 -0
  171. package/src/core/surface/worker.ts +528 -0
  172. package/src/core/validation/connectivity.ts +49 -0
  173. package/src/core/validation/domain.ts +99 -0
  174. package/src/core/validation/index.ts +5 -0
  175. package/src/core/validation/queue-tables.ts +312 -0
  176. package/src/core/validation/rls-role.ts +138 -0
  177. package/src/core/validation/startup.ts +51 -0
  178. package/src/core/validation/surfaces.ts +65 -0
  179. package/src/core/validation/types.ts +25 -0
  180. package/src/data/db/advisory-lock.ts +55 -0
  181. package/src/data/db/aggregates.ts +172 -0
  182. package/src/data/db/array-elements.ts +111 -0
  183. package/src/data/db/bulk-set.ts +53 -0
  184. package/src/data/db/col/index.ts +14 -0
  185. package/src/data/db/composed.ts +53 -0
  186. package/src/data/db/db.ts +276 -0
  187. package/src/data/db/excluded.ts +16 -0
  188. package/src/data/db/expressions.ts +473 -0
  189. package/src/data/db/filter.ts +90 -0
  190. package/src/data/db/function-source.ts +114 -0
  191. package/src/data/db/identifiers.ts +16 -0
  192. package/src/data/db/idx/index.ts +9 -0
  193. package/src/data/db/in-list.ts +49 -0
  194. package/src/data/db/index.ts +9 -0
  195. package/src/data/db/insert-split.ts +137 -0
  196. package/src/data/db/instrumentation.ts +82 -0
  197. package/src/data/db/maintenance.ts +28 -0
  198. package/src/data/db/namespace.ts +107 -0
  199. package/src/data/db/owner-labels.ts +9 -0
  200. package/src/data/db/pool.ts +187 -0
  201. package/src/data/db/query-label.ts +13 -0
  202. package/src/data/db/query-watch.ts +116 -0
  203. package/src/data/db/raw-client-warning.ts +37 -0
  204. package/src/data/db/readonly.ts +23 -0
  205. package/src/data/db/reservation.ts +151 -0
  206. package/src/data/db/schema-features.ts +14 -0
  207. package/src/data/db/serializable.ts +83 -0
  208. package/src/data/db/sql.ts +1 -0
  209. package/src/data/db/timeouts.ts +76 -0
  210. package/src/data/db/types.ts +18 -0
  211. package/src/data/db/unbound.ts +33 -0
  212. package/src/data/db/values.ts +29 -0
  213. package/src/data/domain/brand.ts +15 -0
  214. package/src/data/domain/build-factory.ts +193 -0
  215. package/src/data/domain/build-schema.ts +161 -0
  216. package/src/data/domain/build-table.ts +355 -0
  217. package/src/data/domain/define.ts +239 -0
  218. package/src/data/domain/external-registry.ts +34 -0
  219. package/src/data/domain/external.ts +77 -0
  220. package/src/data/domain/field.ts +279 -0
  221. package/src/data/domain/index.ts +13 -0
  222. package/src/data/domain/naming.ts +6 -0
  223. package/src/data/domain/projections.ts +57 -0
  224. package/src/data/domain/trace-safe-projection.ts +31 -0
  225. package/src/data/domain/traits.ts +108 -0
  226. package/src/data/domain/types.ts +289 -0
  227. package/src/data/jsonb/composed.ts +18 -0
  228. package/src/data/jsonb/index.ts +1 -0
  229. package/src/data/jsonb/namespace.ts +30 -0
  230. package/src/data/jsonb/query.ts +86 -0
  231. package/src/data/migrate/append-only-generate.ts +47 -0
  232. package/src/data/migrate/applied.ts +47 -0
  233. package/src/data/migrate/apply.ts +309 -0
  234. package/src/data/migrate/check.ts +119 -0
  235. package/src/data/migrate/diff.ts +357 -0
  236. package/src/data/migrate/door-generate.ts +130 -0
  237. package/src/data/migrate/framework-rls.ts +58 -0
  238. package/src/data/migrate/generate.ts +201 -0
  239. package/src/data/migrate/history.ts +215 -0
  240. package/src/data/migrate/index.ts +9 -0
  241. package/src/data/migrate/internal-definitions.ts +175 -0
  242. package/src/data/migrate/post-process.ts +73 -0
  243. package/src/data/migrate/pragma.ts +73 -0
  244. package/src/data/migrate/provision-framework.ts +147 -0
  245. package/src/data/migrate/rls-generate.ts +61 -0
  246. package/src/data/migrate/run.ts +124 -0
  247. package/src/data/migrate/safety.ts +167 -0
  248. package/src/data/migrate/snapshot.ts +368 -0
  249. package/src/data/migrate/sql-emit.ts +392 -0
  250. package/src/data/migrate/statements.ts +312 -0
  251. package/src/data/query/alias.ts +126 -0
  252. package/src/data/query/batch.ts +8 -0
  253. package/src/data/query/cache/core/cache.ts +78 -0
  254. package/src/data/query/cache/core/index.ts +1 -0
  255. package/src/data/query/cache/core/types.ts +29 -0
  256. package/src/data/query/cache/readme.md +230 -0
  257. package/src/data/query/casing.ts +76 -0
  258. package/src/data/query/collation.ts +11 -0
  259. package/src/data/query/column-builder.ts +370 -0
  260. package/src/data/query/column.ts +150 -0
  261. package/src/data/query/entity.ts +42 -0
  262. package/src/data/query/errors.ts +33 -0
  263. package/src/data/query/index.ts +14 -0
  264. package/src/data/query/logger.ts +47 -0
  265. package/src/data/query/operations.ts +45 -0
  266. package/src/data/query/pg-core/alias.ts +12 -0
  267. package/src/data/query/pg-core/checks.ts +32 -0
  268. package/src/data/query/pg-core/columns/all.ts +71 -0
  269. package/src/data/query/pg-core/columns/bigint.ts +110 -0
  270. package/src/data/query/pg-core/columns/bigserial.ts +128 -0
  271. package/src/data/query/pg-core/columns/boolean.ts +43 -0
  272. package/src/data/query/pg-core/columns/char.ts +85 -0
  273. package/src/data/query/pg-core/columns/cidr.ts +43 -0
  274. package/src/data/query/pg-core/columns/common.ts +366 -0
  275. package/src/data/query/pg-core/columns/custom.ts +232 -0
  276. package/src/data/query/pg-core/columns/date.common.ts +15 -0
  277. package/src/data/query/pg-core/columns/date.ts +112 -0
  278. package/src/data/query/pg-core/columns/double-precision.ts +55 -0
  279. package/src/data/query/pg-core/columns/enum.ts +201 -0
  280. package/src/data/query/pg-core/columns/index.ts +35 -0
  281. package/src/data/query/pg-core/columns/inet.ts +43 -0
  282. package/src/data/query/pg-core/columns/int.common.ts +57 -0
  283. package/src/data/query/pg-core/columns/integer.ts +53 -0
  284. package/src/data/query/pg-core/columns/interval.ts +83 -0
  285. package/src/data/query/pg-core/columns/json.ts +58 -0
  286. package/src/data/query/pg-core/columns/jsonb.ts +56 -0
  287. package/src/data/query/pg-core/columns/line.ts +117 -0
  288. package/src/data/query/pg-core/columns/macaddr.ts +43 -0
  289. package/src/data/query/pg-core/columns/macaddr8.ts +43 -0
  290. package/src/data/query/pg-core/columns/numeric.ts +227 -0
  291. package/src/data/query/pg-core/columns/point.ts +127 -0
  292. package/src/data/query/pg-core/columns/postgis_extension/geometry.ts +119 -0
  293. package/src/data/query/pg-core/columns/postgis_extension/utils.ts +47 -0
  294. package/src/data/query/pg-core/columns/real.ts +58 -0
  295. package/src/data/query/pg-core/columns/serial.ts +55 -0
  296. package/src/data/query/pg-core/columns/smallint.ts +53 -0
  297. package/src/data/query/pg-core/columns/smallserial.ts +60 -0
  298. package/src/data/query/pg-core/columns/text.ts +67 -0
  299. package/src/data/query/pg-core/columns/time.ts +72 -0
  300. package/src/data/query/pg-core/columns/timestamp.ts +163 -0
  301. package/src/data/query/pg-core/columns/uuid.ts +51 -0
  302. package/src/data/query/pg-core/columns/varchar.ts +89 -0
  303. package/src/data/query/pg-core/columns/vector_extension/bit.ts +68 -0
  304. package/src/data/query/pg-core/columns/vector_extension/halfvec.ts +80 -0
  305. package/src/data/query/pg-core/columns/vector_extension/sparsevec.ts +67 -0
  306. package/src/data/query/pg-core/columns/vector_extension/vector.ts +80 -0
  307. package/src/data/query/pg-core/db.ts +674 -0
  308. package/src/data/query/pg-core/dialect.ts +1404 -0
  309. package/src/data/query/pg-core/expressions.ts +25 -0
  310. package/src/data/query/pg-core/foreign-keys.ts +130 -0
  311. package/src/data/query/pg-core/index.ts +21 -0
  312. package/src/data/query/pg-core/indexes.ts +276 -0
  313. package/src/data/query/pg-core/policies.ts +56 -0
  314. package/src/data/query/pg-core/primary-keys.ts +62 -0
  315. package/src/data/query/pg-core/query-builders/count.ts +86 -0
  316. package/src/data/query/pg-core/query-builders/delete.ts +293 -0
  317. package/src/data/query/pg-core/query-builders/index.ts +7 -0
  318. package/src/data/query/pg-core/query-builders/insert.ts +452 -0
  319. package/src/data/query/pg-core/query-builders/query-builder.ts +123 -0
  320. package/src/data/query/pg-core/query-builders/query.ts +157 -0
  321. package/src/data/query/pg-core/query-builders/raw.ts +49 -0
  322. package/src/data/query/pg-core/query-builders/refresh-materialized-view.ts +108 -0
  323. package/src/data/query/pg-core/query-builders/select.ts +1346 -0
  324. package/src/data/query/pg-core/query-builders/select.types.ts +437 -0
  325. package/src/data/query/pg-core/query-builders/update.ts +626 -0
  326. package/src/data/query/pg-core/roles.ts +41 -0
  327. package/src/data/query/pg-core/schema.ts +72 -0
  328. package/src/data/query/pg-core/sequence.ts +41 -0
  329. package/src/data/query/pg-core/session.ts +292 -0
  330. package/src/data/query/pg-core/subquery.ts +46 -0
  331. package/src/data/query/pg-core/table.ts +252 -0
  332. package/src/data/query/pg-core/unique-constraint.ts +73 -0
  333. package/src/data/query/pg-core/utils/array.ts +95 -0
  334. package/src/data/query/pg-core/utils/index.ts +1 -0
  335. package/src/data/query/pg-core/utils.ts +104 -0
  336. package/src/data/query/pg-core/view-base.ts +14 -0
  337. package/src/data/query/pg-core/view-common.ts +1 -0
  338. package/src/data/query/pg-core/view.ts +435 -0
  339. package/src/data/query/pg-core/with-builder.ts +41 -0
  340. package/src/data/query/postgres-js/README.md +46 -0
  341. package/src/data/query/postgres-js/array-parser.ts +45 -0
  342. package/src/data/query/postgres-js/driver-error-boundary.ts +149 -0
  343. package/src/data/query/postgres-js/driver.ts +163 -0
  344. package/src/data/query/postgres-js/index.ts +2 -0
  345. package/src/data/query/postgres-js/session.ts +234 -0
  346. package/src/data/query/primary-key.ts +11 -0
  347. package/src/data/query/query-builders/query-builder.ts +19 -0
  348. package/src/data/query/query-builders/select.types.ts +175 -0
  349. package/src/data/query/query-promise.ts +35 -0
  350. package/src/data/query/relations.ts +725 -0
  351. package/src/data/query/runnable-query.ts +12 -0
  352. package/src/data/query/selection-proxy.ts +121 -0
  353. package/src/data/query/session.ts +8 -0
  354. package/src/data/query/sql/expressions/conditions.ts +741 -0
  355. package/src/data/query/sql/expressions/index.ts +2 -0
  356. package/src/data/query/sql/expressions/select.ts +45 -0
  357. package/src/data/query/sql/functions/aggregate.ts +129 -0
  358. package/src/data/query/sql/functions/index.ts +2 -0
  359. package/src/data/query/sql/functions/vector.ts +178 -0
  360. package/src/data/query/sql/index.ts +3 -0
  361. package/src/data/query/sql/sql.ts +794 -0
  362. package/src/data/query/subquery.ts +53 -0
  363. package/src/data/query/table.ts +208 -0
  364. package/src/data/query/table.utils.ts +2 -0
  365. package/src/data/query/tracing-utils.ts +3 -0
  366. package/src/data/query/tracing.ts +11 -0
  367. package/src/data/query/utils.ts +357 -0
  368. package/src/data/query/version.ts +4 -0
  369. package/src/data/query/view-common.ts +1 -0
  370. package/src/data/schema/branded.ts +13 -0
  371. package/src/data/schema/generated/coerce.ts +5 -0
  372. package/src/data/schema/generated/combinator.ts +6 -0
  373. package/src/data/schema/generated/encoding.ts +5 -0
  374. package/src/data/schema/generated/error.ts +6 -0
  375. package/src/data/schema/generated/format.ts +5 -0
  376. package/src/data/schema/generated/type-constructor.ts +10 -0
  377. package/src/data/schema/generated/type.ts +6 -0
  378. package/src/data/schema/index.ts +6 -0
  379. package/src/data/schema/json-schema.ts +33 -0
  380. package/src/data/schema/namespace.ts +55 -0
  381. package/src/data/schema/parse.ts +39 -0
  382. package/src/data/schema/types.ts +39 -0
  383. package/src/data/temporal/append-only.ts +134 -0
  384. package/src/data/temporal/close.ts +27 -0
  385. package/src/data/temporal/columns.ts +10 -0
  386. package/src/data/temporal/definition-queries.ts +70 -0
  387. package/src/data/temporal/errors.ts +10 -0
  388. package/src/data/temporal/get-current.ts +22 -0
  389. package/src/data/temporal/index.ts +9 -0
  390. package/src/data/temporal/namespace.ts +36 -0
  391. package/src/data/temporal/query.ts +25 -0
  392. package/src/data/temporal/resolve.ts +17 -0
  393. package/src/data/temporal/revise.ts +67 -0
  394. package/src/data/temporal/types.ts +22 -0
  395. package/src/data/valve/artifact.ts +33 -0
  396. package/src/data/valve/contract-diff.ts +21 -0
  397. package/src/data/valve/cross.ts +57 -0
  398. package/src/data/valve/crossing-ledger.ts +50 -0
  399. package/src/data/valve/decision.ts +44 -0
  400. package/src/data/valve/durable-crossing.ts +52 -0
  401. package/src/data/valve/execute-crossing.ts +77 -0
  402. package/src/data/valve/faucet.ts +56 -0
  403. package/src/data/valve/gate.ts +114 -0
  404. package/src/data/valve/graph.ts +77 -0
  405. package/src/data/valve/index.ts +53 -0
  406. package/src/data/valve/namespace.ts +57 -0
  407. package/src/data/valve/registry.ts +50 -0
  408. package/src/data/valve/schema/valve-crossing-fields.ts +23 -0
  409. package/src/data/valve/schema/valve-crossing.ts +9 -0
  410. package/src/data/valve/supply.ts +154 -0
  411. package/src/data/valve/types.ts +143 -0
  412. package/src/data/vector/columns.ts +27 -0
  413. package/src/data/vector/index.ts +4 -0
  414. package/src/data/vector/namespace.ts +33 -0
  415. package/src/data/vector/query.ts +85 -0
  416. package/src/data/vector/validate.ts +16 -0
  417. package/src/data/write/audit/actor.ts +15 -0
  418. package/src/data/write/audit/audit-columns.ts +18 -0
  419. package/src/data/write/audit/audit-select.ts +111 -0
  420. package/src/data/write/audit/bound-transaction.ts +14 -0
  421. package/src/data/write/audit/cte-compile.ts +32 -0
  422. package/src/data/write/audit/decode-returning.ts +47 -0
  423. package/src/data/write/audit/final-select.ts +43 -0
  424. package/src/data/write/audit/infra-audit.ts +36 -0
  425. package/src/data/write/audit/primary-key.ts +65 -0
  426. package/src/data/write/audit/projection.ts +41 -0
  427. package/src/data/write/audit/read-touches.ts +86 -0
  428. package/src/data/write/audit/touch-match.ts +78 -0
  429. package/src/data/write/audit/verb-delete.ts +62 -0
  430. package/src/data/write/audit/verb-insert.ts +36 -0
  431. package/src/data/write/audit/verb-select.ts +47 -0
  432. package/src/data/write/audit/verb-update.ts +60 -0
  433. package/src/data/write/audit/verb-upsert.ts +104 -0
  434. package/src/data/write/conflict.ts +43 -0
  435. package/src/data/write/delete.ts +38 -0
  436. package/src/data/write/execute.ts +243 -0
  437. package/src/data/write/index.ts +11 -0
  438. package/src/data/write/insert.ts +63 -0
  439. package/src/data/write/state.ts +43 -0
  440. package/src/data/write/update.ts +54 -0
  441. package/src/data/write/upsert.ts +58 -0
  442. package/src/data/write/values.ts +19 -0
  443. package/src/index.ts +230 -0
  444. package/src/infra/access-links/code.ts +70 -0
  445. package/src/infra/access-links/handle.ts +892 -0
  446. package/src/infra/access-links/index.ts +9 -0
  447. package/src/infra/access-links/namespace.ts +30 -0
  448. package/src/infra/access-links/person-check.ts +177 -0
  449. package/src/infra/access-links/schema.ts +71 -0
  450. package/src/infra/alerts/async-sources.ts +231 -0
  451. package/src/infra/alerts/audit.ts +36 -0
  452. package/src/infra/alerts/beat.ts +123 -0
  453. package/src/infra/alerts/client-faults.ts +111 -0
  454. package/src/infra/alerts/faults.ts +156 -0
  455. package/src/infra/alerts/handle.ts +778 -0
  456. package/src/infra/alerts/http.ts +192 -0
  457. package/src/infra/alerts/index.ts +12 -0
  458. package/src/infra/alerts/infra-sources.ts +180 -0
  459. package/src/infra/alerts/namespace.ts +81 -0
  460. package/src/infra/alerts/redact.ts +21 -0
  461. package/src/infra/alerts/request-faults.ts +41 -0
  462. package/src/infra/alerts/schema.ts +124 -0
  463. package/src/infra/alerts/sinks.ts +125 -0
  464. package/src/infra/alerts/source-options.ts +26 -0
  465. package/src/infra/alerts/sources.ts +503 -0
  466. package/src/infra/alerts/subsystem-sources.ts +137 -0
  467. package/src/infra/audit/emit.ts +87 -0
  468. package/src/infra/audit/index.ts +2 -0
  469. package/src/infra/audit/namespace.ts +22 -0
  470. package/src/infra/audit/schema.sql +35 -0
  471. package/src/infra/audit/types.ts +18 -0
  472. package/src/infra/aws/credentials.ts +147 -0
  473. package/src/infra/aws/sigv4.ts +140 -0
  474. package/src/infra/cache/create.ts +58 -0
  475. package/src/infra/cache/entry.ts +81 -0
  476. package/src/infra/cache/index.ts +4 -0
  477. package/src/infra/cache/namespace.ts +10 -0
  478. package/src/infra/cache/tenant.ts +93 -0
  479. package/src/infra/cache/types.ts +24 -0
  480. package/src/infra/crawl/audit.ts +35 -0
  481. package/src/infra/crawl/errors.ts +25 -0
  482. package/src/infra/crawl/handle.ts +693 -0
  483. package/src/infra/crawl/index.ts +9 -0
  484. package/src/infra/crawl/links.ts +160 -0
  485. package/src/infra/crawl/namespace.ts +36 -0
  486. package/src/infra/crawl/robots.ts +100 -0
  487. package/src/infra/crawl/schema.ts +89 -0
  488. package/src/infra/credit-line/handle.ts +883 -0
  489. package/src/infra/credit-line/index.ts +6 -0
  490. package/src/infra/credit-line/namespace.ts +7 -0
  491. package/src/infra/credit-line/schema.ts +96 -0
  492. package/src/infra/credits/handle.ts +706 -0
  493. package/src/infra/credits/index.ts +5 -0
  494. package/src/infra/credits/namespace.ts +7 -0
  495. package/src/infra/credits/schema.ts +83 -0
  496. package/src/infra/email/audit.ts +36 -0
  497. package/src/infra/email/inbound.ts +622 -0
  498. package/src/infra/email/index.ts +11 -0
  499. package/src/infra/email/mail.ts +517 -0
  500. package/src/infra/email/mime.ts +447 -0
  501. package/src/infra/email/namespace.ts +22 -0
  502. package/src/infra/email/providers.ts +111 -0
  503. package/src/infra/email/schema.ts +96 -0
  504. package/src/infra/email/sns.ts +200 -0
  505. package/src/infra/email/unsubscribe.ts +44 -0
  506. package/src/infra/fetch/address.ts +88 -0
  507. package/src/infra/fetch/connect-retry.ts +87 -0
  508. package/src/infra/fetch/handle.ts +329 -0
  509. package/src/infra/fetch/index.ts +7 -0
  510. package/src/infra/fetch/namespace.ts +70 -0
  511. package/src/infra/idempotency/index.ts +3 -0
  512. package/src/infra/idempotency/middleware.ts +87 -0
  513. package/src/infra/idempotency/store.ts +92 -0
  514. package/src/infra/idempotency/types.ts +30 -0
  515. package/src/infra/join-requests/handle.ts +312 -0
  516. package/src/infra/join-requests/index.ts +2 -0
  517. package/src/infra/join-requests/namespace.ts +7 -0
  518. package/src/infra/join-requests/schema.ts +33 -0
  519. package/src/infra/outbox/core.ts +165 -0
  520. package/src/infra/quota/errors.ts +32 -0
  521. package/src/infra/quota/handle.ts +215 -0
  522. package/src/infra/quota/index.ts +5 -0
  523. package/src/infra/quota/namespace.ts +7 -0
  524. package/src/infra/quota/schema.ts +15 -0
  525. package/src/infra/storage/backend.ts +34 -0
  526. package/src/infra/storage/handle.ts +513 -0
  527. package/src/infra/storage/index.ts +9 -0
  528. package/src/infra/storage/local.ts +104 -0
  529. package/src/infra/storage/namespace.ts +13 -0
  530. package/src/infra/storage/s3.ts +257 -0
  531. package/src/infra/storage/schema.ts +24 -0
  532. package/src/infra/support/handle.ts +869 -0
  533. package/src/infra/support/http.ts +229 -0
  534. package/src/infra/support/index.ts +7 -0
  535. package/src/infra/support/namespace.ts +12 -0
  536. package/src/infra/support/schema.ts +96 -0
  537. package/src/infra/webhook/inbound.ts +45 -0
  538. package/src/infra/webhook/index.ts +5 -0
  539. package/src/infra/webhook/namespace.ts +24 -0
  540. package/src/infra/webhook/outbound.ts +315 -0
  541. package/src/infra/webhook/sign.ts +32 -0
  542. package/src/infra/webhook/verify.ts +82 -0
  543. package/src/invariants/assert.ts +26 -0
  544. package/src/invariants/index.ts +1 -0
  545. package/src/lint/config.ts +56 -0
  546. package/src/lint/doctrine.ts +163 -0
  547. package/src/lint/include.ts +97 -0
  548. package/src/lint/index.ts +3 -0
  549. package/src/lint/preset.ts +10 -0
  550. package/src/lint/resolver.ts +218 -0
  551. package/src/lint/rules/no-reexports.ts +46 -0
  552. package/src/request/behavior/audited.ts +85 -0
  553. package/src/request/behavior/cached.ts +62 -0
  554. package/src/request/behavior/compose.ts +28 -0
  555. package/src/request/behavior/index.ts +5 -0
  556. package/src/request/behavior/namespace.ts +16 -0
  557. package/src/request/behavior/types.ts +26 -0
  558. package/src/request/behavior/versioned.ts +54 -0
  559. package/src/request/context/create.ts +103 -0
  560. package/src/request/context/guard.ts +49 -0
  561. package/src/request/context/index.ts +4 -0
  562. package/src/request/context/run-in-transaction.ts +200 -0
  563. package/src/request/context/store.ts +28 -0
  564. package/src/request/context/types.ts +50 -0
  565. package/src/request/di/builder.ts +535 -0
  566. package/src/request/di/index.ts +8 -0
  567. package/src/request/di/resolve.ts +271 -0
  568. package/src/request/di/types.ts +262 -0
  569. package/src/request/fitting/namespace.ts +7 -0
  570. package/src/request/fixture/batch.ts +71 -0
  571. package/src/request/fixture/builder.ts +26 -0
  572. package/src/request/fixture/errors.ts +10 -0
  573. package/src/request/fixture/index.ts +8 -0
  574. package/src/request/fixture/namespace.ts +23 -0
  575. package/src/request/fixture/paginate.ts +44 -0
  576. package/src/request/fixture/to-handlers.ts +260 -0
  577. package/src/request/fixture/types.ts +79 -0
  578. package/src/request/flow/act-as.ts +78 -0
  579. package/src/request/flow/namespace.ts +17 -0
  580. package/src/request/http/error-step.ts +42 -0
  581. package/src/request/http/file.ts +141 -0
  582. package/src/request/http/index.ts +4 -0
  583. package/src/request/http/middleware.ts +141 -0
  584. package/src/request/http/namespace.ts +5 -0
  585. package/src/request/http/rate-limit-classes.ts +170 -0
  586. package/src/request/http/response.ts +82 -0
  587. package/src/request/http/server.ts +916 -0
  588. package/src/request/http/sniff.ts +261 -0
  589. package/src/request/http/types.ts +250 -0
  590. package/src/request/http/upload.ts +284 -0
  591. package/src/request/internal/text.ts +6 -0
  592. package/src/request/openapi/generate.ts +205 -0
  593. package/src/request/openapi/index.ts +2 -0
  594. package/src/request/openapi/namespace.ts +7 -0
  595. package/src/test/auth.ts +13 -0
  596. package/src/test/auto-setup.ts +45 -0
  597. package/src/test/context.ts +91 -0
  598. package/src/test/crawl-fixture.ts +157 -0
  599. package/src/test/discover-fork.ts +17 -0
  600. package/src/test/fingerprint.ts +135 -0
  601. package/src/test/globals.d.ts +7 -0
  602. package/src/test/index.ts +19 -0
  603. package/src/test/namespace.ts +87 -0
  604. package/src/test/plugin.ts +189 -0
  605. package/src/test/progress.ts +185 -0
  606. package/src/test/reap-after-run.ts +28 -0
  607. package/src/test/reap.ts +286 -0
  608. package/src/test/reset.ts +253 -0
  609. package/src/test/savepoints.ts +172 -0
  610. package/src/test/setup.ts +1422 -0
  611. package/src/test/system-channel.ts +50 -0
  612. package/src/test/vitest-plugin.ts +142 -0
  613. package/src/test/vitest-workspace.ts +507 -0
  614. package/src/test/vitest.ts +333 -0
  615. package/src/test/watch.ts +105 -0
  616. package/src/trace/capture/bounded-serializer.ts +146 -0
  617. package/src/trace/cardinality/analyze.ts +173 -0
  618. package/src/trace/cardinality/branch-context.ts +214 -0
  619. package/src/trace/cardinality/check.ts +33 -0
  620. package/src/trace/cardinality/classify-edge.ts +64 -0
  621. package/src/trace/cardinality/structural-max.ts +107 -0
  622. package/src/trace/check/coverage-check.ts +188 -0
  623. package/src/trace/check/flow-check.ts +103 -0
  624. package/src/trace/check/flow-step-site.ts +234 -0
  625. package/src/trace/check/io-pattern.ts +116 -0
  626. package/src/trace/codemod/add-cardinality.ts +152 -0
  627. package/src/trace/codemod/run.ts +80 -0
  628. package/src/trace/entry/child-trace.ts +21 -0
  629. package/src/trace/entry/close-trace.ts +87 -0
  630. package/src/trace/entry/finalize-trace.ts +28 -0
  631. package/src/trace/entry/open-trace.ts +138 -0
  632. package/src/trace/entry/parent-trace.ts +20 -0
  633. package/src/trace/entry/parse-traceparent.ts +39 -0
  634. package/src/trace/entry/process-start.ts +38 -0
  635. package/src/trace/entry/trace-context.ts +26 -0
  636. package/src/trace/entry/tracing-enabled.ts +10 -0
  637. package/src/trace/external/record.ts +195 -0
  638. package/src/trace/external/report.ts +66 -0
  639. package/src/trace/namespace.ts +19 -0
  640. package/src/trace/partition/day.ts +47 -0
  641. package/src/trace/partition/index.ts +14 -0
  642. package/src/trace/partition/partition-name.ts +23 -0
  643. package/src/trace/partition/plan.ts +82 -0
  644. package/src/trace/partition/schedule.ts +26 -0
  645. package/src/trace/partition/startup-hook.ts +39 -0
  646. package/src/trace/partition/sync.ts +154 -0
  647. package/src/trace/partition/tables.ts +53 -0
  648. package/src/trace/read/tree.ts +83 -0
  649. package/src/trace/retain/copy-on-error.ts +76 -0
  650. package/src/trace/runtime/expire-stale-traces.ts +54 -0
  651. package/src/trace/runtime/flow-step.ts +147 -0
  652. package/src/trace/runtime/flush-steps.ts +31 -0
  653. package/src/trace/runtime/pending-writes.ts +54 -0
  654. package/src/trace/runtime/step-buffer.ts +298 -0
  655. package/src/trace/runtime/step-error.ts +21 -0
  656. package/src/trace/runtime/step-meta.ts +55 -0
  657. package/src/trace/runtime/thenable.ts +12 -0
  658. package/src/trace/runtime/truncate-trace.ts +62 -0
  659. package/src/trace/schema/trace-fields.ts +52 -0
  660. package/src/trace/schema/trace-retained.ts +22 -0
  661. package/src/trace/schema/trace-step-fields.ts +44 -0
  662. package/src/trace/schema/trace-step-retained.ts +25 -0
  663. package/src/trace/schema/trace-step.ts +18 -0
  664. package/src/trace/schema/trace.ts +12 -0
  665. package/src/trace/transformer/apply.ts +79 -0
  666. package/src/trace/transformer/build-meta-literal.ts +30 -0
  667. package/src/trace/transformer/find-flow-step-sites.ts +85 -0
  668. package/src/trace/transformer/inject-flow-step-import.ts +39 -0
  669. package/src/trace/transformer/transform-error.ts +14 -0
  670. package/src/trace/transformer/transform-flow-steps.ts +123 -0
  671. package/src/trace/transformer/wrap-function-declaration.ts +62 -0
  672. package/src/trace/transformer/wrap-variable-declaration.ts +67 -0
  673. package/src/workspace/affected.ts +110 -0
  674. package/src/workspace/changed-files.ts +163 -0
  675. package/src/workspace/check-deps.ts +213 -0
  676. package/src/workspace/checksum.ts +20 -0
  677. package/src/workspace/dependency-graph.ts +67 -0
  678. package/src/workspace/detect-pipework-copies.ts +93 -0
  679. package/src/workspace/discover.ts +206 -0
  680. package/src/workspace/generate.ts +401 -0
  681. package/src/workspace/index.ts +7 -0
  682. package/src/workspace/module-imports-pipework.ts +39 -0
  683. package/src/workspace/module-package-name.ts +12 -0
  684. package/src/workspace/scrub-git-env.ts +23 -0
  685. package/src/workspace/source-paths.ts +36 -0
  686. package/src/workspace/sync.ts +182 -0
  687. package/dist/REFERENCE.md +0 -1477
  688. package/dist/infra/access-links/rate-limit.d.ts +0 -10
  689. package/dist/infra/access-links/rate-limit.d.ts.map +0 -1
  690. package/dist/infra/access-links/rate-limit.js +0 -28
  691. package/dist/infra/access-links/rate-limit.js.map +0 -1
  692. package/dist/infra/email/sigv4.d.ts +0 -24
  693. package/dist/infra/email/sigv4.d.ts.map +0 -1
  694. package/dist/infra/email/sigv4.js +0 -55
  695. package/dist/infra/email/sigv4.js.map +0 -1
@@ -0,0 +1,9 @@
1
+ export { createAccessLinks } from './handle.js'
2
+ export type {
3
+ AccessLinks, AccessLinksConfig, AccessLink, PublicLink, AccessLinkUse, AccessLinkTarget, AccessLinkListed, AccessLinkPage,
4
+ AccessLinkCheck, AccessLinkCheckArgs, LinkOptions, ResolveResult, BeginUseResult,
5
+ CompleteUseResult, AbandonUseResult,
6
+ } from './handle.js'
7
+ export type { CodeConfig } from './code.js'
8
+ export type { RateLimitConfig } from './handle.js'
9
+ export type { PersonCheck, PersonCheckConfig, PersonChallenge, PersonPass } from './person-check.js'
@@ -0,0 +1,30 @@
1
+ import { createAccessLinks } from './handle.js'
2
+ import { personCheck } from './person-check.js'
3
+
4
+ /** Access links namespace — short revocable bearer links to a tenant's resource. */
5
+ export const accessLinks = {
6
+ /** Creates an access-link handle: tenant-scoped management plus the public resolve / beginUse / completeUse / abandonUse. */
7
+ create: createAccessLinks,
8
+ /**
9
+ * A self-hosted person check for `requireChecks`: ALTCHA-format proof-of-work challenges bound to a link code
10
+ * and an expiry, HMAC-signed with `secret` (32 bytes or more, the same in every process). A solution is accepted
11
+ * once across all processes (`access_link_check_spent`, in the `accessLinks` tables) and yields a short-lived
12
+ * signed pass that is the proof for the link's check at `beginUse`. `personCheck.solve(challenge)` solves a
13
+ * challenge in code, for tests.
14
+ *
15
+ * The app writes two public routes: GET returns `check.challenge(code)` for the widget (`challengeurl`, or
16
+ * `challengejson`); POST gives `check.verify(code, payload)` the widget's payload and returns the pass, or refuses
17
+ * when it is `null`. The page carries the ALTCHA widget (MIT; serve its script yourself, no third party sees the
18
+ * visitor), which solves in the background and falls back to a keyboard and screen-reader checkbox. pipework
19
+ * does not ship the widget.
20
+ *
21
+ * ```ts
22
+ * const person = accessLinks.personCheck({ secret: config.linkSecret })
23
+ * const links = accessLinks.create({ permissions: ['view'], checks: { person: person.check } })
24
+ * await links.link({ target, permissions: ['view'], requireChecks: ['person'] })
25
+ * // public route: begin with the pass the visitor got from POST
26
+ * await links.beginUse(code, { kind: 'view', proof: { person: pass } })
27
+ * ```
28
+ */
29
+ personCheck,
30
+ } as const
@@ -0,0 +1,177 @@
1
+ import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto'
2
+ import { sql } from '../../data/db/sql.js'
3
+ import { pipeSystem } from '../../data/db/db.js'
4
+ import type { AccessLinkCheck } from './handle.js'
5
+
6
+ const ALGORITHM = 'SHA-256'
7
+ const MIN_SECRET_BYTES = 32
8
+ const MAX_NUMBER_LIMIT = 10_000_000
9
+ const PRUNE_BATCH = 100
10
+
11
+ /** A proof-of-work challenge in the shape the ALTCHA widget takes. */
12
+ export interface PersonChallenge {
13
+ readonly algorithm: 'SHA-256'
14
+ readonly challenge: string
15
+ readonly maxnumber: number
16
+ readonly salt: string
17
+ readonly signature: string
18
+ }
19
+
20
+ export interface PersonCheckConfig {
21
+ /** Signs challenges and passes; 32 bytes or more, the same in every process. */
22
+ readonly secret: string | Buffer
23
+ /** The largest number the browser may have to try; the work is half this on average. Default 100_000; 1 to 10_000_000. */
24
+ readonly maxNumber?: number
25
+ /** How long a challenge can be solved for. Default 10 minutes. */
26
+ readonly challengeMs?: number
27
+ /** How long a pass is good for. Default 30 minutes. */
28
+ readonly passMs?: number
29
+ /** Database holding `access_link_check_spent`; default the default database. */
30
+ readonly database?: string
31
+ }
32
+
33
+ export interface PersonPass {
34
+ readonly pass: string
35
+ readonly expiresAt: Date
36
+ }
37
+
38
+ export interface PersonCheck {
39
+ /** A challenge for one link's code, for the widget. */
40
+ challenge(code: string): PersonChallenge
41
+ /**
42
+ * Accepts a widget solution (its base64 payload) for `code` once, across processes, and returns a pass;
43
+ * `null` for anything else: malformed, tampered, expired, for another link, or already spent. Never throws on bad input.
44
+ * Needs a Flow (it records the spent solution through `pipe.system()`).
45
+ */
46
+ verify(code: string, solution: unknown): Promise<PersonPass | null>
47
+ /**
48
+ * True when `pass` is a pass issued for `code`, unexpired and correctly signed: the same answer `check` gives at
49
+ * `beginUse`. Use it on a public route right after `resolve(code)` to refuse a bad pass before answering anything
50
+ * about the link. Synchronous; no database, no Flow; never throws.
51
+ */
52
+ checkPass(code: string, pass: unknown): boolean
53
+ /** The `AccessLinkCheck` to register under a name in `accessLinks.create({ checks })`: the proof is a pass issued for this link. */
54
+ readonly check: AccessLinkCheck
55
+ }
56
+
57
+ function sha256Hex(data: string): string {
58
+ return createHash('sha256').update(data).digest('hex')
59
+ }
60
+
61
+ function sameBytes(a: Buffer, b: Buffer): boolean {
62
+ return a.length === b.length && a.length > 0 && timingSafeEqual(a, b)
63
+ }
64
+
65
+ function sameHex(a: string, b: string): boolean {
66
+ return sameBytes(Buffer.from(a, 'hex'), Buffer.from(b, 'hex'))
67
+ }
68
+
69
+ function positive(name: string, value: number): number {
70
+ if (!Number.isFinite(value) || value <= 0) throw new Error(`personCheck: ${name} must be a positive number`)
71
+ return value
72
+ }
73
+
74
+ function createPersonCheck(config: PersonCheckConfig): PersonCheck {
75
+ const secret = typeof config.secret === 'string' ? Buffer.from(config.secret, 'utf8') : config.secret
76
+ if (!Buffer.isBuffer(secret) || secret.length < MIN_SECRET_BYTES) {
77
+ throw new Error(`personCheck: secret must be ${String(MIN_SECRET_BYTES)} bytes or more, the same in every process (no random fallback: a per-process secret breaks passes across processes)`)
78
+ }
79
+ const maxNumber = config.maxNumber ?? 100_000
80
+ if (!Number.isInteger(maxNumber) || maxNumber < 1 || maxNumber > MAX_NUMBER_LIMIT) {
81
+ throw new Error(`personCheck: maxNumber must be an integer from 1 to ${String(MAX_NUMBER_LIMIT)}`)
82
+ }
83
+ const challengeMs = positive('challengeMs', config.challengeMs ?? 600_000)
84
+ const passMs = positive('passMs', config.passMs ?? 1_800_000)
85
+ const database = config.database
86
+
87
+ const key = (purpose: 'challenge' | 'pass'): Buffer =>
88
+ createHmac('sha256', secret).update(`pipework access links person check: ${purpose}`).digest()
89
+ const challengeKey = key('challenge')
90
+ const passKey = key('pass')
91
+
92
+ function challenge(code: string): PersonChallenge {
93
+ const expires = Math.floor((Date.now() + challengeMs) / 1000)
94
+ const salt = `${randomBytes(12).toString('hex')}?expires=${String(expires)}&link=${encodeURIComponent(code)}`
95
+ const secretNumber = randomBytes(4).readUInt32BE() % (maxNumber + 1)
96
+ const digest = sha256Hex(salt + String(secretNumber))
97
+ const signature = createHmac('sha256', challengeKey).update(digest).digest('hex')
98
+ return { algorithm: ALGORITHM, challenge: digest, maxnumber: maxNumber, salt, signature }
99
+ }
100
+
101
+ function issuePass(code: string): PersonPass {
102
+ const exp = Date.now() + passMs
103
+ const body = Buffer.from(JSON.stringify({ code, exp })).toString('base64url')
104
+ const mac = createHmac('sha256', passKey).update(body).digest('base64url')
105
+ return { pass: `${body}.${mac}`, expiresAt: new Date(exp) }
106
+ }
107
+
108
+ // The challenge's expiry in ms when `solution` solves a live challenge made for `code`; otherwise null.
109
+ function solvedUntil(code: string, solution: unknown): { id: string; untilMs: number } | null {
110
+ if (typeof solution !== 'string') return null
111
+ let payload: unknown
112
+ try {
113
+ payload = JSON.parse(Buffer.from(solution, 'base64').toString('utf8'))
114
+ } catch {
115
+ return null
116
+ }
117
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) return null
118
+ const { algorithm, challenge: id, number, salt, signature } = payload as Record<string, unknown>
119
+ if (algorithm !== ALGORITHM || typeof id !== 'string' || typeof salt !== 'string' || typeof signature !== 'string') return null
120
+ if (typeof number !== 'number' || !Number.isSafeInteger(number) || number < 0) return null
121
+ const params = new URLSearchParams(salt.split('?')[1] ?? '')
122
+ const expires = Number(params.get('expires'))
123
+ if (!Number.isFinite(expires) || expires * 1000 <= Date.now() || params.get('link') !== code) return null
124
+ const expected = createHmac('sha256', challengeKey).update(id).digest('hex')
125
+ if (!sameHex(expected, signature)) return null
126
+ if (!sameHex(sha256Hex(salt + String(number)), id)) return null
127
+ return { id, untilMs: expires * 1000 }
128
+ }
129
+
130
+ async function verify(code: string, solution: unknown): Promise<PersonPass | null> {
131
+ const solved = solvedUntil(code, solution)
132
+ if (solved === null) return null
133
+ const db = pipeSystem(database)
134
+ const spent = await db.execute(sql`
135
+ INSERT INTO access_link_check_spent (challenge, expires_at)
136
+ VALUES (${solved.id}, ${new Date(solved.untilMs).toISOString()})
137
+ ON CONFLICT DO NOTHING RETURNING 1 AS spent`)
138
+ if (spent.length === 0) return null
139
+ // Kept an hour past expiry so clock skew between processes and the database never reopens a solution.
140
+ await db.execute(sql`
141
+ DELETE FROM access_link_check_spent WHERE ctid IN (
142
+ SELECT ctid FROM access_link_check_spent WHERE expires_at < now() - interval '1 hour' LIMIT ${PRUNE_BATCH})`)
143
+ return issuePass(code)
144
+ }
145
+
146
+ function checkPass(code: string, pass: unknown): boolean {
147
+ if (typeof pass !== 'string') return false
148
+ const parts = pass.split('.')
149
+ const [body, mac] = parts
150
+ if (parts.length !== 2 || body === undefined || mac === undefined) return false
151
+ const expected = createHmac('sha256', passKey).update(body).digest()
152
+ if (!sameBytes(Buffer.from(mac, 'base64url'), expected)) return false
153
+ try {
154
+ const claims = JSON.parse(Buffer.from(body, 'base64url').toString('utf8')) as { code?: unknown; exp?: unknown }
155
+ return typeof claims.exp === 'number' && claims.exp > Date.now() && claims.code === code
156
+ } catch {
157
+ return false
158
+ }
159
+ }
160
+
161
+ const check: AccessLinkCheck = ({ link, proof }) => Promise.resolve(checkPass(link.code, proof))
162
+
163
+ return { challenge, verify, checkPass, check }
164
+ }
165
+
166
+ /** Solves a challenge the way the widget does and returns its base64 payload. For tests: it does the work the browser would. */
167
+ function solvePersonChallenge(c: PersonChallenge): string {
168
+ for (let n = 0; n <= c.maxnumber; n++) {
169
+ if (sha256Hex(c.salt + String(n)) === c.challenge) {
170
+ const payload = { algorithm: c.algorithm, challenge: c.challenge, number: n, salt: c.salt, signature: c.signature, took: 0 }
171
+ return Buffer.from(JSON.stringify(payload)).toString('base64')
172
+ }
173
+ }
174
+ throw new Error('personCheck.solve: challenge has no solution')
175
+ }
176
+
177
+ export const personCheck = Object.assign(createPersonCheck, { solve: solvePersonChallenge })
@@ -0,0 +1,71 @@
1
+ // The four tables behind access links. `access_link_code` is deliberately NOT
2
+ // tenant-scoped: it maps a code to its tenant so a request with no tenant can
3
+ // find where to look, and holds nothing else. `access_link` and
4
+ // `access_link_use` carry a `.tenant()` key, so generated migrations put them
5
+ // under forced row-level security.
6
+
7
+ import { define } from '../../data/domain/index.js'
8
+ import { field } from '../../data/domain/field.js'
9
+ import { schema } from '../../data/schema/namespace.js'
10
+ import { index } from '../../data/db/idx/index.js'
11
+
12
+ export const ACCESS_LINK_USE_STATUSES = ['pending', 'completed', 'abandoned'] as const
13
+
14
+ export const accessLinkCode = define('access_link_code', {
15
+ code: field.text().primaryKey(),
16
+ tenantId: field.uuid(),
17
+ linkId: field.uuid(),
18
+ createdAt: field.timestamp().default('now'),
19
+ })
20
+
21
+ export const accessLink = define('access_link', {
22
+ id: field.uuid().defaultRandom().primaryKey(),
23
+ tenantId: field.uuid().tenant(),
24
+ code: field.text().unique(),
25
+ targetType: field.text(),
26
+ targetId: field.text(),
27
+ permissions: field.jsonb(schema.check.json()),
28
+ note: field.text().nullable(),
29
+ createdBy: field.text().nullable(),
30
+ createdAt: field.timestamp().default('now'),
31
+ expiresAt: field.timestamp().nullable(),
32
+ maxUses: field.integer().nullable(),
33
+ requireChecks: field.jsonb(schema.check.json()).default([]),
34
+ checkData: field.jsonb(schema.check.json()).nullable(),
35
+ revokedAt: field.timestamp().nullable(),
36
+ revokedBy: field.text().nullable(),
37
+ })
38
+
39
+ export const accessLinkUse = define('access_link_use', {
40
+ id: field.uuid().defaultRandom().primaryKey(),
41
+ tenantId: field.uuid().tenant(),
42
+ linkId: field.uuid(),
43
+ kind: field.text(),
44
+ status: field.enum(ACCESS_LINK_USE_STATUSES),
45
+ startedAt: field.timestamp().default('now'),
46
+ reservedUntil: field.timestamp(),
47
+ completedAt: field.timestamp().nullable(),
48
+ client: field.text().nullable(),
49
+ location: field.text().nullable(),
50
+ detail: field.jsonb(schema.check.json()).nullable(),
51
+ claimedAt: field.timestamp().nullable(),
52
+ }, {
53
+ indexes: (t) => ({
54
+ byLink: index('idx_access_link_use_link').on(t['linkId'], t['status']),
55
+ }),
56
+ })
57
+
58
+ // Person-check solutions already accepted, kept until the challenge has expired:
59
+ // one solved challenge makes one pass, across processes. Not tenant-scoped.
60
+ export const accessLinkCheckSpent = define('access_link_check_spent', {
61
+ challenge: field.text().primaryKey(),
62
+ expiresAt: field.timestamp(),
63
+ })
64
+
65
+ // Codes that matched no link, counted per client in a fixed window: the guessing
66
+ // limit, shared by every process. Not tenant-scoped.
67
+ export const accessLinkMiss = define('access_link_miss', {
68
+ client: field.text().primaryKey(),
69
+ count: field.integer().default(0),
70
+ windowStart: field.timestamp().default('now'),
71
+ })
@@ -0,0 +1,231 @@
1
+ // Sources over the failures the async subsystems already record: cron schedules, step graphs, pipelines and
2
+ // trace expiry. Each is a composition of `querySource` and `newRowsSource`.
3
+
4
+ import { sql } from '../../data/db/sql.js'
5
+ import { composeSources, newRowsSource, querySource, type AlertSource } from './sources.js'
6
+ import { common, positive, text, FIFTEEN_MIN, HOUR, type Basics } from './source-options.js'
7
+
8
+ const FIVE_MIN = 5 * 60_000
9
+ const TRACE_SWEEP_GRACE = 26 * HOUR
10
+
11
+ /** The `error` jsonb of a recorded failure as one line: its `message`, `error` or `step`/`error` pair, else the JSON. */
12
+ function errorText(v: unknown): string | null {
13
+ if (v === null || v === undefined) return null
14
+ const parsed = typeof v === 'string' ? safeParse(v) : v
15
+ if (typeof parsed === 'string') return parsed
16
+ if (typeof parsed === 'object' && parsed !== null) {
17
+ const o = parsed as Record<string, unknown>
18
+ const inner = o['message'] ?? o['error']
19
+ if (typeof inner === 'string') return typeof o['step'] === 'string' ? `${o['step']}: ${inner}` : inner
20
+ if (typeof inner === 'object' && inner !== null) return errorText(inner)
21
+ }
22
+ return JSON.stringify(parsed)
23
+ }
24
+
25
+ function safeParse(s: string): unknown {
26
+ try { return JSON.parse(s) } catch { return s }
27
+ }
28
+
29
+ /**
30
+ * Raises `schedule-overdue:<schedule name>` while a cron schedule's `next_run_at` is more than `graceMs` (default 5 min)
31
+ * in the past. Every scheduler pass moves `next_run_at` into the future, whether it fires the job or skips it because
32
+ * the previous run is still pending, so an overdue row means no scheduler is running (or it cannot reach the row).
33
+ * Resolves when the scheduler catches up. Needs a worker surface that declares schedules (the `pipework_schedules` table; no blueprint key).
34
+ */
35
+ export function schedulesSource(options: Basics & { graceMs?: number } = {}): AlertSource {
36
+ const grace = positive(options.graceMs ?? FIVE_MIN, 'graceMs', 'alerts.source.schedules')
37
+ return querySource({
38
+ ...common(options),
39
+ name: 'schedule-overdue',
40
+ query: sql`
41
+ SELECT name, next_run_at, last_run_at, (extract(epoch FROM clock_timestamp() - next_run_at) * 1000)::bigint AS overdue_ms
42
+ FROM pipework_schedules
43
+ WHERE next_run_at < clock_timestamp() - ${grace} * interval '1 millisecond'
44
+ ORDER BY name`,
45
+ id: r => String(r['name']),
46
+ message: r => `schedule ${String(r['name'])} is ${String(r['overdue_ms'])} ms past its next run; is the scheduler running?`,
47
+ subject: r => ({ entity: 'schedule', entityId: String(r['name']) }),
48
+ context: r => ({
49
+ schedule: String(r['name']),
50
+ overdueMs: Number(r['overdue_ms']),
51
+ nextRunAt: new Date(String(r['next_run_at'])).toISOString(),
52
+ lastRunAt: r['last_run_at'] === null ? null : new Date(String(r['last_run_at'])).toISOString(),
53
+ }),
54
+ })
55
+ }
56
+
57
+ /**
58
+ * Raises `step-run-failed:<graph>` for step runs that failed since the last tick (message with the count and the last
59
+ * error; context `count`, `lastError`, `lastRunId`, `lastTenantId`), `step-run-unnotified:<graph>` while runs finished
60
+ * more than `notifyAfterMs` (default 15 min) ago still have `notified = false` (the graph's `onDone` has not run), and
61
+ * `step-task-waiting:<graph>` while a task has waited for a person's decision more than `waitingAfterMs` (default 1 h);
62
+ * the wait is measured from its run's `created_at`, as tasks carry no timestamp, and `step-run-stalled:<graph>` while runs
63
+ * are still `running` more than `runningAfterMs` (default 1 h) after `created_at` with no task `waiting` (a `waiting` task is a
64
+ * person's decision and is covered above; `blocked` and `queued` tasks are work in flight, so a run stuck on them is stalled). A failed task always fails its run in the
65
+ * same transaction, so failed runs cover failed tasks. The failed condition clears by the handle's quiet period; the other two
66
+ * resolve when the rows drain. Needs a step graph defined in the app (the `pipework_step_run` and `pipework_step_task` tables; no blueprint key).
67
+ */
68
+ export function stepsSource(options: Basics & { notifyAfterMs?: number; waitingAfterMs?: number; runningAfterMs?: number; settleMs?: number } = {}): AlertSource {
69
+ const notifyAfter = positive(options.notifyAfterMs ?? FIFTEEN_MIN, 'notifyAfterMs', 'alerts.source.steps')
70
+ const waitingAfter = positive(options.waitingAfterMs ?? HOUR, 'waitingAfterMs', 'alerts.source.steps')
71
+ const runningAfter = positive(options.runningAfterMs ?? HOUR, 'runningAfterMs', 'alerts.source.steps')
72
+ const failed = newRowsSource({
73
+ ...common(options),
74
+ name: 'step-run-failed',
75
+ table: 'pipework_step_run',
76
+ at: 'finished_at',
77
+ where: sql`t.status = 'failed'`,
78
+ groupBy: 'graph',
79
+ ...(options.settleMs !== undefined ? { settleMs: options.settleMs } : {}),
80
+ message: g => `${String(g.count)} run(s) of step graph ${g.id} failed; last error: ${text(errorText(g.last['error']))}`,
81
+ context: g => ({
82
+ graph: g.id,
83
+ count: g.count,
84
+ lastError: errorText(g.last['error']),
85
+ lastRunId: String(g.last['id']),
86
+ lastTenantId: String(g.last['tenant_id']),
87
+ }),
88
+ })
89
+ const unnotified = querySource({
90
+ ...common(options),
91
+ name: 'step-run-unnotified',
92
+ query: sql`
93
+ SELECT graph, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(finished_at)) * 1000)::bigint AS oldest_ms
94
+ FROM pipework_step_run
95
+ WHERE status = 'done' AND notified = false
96
+ AND finished_at < clock_timestamp() - ${notifyAfter} * interval '1 millisecond'
97
+ GROUP BY graph ORDER BY graph`,
98
+ id: r => String(r['graph']),
99
+ message: r => `${String(r['n'])} finished run(s) of step graph ${String(r['graph'])} not notified for more than ${String(notifyAfter)} ms (oldest ${String(r['oldest_ms'])} ms)`,
100
+ subject: r => ({ entity: 'step-graph', entityId: String(r['graph']) }),
101
+ context: r => ({ graph: String(r['graph']), count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
102
+ })
103
+ const waiting = querySource({
104
+ ...common(options),
105
+ name: 'step-task-waiting',
106
+ query: sql`
107
+ SELECT r.graph, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(r.created_at)) * 1000)::bigint AS oldest_ms
108
+ FROM pipework_step_task k JOIN pipework_step_run r ON r.id = k.run_id
109
+ WHERE k.status = 'waiting' AND r.status = 'running'
110
+ AND r.created_at < clock_timestamp() - ${waitingAfter} * interval '1 millisecond'
111
+ GROUP BY r.graph ORDER BY r.graph`,
112
+ id: r => String(r['graph']),
113
+ message: r => `${String(r['n'])} task(s) of step graph ${String(r['graph'])} waiting more than ${String(waitingAfter)} ms (oldest run ${String(r['oldest_ms'])} ms old)`,
114
+ subject: r => ({ entity: 'step-graph', entityId: String(r['graph']) }),
115
+ context: r => ({ graph: String(r['graph']), count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
116
+ })
117
+ const stalled = querySource({
118
+ ...common(options),
119
+ name: 'step-run-stalled',
120
+ query: sql`
121
+ SELECT r.graph, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(r.created_at)) * 1000)::bigint AS oldest_ms
122
+ FROM pipework_step_run r
123
+ WHERE r.status = 'running'
124
+ AND r.created_at < clock_timestamp() - ${runningAfter} * interval '1 millisecond'
125
+ AND NOT EXISTS (SELECT 1 FROM pipework_step_task k WHERE k.run_id = r.id AND k.status = 'waiting')
126
+ GROUP BY r.graph ORDER BY r.graph`,
127
+ id: r => String(r['graph']),
128
+ message: r => `${String(r['n'])} run(s) of step graph ${String(r['graph'])} still running after more than ${String(runningAfter)} ms with no person step waiting (oldest ${String(r['oldest_ms'])} ms)`,
129
+ subject: r => ({ entity: 'step-graph', entityId: String(r['graph']) }),
130
+ context: r => ({ graph: String(r['graph']), count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
131
+ })
132
+ return composeSources('steps', [failed, unnotified, waiting, stalled])
133
+ }
134
+
135
+ /**
136
+ * Raises `pipeline-failed:<pipeline name>` for pipeline runs that failed since the last tick (context `count`,
137
+ * `lastError`, `lastRunId`, `lastTenantId`), and `pipeline-waiting:<pipeline name>` while runs sit in `waiting` more than
138
+ * `waitingAfterMs` (default 1 h) after they started (the table keeps no time for when a run began to wait, so the age is
139
+ * `started_at`), and `pipeline-stalled:<pipeline name>` while runs are `running` more than `runningAfterMs` (default 1 h)
140
+ * after `started_at` (the process died mid-run). The failed condition clears by the handle's quiet period; the others
141
+ * resolve when the runs resume or finish. Needs the app's own `pipework_pipeline_runs` table (pipelines have no blueprint key; the app creates it).
142
+ */
143
+ export function pipelinesSource(options: Basics & { waitingAfterMs?: number; runningAfterMs?: number; settleMs?: number } = {}): AlertSource {
144
+ const waitingAfter = positive(options.waitingAfterMs ?? HOUR, 'waitingAfterMs', 'alerts.source.pipelines')
145
+ const runningAfter = positive(options.runningAfterMs ?? HOUR, 'runningAfterMs', 'alerts.source.pipelines')
146
+ const failed = newRowsSource({
147
+ ...common(options),
148
+ name: 'pipeline-failed',
149
+ table: 'pipework_pipeline_runs',
150
+ at: 'completed_at',
151
+ where: sql`t.status = 'failed'`,
152
+ groupBy: 'pipeline_name',
153
+ ...(options.settleMs !== undefined ? { settleMs: options.settleMs } : {}),
154
+ message: g => `${String(g.count)} run(s) of pipeline ${g.id} failed; last error: ${text(errorText(g.last['error']))}`,
155
+ context: g => ({
156
+ pipeline: g.id,
157
+ count: g.count,
158
+ lastError: errorText(g.last['error']),
159
+ lastRunId: String(g.last['id']),
160
+ lastTenantId: String(g.last['tenant_id']),
161
+ }),
162
+ })
163
+ const waiting = querySource({
164
+ ...common(options),
165
+ name: 'pipeline-waiting',
166
+ query: sql`
167
+ SELECT pipeline_name, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(started_at)) * 1000)::bigint AS oldest_ms
168
+ FROM pipework_pipeline_runs
169
+ WHERE status = 'waiting' AND started_at < clock_timestamp() - ${waitingAfter} * interval '1 millisecond'
170
+ GROUP BY pipeline_name ORDER BY pipeline_name`,
171
+ id: r => String(r['pipeline_name']),
172
+ message: r => `${String(r['n'])} run(s) of pipeline ${String(r['pipeline_name'])} waiting more than ${String(waitingAfter)} ms (oldest ${String(r['oldest_ms'])} ms)`,
173
+ subject: r => ({ entity: 'pipeline', entityId: String(r['pipeline_name']) }),
174
+ context: r => ({ pipeline: String(r['pipeline_name']), count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
175
+ })
176
+ const stalled = querySource({
177
+ ...common(options),
178
+ name: 'pipeline-stalled',
179
+ query: sql`
180
+ SELECT pipeline_name, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(started_at)) * 1000)::bigint AS oldest_ms
181
+ FROM pipework_pipeline_runs
182
+ WHERE status = 'running' AND started_at < clock_timestamp() - ${runningAfter} * interval '1 millisecond'
183
+ GROUP BY pipeline_name ORDER BY pipeline_name`,
184
+ id: r => String(r['pipeline_name']),
185
+ message: r => `${String(r['n'])} run(s) of pipeline ${String(r['pipeline_name'])} still running after more than ${String(runningAfter)} ms (oldest ${String(r['oldest_ms'])} ms)`,
186
+ subject: r => ({ entity: 'pipeline', entityId: String(r['pipeline_name']) }),
187
+ context: r => ({ pipeline: String(r['pipeline_name']), count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
188
+ })
189
+ return composeSources('pipelines', [failed, waiting, stalled])
190
+ }
191
+
192
+ /**
193
+ * Raises `trace-expired:<entry kind>` (http, job, cron, test) for traces the expiry sweep force-closed since the last
194
+ * tick: the sweep stamps `status = 'expired'` and `closed_at`, so `closed_at` is the cursor. Also raises
195
+ * `trace-expiry-undrained:all` while traces still `open` have an `expires_at` more than `graceMs` (default 26 h: the maintenance job that
196
+ * runs the sweep is daily, so a trace legitimately stays open up to a day past its deadline) in the past, which means
197
+ * nothing is running the expiry job. The expired condition clears by the handle's quiet period; the
198
+ * undrained one resolves once the sweep runs. Needs the blueprint's `trace` block (the `trace` table).
199
+ */
200
+ export function tracesSource(options: Basics & { graceMs?: number; settleMs?: number } = {}): AlertSource {
201
+ const grace = positive(options.graceMs ?? TRACE_SWEEP_GRACE, 'graceMs', 'alerts.source.traces')
202
+ const expired = newRowsSource({
203
+ ...common(options),
204
+ name: 'trace-expired',
205
+ table: 'trace',
206
+ at: 'closed_at',
207
+ where: sql`t.status = 'expired'`,
208
+ groupBy: 'entry_kind',
209
+ ...(options.settleMs !== undefined ? { settleMs: options.settleMs } : {}),
210
+ message: g => `${String(g.count)} ${g.id} trace(s) force-closed after expiring; last trace ${String(g.last['id'])}`,
211
+ context: g => ({
212
+ entryKind: g.id,
213
+ count: g.count,
214
+ lastTraceId: String(g.last['id']),
215
+ lastTenantId: g.last['tenant_id'] === null || g.last['tenant_id'] === undefined ? null : String(g.last['tenant_id']),
216
+ }),
217
+ })
218
+ const undrained = querySource({
219
+ ...common(options),
220
+ name: 'trace-expiry-undrained',
221
+ query: sql`
222
+ SELECT count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(expires_at)) * 1000)::bigint AS oldest_ms
223
+ FROM "trace"
224
+ WHERE status = 'open' AND expires_at <= clock_timestamp() - ${grace} * interval '1 millisecond'
225
+ HAVING count(*) > 0`,
226
+ id: () => 'all',
227
+ message: r => `${String(r['n'])} open trace(s) expired more than ${String(grace)} ms ago and not closed; is the trace expiry job scheduled?`,
228
+ context: r => ({ count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
229
+ })
230
+ return composeSources('traces', [expired, undrained])
231
+ }
@@ -0,0 +1,36 @@
1
+ import { sql } from '../../data/db/sql.js'
2
+ import { pipeSystem } from '../../data/db/db.js'
3
+ import type { DB } from '../../data/db/index.js'
4
+ import { boundTransaction } from '../../data/write/audit/bound-transaction.js'
5
+ import { getContext } from '../../request/context/store.js'
6
+ import { resolveActorId } from '../../data/write/audit/actor.js'
7
+ import { getBaseLogger } from '../../core/logging/index.js'
8
+ import { recordFault, faultMessage } from './faults.js'
9
+
10
+ /**
11
+ * Writes an `Alert` audit record under the Flow's open trace step; does nothing when no step is open.
12
+ * In the Flow's transaction on that database when one is open (a savepoint, so a failed write cannot abort it; it
13
+ * commits or rolls back with it), else on the system channel.
14
+ */
15
+ export async function auditAlert(verb: string, key: string, details: Record<string, unknown>, database?: string): Promise<void> {
16
+ const flow = getContext()
17
+ const trace = flow?.trace
18
+ const stepId = trace?.buffer.openStepId ?? null
19
+ if (flow === undefined || trace === undefined || trace === null || stepId === null) return
20
+ try {
21
+ const write = async (db: DB): Promise<void> => {
22
+ await db.execute(sql`
23
+ INSERT INTO audit_record
24
+ (trace_day, trace_id, trace_step_id, tenant_id, actor_id, entity, verb, entity_id, after, occurred_at)
25
+ VALUES (${trace.openedDay}, ${trace.id}, ${stepId}, ${flow.tenant}, ${resolveActorId(flow)},
26
+ 'Alert', ${verb}, ${key}, ${JSON.stringify(details)}::jsonb, now())
27
+ `)
28
+ }
29
+ const tx = boundTransaction(flow, database)
30
+ if (tx === null) await write(pipeSystem(database))
31
+ else await tx.transaction(write)
32
+ } catch (err) {
33
+ getBaseLogger().error({ err, traceId: trace.id }, `Failed to write the alert ${verb} audit record`)
34
+ void recordFault('audit-write', faultMessage(err), { entity: 'Alert', verb })
35
+ }
36
+ }