wood-fired-tasks 1.12.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 (553) hide show
  1. package/AGENTS.md +112 -0
  2. package/CHANGELOG.md +271 -0
  3. package/CLAUDE.md +21 -0
  4. package/LICENSE +21 -0
  5. package/README.md +687 -0
  6. package/SECURITY.md +299 -0
  7. package/dist/api/hooks/error-handler.d.ts +6 -0
  8. package/dist/api/hooks/error-handler.js +128 -0
  9. package/dist/api/hooks/error-handler.js.map +1 -0
  10. package/dist/api/plugins/auth/index.d.ts +78 -0
  11. package/dist/api/plugins/auth/index.js +300 -0
  12. package/dist/api/plugins/auth/index.js.map +1 -0
  13. package/dist/api/plugins/auth/keys.d.ts +23 -0
  14. package/dist/api/plugins/auth/keys.js +100 -0
  15. package/dist/api/plugins/auth/keys.js.map +1 -0
  16. package/dist/api/plugins/auth/strategies/legacy.d.ts +46 -0
  17. package/dist/api/plugins/auth/strategies/legacy.js +87 -0
  18. package/dist/api/plugins/auth/strategies/legacy.js.map +1 -0
  19. package/dist/api/plugins/auth/strategies/pat.d.ts +37 -0
  20. package/dist/api/plugins/auth/strategies/pat.js +99 -0
  21. package/dist/api/plugins/auth/strategies/pat.js.map +1 -0
  22. package/dist/api/plugins/auth/strategies/session.d.ts +37 -0
  23. package/dist/api/plugins/auth/strategies/session.js +30 -0
  24. package/dist/api/plugins/auth/strategies/session.js.map +1 -0
  25. package/dist/api/plugins/auth/strategies/types.d.ts +12 -0
  26. package/dist/api/plugins/auth/strategies/types.js +2 -0
  27. package/dist/api/plugins/auth/strategies/types.js.map +1 -0
  28. package/dist/api/plugins/auth.d.ts +29 -0
  29. package/dist/api/plugins/auth.js +30 -0
  30. package/dist/api/plugins/auth.js.map +1 -0
  31. package/dist/api/plugins/swagger.d.ts +31 -0
  32. package/dist/api/plugins/swagger.js +83 -0
  33. package/dist/api/plugins/swagger.js.map +1 -0
  34. package/dist/api/routes/auth/auth-error.d.ts +23 -0
  35. package/dist/api/routes/auth/auth-error.js +68 -0
  36. package/dist/api/routes/auth/auth-error.js.map +1 -0
  37. package/dist/api/routes/auth/callback.d.ts +44 -0
  38. package/dist/api/routes/auth/callback.js +175 -0
  39. package/dist/api/routes/auth/callback.js.map +1 -0
  40. package/dist/api/routes/auth/csrf.d.ts +24 -0
  41. package/dist/api/routes/auth/csrf.js +75 -0
  42. package/dist/api/routes/auth/csrf.js.map +1 -0
  43. package/dist/api/routes/auth/device-code.d.ts +41 -0
  44. package/dist/api/routes/auth/device-code.js +51 -0
  45. package/dist/api/routes/auth/device-code.js.map +1 -0
  46. package/dist/api/routes/auth/device-disabled-stub.d.ts +33 -0
  47. package/dist/api/routes/auth/device-disabled-stub.js +16 -0
  48. package/dist/api/routes/auth/device-disabled-stub.js.map +1 -0
  49. package/dist/api/routes/auth/device-html.d.ts +41 -0
  50. package/dist/api/routes/auth/device-html.js +243 -0
  51. package/dist/api/routes/auth/device-html.js.map +1 -0
  52. package/dist/api/routes/auth/device-token.d.ts +38 -0
  53. package/dist/api/routes/auth/device-token.js +153 -0
  54. package/dist/api/routes/auth/device-token.js.map +1 -0
  55. package/dist/api/routes/auth/disabled-stub.d.ts +31 -0
  56. package/dist/api/routes/auth/disabled-stub.js +21 -0
  57. package/dist/api/routes/auth/disabled-stub.js.map +1 -0
  58. package/dist/api/routes/auth/index.d.ts +65 -0
  59. package/dist/api/routes/auth/index.js +47 -0
  60. package/dist/api/routes/auth/index.js.map +1 -0
  61. package/dist/api/routes/auth/login.d.ts +27 -0
  62. package/dist/api/routes/auth/login.js +91 -0
  63. package/dist/api/routes/auth/login.js.map +1 -0
  64. package/dist/api/routes/auth/logout.d.ts +29 -0
  65. package/dist/api/routes/auth/logout.js +43 -0
  66. package/dist/api/routes/auth/logout.js.map +1 -0
  67. package/dist/api/routes/comments/index.d.ts +3 -0
  68. package/dist/api/routes/comments/index.js +74 -0
  69. package/dist/api/routes/comments/index.js.map +1 -0
  70. package/dist/api/routes/comments/schemas.d.ts +37 -0
  71. package/dist/api/routes/comments/schemas.js +24 -0
  72. package/dist/api/routes/comments/schemas.js.map +1 -0
  73. package/dist/api/routes/dependencies/index.d.ts +3 -0
  74. package/dist/api/routes/dependencies/index.js +60 -0
  75. package/dist/api/routes/dependencies/index.js.map +1 -0
  76. package/dist/api/routes/dependencies/schemas.d.ts +24 -0
  77. package/dist/api/routes/dependencies/schemas.js +15 -0
  78. package/dist/api/routes/dependencies/schemas.js.map +1 -0
  79. package/dist/api/routes/events.d.ts +3 -0
  80. package/dist/api/routes/events.js +157 -0
  81. package/dist/api/routes/events.js.map +1 -0
  82. package/dist/api/routes/health.d.ts +27 -0
  83. package/dist/api/routes/health.js +231 -0
  84. package/dist/api/routes/health.js.map +1 -0
  85. package/dist/api/routes/me/index.d.ts +3 -0
  86. package/dist/api/routes/me/index.js +8 -0
  87. package/dist/api/routes/me/index.js.map +1 -0
  88. package/dist/api/routes/me/profile.d.ts +3 -0
  89. package/dist/api/routes/me/profile.js +58 -0
  90. package/dist/api/routes/me/profile.js.map +1 -0
  91. package/dist/api/routes/me/tokens.d.ts +3 -0
  92. package/dist/api/routes/me/tokens.js +410 -0
  93. package/dist/api/routes/me/tokens.js.map +1 -0
  94. package/dist/api/routes/projects/dependency-graph.d.ts +26 -0
  95. package/dist/api/routes/projects/dependency-graph.js +56 -0
  96. package/dist/api/routes/projects/dependency-graph.js.map +1 -0
  97. package/dist/api/routes/projects/index.d.ts +3 -0
  98. package/dist/api/routes/projects/index.js +96 -0
  99. package/dist/api/routes/projects/index.js.map +1 -0
  100. package/dist/api/routes/projects/schemas.d.ts +37 -0
  101. package/dist/api/routes/projects/schemas.js +27 -0
  102. package/dist/api/routes/projects/schemas.js.map +1 -0
  103. package/dist/api/routes/projects/topology.d.ts +27 -0
  104. package/dist/api/routes/projects/topology.js +51 -0
  105. package/dist/api/routes/projects/topology.js.map +1 -0
  106. package/dist/api/routes/tasks/index.d.ts +3 -0
  107. package/dist/api/routes/tasks/index.js +330 -0
  108. package/dist/api/routes/tasks/index.js.map +1 -0
  109. package/dist/api/routes/tasks/schemas.d.ts +316 -0
  110. package/dist/api/routes/tasks/schemas.js +129 -0
  111. package/dist/api/routes/tasks/schemas.js.map +1 -0
  112. package/dist/api/routes/web/index.d.ts +23 -0
  113. package/dist/api/routes/web/index.js +30 -0
  114. package/dist/api/routes/web/index.js.map +1 -0
  115. package/dist/api/routes/web/login.d.ts +20 -0
  116. package/dist/api/routes/web/login.js +20 -0
  117. package/dist/api/routes/web/login.js.map +1 -0
  118. package/dist/api/routes/web/me.d.ts +23 -0
  119. package/dist/api/routes/web/me.js +31 -0
  120. package/dist/api/routes/web/me.js.map +1 -0
  121. package/dist/api/routes/web/tokens.d.ts +29 -0
  122. package/dist/api/routes/web/tokens.js +65 -0
  123. package/dist/api/routes/web/tokens.js.map +1 -0
  124. package/dist/api/server.d.ts +44 -0
  125. package/dist/api/server.js +521 -0
  126. package/dist/api/server.js.map +1 -0
  127. package/dist/api/start.d.ts +1 -0
  128. package/dist/api/start.js +79 -0
  129. package/dist/api/start.js.map +1 -0
  130. package/dist/cli/api/client.d.ts +126 -0
  131. package/dist/cli/api/client.js +408 -0
  132. package/dist/cli/api/client.js.map +1 -0
  133. package/dist/cli/api/errors.d.ts +16 -0
  134. package/dist/cli/api/errors.js +20 -0
  135. package/dist/cli/api/errors.js.map +1 -0
  136. package/dist/cli/api/types.d.ts +205 -0
  137. package/dist/cli/api/types.js +15 -0
  138. package/dist/cli/api/types.js.map +1 -0
  139. package/dist/cli/auth/browser-open.d.ts +1 -0
  140. package/dist/cli/auth/browser-open.js +116 -0
  141. package/dist/cli/auth/browser-open.js.map +1 -0
  142. package/dist/cli/auth/credentials.d.ts +27 -0
  143. package/dist/cli/auth/credentials.js +179 -0
  144. package/dist/cli/auth/credentials.js.map +1 -0
  145. package/dist/cli/auth/device-flow.d.ts +75 -0
  146. package/dist/cli/auth/device-flow.js +149 -0
  147. package/dist/cli/auth/device-flow.js.map +1 -0
  148. package/dist/cli/bin/tasks-client.d.ts +2 -0
  149. package/dist/cli/bin/tasks-client.js +86 -0
  150. package/dist/cli/bin/tasks-client.js.map +1 -0
  151. package/dist/cli/bin/tasks.d.ts +3 -0
  152. package/dist/cli/bin/tasks.js +127 -0
  153. package/dist/cli/bin/tasks.js.map +1 -0
  154. package/dist/cli/commands/backup.d.ts +3 -0
  155. package/dist/cli/commands/backup.js +65 -0
  156. package/dist/cli/commands/backup.js.map +1 -0
  157. package/dist/cli/commands/claim.d.ts +2 -0
  158. package/dist/cli/commands/claim.js +37 -0
  159. package/dist/cli/commands/claim.js.map +1 -0
  160. package/dist/cli/commands/comment-add.d.ts +2 -0
  161. package/dist/cli/commands/comment-add.js +45 -0
  162. package/dist/cli/commands/comment-add.js.map +1 -0
  163. package/dist/cli/commands/comment-delete.d.ts +2 -0
  164. package/dist/cli/commands/comment-delete.js +56 -0
  165. package/dist/cli/commands/comment-delete.js.map +1 -0
  166. package/dist/cli/commands/comment-list.d.ts +2 -0
  167. package/dist/cli/commands/comment-list.js +56 -0
  168. package/dist/cli/commands/comment-list.js.map +1 -0
  169. package/dist/cli/commands/completed.d.ts +10 -0
  170. package/dist/cli/commands/completed.js +168 -0
  171. package/dist/cli/commands/completed.js.map +1 -0
  172. package/dist/cli/commands/completions.d.ts +10 -0
  173. package/dist/cli/commands/completions.js +179 -0
  174. package/dist/cli/commands/completions.js.map +1 -0
  175. package/dist/cli/commands/create.d.ts +2 -0
  176. package/dist/cli/commands/create.js +99 -0
  177. package/dist/cli/commands/create.js.map +1 -0
  178. package/dist/cli/commands/db-check.d.ts +3 -0
  179. package/dist/cli/commands/db-check.js +63 -0
  180. package/dist/cli/commands/db-check.js.map +1 -0
  181. package/dist/cli/commands/db-migrate-identities.d.ts +37 -0
  182. package/dist/cli/commands/db-migrate-identities.js +352 -0
  183. package/dist/cli/commands/db-migrate-identities.js.map +1 -0
  184. package/dist/cli/commands/db-mint-token.d.ts +33 -0
  185. package/dist/cli/commands/db-mint-token.js +150 -0
  186. package/dist/cli/commands/db-mint-token.js.map +1 -0
  187. package/dist/cli/commands/db.d.ts +10 -0
  188. package/dist/cli/commands/db.js +16 -0
  189. package/dist/cli/commands/db.js.map +1 -0
  190. package/dist/cli/commands/delete.d.ts +2 -0
  191. package/dist/cli/commands/delete.js +54 -0
  192. package/dist/cli/commands/delete.js.map +1 -0
  193. package/dist/cli/commands/dep-add.d.ts +2 -0
  194. package/dist/cli/commands/dep-add.js +43 -0
  195. package/dist/cli/commands/dep-add.js.map +1 -0
  196. package/dist/cli/commands/dep-list.d.ts +2 -0
  197. package/dist/cli/commands/dep-list.js +36 -0
  198. package/dist/cli/commands/dep-list.js.map +1 -0
  199. package/dist/cli/commands/dep-remove.d.ts +2 -0
  200. package/dist/cli/commands/dep-remove.js +55 -0
  201. package/dist/cli/commands/dep-remove.js.map +1 -0
  202. package/dist/cli/commands/doctor.d.ts +3 -0
  203. package/dist/cli/commands/doctor.js +139 -0
  204. package/dist/cli/commands/doctor.js.map +1 -0
  205. package/dist/cli/commands/health.d.ts +2 -0
  206. package/dist/cli/commands/health.js +28 -0
  207. package/dist/cli/commands/health.js.map +1 -0
  208. package/dist/cli/commands/list.d.ts +2 -0
  209. package/dist/cli/commands/list.js +103 -0
  210. package/dist/cli/commands/list.js.map +1 -0
  211. package/dist/cli/commands/login.d.ts +2 -0
  212. package/dist/cli/commands/login.js +210 -0
  213. package/dist/cli/commands/login.js.map +1 -0
  214. package/dist/cli/commands/logout.d.ts +31 -0
  215. package/dist/cli/commands/logout.js +184 -0
  216. package/dist/cli/commands/logout.js.map +1 -0
  217. package/dist/cli/commands/project-create.d.ts +2 -0
  218. package/dist/cli/commands/project-create.js +44 -0
  219. package/dist/cli/commands/project-create.js.map +1 -0
  220. package/dist/cli/commands/project-delete.d.ts +2 -0
  221. package/dist/cli/commands/project-delete.js +54 -0
  222. package/dist/cli/commands/project-delete.js.map +1 -0
  223. package/dist/cli/commands/project-list.d.ts +2 -0
  224. package/dist/cli/commands/project-list.js +55 -0
  225. package/dist/cli/commands/project-list.js.map +1 -0
  226. package/dist/cli/commands/project-show.d.ts +2 -0
  227. package/dist/cli/commands/project-show.js +38 -0
  228. package/dist/cli/commands/project-show.js.map +1 -0
  229. package/dist/cli/commands/project-update.d.ts +2 -0
  230. package/dist/cli/commands/project-update.js +56 -0
  231. package/dist/cli/commands/project-update.js.map +1 -0
  232. package/dist/cli/commands/show.d.ts +2 -0
  233. package/dist/cli/commands/show.js +38 -0
  234. package/dist/cli/commands/show.js.map +1 -0
  235. package/dist/cli/commands/stats.d.ts +3 -0
  236. package/dist/cli/commands/stats.js +81 -0
  237. package/dist/cli/commands/stats.js.map +1 -0
  238. package/dist/cli/commands/subtask-create.d.ts +2 -0
  239. package/dist/cli/commands/subtask-create.js +85 -0
  240. package/dist/cli/commands/subtask-create.js.map +1 -0
  241. package/dist/cli/commands/subtask-list.d.ts +2 -0
  242. package/dist/cli/commands/subtask-list.js +61 -0
  243. package/dist/cli/commands/subtask-list.js.map +1 -0
  244. package/dist/cli/commands/topology.d.ts +16 -0
  245. package/dist/cli/commands/topology.js +52 -0
  246. package/dist/cli/commands/topology.js.map +1 -0
  247. package/dist/cli/commands/update.d.ts +2 -0
  248. package/dist/cli/commands/update.js +90 -0
  249. package/dist/cli/commands/update.js.map +1 -0
  250. package/dist/cli/commands/whoami.d.ts +30 -0
  251. package/dist/cli/commands/whoami.js +201 -0
  252. package/dist/cli/commands/whoami.js.map +1 -0
  253. package/dist/cli/config/env.d.ts +4 -0
  254. package/dist/cli/config/env.js +25 -0
  255. package/dist/cli/config/env.js.map +1 -0
  256. package/dist/cli/output/error-handler.d.ts +5 -0
  257. package/dist/cli/output/error-handler.js +41 -0
  258. package/dist/cli/output/error-handler.js.map +1 -0
  259. package/dist/cli/output/formatters.d.ts +86 -0
  260. package/dist/cli/output/formatters.js +352 -0
  261. package/dist/cli/output/formatters.js.map +1 -0
  262. package/dist/cli/output/json-output.d.ts +39 -0
  263. package/dist/cli/output/json-output.js +50 -0
  264. package/dist/cli/output/json-output.js.map +1 -0
  265. package/dist/cli/output/spinner.d.ts +14 -0
  266. package/dist/cli/output/spinner.js +48 -0
  267. package/dist/cli/output/spinner.js.map +1 -0
  268. package/dist/cli/prompts/interactive.d.ts +25 -0
  269. package/dist/cli/prompts/interactive.js +80 -0
  270. package/dist/cli/prompts/interactive.js.map +1 -0
  271. package/dist/config/env.d.ts +182 -0
  272. package/dist/config/env.js +311 -0
  273. package/dist/config/env.js.map +1 -0
  274. package/dist/db/database.d.ts +11 -0
  275. package/dist/db/database.js +25 -0
  276. package/dist/db/database.js.map +1 -0
  277. package/dist/db/migrate.d.ts +10 -0
  278. package/dist/db/migrate.js +137 -0
  279. package/dist/db/migrate.js.map +1 -0
  280. package/dist/db/migrations/001-initial-schema.d.ts +3 -0
  281. package/dist/db/migrations/001-initial-schema.js +100 -0
  282. package/dist/db/migrations/001-initial-schema.js.map +1 -0
  283. package/dist/db/migrations/002-task-hierarchy-and-dependencies.d.ts +3 -0
  284. package/dist/db/migrations/002-task-hierarchy-and-dependencies.js +42 -0
  285. package/dist/db/migrations/002-task-hierarchy-and-dependencies.js.map +1 -0
  286. package/dist/db/migrations/003-comments-and-estimates.d.ts +3 -0
  287. package/dist/db/migrations/003-comments-and-estimates.js +36 -0
  288. package/dist/db/migrations/003-comments-and-estimates.js.map +1 -0
  289. package/dist/db/migrations/004-claim-protocol.d.ts +3 -0
  290. package/dist/db/migrations/004-claim-protocol.js +41 -0
  291. package/dist/db/migrations/004-claim-protocol.js.map +1 -0
  292. package/dist/db/migrations/005-backlogged-status.d.ts +3 -0
  293. package/dist/db/migrations/005-backlogged-status.js +156 -0
  294. package/dist/db/migrations/005-backlogged-status.js.map +1 -0
  295. package/dist/db/migrations/006-slack-channel-subscriptions.d.ts +3 -0
  296. package/dist/db/migrations/006-slack-channel-subscriptions.js +23 -0
  297. package/dist/db/migrations/006-slack-channel-subscriptions.js.map +1 -0
  298. package/dist/db/migrations/007-completed-at.d.ts +19 -0
  299. package/dist/db/migrations/007-completed-at.js +36 -0
  300. package/dist/db/migrations/007-completed-at.js.map +1 -0
  301. package/dist/db/migrations/008-identity-tables.d.ts +21 -0
  302. package/dist/db/migrations/008-identity-tables.js +84 -0
  303. package/dist/db/migrations/008-identity-tables.js.map +1 -0
  304. package/dist/db/migrations/009-parallel-fk-columns.d.ts +33 -0
  305. package/dist/db/migrations/009-parallel-fk-columns.js +62 -0
  306. package/dist/db/migrations/009-parallel-fk-columns.js.map +1 -0
  307. package/dist/db/migrations/010-identity-uniqueness-indexes.d.ts +47 -0
  308. package/dist/db/migrations/010-identity-uniqueness-indexes.js +65 -0
  309. package/dist/db/migrations/010-identity-uniqueness-indexes.js.map +1 -0
  310. package/dist/db/migrations/011-acceptance-criteria.d.ts +30 -0
  311. package/dist/db/migrations/011-acceptance-criteria.js +41 -0
  312. package/dist/db/migrations/011-acceptance-criteria.js.map +1 -0
  313. package/dist/db/migrations/012-verification-evidence.d.ts +41 -0
  314. package/dist/db/migrations/012-verification-evidence.js +49 -0
  315. package/dist/db/migrations/012-verification-evidence.js.map +1 -0
  316. package/dist/events/event-bus.d.ts +101 -0
  317. package/dist/events/event-bus.js +184 -0
  318. package/dist/events/event-bus.js.map +1 -0
  319. package/dist/events/sse-manager.d.ts +79 -0
  320. package/dist/events/sse-manager.js +220 -0
  321. package/dist/events/sse-manager.js.map +1 -0
  322. package/dist/events/types.d.ts +43 -0
  323. package/dist/events/types.js +22 -0
  324. package/dist/events/types.js.map +1 -0
  325. package/dist/index.d.ts +110 -0
  326. package/dist/index.js +209 -0
  327. package/dist/index.js.map +1 -0
  328. package/dist/lib/audit/schema.d.ts +201 -0
  329. package/dist/lib/audit/schema.js +141 -0
  330. package/dist/lib/audit/schema.js.map +1 -0
  331. package/dist/lib/decompose/schema.d.ts +157 -0
  332. package/dist/lib/decompose/schema.js +138 -0
  333. package/dist/lib/decompose/schema.js.map +1 -0
  334. package/dist/lib/loop-run/integration-audit-schema.d.ts +78 -0
  335. package/dist/lib/loop-run/integration-audit-schema.js +68 -0
  336. package/dist/lib/loop-run/integration-audit-schema.js.map +1 -0
  337. package/dist/lib/loop-run/schema.d.ts +49 -0
  338. package/dist/lib/loop-run/schema.js +67 -0
  339. package/dist/lib/loop-run/schema.js.map +1 -0
  340. package/dist/mcp/errors.d.ts +11 -0
  341. package/dist/mcp/errors.js +29 -0
  342. package/dist/mcp/errors.js.map +1 -0
  343. package/dist/mcp/identity-resolution.d.ts +76 -0
  344. package/dist/mcp/identity-resolution.js +189 -0
  345. package/dist/mcp/identity-resolution.js.map +1 -0
  346. package/dist/mcp/index.d.ts +1 -0
  347. package/dist/mcp/index.js +126 -0
  348. package/dist/mcp/index.js.map +1 -0
  349. package/dist/mcp/remote/index.d.ts +21 -0
  350. package/dist/mcp/remote/index.js +95 -0
  351. package/dist/mcp/remote/index.js.map +1 -0
  352. package/dist/mcp/remote/register-tools.d.ts +26 -0
  353. package/dist/mcp/remote/register-tools.js +751 -0
  354. package/dist/mcp/remote/register-tools.js.map +1 -0
  355. package/dist/mcp/remote/rest-client.d.ts +66 -0
  356. package/dist/mcp/remote/rest-client.js +300 -0
  357. package/dist/mcp/remote/rest-client.js.map +1 -0
  358. package/dist/mcp/resources/events.d.ts +28 -0
  359. package/dist/mcp/resources/events.js +98 -0
  360. package/dist/mcp/resources/events.js.map +1 -0
  361. package/dist/mcp/server.d.ts +59 -0
  362. package/dist/mcp/server.js +72 -0
  363. package/dist/mcp/server.js.map +1 -0
  364. package/dist/mcp/tools/comment-tools.d.ts +12 -0
  365. package/dist/mcp/tools/comment-tools.js +115 -0
  366. package/dist/mcp/tools/comment-tools.js.map +1 -0
  367. package/dist/mcp/tools/dependency-tools.d.ts +3 -0
  368. package/dist/mcp/tools/dependency-tools.js +91 -0
  369. package/dist/mcp/tools/dependency-tools.js.map +1 -0
  370. package/dist/mcp/tools/health-tools.d.ts +9 -0
  371. package/dist/mcp/tools/health-tools.js +82 -0
  372. package/dist/mcp/tools/health-tools.js.map +1 -0
  373. package/dist/mcp/tools/project-tools.d.ts +13 -0
  374. package/dist/mcp/tools/project-tools.js +167 -0
  375. package/dist/mcp/tools/project-tools.js.map +1 -0
  376. package/dist/mcp/tools/task-tools.d.ts +41 -0
  377. package/dist/mcp/tools/task-tools.js +434 -0
  378. package/dist/mcp/tools/task-tools.js.map +1 -0
  379. package/dist/mcp/tools/topology-tools.d.ts +14 -0
  380. package/dist/mcp/tools/topology-tools.js +46 -0
  381. package/dist/mcp/tools/topology-tools.js.map +1 -0
  382. package/dist/repositories/api-token.repository.d.ts +40 -0
  383. package/dist/repositories/api-token.repository.js +63 -0
  384. package/dist/repositories/api-token.repository.js.map +1 -0
  385. package/dist/repositories/comment.repository.d.ts +17 -0
  386. package/dist/repositories/comment.repository.js +73 -0
  387. package/dist/repositories/comment.repository.js.map +1 -0
  388. package/dist/repositories/dependency.repository.d.ts +19 -0
  389. package/dist/repositories/dependency.repository.js +55 -0
  390. package/dist/repositories/dependency.repository.js.map +1 -0
  391. package/dist/repositories/errors.d.ts +29 -0
  392. package/dist/repositories/errors.js +48 -0
  393. package/dist/repositories/errors.js.map +1 -0
  394. package/dist/repositories/interfaces.d.ts +200 -0
  395. package/dist/repositories/interfaces.js +2 -0
  396. package/dist/repositories/interfaces.js.map +1 -0
  397. package/dist/repositories/project.repository.d.ts +21 -0
  398. package/dist/repositories/project.repository.js +97 -0
  399. package/dist/repositories/project.repository.js.map +1 -0
  400. package/dist/repositories/row-mapper.d.ts +40 -0
  401. package/dist/repositories/row-mapper.js +38 -0
  402. package/dist/repositories/row-mapper.js.map +1 -0
  403. package/dist/repositories/task.repository.d.ts +45 -0
  404. package/dist/repositories/task.repository.js +612 -0
  405. package/dist/repositories/task.repository.js.map +1 -0
  406. package/dist/repositories/types.d.ts +20 -0
  407. package/dist/repositories/types.js +11 -0
  408. package/dist/repositories/types.js.map +1 -0
  409. package/dist/repositories/user.repository.d.ts +121 -0
  410. package/dist/repositories/user.repository.js +209 -0
  411. package/dist/repositories/user.repository.js.map +1 -0
  412. package/dist/schemas/comment.schema.d.ts +24 -0
  413. package/dist/schemas/comment.schema.js +27 -0
  414. package/dist/schemas/comment.schema.js.map +1 -0
  415. package/dist/schemas/dependency-graph.schema.d.ts +181 -0
  416. package/dist/schemas/dependency-graph.schema.js +98 -0
  417. package/dist/schemas/dependency-graph.schema.js.map +1 -0
  418. package/dist/schemas/dependency.schema.d.ts +9 -0
  419. package/dist/schemas/dependency.schema.js +16 -0
  420. package/dist/schemas/dependency.schema.js.map +1 -0
  421. package/dist/schemas/idempotency.schema.d.ts +18 -0
  422. package/dist/schemas/idempotency.schema.js +22 -0
  423. package/dist/schemas/idempotency.schema.js.map +1 -0
  424. package/dist/schemas/task.schema.d.ts +369 -0
  425. package/dist/schemas/task.schema.js +276 -0
  426. package/dist/schemas/task.schema.js.map +1 -0
  427. package/dist/schemas/topology.schema.d.ts +56 -0
  428. package/dist/schemas/topology.schema.js +48 -0
  429. package/dist/schemas/topology.schema.js.map +1 -0
  430. package/dist/services/auth-audit.d.ts +46 -0
  431. package/dist/services/auth-audit.js +28 -0
  432. package/dist/services/auth-audit.js.map +1 -0
  433. package/dist/services/claim-release.service.d.ts +42 -0
  434. package/dist/services/claim-release.service.js +90 -0
  435. package/dist/services/claim-release.service.js.map +1 -0
  436. package/dist/services/comment.service.d.ts +44 -0
  437. package/dist/services/comment.service.js +96 -0
  438. package/dist/services/comment.service.js.map +1 -0
  439. package/dist/services/dependency-graph.service.d.ts +33 -0
  440. package/dist/services/dependency-graph.service.js +453 -0
  441. package/dist/services/dependency-graph.service.js.map +1 -0
  442. package/dist/services/dependency.service.d.ts +32 -0
  443. package/dist/services/dependency.service.js +79 -0
  444. package/dist/services/dependency.service.js.map +1 -0
  445. package/dist/services/device-flow-store.d.ts +155 -0
  446. package/dist/services/device-flow-store.js +323 -0
  447. package/dist/services/device-flow-store.js.map +1 -0
  448. package/dist/services/errors.d.ts +28 -0
  449. package/dist/services/errors.js +44 -0
  450. package/dist/services/errors.js.map +1 -0
  451. package/dist/services/idempotency.service.d.ts +37 -0
  452. package/dist/services/idempotency.service.js +54 -0
  453. package/dist/services/idempotency.service.js.map +1 -0
  454. package/dist/services/identity-seeder.d.ts +56 -0
  455. package/dist/services/identity-seeder.js +131 -0
  456. package/dist/services/identity-seeder.js.map +1 -0
  457. package/dist/services/oidc-boot.d.ts +73 -0
  458. package/dist/services/oidc-boot.js +66 -0
  459. package/dist/services/oidc-boot.js.map +1 -0
  460. package/dist/services/oidc-client.d.ts +99 -0
  461. package/dist/services/oidc-client.js +108 -0
  462. package/dist/services/oidc-client.js.map +1 -0
  463. package/dist/services/pat-hash.d.ts +23 -0
  464. package/dist/services/pat-hash.js +73 -0
  465. package/dist/services/pat-hash.js.map +1 -0
  466. package/dist/services/pat-touch-debounce.d.ts +65 -0
  467. package/dist/services/pat-touch-debounce.js +82 -0
  468. package/dist/services/pat-touch-debounce.js.map +1 -0
  469. package/dist/services/project.service.d.ts +41 -0
  470. package/dist/services/project.service.js +133 -0
  471. package/dist/services/project.service.js.map +1 -0
  472. package/dist/services/slack.service.d.ts +31 -0
  473. package/dist/services/slack.service.js +52 -0
  474. package/dist/services/slack.service.js.map +1 -0
  475. package/dist/services/task.service.d.ts +151 -0
  476. package/dist/services/task.service.js +425 -0
  477. package/dist/services/task.service.js.map +1 -0
  478. package/dist/services/topology.service.d.ts +65 -0
  479. package/dist/services/topology.service.js +170 -0
  480. package/dist/services/topology.service.js.map +1 -0
  481. package/dist/services/user-upsert.d.ts +43 -0
  482. package/dist/services/user-upsert.js +53 -0
  483. package/dist/services/user-upsert.js.map +1 -0
  484. package/dist/services/workflow-engine.d.ts +93 -0
  485. package/dist/services/workflow-engine.js +250 -0
  486. package/dist/services/workflow-engine.js.map +1 -0
  487. package/dist/slack/commands/tasks-command.d.ts +88 -0
  488. package/dist/slack/commands/tasks-command.js +920 -0
  489. package/dist/slack/commands/tasks-command.js.map +1 -0
  490. package/dist/slack/formatters/project-formatter.d.ts +19 -0
  491. package/dist/slack/formatters/project-formatter.js +94 -0
  492. package/dist/slack/formatters/project-formatter.js.map +1 -0
  493. package/dist/slack/notifier.d.ts +41 -0
  494. package/dist/slack/notifier.js +111 -0
  495. package/dist/slack/notifier.js.map +1 -0
  496. package/dist/slack/repositories/channel-subscription.repository.d.ts +25 -0
  497. package/dist/slack/repositories/channel-subscription.repository.js +51 -0
  498. package/dist/slack/repositories/channel-subscription.repository.js.map +1 -0
  499. package/dist/slack/task-formatter.d.ts +31 -0
  500. package/dist/slack/task-formatter.js +151 -0
  501. package/dist/slack/task-formatter.js.map +1 -0
  502. package/dist/slack/user-identity.d.ts +37 -0
  503. package/dist/slack/user-identity.js +70 -0
  504. package/dist/slack/user-identity.js.map +1 -0
  505. package/dist/types/identity.d.ts +84 -0
  506. package/dist/types/identity.js +6 -0
  507. package/dist/types/identity.js.map +1 -0
  508. package/dist/types/task.d.ts +202 -0
  509. package/dist/types/task.js +17 -0
  510. package/dist/types/task.js.map +1 -0
  511. package/dist/utils/cycle-detector.d.ts +25 -0
  512. package/dist/utils/cycle-detector.js +86 -0
  513. package/dist/utils/cycle-detector.js.map +1 -0
  514. package/dist/utils/exit-codes.d.ts +64 -0
  515. package/dist/utils/exit-codes.js +57 -0
  516. package/dist/utils/exit-codes.js.map +1 -0
  517. package/dist/utils/is-main.d.ts +12 -0
  518. package/dist/utils/is-main.js +24 -0
  519. package/dist/utils/is-main.js.map +1 -0
  520. package/dist/utils/version.d.ts +1 -0
  521. package/dist/utils/version.js +22 -0
  522. package/dist/utils/version.js.map +1 -0
  523. package/dist/web/html.d.ts +73 -0
  524. package/dist/web/html.js +154 -0
  525. package/dist/web/html.js.map +1 -0
  526. package/dist/web/pages/device.d.ts +19 -0
  527. package/dist/web/pages/device.js +76 -0
  528. package/dist/web/pages/device.js.map +1 -0
  529. package/dist/web/pages/error.d.ts +6 -0
  530. package/dist/web/pages/error.js +23 -0
  531. package/dist/web/pages/error.js.map +1 -0
  532. package/dist/web/pages/login.d.ts +5 -0
  533. package/dist/web/pages/login.js +22 -0
  534. package/dist/web/pages/login.js.map +1 -0
  535. package/dist/web/pages/me.d.ts +9 -0
  536. package/dist/web/pages/me.js +37 -0
  537. package/dist/web/pages/me.js.map +1 -0
  538. package/dist/web/pages/tokens.d.ts +20 -0
  539. package/dist/web/pages/tokens.js +89 -0
  540. package/dist/web/pages/tokens.js.map +1 -0
  541. package/dist/web/session-constants.d.ts +21 -0
  542. package/dist/web/session-constants.js +22 -0
  543. package/dist/web/session-constants.js.map +1 -0
  544. package/dist/web/session-flash.d.ts +15 -0
  545. package/dist/web/session-flash.js +11 -0
  546. package/dist/web/session-flash.js.map +1 -0
  547. package/dist/web/session-user.d.ts +59 -0
  548. package/dist/web/session-user.js +44 -0
  549. package/dist/web/session-user.js.map +1 -0
  550. package/docs/AGENT_CONTEXT.md +280 -0
  551. package/docs/README.md +49 -0
  552. package/llms.txt +33 -0
  553. package/package.json +129 -0
@@ -0,0 +1,300 @@
1
+ import fp from 'fastify-plugin';
2
+ import { parseApiKeyEntries, config, } from '../../../config/env.js';
3
+ import { validateApiKeysForProduction } from './keys.js';
4
+ import { logAuthFailure, } from '../../../services/auth-audit.js';
5
+ import { tryAuth as tryPat } from './strategies/pat.js';
6
+ import { tryAuth as trySession } from './strategies/session.js';
7
+ import { tryAuth as tryLegacy, precomputeHashedEntries, } from './strategies/legacy.js';
8
+ import { shouldTouchLastUsed } from '../../../services/pat-touch-debounce.js';
9
+ /**
10
+ * Throws if `preHandler` has not run yet (or if `skipAuth` was set). Use in
11
+ * route handlers / tool helpers that need a non-null `request.user`. The
12
+ * type narrowing is the entire point — once `requireUser` returns, the
13
+ * caller has an `AuthenticatedUser` without further null checks.
14
+ *
15
+ * CR-01 (Phase 30 review) — belt-and-suspenders: check for BOTH `null`
16
+ * (the initialized default via `decorateRequest('user', null)`) AND
17
+ * `undefined` (the value the slot holds when the route was registered
18
+ * OUTSIDE any scope that ran the auth-chain plugin — e.g. a top-level
19
+ * device-flow route mounted as a sibling of the `/api/v1` scope). The
20
+ * scope-wiring fix in server.ts addresses the production wiring, but
21
+ * leaving the guard narrow would silently re-open the bug if a future
22
+ * refactor moved a sessionOnly route outside the chain again.
23
+ */
24
+ export function requireUser(request) {
25
+ if (request.user === null || request.user === undefined) {
26
+ throw new Error('requireUser: request.user is null/undefined — auth preHandler did ' +
27
+ 'not run, or the route is registered with config.skipAuth=true, ' +
28
+ 'or the route is outside any scope that registered the auth chain.');
29
+ }
30
+ return request.user;
31
+ }
32
+ /**
33
+ * Apply a successful strategy outcome to the request: populate principal
34
+ * slots, then re-child the request logger so every subsequent
35
+ * `request.log.*()` call carries audit fields.
36
+ *
37
+ * `apiKeyLabel` is passed through into the bindings even on non-legacy paths
38
+ * (where it stays `undefined`). This matches the codebase precedent at
39
+ * the pre-split auth.ts:215-216 and the RESEARCH §8 pattern.
40
+ */
41
+ function applyPrincipal(request, result, apiKeyLabel) {
42
+ request.user = result.user;
43
+ request.authMethod = result.authMethod;
44
+ request.tokenId = result.tokenId;
45
+ if (apiKeyLabel !== undefined) {
46
+ request.apiKeyLabel = apiKeyLabel;
47
+ }
48
+ request.log = request.log.child({
49
+ user_id: result.user.id,
50
+ token_id: result.tokenId,
51
+ auth_method: result.authMethod,
52
+ apiKeyLabel: request.apiKeyLabel,
53
+ });
54
+ }
55
+ /**
56
+ * Post-auth gate: if the matched route declares `config.sessionOnly: true`
57
+ * AND the active auth method is NOT 'session', return 403. This is the only
58
+ * place "PATs cannot mint PATs" is enforced — Plan 5 registers the
59
+ * `/me/tokens` routes with the flag set.
60
+ *
61
+ * Returns `true` when the gate fired and the reply has been sent; the caller
62
+ * MUST stop processing.
63
+ */
64
+ function enforceSessionOnly(request, reply) {
65
+ if (request.routeOptions.config.sessionOnly === true &&
66
+ request.authMethod !== 'session') {
67
+ reply.code(403).send({
68
+ error: 'session_required',
69
+ message: 'This endpoint cannot be called with a Personal Access Token.',
70
+ });
71
+ return true;
72
+ }
73
+ return false;
74
+ }
75
+ /**
76
+ * Schedule the best-effort `last_used_at` update for a freshly-matched PAT.
77
+ *
78
+ * Plan 28-06 gates the write through the in-process debounce module
79
+ * (`pat-touch-debounce.ts`) so each token id triggers at most one SQL UPDATE
80
+ * per 10-minute window — satisfying REQUIREMENTS.md PAT-03's
81
+ * "≤ 1 write / 10 min / token" cap. No-op for non-PAT matches
82
+ * (tokenId === null) or when the gate returns `false` (recent write within
83
+ * window).
84
+ *
85
+ * better-sqlite3 is synchronous, so `touchLastUsed` returns `void` — wrap in
86
+ * try/catch (no `.catch(...)` on a void return). Errors are warn-logged but
87
+ * NEVER bubble up.
88
+ */
89
+ function scheduleLastUsedTouch(fastify, tokenId, log) {
90
+ if (tokenId === null)
91
+ return;
92
+ if (!shouldTouchLastUsed(tokenId))
93
+ return;
94
+ setImmediate(() => {
95
+ try {
96
+ fastify.apiTokenRepository.touchLastUsed(tokenId);
97
+ }
98
+ catch (err) {
99
+ log.warn({ err, tokenId }, 'touchLastUsed failed');
100
+ }
101
+ });
102
+ }
103
+ /**
104
+ * Send a uniform 401 response after emitting the categorical audit log.
105
+ *
106
+ * Returns `void` so callers `return sendUnauthorized(...)` cleanly from the
107
+ * preHandler hook. The body is intentionally minimal — Threat T-28-04-02
108
+ * keeps `reasonCode` off the wire so callers can't probe distinct failure
109
+ * modes.
110
+ */
111
+ function sendUnauthorized(request, reply, strategy, reasonCode) {
112
+ logAuthFailure(request.log, {
113
+ strategy,
114
+ reasonCode,
115
+ requestId: request.id,
116
+ peerIp: request.ip,
117
+ });
118
+ reply.code(401).send({
119
+ error: 'UNAUTHORIZED',
120
+ message: 'Authentication required',
121
+ });
122
+ }
123
+ /**
124
+ * Strategy chain threw (DB locked, connection lost, prepared-statement
125
+ * compile error from a runtime migration, etc.). Surface as a categorical
126
+ * 500 — explicitly NOT a 401, because pretending auth failed would
127
+ * mis-route operators and make a degraded DB indistinguishable from a
128
+ * brute-force probe. Two log lines:
129
+ *
130
+ * 1. `auth.error` (request.log.error) — carries the underlying `err`
131
+ * object for postmortem.
132
+ * 2. `auth.failure` warn line via logAuthFailure — keeps the audit feed
133
+ * aware that an authentication attempt did NOT succeed during the
134
+ * outage window. Reuses the Phase 27 AuthFailureReason enum
135
+ * ('unknown_token') so we don't widen the enum just to carry an
136
+ * operational signal; the `auth.error` line above is where the
137
+ * diagnostics live.
138
+ *
139
+ * Response body is `{ error: 'INTERNAL_ERROR' }` — no `reasonCode`, no
140
+ * stack, no token-shaped data. Threat T-28-04-02 still applies.
141
+ *
142
+ * WR-01 (Phase 28 review).
143
+ */
144
+ function sendInternalError(request, reply, err) {
145
+ request.log.error({ err, requestId: request.id }, 'auth.error');
146
+ logAuthFailure(request.log, {
147
+ strategy: 'legacy',
148
+ reasonCode: 'unknown_token',
149
+ requestId: request.id,
150
+ peerIp: request.ip,
151
+ });
152
+ reply.code(500).send({ error: 'INTERNAL_ERROR' });
153
+ }
154
+ const authChainImpl = async (fastify) => {
155
+ // Parse and (in production) validate API_KEYS at register time so a
156
+ // misconfigured prod boot fails fast. Same fail-fast semantics as the
157
+ // pre-split plugin — `validateApiKeysForProduction` throws synchronously
158
+ // and Fastify bubbles the error up to createServer which closes the
159
+ // server and disposes the App (server.ts:345-363 catch).
160
+ const entries = parseApiKeyEntries(process.env.API_KEYS);
161
+ const keys = entries.map((e) => e.key);
162
+ if (process.env.NODE_ENV === 'production') {
163
+ validateApiKeysForProduction(keys);
164
+ }
165
+ else if (entries.length === 0) {
166
+ fastify.log.warn('No API keys configured in API_KEYS env var. All API requests will be rejected.');
167
+ }
168
+ const hashedEntries = precomputeHashedEntries(entries);
169
+ // Decorators MUST land before any route registers in this scope. The fp()
170
+ // wrap below lifts these into the parent scope so sibling /api/v1/* routes
171
+ // see populated slots after a successful preHandler run.
172
+ fastify.decorateRequest('user', null);
173
+ fastify.decorateRequest('authMethod', null);
174
+ fastify.decorateRequest('tokenId', null);
175
+ // MIGR-01 compat: `apiKeyLabel` decoration retained so existing
176
+ // routes/tests (events.ts SSE fingerprinting, auth-logging.test.ts) keep
177
+ // working. Default `undefined` matches the pre-split contract.
178
+ fastify.decorateRequest('apiKeyLabel', undefined);
179
+ const patDeps = {
180
+ apiTokenRepository: fastify.apiTokenRepository,
181
+ userRepository: fastify.userRepository,
182
+ };
183
+ fastify.addHook('preHandler', async (request, reply) => {
184
+ // 0. Route-level skipAuth opt-out (defined but unused in Phase 28 —
185
+ // Plan 4 ships the flag for future Phase 29 /auth/login etc.).
186
+ if (request.routeOptions.config.skipAuth === true) {
187
+ return;
188
+ }
189
+ // WR-01 (Phase 28 review) — wrap the entire chain walk so that a
190
+ // throwing strategy (e.g. `apiTokenRepository.findByHash` raising
191
+ // because the DB is locked) becomes a categorical 500 with an
192
+ // `auth.error` log AND an `auth.failure` audit line, rather than
193
+ // a generic Fastify 500 that the audit aggregator never sees. We
194
+ // deliberately do NOT downgrade to 401 — a degraded DB should not
195
+ // look like a brute-force probe.
196
+ try {
197
+ // 1. PAT
198
+ const patOutcome = await tryPat(request, patDeps);
199
+ if (patOutcome.kind === 'fail') {
200
+ return sendUnauthorized(request, reply, 'pat', patOutcome.reasonCode);
201
+ }
202
+ if (patOutcome.kind === 'match') {
203
+ applyPrincipal(request, patOutcome.result);
204
+ scheduleLastUsedTouch(fastify, patOutcome.result.tokenId, request.log);
205
+ if (enforceSessionOnly(request, reply))
206
+ return;
207
+ return;
208
+ }
209
+ // 2. Session (Phase 29 — real implementation reads
210
+ // request.session.get('user') and re-validates against
211
+ // userRepository.findById; returns 'skip' when no session backend
212
+ // is registered (OIDC-disabled mode) so the legacy strategy still
213
+ // gets a chance.
214
+ const sessionOutcome = await trySession(request, {
215
+ userRepository: fastify.userRepository,
216
+ });
217
+ if (sessionOutcome.kind === 'fail') {
218
+ // Defensive — Phase 28 stub never returns fail. Keep the branch so
219
+ // Phase 29's swap doesn't need to add it.
220
+ return sendUnauthorized(request, reply, 'session', sessionOutcome.reasonCode);
221
+ }
222
+ if (sessionOutcome.kind === 'match') {
223
+ applyPrincipal(request, sessionOutcome.result);
224
+ if (enforceSessionOnly(request, reply))
225
+ return;
226
+ return;
227
+ }
228
+ // 3. Legacy API_KEYS
229
+ const legacyOutcome = await tryLegacy(request, {
230
+ userRepository: fastify.userRepository,
231
+ hashedEntries,
232
+ });
233
+ if (legacyOutcome.kind === 'fail') {
234
+ return sendUnauthorized(request, reply, 'legacy', legacyOutcome.reasonCode);
235
+ }
236
+ if (legacyOutcome.kind === 'match') {
237
+ applyPrincipal(request, legacyOutcome.result, legacyOutcome.label);
238
+ // Plan 31-05 (MIGR-02): emit one warn log per legacy-authed request
239
+ // so operators can grep their log feed for sunset-readiness reporting
240
+ // (`event: 'legacy_auth_used'`). The onSend hook below stamps the
241
+ // RFC 8594 Deprecation/Sunset headers; this line is the canonical
242
+ // audit signal — the headers are advisory to the client, the log
243
+ // is the operator-side source of truth.
244
+ request.log.warn({
245
+ event: 'legacy_auth_used',
246
+ userId: legacyOutcome.result.user.id,
247
+ apiKeyLabel: legacyOutcome.label,
248
+ requestId: request.id,
249
+ requestUrl: request.url,
250
+ sunset: config.LEGACY_AUTH_SUNSET_DATE,
251
+ }, 'legacy_auth_used');
252
+ if (enforceSessionOnly(request, reply))
253
+ return;
254
+ return;
255
+ }
256
+ // 4. Catch-all — no strategy saw a credential. Per Plan-04 Decision
257
+ // Q6, the audit log records `strategy: 'legacy', reasonCode:
258
+ // 'missing_credential'` so the failure mode matches the pre-split
259
+ // plugin's "missing X-API-Key" branch.
260
+ return sendUnauthorized(request, reply, 'legacy', 'missing_credential');
261
+ }
262
+ catch (err) {
263
+ return sendInternalError(request, reply, err);
264
+ }
265
+ });
266
+ // Plan 31-05 (MIGR-02): RFC 8594 Deprecation + Sunset response headers
267
+ // for every legacy-X-API-Key-authed request. Gated strictly on
268
+ // `request.authMethod === 'legacy'` so PAT, session, anonymous (skipAuth),
269
+ // and failed-auth responses NEVER carry the headers (Pitfall 4 in
270
+ // 31-RESEARCH §Common Pitfalls).
271
+ //
272
+ // Callback-style (4-arg) signature is used INTENTIONALLY rather than async
273
+ // — registering an async onSend hook inside this fp()-wrapped plugin
274
+ // delays `reply.sent` from becoming true synchronously when the preHandler
275
+ // calls `reply.send()` (e.g. from `enforceSessionOnly`'s 403). The
276
+ // me-tokens session-only tests then see the route handler run after the
277
+ // 403 reply was queued. The synchronous callback form keeps reply.send()
278
+ // synchronous, preserving the Phase 28 sessionOnly invariant.
279
+ fastify.addHook('onSend', (request, reply, payload, done) => {
280
+ if (request.authMethod === 'legacy') {
281
+ reply.header('Deprecation', 'true');
282
+ reply.header('Sunset', config.LEGACY_AUTH_SUNSET_DATE);
283
+ }
284
+ done(null, payload);
285
+ });
286
+ };
287
+ /**
288
+ * Wrap with fastify-plugin to escape the encapsulated scope. Without `fp()`
289
+ * the preHandler hook only fires for routes registered INSIDE this plugin —
290
+ * every sibling under `/api/v1/*` would bypass auth entirely. The
291
+ * `{ name: 'wft-auth', fastify: '5.x' }` options match the pre-split
292
+ * plugin's identity so any downstream `fastify.hasPlugin('wft-auth')`
293
+ * checks keep working.
294
+ */
295
+ const authChain = fp(authChainImpl, {
296
+ name: 'wft-auth',
297
+ fastify: '5.x',
298
+ });
299
+ export default authChain;
300
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/api/plugins/auth/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * SHA-256 hash of a key, returned as a 32-byte Buffer.
3
+ *
4
+ * Hashing both sides of the comparison guarantees `timingSafeEqual` receives
5
+ * equal-length buffers, eliminating the length-leak that arises from comparing
6
+ * raw keys of different lengths.
7
+ */
8
+ export declare function hashKey(key: string): Buffer;
9
+ /**
10
+ * Validate API keys for a production environment.
11
+ *
12
+ * Throws an Error listing every failure mode. The error message references
13
+ * keys by their 1-based index — it never includes the key value itself, so
14
+ * the error is safe to log.
15
+ *
16
+ * Rules:
17
+ * - At least one key must be present.
18
+ * - Every key must be at least 32 characters.
19
+ * - No key may contain a known placeholder substring (case-insensitive).
20
+ * - No key may equal a known placeholder value (lowercased).
21
+ * - No key may be a single character repeated (zero entropy).
22
+ */
23
+ export declare function validateApiKeysForProduction(keys: string[]): void;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * API key helpers — hashing + production validation.
3
+ *
4
+ * Extracted from `src/api/plugins/auth.ts` during the Phase 28 (Plan 28-04)
5
+ * chain split. The runtime behaviour of `hashKey` and
6
+ * `validateApiKeysForProduction` is byte-identical to the pre-split
7
+ * implementation; only the file location changed so the new chain plugin at
8
+ * `src/api/plugins/auth/index.ts` and the legacy strategy at
9
+ * `src/api/plugins/auth/strategies/legacy.ts` can import them without a
10
+ * circular reference back through the shim at `src/api/plugins/auth.ts`.
11
+ *
12
+ * The legacy shim re-exports both functions verbatim, so existing
13
+ * `import { hashKey } from '../plugins/auth.js'` callers (e.g. routes/events.ts,
14
+ * sse-caps.test.ts, the legacy strategy module) keep working unchanged.
15
+ */
16
+ import { createHash } from 'crypto';
17
+ /**
18
+ * Placeholder substrings rejected in production keys (case-insensitive contains check).
19
+ *
20
+ * Substring matches catch keys that embed an obvious placeholder phrase even if
21
+ * padded to satisfy the length floor (e.g. "change-me-to-a-real-keyxxxxxxxxxx").
22
+ */
23
+ const PLACEHOLDER_SUBSTRINGS = [
24
+ 'change-me-to-a-real-key',
25
+ 'changeme',
26
+ 'placeholder',
27
+ 'example',
28
+ ];
29
+ /**
30
+ * Placeholder values rejected in production keys (exact lowercase match).
31
+ *
32
+ * Exact matches catch the literal short placeholders the audit named without
33
+ * false-positive on legitimate keys that happen to include the chars "test"
34
+ * or "dev" elsewhere.
35
+ */
36
+ const PLACEHOLDER_EXACT = new Set(['test', 'dev', 'placeholder']);
37
+ /**
38
+ * Minimum length required for each API key when NODE_ENV=production.
39
+ * 32 characters gives ~190 bits of entropy if generated from a hex/base64 RNG.
40
+ */
41
+ const MIN_PRODUCTION_KEY_LENGTH = 32;
42
+ /**
43
+ * SHA-256 hash of a key, returned as a 32-byte Buffer.
44
+ *
45
+ * Hashing both sides of the comparison guarantees `timingSafeEqual` receives
46
+ * equal-length buffers, eliminating the length-leak that arises from comparing
47
+ * raw keys of different lengths.
48
+ */
49
+ export function hashKey(key) {
50
+ return createHash('sha256').update(key, 'utf8').digest();
51
+ }
52
+ /**
53
+ * Validate API keys for a production environment.
54
+ *
55
+ * Throws an Error listing every failure mode. The error message references
56
+ * keys by their 1-based index — it never includes the key value itself, so
57
+ * the error is safe to log.
58
+ *
59
+ * Rules:
60
+ * - At least one key must be present.
61
+ * - Every key must be at least 32 characters.
62
+ * - No key may contain a known placeholder substring (case-insensitive).
63
+ * - No key may equal a known placeholder value (lowercased).
64
+ * - No key may be a single character repeated (zero entropy).
65
+ */
66
+ export function validateApiKeysForProduction(keys) {
67
+ const errors = [];
68
+ if (keys.length === 0) {
69
+ errors.push('API_KEYS must contain at least one key (got empty list)');
70
+ }
71
+ for (let i = 0; i < keys.length; i++) {
72
+ const k = keys[i];
73
+ const idx = i + 1;
74
+ if (k.length === 0) {
75
+ errors.push(`key #${idx}: empty value`);
76
+ continue;
77
+ }
78
+ if (k.length < MIN_PRODUCTION_KEY_LENGTH) {
79
+ errors.push(`key #${idx}: must be at least ${MIN_PRODUCTION_KEY_LENGTH} characters (got ${k.length})`);
80
+ }
81
+ const lower = k.toLowerCase();
82
+ for (const phrase of PLACEHOLDER_SUBSTRINGS) {
83
+ if (lower.includes(phrase)) {
84
+ errors.push(`key #${idx}: contains placeholder phrase "${phrase}"`);
85
+ }
86
+ }
87
+ if (PLACEHOLDER_EXACT.has(lower)) {
88
+ errors.push(`key #${idx}: matches known placeholder value`);
89
+ }
90
+ if (new Set(k).size === 1) {
91
+ errors.push(`key #${idx}: single character repeated (no entropy)`);
92
+ }
93
+ }
94
+ if (errors.length > 0) {
95
+ throw new Error(`API_KEYS validation failed for production:\n - ${errors.join('\n - ')}\n` +
96
+ `Set API_KEYS to comma-separated keys of at least ${MIN_PRODUCTION_KEY_LENGTH} ` +
97
+ `characters each, with sufficient entropy and no placeholder phrases.`);
98
+ }
99
+ }
100
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../../src/api/plugins/auth/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;;;GAKG;AACH,MAAM,sBAAsB,GAAG;IAC7B,yBAAyB;IACzB,UAAU;IACV,aAAa;IACb,SAAS;CACV,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC;AAElE;;;GAGG;AACH,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAErC;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,4BAA4B,CAAC,IAAc;IACzD,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;IACzE,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QAElB,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnB,MAAM,CAAC,IAAI,CAAC,QAAQ,GAAG,eAAe,CAAC,CAAC;YACxC,SAAS;QACX,CAAC;QACD,IAAI,CAAC,CAAC,MAAM,GAAG,yBAAyB,EAAE,CAAC;YACzC,MAAM,CAAC,IAAI,CACT,QAAQ,GAAG,sBAAsB,yBAAyB,oBAAoB,CAAC,CAAC,MAAM,GAAG,CAC1F,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAC9B,KAAK,MAAM,MAAM,IAAI,sBAAsB,EAAE,CAAC;YAC5C,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3B,MAAM,CAAC,IAAI,CAAC,QAAQ,GAAG,kCAAkC,MAAM,GAAG,CAAC,CAAC;YACtE,CAAC;QACH,CAAC;QACD,IAAI,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC,QAAQ,GAAG,mCAAmC,CAAC,CAAC;QAC9D,CAAC;QACD,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,QAAQ,GAAG,0CAA0C,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,mDAAmD,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI;YAC1E,oDAAoD,yBAAyB,GAAG;YAChF,sEAAsE,CACzE,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,46 @@
1
+ import type { FastifyRequest } from 'fastify';
2
+ import type { UserRepository } from '../../../../repositories/user.repository.js';
3
+ import type { ApiKeyEntry } from '../../../../config/env.js';
4
+ import type { StrategyOutcome } from './types.js';
5
+ export interface LegacyDeps {
6
+ userRepository: UserRepository;
7
+ /**
8
+ * Pre-computed SHA-256 hashes of every configured API_KEYS entry. The
9
+ * chain plugin computes this once at register time via
10
+ * `precomputeHashedEntries` and passes it in so the strategy never
11
+ * re-hashes per request.
12
+ */
13
+ hashedEntries: Array<{
14
+ hash: Buffer;
15
+ label: string;
16
+ }>;
17
+ }
18
+ /**
19
+ * Pre-compute the SHA-256 hash + label for each parsed API_KEYS entry.
20
+ *
21
+ * Called once by the chain plugin at register time. The result feeds into
22
+ * `LegacyDeps.hashedEntries` for every subsequent request, avoiding
23
+ * per-request rehash of the configured key list.
24
+ */
25
+ export declare function precomputeHashedEntries(entries: ApiKeyEntry[]): Array<{
26
+ hash: Buffer;
27
+ label: string;
28
+ }>;
29
+ /**
30
+ * Inspect the request for a legacy `x-api-key` header and return the
31
+ * outcome.
32
+ *
33
+ * Decision tree:
34
+ * 1. No / empty `x-api-key` header → skip
35
+ * (chain catch-all wraps this as `missing_credential` in the audit
36
+ * log if no other strategy matched.)
37
+ * 2. Supplied hash compares equal to a configured one → look up legacy
38
+ * user via `findLegacyByDisplayName(label)`:
39
+ * - user row missing OR user.disabled_at set → fail/user_disabled
40
+ * - otherwise → match
41
+ * 3. Supplied hash equals NONE of the configured hashes → fail/unknown_token
42
+ *
43
+ * The strategy emits NO log lines — the chain owns audit logging via
44
+ * Phase 27's `logAuthFailure` helper.
45
+ */
46
+ export declare function tryAuth(request: FastifyRequest, deps: LegacyDeps): Promise<StrategyOutcome>;
@@ -0,0 +1,87 @@
1
+ // Legacy API_KEYS auth strategy (MIGR-01 break-glass).
2
+ //
3
+ // Behaviour ported nearly byte-for-byte from `src/api/plugins/auth.ts:170-216`
4
+ // (the existing inline preHandler that this strategy will replace once the
5
+ // chain plugin lands in Plan 28-04). The boundary changes are deliberate:
6
+ //
7
+ // - Returns a categorical `StrategyOutcome` instead of mutating
8
+ // `request.apiKeyLabel` / `request.log` and sending a 401. The chain
9
+ // plugin (Plan 28-04) applies the outcome.
10
+ // - On a successful hash match, performs the additional
11
+ // `userRepository.findLegacyByDisplayName(label)` lookup so the
12
+ // chain can populate `request.user` from a real `users` row (the
13
+ // legacy identity seeded by Phase 27's identity-seeder service).
14
+ //
15
+ // The constant-time comparison loop is preserved verbatim — every
16
+ // configured hash is compared against the supplied hash regardless of
17
+ // where (or whether) a match occurs. This is the timing-attack defence
18
+ // originally documented at `src/api/plugins/auth.ts:198` and re-asserted
19
+ // by the unit test `does NOT short-circuit on first match`.
20
+ import { timingSafeEqual } from 'node:crypto';
21
+ import { hashKey } from '../keys.js';
22
+ import { toAuthenticatedUser } from './pat.js';
23
+ /**
24
+ * Pre-compute the SHA-256 hash + label for each parsed API_KEYS entry.
25
+ *
26
+ * Called once by the chain plugin at register time. The result feeds into
27
+ * `LegacyDeps.hashedEntries` for every subsequent request, avoiding
28
+ * per-request rehash of the configured key list.
29
+ */
30
+ export function precomputeHashedEntries(entries) {
31
+ return entries.map((e) => ({ hash: hashKey(e.key), label: e.label }));
32
+ }
33
+ /**
34
+ * Inspect the request for a legacy `x-api-key` header and return the
35
+ * outcome.
36
+ *
37
+ * Decision tree:
38
+ * 1. No / empty `x-api-key` header → skip
39
+ * (chain catch-all wraps this as `missing_credential` in the audit
40
+ * log if no other strategy matched.)
41
+ * 2. Supplied hash compares equal to a configured one → look up legacy
42
+ * user via `findLegacyByDisplayName(label)`:
43
+ * - user row missing OR user.disabled_at set → fail/user_disabled
44
+ * - otherwise → match
45
+ * 3. Supplied hash equals NONE of the configured hashes → fail/unknown_token
46
+ *
47
+ * The strategy emits NO log lines — the chain owns audit logging via
48
+ * Phase 27's `logAuthFailure` helper.
49
+ */
50
+ export async function tryAuth(request, deps) {
51
+ const supplied = request.headers['x-api-key'];
52
+ if (typeof supplied !== 'string' || supplied.length === 0) {
53
+ return { kind: 'skip' };
54
+ }
55
+ const suppliedHash = hashKey(supplied);
56
+ // Constant-time compare against EVERY configured hash. Do NOT break on
57
+ // first match — keeping the comparison count fixed prevents leaking the
58
+ // match position (or the existence of any match at all) via wall-clock
59
+ // timing. Preserved verbatim from src/api/plugins/auth.ts:194-200.
60
+ let matchedLabel;
61
+ for (const entry of deps.hashedEntries) {
62
+ if (timingSafeEqual(entry.hash, suppliedHash)) {
63
+ matchedLabel = entry.label;
64
+ // Do NOT break — keeps total comparison count fixed.
65
+ }
66
+ }
67
+ if (matchedLabel === undefined) {
68
+ return { kind: 'fail', reasonCode: 'unknown_token' };
69
+ }
70
+ // Match — resolve the legacy `users` row Phase 27 seeded so the chain
71
+ // can populate `request.user` with a real principal. `is_legacy=1` is
72
+ // already enforced inside the repository query.
73
+ const user = deps.userRepository.findLegacyByDisplayName(matchedLabel);
74
+ if (user === null || user.disabled_at !== null) {
75
+ return { kind: 'fail', reasonCode: 'user_disabled' };
76
+ }
77
+ return {
78
+ kind: 'match',
79
+ result: {
80
+ user: toAuthenticatedUser(user),
81
+ authMethod: 'legacy',
82
+ tokenId: null,
83
+ },
84
+ label: matchedLabel,
85
+ };
86
+ }
87
+ //# sourceMappingURL=legacy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy.js","sourceRoot":"","sources":["../../../../../src/api/plugins/auth/strategies/legacy.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,EAAE;AACF,+EAA+E;AAC/E,2EAA2E;AAC3E,0EAA0E;AAC1E,EAAE;AACF,kEAAkE;AAClE,yEAAyE;AACzE,+CAA+C;AAC/C,0DAA0D;AAC1D,oEAAoE;AACpE,qEAAqE;AACrE,qEAAqE;AACrE,EAAE;AACF,kEAAkE;AAClE,sEAAsE;AACtE,uEAAuE;AACvE,yEAAyE;AACzE,4DAA4D;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErC,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAc/C;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAsB;IAEtB,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;AACxE,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,OAAuB,EACvB,IAAgB;IAEhB,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9C,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEvC,uEAAuE;IACvE,wEAAwE;IACxE,uEAAuE;IACvE,mEAAmE;IACnE,IAAI,YAAgC,CAAC;IACrC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;QACvC,IAAI,eAAe,CAAC,KAAK,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,CAAC;YAC9C,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC;YAC3B,qDAAqD;QACvD,CAAC;IACH,CAAC;IAED,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC;IACvD,CAAC;IAED,sEAAsE;IACtE,sEAAsE;IACtE,gDAAgD;IAChD,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC,uBAAuB,CAAC,YAAY,CAAC,CAAC;IACvE,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC;IACvD,CAAC;IAED,OAAO;QACL,IAAI,EAAE,OAAO;QACb,MAAM,EAAE;YACN,IAAI,EAAE,mBAAmB,CAAC,IAAI,CAAC;YAC/B,UAAU,EAAE,QAAQ;YACpB,OAAO,EAAE,IAAI;SACd;QACD,KAAK,EAAE,YAAY;KACpB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { FastifyRequest } from 'fastify';
2
+ import type { ApiTokenRepository } from '../../../../repositories/api-token.repository.js';
3
+ import type { UserRepository } from '../../../../repositories/user.repository.js';
4
+ import type { AuthenticatedUser, User } from '../../../../types/identity.js';
5
+ import type { StrategyOutcome } from './types.js';
6
+ export interface PatDeps {
7
+ apiTokenRepository: ApiTokenRepository;
8
+ userRepository: UserRepository;
9
+ }
10
+ /**
11
+ * Map a snake_case `users` row to the camelCase boundary projection that
12
+ * the auth chain populates on `request.user`.
13
+ *
14
+ * SQLite returns booleans as INTEGER (0|1); the projection normalises to
15
+ * the boolean type the downstream consumers expect. Exported so the legacy
16
+ * strategy can share the conversion in Task 3.
17
+ */
18
+ export declare function toAuthenticatedUser(user: User): AuthenticatedUser;
19
+ /**
20
+ * Inspect the request for a PAT credential and return the outcome.
21
+ *
22
+ * Decision tree:
23
+ * 1. No Authorization header → skip
24
+ * 2. Authorization doesn't start with `Bearer ` → skip
25
+ * 3. Bearer body doesn't start with `wft_pat_` → skip (legacy may try)
26
+ * 4. PAT body shape wrong (length / charset) → fail/wrong_prefix
27
+ * 5. findByHash returns null → fail/unknown_token
28
+ * 6. row.revoked_at is set → fail/revoked
29
+ * 7. row.expires_at is past wall-clock now → fail/expired
30
+ * 8. user row missing OR user.disabled_at set → fail/user_disabled
31
+ * 9. all checks pass → match
32
+ *
33
+ * No side effects: no log writes, no last_used_at update — the chain (Plan
34
+ * 4) does both centrally after a strategy returns. Keeps this function
35
+ * pure and unit-testable without a Fastify instance.
36
+ */
37
+ export declare function tryAuth(request: FastifyRequest, deps: PatDeps): Promise<StrategyOutcome>;