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
package/SECURITY.md ADDED
@@ -0,0 +1,299 @@
1
+ # Security Policy
2
+
3
+ For repository structure and agent-context entry, see [`AGENTS.md`](AGENTS.md).
4
+
5
+ We take the security of wood-fired-tasks seriously. This document explains
6
+ which versions receive security fixes, how to report a vulnerability, and
7
+ what is in scope.
8
+
9
+ ## Supported Versions
10
+
11
+ Only the current `main` branch and the most recent tagged release receive
12
+ security updates. Older tags are provided as-is.
13
+
14
+ | Version | Supported |
15
+ | ----------------- | ------------------ |
16
+ | `main` (HEAD) | :white_check_mark: |
17
+ | `v1.12` (latest) | :white_check_mark: |
18
+ | `v1.0` – `v1.11` | :x: |
19
+
20
+ "Latest" tracks whichever tag is most recent on GitHub; at the time of
21
+ writing that is `v1.12`. If you are reading this on an older checkout,
22
+ verify the current latest release via
23
+ `git tag --sort=-creatordate | head -1` or the GitHub Releases page.
24
+
25
+ ## Reporting a Vulnerability
26
+
27
+ **Preferred:** open a private report via GitHub Security Advisories:
28
+
29
+ https://github.com/Wood-Fired-Games/wood-fired-tasks/security/advisories/new
30
+
31
+ **Fallback:** email `security@woodfiredgames.com` with steps to reproduce,
32
+ affected version/commit, and the impact you observed. Please do not file
33
+ public GitHub issues for suspected vulnerabilities.
34
+
35
+ We will:
36
+
37
+ - Acknowledge your report within **5 business days**.
38
+ - Aim to ship a fix or documented workaround within **30 days** for issues
39
+ rated high or critical. Lower-severity issues are batched into the next
40
+ routine release.
41
+ - Credit reporters in the release notes unless you ask us not to.
42
+
43
+ ## Scope
44
+
45
+ **In scope:**
46
+
47
+ - The Fastify REST API (TypeScript, Node ≥22) under `src/api/` — routes,
48
+ plugins (auth, rate-limit, SSE), and request/response validation.
49
+ - The MCP server under `src/mcp/` — both transports: the **stdio** server
50
+ (`npm run mcp:start` / `npm run mcp:dev` / installed Claude Code stdio
51
+ target) and the **remote HTTP** server (`npm run mcp:remote`), including
52
+ its tool implementations and prompt/resource handlers.
53
+ - The `tasks` CLI under `src/cli/` — command parsers, HTTP client, and the
54
+ small set of offline subcommands that touch SQLite directly
55
+ (`backup`, `doctor`, `stats`, `db-check`, `completed`).
56
+ - The Slack integration under `src/slack/` — Bolt subprocess, slash-command
57
+ handlers, signed-request verification, and the EventBus → Slack notifier
58
+ path.
59
+ - The shared service / repository / workflow layer under `src/services/`,
60
+ `src/repositories/`, and `src/events/` that all four entry points sit on
61
+ top of.
62
+
63
+ **Out of scope:**
64
+
65
+ - Third-party dependencies — please report those directly upstream
66
+ (e.g. Fastify, `@slack/bolt`, `@modelcontextprotocol/sdk`,
67
+ `better-sqlite3`, `commander`, `zod`).
68
+ - User-side customizations layered on top of the project, including
69
+ custom auth proxies in front of the API, self-hosted reverse proxies,
70
+ or forked deployments with modified middleware.
71
+ - Findings from automated scanners (SAST/DAST/dependency CVE noise)
72
+ submitted without a working proof-of-concept against this codebase.
73
+
74
+ ## What We Consider Security-Relevant
75
+
76
+ Issues we will prioritize include, but are not limited to:
77
+
78
+ - Authentication bypass on any endpoint — reaching a `/api/v1` route
79
+ without a valid PAT, session, or `X-API-Key` credential, or bypassing
80
+ the SSE auth path. (Note: there is no separate authorization layer to
81
+ bypass — see "Authentication Is Not Authorization" below. Any valid
82
+ credential is already full-access.)
83
+ - Secrets exposure (API keys, Slack tokens, `.env` leakage, log
84
+ scrubbing gaps in pino redaction, Slack signing-secret disclosure).
85
+ - SQL injection or FTS5 injection in task/comment/project queries
86
+ (better-sqlite3 prepared statements, search filters, sort/order
87
+ parameters).
88
+ - Server-Side Request Forgery (SSRF) in any outbound HTTP call.
89
+ - Prompt-injection vectors via MCP tool descriptions, task fields,
90
+ comment bodies, or resource contents that cause an MCP client to
91
+ take unintended action.
92
+ - Signature-verification bypass on the Slack webhook / events endpoint,
93
+ or replay of signed Slack requests.
94
+ - Anything that allows **unauthenticated** mutation of tasks, projects,
95
+ comments, dependencies, or Slack channel subscriptions — i.e. mutating
96
+ state without presenting any valid credential, or escalating
97
+ read-only access to write access on either MCP transport. (Mutation by
98
+ an *authenticated* identity is by design — every credential is
99
+ full-access; see "Authentication Is Not Authorization".)
100
+
101
+ Thank you for helping keep wood-fired-tasks and its users safe.
102
+
103
+ ## Authentication Architecture
104
+
105
+ As of v1.6, the REST API supports three authentication strategies, tried
106
+ in order by a Fastify chain plugin (`src/api/plugins/auth/index.ts`). The
107
+ first strategy that produces a valid `request.user` wins; the request
108
+ proceeds with that user's id stamped onto every write (`created_by_user_id`,
109
+ `assignee_user_id`, `author_user_id`) and surfaced in the per-request audit
110
+ log (`user_id`, `token_id`, `auth_method`).
111
+
112
+ | Order | Strategy | Credential | Wire format |
113
+ |-------|----------|------------|-------------|
114
+ | 1 | **PAT (Personal Access Token)** | A token row in `api_tokens` | `Authorization: Bearer wft_pat_<…>` |
115
+ | 2 | **Session** | An OIDC-derived sealed-box session cookie | `Cookie: wft_session=<…>` |
116
+ | 3 | **Legacy** | An entry in the `API_KEYS` env list | `X-API-Key: <…>` |
117
+
118
+ The three strategies coexist intentionally — legacy keeps existing
119
+ deployments running while operators migrate; PAT is the recommended
120
+ machine credential; session is the recommended user credential.
121
+
122
+ ### PAT lifecycle
123
+
124
+ PATs are minted from a logged-in `/me` web session **or** offline via the
125
+ CLI (`tasks db mint-token --user <id|email|displayName> --name <label>`,
126
+ see [`docs/CLI.md`](docs/CLI.md)). The raw token value is shown **once at
127
+ mint time** — the database only stores a SHA-256 hash, so a lost PAT
128
+ cannot be recovered (only re-minted).
129
+
130
+ **PATs have no default expiry.** The `api_tokens.expires_at` column is
131
+ nullable and is left `NULL` unless you explicitly pass
132
+ `--expires-at <ISO-8601>` at mint time (e.g.
133
+ `tasks db mint-token --user alice@example.com --name ci-runner --expires-at 2027-05-22T00:00:00Z`).
134
+ A token with a NULL `expires_at` is valid until it is revoked. Because a
135
+ non-expiring credential never rotates itself, operators are responsible
136
+ for hygiene:
137
+
138
+ - **Rotate** by minting a replacement PAT (with a fresh `--expires-at`),
139
+ deploying it, then revoking the old one — one PAT per machine/agent so a
140
+ rotation never disturbs unrelated clients.
141
+ - **Revoke** explicitly via the `/me` UI, the `DELETE /me/tokens/:id`
142
+ endpoint, or `tasks logout` (revokes the active PAT and removes the
143
+ local credentials file). Revoked PATs are rejected immediately on the
144
+ next request — there is no cache.
145
+ - **Set an expiry** on every new PAT (`--expires-at`) so credentials age
146
+ out even if a manual revocation is forgotten. The expiry is enforced by
147
+ the PAT auth strategy: once `expires_at` is in the past the token fails
148
+ with `reasonCode: expired`.
149
+
150
+ The PAT prefix (`wft_pat_`) is part of the wire format: the remote MCP
151
+ server and the CLI HTTP client switch their auth header based on the
152
+ prefix, so the same env var (`WFT_API_KEY` for MCP, `API_KEY` for CLI)
153
+ transparently accepts a PAT or a legacy key.
154
+
155
+ ### Session lifecycle
156
+
157
+ OIDC sign-in (`/auth/login` → Google → `/auth/callback`) creates a
158
+ sealed-box-encrypted cookie containing the user id and a small set of
159
+ claims. The cookie:
160
+
161
+ - Uses `SESSION_COOKIE_SECRET` (32 bytes, generated via
162
+ `openssl rand -base64 32`) as the sodium sealed-box key.
163
+ - Has `maxAge=8h`, `httpOnly=true`, and `sameSite=lax`.
164
+ - Sets the `secure` attribute **only when `NODE_ENV=production`**
165
+ (`src/api/server.ts` — `secure: config.NODE_ENV === 'production'`).
166
+ - Has **no DB-side sessions table** — the cookie is self-contained.
167
+ Rotating `SESSION_COOKIE_SECRET` invalidates every active session
168
+ immediately because the existing cookies can no longer be decrypted.
169
+
170
+ > **Run production behind HTTPS — even on a LAN.** Because the cookie is
171
+ > flagged `secure` whenever `NODE_ENV=production`, a production server
172
+ > reached over plain `http://` will have its `Set-Cookie` dropped by the
173
+ > browser, silently breaking the OIDC login flow (the session never
174
+ > persists, so the callback loops back to `/auth/login`). This applies to
175
+ > internal / LAN deployments too: terminate TLS in front of the service
176
+ > (reverse proxy or a self-signed cert the clients trust) before exposing
177
+ > the browser login. The matching `secure=false` in non-production exists
178
+ > only so local `http://localhost` development works — do not run a
179
+ > public or shared instance with `NODE_ENV` unset.
180
+
181
+ The OIDC flow itself uses **PKCE + state** to prevent CSRF / replay
182
+ against the callback endpoint, and validates the issuer + audience
183
+ against `OIDC_ISSUER_URL` + `OIDC_CLIENT_ID` before binding the local
184
+ session.
185
+
186
+ ### Per-request audit
187
+
188
+ Every authenticated request emits a structured pino log line carrying:
189
+
190
+ - `user_id` — the local `users.id` (NULL for service accounts like
191
+ `mcp-bot` / `slack-bot` only when the bot row is missing; the seed
192
+ guarantees they exist).
193
+ - `token_id` — the `api_tokens.id` when strategy=PAT; NULL
194
+ otherwise.
195
+ - `auth_method` — one of `pat`, `session`, `legacy`.
196
+ - `apiKeyLabel` — the human-friendly label for legacy keys, e.g.
197
+ `key_alice-laptop`. Absent for PAT / session.
198
+
199
+ Failures emit a counterpart `tag: auth.failure` line with a coarse
200
+ `reasonCode` (`missing_credential`, `unknown_token`, `revoked_token`, …)
201
+ so secret values never appear in logs. The `auth-audit` helper enforces
202
+ this — it is the **only** sanctioned way for the auth plugin to
203
+ log into the request.
204
+
205
+ ## Legacy `X-API-Key` Status
206
+
207
+ The legacy `X-API-Key` strategy is **deprecated but still fully
208
+ supported as of v1.11.** It remains the third link in the auth chain
209
+ (`src/api/plugins/auth/index.ts` walks PAT → session → legacy), so a
210
+ request carrying a valid `API_KEYS` entry still authenticates and
211
+ mutates data. PAT and OIDC session are the preferred credentials; legacy
212
+ keys exist to keep older deployments running while operators migrate.
213
+
214
+ There is **no scheduled removal version.** Earlier drafts of this
215
+ document described a "v1.7 sunset" that would drop `API_KEYS` support —
216
+ that never happened. v1.7 through v1.11 shipped with the legacy strategy
217
+ intact, and no removal date is currently committed.
218
+
219
+ Legacy authentication is surfaced so operators can track migration
220
+ progress, not blocked:
221
+
222
+ - Every legacy-authed REST response carries two RFC 8594 headers:
223
+
224
+ ```
225
+ Deprecation: true
226
+ Sunset: 2026-12-31
227
+ ```
228
+
229
+ The `Sunset` value comes from the `LEGACY_AUTH_SUNSET_DATE` env var
230
+ (default `2026-12-31`, must be `YYYY-MM-DD`). It is an advisory
231
+ migration target, **not** an enforced cutoff — the strategy keeps
232
+ working past that date. PAT-authed and session-authed requests carry
233
+ **neither** header.
234
+
235
+ - Every legacy-authed request also emits a `warn`-level log line:
236
+
237
+ ```json
238
+ {
239
+ "level": 40,
240
+ "event": "legacy_auth_used",
241
+ "userId": 1,
242
+ "apiKeyLabel": "key_alice-laptop",
243
+ "requestId": "…",
244
+ "requestUrl": "/api/v1/tasks",
245
+ "sunset": "2026-12-31"
246
+ }
247
+ ```
248
+
249
+ Aggregate `legacy_auth_used` over a rolling window to gauge migration
250
+ readiness — a steady decline to zero means clients have all moved to
251
+ PAT or session. New deployments should issue PATs (one per
252
+ machine/agent) or use OIDC sessions rather than `API_KEYS`.
253
+
254
+ If a future release does remove the legacy strategy, the
255
+ `tasks db migrate-identities` tool (idempotent; backfills identity FKs
256
+ for historical rows that carry only the legacy TEXT identity columns)
257
+ is the supported pre-upgrade step. It is safe to run today.
258
+
259
+ ## CORS
260
+
261
+ The REST API **does not register a CORS plugin** — there is no
262
+ `@fastify/cors` (or equivalent) registration anywhere in `src/api/`, and
263
+ `cors` is not a project dependency. This is intentional: the API is built
264
+ for server-to-server and agent traffic (PAT / `X-API-Key` in headers),
265
+ plus a same-origin browser surface (`/auth/*`, `/me`, `/login`) that does
266
+ not need cross-origin access. With no `Access-Control-Allow-Origin`
267
+ header emitted, browsers block cross-origin reads of API responses by
268
+ default.
269
+
270
+ > **Never add `origin: true` (reflect-any-origin) CORS.** The OIDC
271
+ > session is a **credentialed cookie** (`Cookie: wft_session=…`).
272
+ > Combining a reflect-any-origin CORS policy
273
+ > (`origin: true` / `Access-Control-Allow-Origin: <reflected>`) with
274
+ > `Access-Control-Allow-Credentials: true` would let any website the
275
+ > victim visits make authenticated, cookie-bearing requests to the API on
276
+ > the victim's behalf — a cross-site request forgery / data-exfiltration
277
+ > hole. If you must enable CORS, set an explicit, hard-coded allow-list of
278
+ > trusted origins; do not reflect the request origin while credentials are
279
+ > allowed.
280
+
281
+ ## Authentication Is Not Authorization
282
+
283
+ Authentication identifies the caller; it does **not** scope what the
284
+ caller may do. Wood Fired Tasks has **no RBAC, no ACL, and no tenant /
285
+ project isolation.** Every authenticated identity — whether it arrived
286
+ via PAT, OIDC session, or a legacy `X-API-Key` — is effectively an
287
+ admin: it can read, write, and delete **every** task, project, comment,
288
+ dependency, and Slack subscription across **every** project in the
289
+ database. The `--scopes` minted onto a PAT are advisory metadata only and
290
+ are **not enforced** by any endpoint.
291
+
292
+ The consequence: any valid credential is a full-access credential. If you
293
+ need per-user, per-team, or per-tenant isolation, you must enforce it
294
+ **outside** this service — front it with an authenticating reverse proxy
295
+ that performs its own per-tenant authorization. Treat the loss or leak of
296
+ any single PAT or API key as a full-database compromise and revoke/rotate
297
+ accordingly. Scoped, role-based permissions are tracked as future work;
298
+ until they land, the model above is the whole authorization story.
299
+
@@ -0,0 +1,6 @@
1
+ import type { FastifyError, FastifyReply, FastifyRequest } from 'fastify';
2
+ /**
3
+ * Custom error handler that maps Phase 1 service errors to structured HTTP responses
4
+ * with machine-readable error codes.
5
+ */
6
+ export declare function errorHandler(error: FastifyError | Error, request: FastifyRequest, reply: FastifyReply): void;
@@ -0,0 +1,128 @@
1
+ import { ValidationError, BusinessError, NotFoundError } from '../../services/errors.js';
2
+ /**
3
+ * Generic, status-appropriate messages used when an error's raw `message` is
4
+ * NOT trusted for verbatim forwarding to the client. These never echo
5
+ * third-party / upstream detail.
6
+ */
7
+ const GENERIC_STATUS_MESSAGES = {
8
+ 400: 'Bad request',
9
+ 401: 'Unauthorized',
10
+ 403: 'Forbidden',
11
+ 404: 'Resource not found',
12
+ 405: 'Method not allowed',
13
+ 406: 'Not acceptable',
14
+ 408: 'Request timeout',
15
+ 409: 'Conflict',
16
+ 413: 'Payload too large',
17
+ 414: 'URI too long',
18
+ 415: 'Unsupported media type',
19
+ 422: 'Unprocessable entity',
20
+ 429: 'Too many requests',
21
+ };
22
+ /**
23
+ * Allowlist of Fastify/framework error code prefixes whose `message` is
24
+ * framework-generated (not third-party/upstream content) and therefore safe to
25
+ * forward verbatim. These describe how the *client's own request* was malformed
26
+ * (validation, body parsing, content-type, payload size) and never carry
27
+ * internal/upstream secrets.
28
+ */
29
+ const ALLOWLISTED_CODE_PREFIXES = [
30
+ 'FST_ERR_VALIDATION', // Zod / JSON-schema request validation failures
31
+ 'FST_ERR_CTP_', // content-type parser errors (e.g. invalid/empty JSON body)
32
+ 'FST_ERR_RTE_', // routing errors
33
+ ];
34
+ /**
35
+ * Project-authored error codes whose `message` is intentionally client-facing
36
+ * and safe to forward verbatim. Unlike the FST_ERR_* framework codes above,
37
+ * these are constructed by this codebase (not Fastify), so they are matched
38
+ * exactly rather than by prefix. Keep this set minimal — only codes whose
39
+ * message is guaranteed not to carry internal/upstream detail.
40
+ * - TOO_MANY_REQUESTS: @fastify/rate-limit errorResponseBuilder in server.ts
41
+ * emits "Rate limit exceeded, retry in <after>" — a documented, safe
42
+ * client-facing message (see server.ts errorResponseBuilder contract).
43
+ */
44
+ const ALLOWLISTED_EXACT_CODES = new Set(['TOO_MANY_REQUESTS']);
45
+ /**
46
+ * Decide whether an error's raw `message` may be surfaced verbatim to the
47
+ * client. Only errors the project explicitly trusts qualify:
48
+ * - Fastify request-validation errors (carry a `validation` array),
49
+ * - errors whose Fastify `code` matches a known-safe framework prefix, and
50
+ * - project-authored error codes whose message is intentionally client-facing
51
+ * (ALLOWLISTED_EXACT_CODES, e.g. TOO_MANY_REQUESTS).
52
+ * Everything else with a `statusCode` gets a generic status message instead,
53
+ * so third-party / upstream error detail is never leaked.
54
+ *
55
+ * The project's own error classes (ValidationError / BusinessError /
56
+ * NotFoundError) are handled by dedicated branches earlier and do not pass
57
+ * through here.
58
+ */
59
+ function isMessageAllowlisted(error) {
60
+ // Fastify attaches a `validation` array to request-schema validation errors.
61
+ if (Array.isArray(error.validation)) {
62
+ return true;
63
+ }
64
+ const code = error.code;
65
+ if (typeof code === 'string' && code.length > 0) {
66
+ if (ALLOWLISTED_EXACT_CODES.has(code)) {
67
+ return true;
68
+ }
69
+ return ALLOWLISTED_CODE_PREFIXES.some((prefix) => code.startsWith(prefix));
70
+ }
71
+ return false;
72
+ }
73
+ /**
74
+ * Custom error handler that maps Phase 1 service errors to structured HTTP responses
75
+ * with machine-readable error codes.
76
+ */
77
+ export function errorHandler(error, request, reply) {
78
+ // Log the FULL error server-side for debugging. This is the only place the
79
+ // raw message/stack of a non-allowlisted error is ever exposed.
80
+ request.log.error(error);
81
+ // Map Phase 1 custom errors FIRST (before checking Fastify-specific properties)
82
+ if (error instanceof ValidationError) {
83
+ reply.code(400).send({
84
+ error: 'VALIDATION_ERROR',
85
+ message: 'Validation failed',
86
+ details: error.fieldErrors,
87
+ });
88
+ return;
89
+ }
90
+ if (error instanceof NotFoundError) {
91
+ reply.code(404).send({
92
+ error: 'NOT_FOUND',
93
+ message: error.message,
94
+ details: { entity: error.entity, id: error.id },
95
+ });
96
+ return;
97
+ }
98
+ if (error instanceof BusinessError) {
99
+ reply.code(422).send({
100
+ error: 'BUSINESS_RULE_VIOLATION',
101
+ message: error.message,
102
+ });
103
+ return;
104
+ }
105
+ // statusCode-bearing errors (Fastify validation, body parsing, and arbitrary
106
+ // third-party errors that happen to carry a statusCode).
107
+ if ('statusCode' in error) {
108
+ const fastifyError = error;
109
+ const statusCode = fastifyError.statusCode || 400;
110
+ // Only surface the raw message for errors the project explicitly trusts
111
+ // (audit C7). Everything else gets a generic, status-appropriate message so
112
+ // internal/upstream detail is never forwarded verbatim.
113
+ const message = isMessageAllowlisted(fastifyError)
114
+ ? error.message
115
+ : GENERIC_STATUS_MESSAGES[statusCode] ?? 'An unexpected error occurred';
116
+ reply.code(statusCode).send({
117
+ error: fastifyError.code || 'REQUEST_ERROR',
118
+ message,
119
+ });
120
+ return;
121
+ }
122
+ // Fallback for unexpected errors - do NOT leak stack traces
123
+ reply.code(500).send({
124
+ error: 'INTERNAL_ERROR',
125
+ message: 'An unexpected error occurred',
126
+ });
127
+ }
128
+ //# sourceMappingURL=error-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../../src/api/hooks/error-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Phase 28 (Plan 28-04) — unified auth chain plugin.
3
+ *
4
+ * Replaces the legacy single-strategy plugin at `src/api/plugins/auth.ts`
5
+ * with a three-strategy chain (PAT → session-stub → legacy API_KEYS). The
6
+ * file at `src/api/plugins/auth.ts` survives as a thin re-export shim so
7
+ * existing `import authPlugin from './plugins/auth.js'` callers (server.ts,
8
+ * auth-logging.test.ts) keep working without churn.
9
+ *
10
+ * Plugin responsibilities:
11
+ * 1. Decorate `FastifyRequest` with `user`, `authMethod`, `tokenId`,
12
+ * `apiKeyLabel` at plugin load (Fastify requires decoration before any
13
+ * route registers).
14
+ * 2. Validate `process.env.API_KEYS` in production (throws synchronously,
15
+ * which Fastify bubbles up to createServer → exits with non-zero).
16
+ * 3. Pre-compute SHA-256 hashes of every configured API_KEYS entry once
17
+ * at register time; feed the result into the legacy strategy on every
18
+ * request so it never re-hashes.
19
+ * 4. Register a `preHandler` hook that:
20
+ * a. Short-circuits when `request.routeOptions.config.skipAuth === true`.
21
+ * b. Walks PAT → session-stub → legacy. First match wins. PAT failure
22
+ * does NOT fall through to legacy — see `enforceSessionOnly` /
23
+ * strategy-fail short-circuit below.
24
+ * c. On a successful match, populates `request.user`, `request.authMethod`,
25
+ * `request.tokenId` (and `request.apiKeyLabel` for legacy), re-childs
26
+ * the request logger with `{ user_id, token_id, auth_method,
27
+ * apiKeyLabel }` so every downstream log line carries audit fields,
28
+ * and enforces `config.sessionOnly` post-auth.
29
+ * d. On a strategy `fail` outcome, emits one `auth.failure` warn log via
30
+ * the Phase 27 `logAuthFailure` helper and returns a uniform 401
31
+ * (the distinct `reasonCode` lives ONLY in the audit log — never in
32
+ * the response body).
33
+ * e. On total fall-through (every strategy returned `skip`), emits a
34
+ * catch-all `auth.failure` log tagged `strategy: 'legacy'`,
35
+ * `reasonCode: 'missing_credential'` (per Plan-04 Decision Q6).
36
+ *
37
+ * Side-effect contracts:
38
+ * - PAT match schedules `setImmediate(() => apiTokenRepository.touchLastUsed(
39
+ * tokenId))`. The 10-minute Map debounce required by REQUIREMENTS PAT-03
40
+ * lands in Plan 28-06; this plan ships the naive every-request write.
41
+ * - The chain NEVER logs successful auth (no `auth.success` line); the
42
+ * re-childed request logger is the canonical audit trail.
43
+ *
44
+ * fp() wrap with `{ name: 'wft-auth', fastify: '5.x' }` is non-negotiable —
45
+ * without it, sibling routes registered in the same parent scope (the
46
+ * `/api/v1` block in server.ts) bypass the hook entirely. Existing
47
+ * rate-limit.test.ts would catch a regression, but the comment block at the
48
+ * bottom of this file (and at the bottom of the legacy auth.ts) is the
49
+ * primary defence against a reflexive refactor.
50
+ */
51
+ import type { FastifyPluginAsync, FastifyRequest } from 'fastify';
52
+ import type { AuthenticatedUser } from '../../../types/identity.js';
53
+ /**
54
+ * Throws if `preHandler` has not run yet (or if `skipAuth` was set). Use in
55
+ * route handlers / tool helpers that need a non-null `request.user`. The
56
+ * type narrowing is the entire point — once `requireUser` returns, the
57
+ * caller has an `AuthenticatedUser` without further null checks.
58
+ *
59
+ * CR-01 (Phase 30 review) — belt-and-suspenders: check for BOTH `null`
60
+ * (the initialized default via `decorateRequest('user', null)`) AND
61
+ * `undefined` (the value the slot holds when the route was registered
62
+ * OUTSIDE any scope that ran the auth-chain plugin — e.g. a top-level
63
+ * device-flow route mounted as a sibling of the `/api/v1` scope). The
64
+ * scope-wiring fix in server.ts addresses the production wiring, but
65
+ * leaving the guard narrow would silently re-open the bug if a future
66
+ * refactor moved a sessionOnly route outside the chain again.
67
+ */
68
+ export declare function requireUser(request: FastifyRequest): AuthenticatedUser;
69
+ /**
70
+ * Wrap with fastify-plugin to escape the encapsulated scope. Without `fp()`
71
+ * the preHandler hook only fires for routes registered INSIDE this plugin —
72
+ * every sibling under `/api/v1/*` would bypass auth entirely. The
73
+ * `{ name: 'wft-auth', fastify: '5.x' }` options match the pre-split
74
+ * plugin's identity so any downstream `fastify.hasPlugin('wft-auth')`
75
+ * checks keep working.
76
+ */
77
+ declare const authChain: FastifyPluginAsync;
78
+ export default authChain;