@svenroth-ai/shipwright 0.24.7-next.0 → 0.26.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 (239) hide show
  1. package/client/dist/assets/{EmbeddedTerminal-D2BLGWdW.js → EmbeddedTerminal-D3wpPNZP.js} +20 -20
  2. package/client/dist/assets/{abnfDiagram-VRR7QNED-C-W_2Jd4.js → abnfDiagram-VRR7QNED-BkLIltB3.js} +1 -1
  3. package/client/dist/assets/{arc-CpqwESP5.js → arc-Sw-Gh0nI.js} +1 -1
  4. package/client/dist/assets/{architectureDiagram-ZJ3FMSHR-DZVZ895V.js → architectureDiagram-ZJ3FMSHR-rdxUi9Ks.js} +1 -1
  5. package/client/dist/assets/{blockDiagram-677ZJIJ3-CCCZiAA7.js → blockDiagram-677ZJIJ3-Ch_9dj8a.js} +1 -1
  6. package/client/dist/assets/{c4Diagram-LMCZKHZV-Bg1beZAE.js → c4Diagram-LMCZKHZV-CkFlO65x.js} +1 -1
  7. package/client/dist/assets/channel-BYPbLjRl.js +1 -0
  8. package/client/dist/assets/{chunk-2Q5K7J3B-CoaXSarh.js → chunk-2Q5K7J3B-CypX83xu.js} +1 -1
  9. package/client/dist/assets/{chunk-32BRIVSS-CeFXS-CM.js → chunk-32BRIVSS-DxVLYGdl.js} +1 -1
  10. package/client/dist/assets/{chunk-5VM5RSS4-QyJMVe0m.js → chunk-5VM5RSS4-Cxk1evhx.js} +1 -1
  11. package/client/dist/assets/{chunk-EX3LRPZG-BIOtWVF8.js → chunk-EX3LRPZG-n9q5S4yJ.js} +1 -1
  12. package/client/dist/assets/{chunk-JWPE2WC7-Dcq-lWyX.js → chunk-JWPE2WC7-jeL4xvir.js} +1 -1
  13. package/client/dist/assets/{chunk-MOJQB5TN-C95323ja.js → chunk-MOJQB5TN-DDSHN0cj.js} +1 -1
  14. package/client/dist/assets/{chunk-RYQCIY6F-C0HPwpWy.js → chunk-RYQCIY6F-C0bo383g.js} +1 -1
  15. package/client/dist/assets/{chunk-V7JOEXUC-BPrTbZqA.js → chunk-V7JOEXUC-C0jDXXAU.js} +1 -1
  16. package/client/dist/assets/{chunk-VR4S4FIN--BCnNoqt.js → chunk-VR4S4FIN-h-Rr4Pn2.js} +1 -1
  17. package/client/dist/assets/{chunk-XXDRQBXY-Dj8vtcXf.js → chunk-XXDRQBXY-BexSo0-s.js} +1 -1
  18. package/client/dist/assets/classDiagram-OUVF2IWQ-CsFIeTju.js +1 -0
  19. package/client/dist/assets/classDiagram-v2-EOCWNBFH-CsFIeTju.js +1 -0
  20. package/client/dist/assets/{cose-bilkent-JH36ORCC-y51otZKz.js → cose-bilkent-JH36ORCC-BQXzaioj.js} +1 -1
  21. package/client/dist/assets/{cynefin-VYW2F7L2-BRfS3cHo.js → cynefin-VYW2F7L2-BJIofhfe.js} +1 -1
  22. package/client/dist/assets/{cynefinDiagram-TSTJHNR4-Dpg2RtfL.js → cynefinDiagram-TSTJHNR4-DmPqTM1T.js} +1 -1
  23. package/client/dist/assets/{dagre-VKFMJZFB-BdZtXgKZ.js → dagre-VKFMJZFB-1xljvw6X.js} +1 -1
  24. package/client/dist/assets/{diagram-FQU43EPY-K-RCwWxW.js → diagram-FQU43EPY-iyL9muCG.js} +1 -1
  25. package/client/dist/assets/{diagram-G47NLZAW-CHNn7F2-.js → diagram-G47NLZAW-DREp2Th6.js} +1 -1
  26. package/client/dist/assets/{diagram-NH7WQ7WH-B3zu6xib.js → diagram-NH7WQ7WH-h0X3Is2S.js} +1 -1
  27. package/client/dist/assets/{diagram-OA4YK3LP-Wp5ju7L7.js → diagram-OA4YK3LP-Bj8YZRFJ.js} +1 -1
  28. package/client/dist/assets/{diagram-WEI45ONY-C94GpsRk.js → diagram-WEI45ONY-BZOf805d.js} +1 -1
  29. package/client/dist/assets/{ebnfDiagram-CCIWWBDH-DXrTPOrP.js → ebnfDiagram-CCIWWBDH-DO5JnXda.js} +1 -1
  30. package/client/dist/assets/{erDiagram-Q63AITRT-CGejnW0f.js → erDiagram-Q63AITRT-DRgcqg1T.js} +1 -1
  31. package/client/dist/assets/{flowDiagram-23GEKE2U-CFJMM637.js → flowDiagram-23GEKE2U-C3J9QmrW.js} +1 -1
  32. package/client/dist/assets/{ganttDiagram-NO4QXBWP-DnaH94Su.js → ganttDiagram-NO4QXBWP-BIEl3Tkm.js} +1 -1
  33. package/client/dist/assets/{gitGraphDiagram-IHSO6WYX-DT-D0gbn.js → gitGraphDiagram-IHSO6WYX-BNF0tFWC.js} +1 -1
  34. package/client/dist/assets/index-BnTOrFHb.js +829 -0
  35. package/client/dist/assets/index-CYPc6qxw.css +1 -0
  36. package/client/dist/assets/{infoDiagram-FWYZ7A6U-DW3Ps00w.js → infoDiagram-FWYZ7A6U-DEyFxiyY.js} +1 -1
  37. package/client/dist/assets/{ishikawaDiagram-FXEZZL3T-DjxjIT3n.js → ishikawaDiagram-FXEZZL3T-B_ehlg3I.js} +1 -1
  38. package/client/dist/assets/{journeyDiagram-5HDEW3XC-DTQRI93Y.js → journeyDiagram-5HDEW3XC-Dp00FyJS.js} +1 -1
  39. package/client/dist/assets/{kanban-definition-HUTT4EX6-BeDvbiEs.js → kanban-definition-HUTT4EX6-CTAf16q8.js} +1 -1
  40. package/client/dist/assets/{linear-B4foxDlh.js → linear-9vNUgmYd.js} +1 -1
  41. package/client/dist/assets/{mermaid.core-c-L4qgSB.js → mermaid.core-A6tjaEVD.js} +4 -4
  42. package/client/dist/assets/{mindmap-definition-LN4V7U3C-dh8v0J8T.js → mindmap-definition-LN4V7U3C-DRW3bmCY.js} +1 -1
  43. package/client/dist/assets/{pegDiagram-2B236MQR-D5vNrtFh.js → pegDiagram-2B236MQR-DJFCjHp2.js} +1 -1
  44. package/client/dist/assets/{pieDiagram-ENE6RG2P-CIjg_6lR.js → pieDiagram-ENE6RG2P-KRd-sGvC.js} +1 -1
  45. package/client/dist/assets/{quadrantDiagram-ABIIQ3AL-CF37qWQD.js → quadrantDiagram-ABIIQ3AL-9TLZE3Hu.js} +1 -1
  46. package/client/dist/assets/{railroadDiagram-RFXS5EU6-x0Hn30Kj.js → railroadDiagram-RFXS5EU6-y4tLtjXg.js} +1 -1
  47. package/client/dist/assets/{requirementDiagram-TGXJPOKE-BE2QnVbN.js → requirementDiagram-TGXJPOKE-B9EOQDQM.js} +1 -1
  48. package/client/dist/assets/{sankeyDiagram-HTMAVEWB-CUv6cIlV.js → sankeyDiagram-HTMAVEWB-zfwiDzkT.js} +1 -1
  49. package/client/dist/assets/{sequenceDiagram-DBY2YBRQ-C3ACj5LS.js → sequenceDiagram-DBY2YBRQ-DW1LWepL.js} +1 -1
  50. package/client/dist/assets/{sizeCapture-X5ZJPWSS-DTMIb5gX.js → sizeCapture-X5ZJPWSS-BM81Mik0.js} +1 -1
  51. package/client/dist/assets/{stateDiagram-2N3HPSRC-DI1Z8P74.js → stateDiagram-2N3HPSRC-D0EMIw1w.js} +1 -1
  52. package/client/dist/assets/stateDiagram-v2-6OUMAXLB-B3E4_xxT.js +1 -0
  53. package/client/dist/assets/{swimlanes-5IMT3BWC-CLSr9w29.js → swimlanes-5IMT3BWC-CKXNCX6M.js} +2 -2
  54. package/client/dist/assets/swimlanesDiagram-G3AALYLV-BegQBqnC.js +8 -0
  55. package/client/dist/assets/{timeline-definition-FHXFAJF6-BTWmUhDh.js → timeline-definition-FHXFAJF6-ByzIfvL5.js} +1 -1
  56. package/client/dist/assets/{vennDiagram-L72KCM5P-Ce1dlLCM.js → vennDiagram-L72KCM5P-Xep38xBH.js} +1 -1
  57. package/client/dist/assets/{wardleyDiagram-EHGQE667-ClOE-aae.js → wardleyDiagram-EHGQE667-DKWDpx6g.js} +1 -1
  58. package/client/dist/assets/{xychartDiagram-FW5EYKEG-Fa2pqWKw.js → xychartDiagram-FW5EYKEG-BGcwZTnu.js} +1 -1
  59. package/client/dist/index.html +27 -27
  60. package/lib/claude-cli.mjs +173 -173
  61. package/lib/preflight.mjs +252 -252
  62. package/lib/probe-path.mjs +84 -84
  63. package/lib/server.mjs +369 -369
  64. package/lib/util.mjs +157 -157
  65. package/lib/win32-spawn.mjs +253 -253
  66. package/package.json +1 -1
  67. package/scripts/deploy-procs.mjs +208 -208
  68. package/scripts/deploy-swap.mjs +276 -276
  69. package/scripts/kill-targets.js +140 -140
  70. package/scripts/repair-claude-json.mjs +295 -295
  71. package/server/dist/config/default-actions.json +90 -90
  72. package/server/dist/core/grade-runner.d.ts +4 -4
  73. package/server/dist/core/grade-runner.d.ts.map +1 -1
  74. package/server/dist/core/grade-runner.js +51 -26
  75. package/server/dist/core/grade-runner.js.map +1 -1
  76. package/server/dist/core/grade-target.d.ts +8 -0
  77. package/server/dist/core/grade-target.d.ts.map +1 -1
  78. package/server/dist/core/grade-target.js +10 -0
  79. package/server/dist/core/grade-target.js.map +1 -1
  80. package/server/dist/core/mission-context/association.d.ts +46 -0
  81. package/server/dist/core/mission-context/association.d.ts.map +1 -1
  82. package/server/dist/core/mission-context/association.js +69 -0
  83. package/server/dist/core/mission-context/association.js.map +1 -1
  84. package/server/dist/core/mission-context/resolver-iterate.d.ts +6 -0
  85. package/server/dist/core/mission-context/resolver-iterate.d.ts.map +1 -0
  86. package/server/dist/core/mission-context/resolver-iterate.js +202 -0
  87. package/server/dist/core/mission-context/resolver-iterate.js.map +1 -0
  88. package/server/dist/core/mission-context/resolver-parts.d.ts +20 -0
  89. package/server/dist/core/mission-context/resolver-parts.d.ts.map +1 -1
  90. package/server/dist/core/mission-context/resolver-parts.js +92 -0
  91. package/server/dist/core/mission-context/resolver-parts.js.map +1 -1
  92. package/server/dist/core/mission-context/resolver.d.ts +1 -1
  93. package/server/dist/core/mission-context/resolver.d.ts.map +1 -1
  94. package/server/dist/core/mission-context/resolver.js +17 -199
  95. package/server/dist/core/mission-context/resolver.js.map +1 -1
  96. package/server/dist/core/mission-context/run-id-recovery.d.ts +21 -5
  97. package/server/dist/core/mission-context/run-id-recovery.d.ts.map +1 -1
  98. package/server/dist/core/mission-context/run-id-recovery.js +32 -5
  99. package/server/dist/core/mission-context/run-id-recovery.js.map +1 -1
  100. package/server/dist/core/mission-context/scenario.d.ts +18 -11
  101. package/server/dist/core/mission-context/scenario.d.ts.map +1 -1
  102. package/server/dist/core/mission-context/scenario.js +40 -3
  103. package/server/dist/core/mission-context/scenario.js.map +1 -1
  104. package/server/dist/core/path-guard.d.ts +1 -1
  105. package/server/dist/core/path-guard.d.ts.map +1 -1
  106. package/server/dist/core/path-guard.js +9 -2
  107. package/server/dist/core/path-guard.js.map +1 -1
  108. package/server/dist/core/shipslog-docs-reader.d.ts +4 -0
  109. package/server/dist/core/shipslog-docs-reader.d.ts.map +1 -0
  110. package/server/dist/core/shipslog-docs-reader.js +181 -0
  111. package/server/dist/core/shipslog-docs-reader.js.map +1 -0
  112. package/server/dist/core/shipslog-docs-types.d.ts +15 -0
  113. package/server/dist/core/shipslog-docs-types.d.ts.map +1 -0
  114. package/server/dist/core/shipslog-docs-types.js +14 -0
  115. package/server/dist/core/shipslog-docs-types.js.map +1 -0
  116. package/server/dist/core/triage-cli-runner.d.ts.map +1 -1
  117. package/server/dist/core/triage-cli-runner.js +29 -10
  118. package/server/dist/core/triage-cli-runner.js.map +1 -1
  119. package/server/dist/core/uv-runner.d.ts +23 -0
  120. package/server/dist/core/uv-runner.d.ts.map +1 -0
  121. package/server/dist/core/uv-runner.js +39 -0
  122. package/server/dist/core/uv-runner.js.map +1 -0
  123. package/server/dist/external/design-review/serve.js +27 -27
  124. package/server/dist/external/file/exists-routes.d.ts +7 -0
  125. package/server/dist/external/file/exists-routes.d.ts.map +1 -0
  126. package/server/dist/external/file/exists-routes.js +91 -0
  127. package/server/dist/external/file/exists-routes.js.map +1 -0
  128. package/server/dist/external/mission-context/association-write.d.ts +22 -0
  129. package/server/dist/external/mission-context/association-write.d.ts.map +1 -0
  130. package/server/dist/external/mission-context/association-write.js +90 -0
  131. package/server/dist/external/mission-context/association-write.js.map +1 -0
  132. package/server/dist/external/mission-context/routes.d.ts.map +1 -1
  133. package/server/dist/external/mission-context/routes.js +18 -31
  134. package/server/dist/external/mission-context/routes.js.map +1 -1
  135. package/server/dist/external/org/audit-log.d.ts +27 -0
  136. package/server/dist/external/org/audit-log.d.ts.map +1 -0
  137. package/server/dist/external/org/audit-log.js +92 -0
  138. package/server/dist/external/org/audit-log.js.map +1 -0
  139. package/server/dist/external/org/beat-register.d.ts +15 -22
  140. package/server/dist/external/org/beat-register.d.ts.map +1 -1
  141. package/server/dist/external/org/beat-register.js +37 -32
  142. package/server/dist/external/org/beat-register.js.map +1 -1
  143. package/server/dist/external/org/file-read.d.ts +16 -0
  144. package/server/dist/external/org/file-read.d.ts.map +1 -1
  145. package/server/dist/external/org/file-read.js +86 -46
  146. package/server/dist/external/org/file-read.js.map +1 -1
  147. package/server/dist/external/org/file-write-core.d.ts +18 -0
  148. package/server/dist/external/org/file-write-core.d.ts.map +1 -0
  149. package/server/dist/external/org/file-write-core.js +97 -0
  150. package/server/dist/external/org/file-write-core.js.map +1 -0
  151. package/server/dist/external/org/file-write.d.ts +25 -0
  152. package/server/dist/external/org/file-write.d.ts.map +1 -1
  153. package/server/dist/external/org/file-write.js +82 -132
  154. package/server/dist/external/org/file-write.js.map +1 -1
  155. package/server/dist/external/org/last-run.d.ts +25 -16
  156. package/server/dist/external/org/last-run.d.ts.map +1 -1
  157. package/server/dist/external/org/last-run.js +78 -64
  158. package/server/dist/external/org/last-run.js.map +1 -1
  159. package/server/dist/external/org/lead-doc-read.d.ts +21 -0
  160. package/server/dist/external/org/lead-doc-read.d.ts.map +1 -0
  161. package/server/dist/external/org/lead-doc-read.js +84 -0
  162. package/server/dist/external/org/lead-doc-read.js.map +1 -0
  163. package/server/dist/external/org/mount.d.ts +12 -0
  164. package/server/dist/external/org/mount.d.ts.map +1 -0
  165. package/server/dist/external/org/mount.js +34 -0
  166. package/server/dist/external/org/mount.js.map +1 -0
  167. package/server/dist/external/org/org-chart-lookup.d.ts +13 -0
  168. package/server/dist/external/org/org-chart-lookup.d.ts.map +1 -1
  169. package/server/dist/external/org/org-chart-lookup.js +40 -5
  170. package/server/dist/external/org/org-chart-lookup.js.map +1 -1
  171. package/server/dist/external/org/org-chart.d.ts +21 -12
  172. package/server/dist/external/org/org-chart.d.ts.map +1 -1
  173. package/server/dist/external/org/org-chart.js +50 -32
  174. package/server/dist/external/org/org-chart.js.map +1 -1
  175. package/server/dist/external/org/role-extract.d.ts +8 -0
  176. package/server/dist/external/org/role-extract.d.ts.map +1 -0
  177. package/server/dist/external/org/role-extract.js +72 -0
  178. package/server/dist/external/org/role-extract.js.map +1 -0
  179. package/server/dist/external/org/usage.d.ts +15 -11
  180. package/server/dist/external/org/usage.d.ts.map +1 -1
  181. package/server/dist/external/org/usage.js +64 -54
  182. package/server/dist/external/org/usage.js.map +1 -1
  183. package/server/dist/external/routes.d.ts +1 -1
  184. package/server/dist/external/routes.d.ts.map +1 -1
  185. package/server/dist/external/routes.js +19 -17
  186. package/server/dist/external/routes.js.map +1 -1
  187. package/server/dist/external/shipslog-docs/routes.d.ts +10 -0
  188. package/server/dist/external/shipslog-docs/routes.d.ts.map +1 -0
  189. package/server/dist/external/shipslog-docs/routes.js +41 -0
  190. package/server/dist/external/shipslog-docs/routes.js.map +1 -0
  191. package/server/dist/routes/grade.d.ts.map +1 -1
  192. package/server/dist/routes/grade.js +3 -2
  193. package/server/dist/routes/grade.js.map +1 -1
  194. package/server/dist/routes/org-leads-composite.d.ts +21 -0
  195. package/server/dist/routes/org-leads-composite.d.ts.map +1 -0
  196. package/server/dist/routes/org-leads-composite.js +95 -0
  197. package/server/dist/routes/org-leads-composite.js.map +1 -0
  198. package/server/dist/routes/org.d.ts +15 -0
  199. package/server/dist/routes/org.d.ts.map +1 -0
  200. package/server/dist/routes/org.js +198 -0
  201. package/server/dist/routes/org.js.map +1 -0
  202. package/server/dist/terminal/pty-manager.d.ts +9 -0
  203. package/server/dist/terminal/pty-manager.d.ts.map +1 -1
  204. package/server/dist/terminal/pty-manager.js +15 -0
  205. package/server/dist/terminal/pty-manager.js.map +1 -1
  206. package/server/dist/terminal/replay-snapshot.d.ts +10 -1
  207. package/server/dist/terminal/replay-snapshot.d.ts.map +1 -1
  208. package/server/dist/terminal/replay-snapshot.js +36 -1
  209. package/server/dist/terminal/replay-snapshot.js.map +1 -1
  210. package/server/dist/terminal/ws-upgrade-handler.d.ts +17 -1
  211. package/server/dist/terminal/ws-upgrade-handler.d.ts.map +1 -1
  212. package/server/dist/terminal/ws-upgrade-handler.fixtures.d.ts +1 -0
  213. package/server/dist/terminal/ws-upgrade-handler.fixtures.d.ts.map +1 -1
  214. package/server/dist/terminal/ws-upgrade-handler.fixtures.js +3 -2
  215. package/server/dist/terminal/ws-upgrade-handler.fixtures.js.map +1 -1
  216. package/server/dist/terminal/ws-upgrade-handler.js +45 -4
  217. package/server/dist/terminal/ws-upgrade-handler.js.map +1 -1
  218. package/server/dist/test/python-spawn-guard-util.d.ts +14 -0
  219. package/server/dist/test/python-spawn-guard-util.d.ts.map +1 -0
  220. package/server/dist/test/python-spawn-guard-util.js +124 -0
  221. package/server/dist/test/python-spawn-guard-util.js.map +1 -0
  222. package/server/dist/types/org.d.ts +115 -0
  223. package/server/dist/types/org.d.ts.map +1 -0
  224. package/server/dist/types/org.js +16 -0
  225. package/server/dist/types/org.js.map +1 -0
  226. package/server/package.json +1 -1
  227. package/server/profiles/PROFILE_SCHEMA_VERSION +1 -1
  228. package/server/profiles/README.md +27 -27
  229. package/server/profiles/python-plugin-monorepo.json +35 -35
  230. package/server/profiles/supabase-nextjs.json +253 -253
  231. package/server/profiles/vite-hono.json +92 -92
  232. package/client/dist/assets/MarkdownEditorModal-CP5yG9OU.js +0 -212
  233. package/client/dist/assets/channel-BNXbCvDS.js +0 -1
  234. package/client/dist/assets/classDiagram-OUVF2IWQ-w-wCtLx3.js +0 -1
  235. package/client/dist/assets/classDiagram-v2-EOCWNBFH-w-wCtLx3.js +0 -1
  236. package/client/dist/assets/index-CU2QJgvx.js +0 -609
  237. package/client/dist/assets/index-ftvVcUT8.css +0 -1
  238. package/client/dist/assets/stateDiagram-v2-6OUMAXLB-DvwiHlJv.js +0 -1
  239. package/client/dist/assets/swimlanesDiagram-G3AALYLV-JTQZTlZ3.js +0 -8
@@ -20,61 +20,101 @@ import { closeSync, constants as fsConstants, fstatSync, openSync, readFileSync
20
20
  import { realPathGuard } from "../../core/path-guard.js";
21
21
  import { resolveOrgAllowlistedTarget } from "./_helpers.js";
22
22
  import { fileFingerprint } from "../file/_helpers.js";
23
- export function registerOrgFileRead(app, deps) {
23
+ /** Pure core — shared by the secret-gated route and the plain-surface proxy. */
24
+ export function orgFileReadCore(deps, relpath) {
24
25
  const { leadsRoot } = deps;
25
26
  const open = deps.openSync ?? openSync;
26
- app.get("/api/external/org/file", async (c) => {
27
- const relpath = c.req.query("path");
28
- if (!relpath || relpath.length === 0) {
29
- return c.json({ error: "path_required" }, 400);
27
+ if (!relpath || relpath.length === 0) {
28
+ return { status: 400, kind: "json", body: { error: "path_required" } };
29
+ }
30
+ const target = resolveOrgAllowlistedTarget(leadsRoot, relpath);
31
+ if (!target.ok) {
32
+ const status = target.reason === "not_allowlisted" ? 403 : 400;
33
+ const err = target.reason === "traversal" ? "path_traversal" : target.reason;
34
+ return { status, kind: "json", body: { error: err, detail: target.reason } };
35
+ }
36
+ // External-review fix (MEDIUM, spec): `realPathGuard` used to run BEFORE
37
+ // the open below. Its own docstring requires the target to already exist
38
+ // (it calls `realpathSync`, which throws ENOENT for a missing path) —
39
+ // calling it first meant a genuinely-missing allowlisted target (e.g. an
40
+ // as-yet-uncreated `conventions.md`) reported 400 `path_traversal`
41
+ // instead of 404 `not_found`, so the shared-documents viewer's not-found
42
+ // state (`OrgDocViewerModal`) was unreachable for any missing doc. Fixed
43
+ // by reordering to the same open-first pattern `lead-doc-read.ts` /
44
+ // `audit-log.ts` already use — see their comments for the rationale.
45
+ //
46
+ // CodeQL js/file-system-race fix: a separate lstat-check + stat + read
47
+ // (each its own syscall against the PATH) leaves a window where the
48
+ // final component could be swapped for a symlink between checks. Open
49
+ // ONCE with O_NOFOLLOW — the kernel atomically refuses a symlinked final
50
+ // component (ELOOP), so there is no gap left to race — then fstat/read
51
+ // the SAME fd, so what gets served is provably what got checked.
52
+ let fd;
53
+ try {
54
+ fd = open(target.absolute, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
55
+ }
56
+ catch (err) {
57
+ const code = err?.code;
58
+ if (code === "ENOENT") {
59
+ return { status: 404, kind: "json", body: { error: "not_found", path: relpath } };
60
+ }
61
+ if (code === "ELOOP") {
62
+ return { status: 403, kind: "json", body: { error: "symlink_forbidden", path: relpath } };
30
63
  }
31
- const target = resolveOrgAllowlistedTarget(leadsRoot, relpath);
32
- if (!target.ok) {
33
- const status = target.reason === "not_allowlisted" ? 403 : 400;
34
- const err = target.reason === "traversal" ? "path_traversal" : target.reason;
35
- return c.json({ error: err, detail: target.reason }, status);
64
+ return {
65
+ status: 500,
66
+ kind: "json",
67
+ body: { error: "file_stat_failed", detail: String(err).slice(0, 200) },
68
+ };
69
+ }
70
+ try {
71
+ if (!fstatSync(fd).isFile()) {
72
+ return { status: 400, kind: "json", body: { error: "not_a_file", path: relpath } };
36
73
  }
74
+ // Existence is now confirmed (the open above succeeded) — realPathGuard's
75
+ // own contract requires exactly this ordering.
37
76
  const realGuard = realPathGuard(leadsRoot, target.absolute);
38
77
  if (!realGuard.ok) {
39
- return c.json({ error: "path_traversal", detail: realGuard.reason }, 400);
40
- }
41
- // CodeQL js/file-system-race fix: a separate lstat-check + stat + read
42
- // (each its own syscall against the PATH) leaves a window where the
43
- // final component could be swapped for a symlink between checks. Open
44
- // ONCE with O_NOFOLLOW — the kernel atomically refuses a symlinked final
45
- // component (ELOOP), so there is no gap left to race — then fstat/read
46
- // the SAME fd, so what gets served is provably what got checked.
47
- let fd;
48
- try {
49
- fd = open(target.absolute, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
50
- }
51
- catch (err) {
52
- const code = err?.code;
53
- if (code === "ENOENT") {
54
- return c.json({ error: "not_found", path: relpath }, 404);
55
- }
56
- if (code === "ELOOP") {
57
- return c.json({ error: "symlink_forbidden", path: relpath }, 403);
58
- }
59
- return c.json({ error: "file_stat_failed", detail: String(err).slice(0, 200) }, 500);
78
+ return {
79
+ status: 403,
80
+ kind: "json",
81
+ body: { error: "symlink_forbidden", detail: realGuard.reason, path: relpath },
82
+ };
60
83
  }
61
- try {
62
- if (!fstatSync(fd).isFile()) {
63
- return c.json({ error: "not_a_file", path: relpath }, 400);
64
- }
65
- const body = readFileSync(fd);
66
- c.header("Content-Type", "text/markdown; charset=utf-8");
67
- c.header("X-Content-Type-Options", "nosniff");
68
- c.header("ETag", `"${fileFingerprint(body)}"`);
69
- c.header("Cache-Control", "private, max-age=0, must-revalidate");
70
- return c.body(new Uint8Array(body));
71
- }
72
- catch (err) {
73
- return c.json({ error: "file_read_failed", detail: String(err).slice(0, 200) }, 500);
84
+ const body = readFileSync(fd);
85
+ return {
86
+ status: 200,
87
+ kind: "file",
88
+ body,
89
+ headers: {
90
+ "Content-Type": "text/markdown; charset=utf-8",
91
+ "X-Content-Type-Options": "nosniff",
92
+ ETag: `"${fileFingerprint(body)}"`,
93
+ "Cache-Control": "private, max-age=0, must-revalidate",
94
+ },
95
+ };
96
+ }
97
+ catch (err) {
98
+ return {
99
+ status: 500,
100
+ kind: "json",
101
+ body: { error: "file_read_failed", detail: String(err).slice(0, 200) },
102
+ };
103
+ }
104
+ finally {
105
+ closeSync(fd);
106
+ }
107
+ }
108
+ export function registerOrgFileRead(app, deps) {
109
+ app.get("/api/external/org/file", async (c) => {
110
+ const result = orgFileReadCore(deps, c.req.query("path"));
111
+ if (result.kind === "json") {
112
+ return c.json(result.body, result.status);
74
113
  }
75
- finally {
76
- closeSync(fd);
114
+ for (const [key, value] of Object.entries(result.headers)) {
115
+ c.header(key, value);
77
116
  }
117
+ return c.body(new Uint8Array(result.body), result.status);
78
118
  });
79
119
  }
80
120
  //# sourceMappingURL=file-read.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-read.js","sourceRoot":"","sources":["../../../src/external/org/file-read.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"file-read.js","sourceRoot":"","sources":["../../../src/external/org/file-read.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export type WriteOutcome = {
2
+ ok: true;
3
+ status: 200;
4
+ body: {
5
+ written: true;
6
+ fingerprint: string;
7
+ size: number;
8
+ };
9
+ } | {
10
+ ok: false;
11
+ status: number;
12
+ body: Record<string, unknown>;
13
+ };
14
+ export declare function performWrite(absoluteTarget: string, leadsRoot: string, relpath: string, body: string, ifMatch: string | undefined, lstat: (path: string) => {
15
+ isSymbolicLink(): boolean;
16
+ isFile(): boolean;
17
+ }): WriteOutcome;
18
+ //# sourceMappingURL=file-write-core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-write-core.d.ts","sourceRoot":"","sources":["../../../src/external/org/file-write-core.ts"],"names":[],"mappings":"AAiBA,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACrF;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AAEjE,wBAAgB,YAAY,CAC1B,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;IAAE,cAAc,IAAI,OAAO,CAAC;IAAC,MAAM,IAAI,OAAO,CAAA;CAAE,GACxE,YAAY,CAgFd"}
@@ -0,0 +1,97 @@
1
+ /*
2
+ * external/org/file-write-core.ts — the low-level atomic write-with-
3
+ * precondition primitive `file-write.ts`'s `orgFileWriteCore` calls.
4
+ *
5
+ * Split out purely to stay under the 300-line file guideline (CLAUDE.md) —
6
+ * mirrors the existing `beat-register-release.ts` /
7
+ * `beat-register-release-core.ts` split in this same directory. No new
8
+ * behavior; `performWrite`'s body and `WriteOutcome`'s shape are unchanged
9
+ * from before the split.
10
+ */
11
+ import { readFileSync, writeFileSync, renameSync, unlinkSync, existsSync } from "node:fs";
12
+ import { dirname, join } from "node:path";
13
+ import { realPathGuard } from "../../core/path-guard.js";
14
+ import { fileFingerprint, unquoteEtag } from "../file/_helpers.js";
15
+ export function performWrite(absoluteTarget, leadsRoot, relpath, body, ifMatch, lstat) {
16
+ let lst;
17
+ try {
18
+ lst = lstat(absoluteTarget);
19
+ }
20
+ catch (err) {
21
+ const code = err?.code;
22
+ if (code === "ENOENT") {
23
+ return { ok: false, status: 404, body: { error: "not_found", path: relpath } };
24
+ }
25
+ return {
26
+ ok: false,
27
+ status: 500,
28
+ body: { error: "file_stat_failed", detail: String(err).slice(0, 200) },
29
+ };
30
+ }
31
+ if (lst.isSymbolicLink()) {
32
+ return { ok: false, status: 403, body: { error: "symlink_forbidden", path: relpath } };
33
+ }
34
+ if (!lst.isFile()) {
35
+ return { ok: false, status: 400, body: { error: "not_a_file", path: relpath } };
36
+ }
37
+ const realGuard = realPathGuard(leadsRoot, absoluteTarget);
38
+ if (!realGuard.ok) {
39
+ return {
40
+ ok: false,
41
+ status: 400,
42
+ body: { error: "path_traversal", detail: realGuard.reason },
43
+ };
44
+ }
45
+ if (!ifMatch) {
46
+ return { ok: false, status: 400, body: { error: "precondition_required" } };
47
+ }
48
+ let current;
49
+ try {
50
+ current = readFileSync(absoluteTarget);
51
+ }
52
+ catch (err) {
53
+ const code = err?.code;
54
+ if (code === "ENOENT") {
55
+ return { ok: false, status: 404, body: { error: "not_found", path: relpath } };
56
+ }
57
+ return {
58
+ ok: false,
59
+ status: 500,
60
+ body: { error: "file_read_failed", detail: String(err).slice(0, 200) },
61
+ };
62
+ }
63
+ const currentFingerprint = fileFingerprint(current);
64
+ if (unquoteEtag(ifMatch) !== currentFingerprint) {
65
+ return {
66
+ ok: false,
67
+ status: 409,
68
+ body: { error: "fingerprint_mismatch", currentFingerprint },
69
+ };
70
+ }
71
+ const nextBuf = Buffer.from(body, "utf8");
72
+ const tmp = join(dirname(absoluteTarget), `.org-write.tmp-${process.pid}-${Date.now()}`);
73
+ try {
74
+ writeFileSync(tmp, nextBuf);
75
+ renameSync(tmp, absoluteTarget);
76
+ }
77
+ catch (err) {
78
+ try {
79
+ if (existsSync(tmp))
80
+ unlinkSync(tmp);
81
+ }
82
+ catch {
83
+ /* swallow */
84
+ }
85
+ return {
86
+ ok: false,
87
+ status: 500,
88
+ body: { error: "write_failed", detail: String(err).slice(0, 200), path: relpath },
89
+ };
90
+ }
91
+ return {
92
+ ok: true,
93
+ status: 200,
94
+ body: { written: true, fingerprint: fileFingerprint(nextBuf), size: nextBuf.length },
95
+ };
96
+ }
97
+ //# sourceMappingURL=file-write-core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-write-core.js","sourceRoot":"","sources":["../../../src/external/org/file-write-core.ts"],"names":[],"mappings":"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"}
@@ -10,5 +10,30 @@ export interface OrgFileWriteDeps extends DecisionsLockDeps {
10
10
  /** Injectable for tests; production wires the real `withDecisionsLock`. */
11
11
  withDecisionsLock?: typeof withDecisionsLock;
12
12
  }
13
+ export type OrgFileWriteCoreResult = {
14
+ status: 200;
15
+ body: {
16
+ written: true;
17
+ fingerprint: string;
18
+ size: number;
19
+ };
20
+ } | {
21
+ status: 400 | 403 | 404 | 409 | 413 | 500;
22
+ body: Record<string, unknown>;
23
+ };
24
+ export interface OrgFileWriteParams {
25
+ relpath: string | undefined;
26
+ contentLengthHeader: string | undefined;
27
+ body: string;
28
+ ifMatch: string | undefined;
29
+ }
30
+ /**
31
+ * Pure core — shared by the secret-gated route and the plain-surface proxy.
32
+ * Kind restriction (e.g. "only `charter` may pass through the browser-facing
33
+ * proxy") is the CALLER's job, applied against `resolveOrgAllowlistedTarget`'s
34
+ * `kind` before or after invoking this — this core stays as general as the
35
+ * existing route (all six allowlisted kinds).
36
+ */
37
+ export declare function orgFileWriteCore(deps: OrgFileWriteDeps, params: OrgFileWriteParams): Promise<OrgFileWriteCoreResult>;
13
38
  export declare function registerOrgFileWrite(app: Hono, deps: OrgFileWriteDeps): void;
14
39
  //# sourceMappingURL=file-write.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-write.d.ts","sourceRoot":"","sources":["../../../src/external/org/file-write.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAiBjC,OAAO,EACL,iBAAiB,EAEjB,KAAK,iBAAiB,EACvB,MAAM,qBAAqB,CAAC;AAE7B,sFAAsF;AACtF,eAAO,MAAM,mBAAmB,QAAkB,CAAC;AAWnD,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,cAAc,IAAI,OAAO,CAAC;QAAC,MAAM,IAAI,OAAO,CAAA;KAAE,CAAC;IAC/E,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE,OAAO,iBAAiB,CAAC;CAC9C;AA2FD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAiF5E"}
1
+ {"version":3,"file":"file-write.d.ts","sourceRoot":"","sources":["../../../src/external/org/file-write.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAOjC,OAAO,EACL,iBAAiB,EAEjB,KAAK,iBAAiB,EACvB,MAAM,qBAAqB,CAAC;AAG7B,sFAAsF;AACtF,eAAO,MAAM,mBAAmB,QAAkB,CAAC;AAOnD,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,cAAc,IAAI,OAAO,CAAC;QAAC,MAAM,IAAI,OAAO,CAAA;KAAE,CAAC;IAC/E,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE,OAAO,iBAAiB,CAAC;CAC9C;AAED,MAAM,MAAM,sBAAsB,GAC9B;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAC3E;IAAE,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AAEjF,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,mBAAmB,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,sBAAsB,CAAC,CA6EjC;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAyB5E"}
@@ -42,164 +42,114 @@
42
42
  * 413 body over ORG_WRITE_MAX_BYTES
43
43
  * 500 stat / read / write failure
44
44
  */
45
- import { readFileSync, writeFileSync, renameSync, unlinkSync, existsSync, lstatSync, } from "node:fs";
46
- import { dirname, join } from "node:path";
47
- import { realPathGuard } from "../../core/path-guard.js";
45
+ import { lstatSync } from "node:fs";
48
46
  import { resolveOrgAllowlistedTarget, } from "./_helpers.js";
49
- import { fileFingerprint, unquoteEtag } from "../file/_helpers.js";
50
47
  import { withDecisionsLock, OrgSymlinkEscapeError, } from "./decisions-lock.js";
48
+ import { performWrite } from "./file-write-core.js";
51
49
  /** 2 MiB — org docs are small prose files, same cap as the project markdown write. */
52
50
  export const ORG_WRITE_MAX_BYTES = 2 * 1024 * 1024;
53
51
  const LOCKED_KINDS = new Set([
54
52
  "decision_log",
55
53
  "decisions_proposed",
56
54
  ]);
57
- function performWrite(absoluteTarget, leadsRoot, relpath, body, ifMatch, lstat) {
58
- let lst;
59
- try {
60
- lst = lstat(absoluteTarget);
61
- }
62
- catch (err) {
63
- const code = err?.code;
64
- if (code === "ENOENT") {
65
- return { ok: false, status: 404, body: { error: "not_found", path: relpath } };
66
- }
67
- return {
68
- ok: false,
69
- status: 500,
70
- body: { error: "file_stat_failed", detail: String(err).slice(0, 200) },
71
- };
72
- }
73
- if (lst.isSymbolicLink()) {
74
- return { ok: false, status: 403, body: { error: "symlink_forbidden", path: relpath } };
75
- }
76
- if (!lst.isFile()) {
77
- return { ok: false, status: 400, body: { error: "not_a_file", path: relpath } };
78
- }
79
- const realGuard = realPathGuard(leadsRoot, absoluteTarget);
80
- if (!realGuard.ok) {
81
- return {
82
- ok: false,
83
- status: 400,
84
- body: { error: "path_traversal", detail: realGuard.reason },
85
- };
86
- }
87
- if (!ifMatch) {
88
- return { ok: false, status: 400, body: { error: "precondition_required" } };
55
+ /**
56
+ * Pure core — shared by the secret-gated route and the plain-surface proxy.
57
+ * Kind restriction (e.g. "only `charter` may pass through the browser-facing
58
+ * proxy") is the CALLER's job, applied against `resolveOrgAllowlistedTarget`'s
59
+ * `kind` before or after invoking this — this core stays as general as the
60
+ * existing route (all six allowlisted kinds).
61
+ */
62
+ export async function orgFileWriteCore(deps, params) {
63
+ const { leadsRoot } = deps;
64
+ const lstat = deps.lstatSync ?? ((p) => lstatSync(p));
65
+ const lockFn = deps.withDecisionsLock ?? withDecisionsLock;
66
+ const { relpath, contentLengthHeader, body, ifMatch } = params;
67
+ if (!relpath || relpath.length === 0) {
68
+ return { status: 400, body: { error: "path_required" } };
89
69
  }
90
- let current;
91
- try {
92
- current = readFileSync(absoluteTarget);
70
+ const target = resolveOrgAllowlistedTarget(leadsRoot, relpath);
71
+ if (!target.ok) {
72
+ const status = target.reason === "not_allowlisted" ? 403 : 400;
73
+ const err = target.reason === "traversal" ? "path_traversal" : target.reason;
74
+ return { status, body: { error: err, detail: target.reason } };
93
75
  }
94
- catch (err) {
95
- const code = err?.code;
96
- if (code === "ENOENT") {
97
- return { ok: false, status: 404, body: { error: "not_found", path: relpath } };
98
- }
76
+ const declaredLength = Number(contentLengthHeader ?? "");
77
+ if (Number.isFinite(declaredLength) && declaredLength > ORG_WRITE_MAX_BYTES) {
99
78
  return {
100
- ok: false,
101
- status: 500,
102
- body: { error: "file_read_failed", detail: String(err).slice(0, 200) },
79
+ status: 413,
80
+ body: { error: "payload_too_large", maxBytes: ORG_WRITE_MAX_BYTES, size: declaredLength },
103
81
  };
104
82
  }
105
- const currentFingerprint = fileFingerprint(current);
106
- if (unquoteEtag(ifMatch) !== currentFingerprint) {
83
+ const byteLen = Buffer.byteLength(body, "utf8");
84
+ if (byteLen > ORG_WRITE_MAX_BYTES) {
107
85
  return {
108
- ok: false,
109
- status: 409,
110
- body: { error: "fingerprint_mismatch", currentFingerprint },
86
+ status: 413,
87
+ body: { error: "payload_too_large", maxBytes: ORG_WRITE_MAX_BYTES, size: byteLen },
111
88
  };
112
89
  }
113
- const nextBuf = Buffer.from(body, "utf8");
114
- const tmp = join(dirname(absoluteTarget), `.org-write.tmp-${process.pid}-${Date.now()}`);
115
- try {
116
- writeFileSync(tmp, nextBuf);
117
- renameSync(tmp, absoluteTarget);
118
- }
119
- catch (err) {
90
+ let outcome;
91
+ if (LOCKED_KINDS.has(target.kind)) {
92
+ // Pre-lock existence probe (external-review fix) — must run BEFORE
93
+ // withDecisionsLock, since its own ensureFile() would otherwise
94
+ // auto-vivify decisions-proposed.md first and hide a genuinely
95
+ // missing target from performWrite's existence check.
120
96
  try {
121
- if (existsSync(tmp))
122
- unlinkSync(tmp);
97
+ lstat(target.absolute);
123
98
  }
124
- catch {
125
- /* swallow */
99
+ catch (err) {
100
+ const code = err?.code;
101
+ if (code === "ENOENT") {
102
+ return { status: 404, body: { error: "not_found", path: relpath } };
103
+ }
104
+ return {
105
+ status: 500,
106
+ body: { error: "file_stat_failed", detail: String(err).slice(0, 200) },
107
+ };
108
+ }
109
+ try {
110
+ outcome = await lockFn({ leadsRoot, lstatSync: deps.lstatSync }, () => performWrite(target.absolute, leadsRoot, relpath, body, ifMatch, lstat));
111
+ }
112
+ catch (err) {
113
+ // Doubt-review fix: `withDecisionsLock` symlink-checks BOTH
114
+ // decision_log.md and decisions-proposed.md (whichever one this PUT
115
+ // targets, the OTHER is still checked because it shares the lock) —
116
+ // uncaught, that 403-shaped condition fell through to the generic
117
+ // 500 handler instead of the family's typed symlink_forbidden.
118
+ // ELOCKED is deliberately NOT caught here: it propagates to the
119
+ // caller (either the app-level errorHandler for the gated route, or
120
+ // the new router's own catch, both of which map it to the same
121
+ // retryable 409 every other multi-writer state file uses (CLAUDE.md
122
+ // DO-NOT #6)).
123
+ if (err instanceof OrgSymlinkEscapeError) {
124
+ return { status: 403, body: { error: "symlink_forbidden", path: err.path } };
125
+ }
126
+ throw err;
126
127
  }
127
- return {
128
- ok: false,
129
- status: 500,
130
- body: { error: "write_failed", detail: String(err).slice(0, 200), path: relpath },
131
- };
132
128
  }
133
- return {
134
- ok: true,
135
- status: 200,
136
- body: { written: true, fingerprint: fileFingerprint(nextBuf), size: nextBuf.length },
137
- };
129
+ else {
130
+ outcome = performWrite(target.absolute, leadsRoot, relpath, body, ifMatch, lstat);
131
+ }
132
+ return { status: outcome.status, body: outcome.body };
138
133
  }
139
134
  export function registerOrgFileWrite(app, deps) {
140
- const { leadsRoot } = deps;
141
- const lstat = deps.lstatSync ?? ((p) => lstatSync(p));
142
- const lockFn = deps.withDecisionsLock ?? withDecisionsLock;
143
135
  app.put("/api/external/org/file", async (c) => {
144
136
  const relpath = c.req.query("path");
145
- if (!relpath || relpath.length === 0) {
146
- return c.json({ error: "path_required" }, 400);
147
- }
148
- const target = resolveOrgAllowlistedTarget(leadsRoot, relpath);
149
- if (!target.ok) {
150
- const status = target.reason === "not_allowlisted" ? 403 : 400;
151
- const err = target.reason === "traversal" ? "path_traversal" : target.reason;
152
- return c.json({ error: err, detail: target.reason }, status);
153
- }
154
- const declaredLength = Number(c.req.header("content-length") ?? "");
155
- if (Number.isFinite(declaredLength) && declaredLength > ORG_WRITE_MAX_BYTES) {
137
+ const contentLengthHeader = c.req.header("content-length");
138
+ // Bail before reading the request body when the declared Content-Length
139
+ // alone already exceeds the cap — preserved from the pre-extraction
140
+ // handler so an oversized upload is never buffered into memory just to
141
+ // be rejected.
142
+ const declaredLength = Number(contentLengthHeader ?? "");
143
+ if (relpath && Number.isFinite(declaredLength) && declaredLength > ORG_WRITE_MAX_BYTES) {
156
144
  return c.json({ error: "payload_too_large", maxBytes: ORG_WRITE_MAX_BYTES, size: declaredLength }, 413);
157
145
  }
158
- const body = await c.req.text();
159
- const byteLen = Buffer.byteLength(body, "utf8");
160
- if (byteLen > ORG_WRITE_MAX_BYTES) {
161
- return c.json({ error: "payload_too_large", maxBytes: ORG_WRITE_MAX_BYTES, size: byteLen }, 413);
162
- }
163
- const ifMatch = c.req.header("if-match");
164
- let outcome;
165
- if (LOCKED_KINDS.has(target.kind)) {
166
- // Pre-lock existence probe (external-review fix) — must run BEFORE
167
- // withDecisionsLock, since its own ensureFile() would otherwise
168
- // auto-vivify decisions-proposed.md first and hide a genuinely
169
- // missing target from performWrite's existence check.
170
- try {
171
- lstat(target.absolute);
172
- }
173
- catch (err) {
174
- const code = err?.code;
175
- if (code === "ENOENT") {
176
- return c.json({ error: "not_found", path: relpath }, 404);
177
- }
178
- return c.json({ error: "file_stat_failed", detail: String(err).slice(0, 200) }, 500);
179
- }
180
- try {
181
- outcome = await lockFn({ leadsRoot, lstatSync: deps.lstatSync }, () => performWrite(target.absolute, leadsRoot, relpath, body, ifMatch, lstat));
182
- }
183
- catch (err) {
184
- // Doubt-review fix: `withDecisionsLock` symlink-checks BOTH
185
- // decision_log.md and decisions-proposed.md (whichever one this PUT
186
- // targets, the OTHER is still checked because it shares the lock) —
187
- // uncaught, that 403-shaped condition fell through to the generic
188
- // 500 handler instead of the family's typed symlink_forbidden.
189
- // ELOCKED is deliberately NOT caught here: it propagates to the
190
- // app-level errorHandler (middleware/error-handler.ts), which already
191
- // maps it to the same retryable 409 every other multi-writer state
192
- // file uses (CLAUDE.md DO-NOT #6).
193
- if (err instanceof OrgSymlinkEscapeError) {
194
- return c.json({ error: "symlink_forbidden", path: err.path }, 403);
195
- }
196
- throw err;
197
- }
198
- }
199
- else {
200
- outcome = performWrite(target.absolute, leadsRoot, relpath, body, ifMatch, lstat);
201
- }
202
- return c.json(outcome.body, outcome.status);
146
+ const result = await orgFileWriteCore(deps, {
147
+ relpath,
148
+ contentLengthHeader,
149
+ body: await c.req.text(),
150
+ ifMatch: c.req.header("if-match"),
151
+ });
152
+ return c.json(result.body, result.status);
203
153
  });
204
154
  }
205
155
  //# sourceMappingURL=file-write.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-write.js","sourceRoot":"","sources":["../../../src/external/org/file-write.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"file-write.js","sourceRoot":"","sources":["../../../src/external/org/file-write.ts"],"names":[],"mappings":"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"}