clinkx 0.3.0 → 1.0.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 (555) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +10 -0
  3. package/README.md +82 -26
  4. package/clinkx-workflows/dist/allowed-roots.d.ts +1 -0
  5. package/clinkx-workflows/dist/allowed-roots.js +5 -0
  6. package/clinkx-workflows/dist/allowed-roots.js.map +1 -0
  7. package/clinkx-workflows/dist/artifacts.d.ts +20 -1
  8. package/clinkx-workflows/dist/artifacts.js +38 -9
  9. package/clinkx-workflows/dist/artifacts.js.map +1 -1
  10. package/clinkx-workflows/dist/backend.d.ts +4 -0
  11. package/clinkx-workflows/dist/citation-check.d.ts +40 -0
  12. package/clinkx-workflows/dist/citation-check.js +348 -0
  13. package/clinkx-workflows/dist/citation-check.js.map +1 -0
  14. package/clinkx-workflows/dist/clink-client.d.ts +2 -1
  15. package/clinkx-workflows/dist/clink-client.js +17 -4
  16. package/clinkx-workflows/dist/clink-client.js.map +1 -1
  17. package/clinkx-workflows/dist/config.d.ts +145 -16
  18. package/clinkx-workflows/dist/config.js +384 -26
  19. package/clinkx-workflows/dist/config.js.map +1 -1
  20. package/clinkx-workflows/dist/contained-file.d.ts +172 -0
  21. package/clinkx-workflows/dist/contained-file.js +483 -0
  22. package/clinkx-workflows/dist/contained-file.js.map +1 -0
  23. package/clinkx-workflows/dist/context-builder-lifecycle-contract.d.ts +416 -0
  24. package/clinkx-workflows/dist/context-builder-lifecycle-contract.js +328 -0
  25. package/clinkx-workflows/dist/context-builder-lifecycle-contract.js.map +1 -0
  26. package/clinkx-workflows/dist/context-builder-lifecycle-router.d.ts +24 -0
  27. package/clinkx-workflows/dist/context-builder-lifecycle-router.js +39 -0
  28. package/clinkx-workflows/dist/context-builder-lifecycle-router.js.map +1 -0
  29. package/clinkx-workflows/dist/context-cache-key.d.ts +53 -0
  30. package/clinkx-workflows/dist/context-cache-key.js +91 -0
  31. package/clinkx-workflows/dist/context-cache-key.js.map +1 -0
  32. package/clinkx-workflows/dist/context-mode-resolver.d.ts +27 -0
  33. package/clinkx-workflows/dist/context-mode-resolver.js +126 -0
  34. package/clinkx-workflows/dist/context-mode-resolver.js.map +1 -0
  35. package/clinkx-workflows/dist/context-preflight.d.ts +184 -0
  36. package/clinkx-workflows/dist/context-preflight.js +91 -0
  37. package/clinkx-workflows/dist/context-preflight.js.map +1 -0
  38. package/clinkx-workflows/dist/council-expander.d.ts +1 -1
  39. package/clinkx-workflows/dist/council-expander.js +2 -1
  40. package/clinkx-workflows/dist/council-expander.js.map +1 -1
  41. package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +165 -0
  42. package/clinkx-workflows/dist/dashboard/activity-contract.js +105 -0
  43. package/clinkx-workflows/dist/dashboard/activity-contract.js.map +1 -0
  44. package/clinkx-workflows/dist/dashboard/activity-lease.d.ts +25 -0
  45. package/clinkx-workflows/dist/dashboard/activity-lease.js +34 -0
  46. package/clinkx-workflows/dist/dashboard/activity-lease.js.map +1 -0
  47. package/clinkx-workflows/dist/dashboard/activity-reader.d.ts +86 -0
  48. package/clinkx-workflows/dist/dashboard/activity-reader.js +480 -0
  49. package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -0
  50. package/clinkx-workflows/dist/dashboard/api-routes.d.ts +36 -0
  51. package/clinkx-workflows/dist/dashboard/api-routes.js +1047 -0
  52. package/clinkx-workflows/dist/dashboard/api-routes.js.map +1 -0
  53. package/clinkx-workflows/dist/dashboard/artifact-index.d.ts +37 -0
  54. package/clinkx-workflows/dist/dashboard/artifact-index.js +145 -0
  55. package/clinkx-workflows/dist/dashboard/artifact-index.js.map +1 -0
  56. package/clinkx-workflows/dist/dashboard/auth/challenge-store.d.ts +31 -0
  57. package/clinkx-workflows/dist/dashboard/auth/challenge-store.js +65 -0
  58. package/clinkx-workflows/dist/dashboard/auth/challenge-store.js.map +1 -0
  59. package/clinkx-workflows/dist/dashboard/auth/controller.d.ts +26 -0
  60. package/clinkx-workflows/dist/dashboard/auth/controller.js +596 -0
  61. package/clinkx-workflows/dist/dashboard/auth/controller.js.map +1 -0
  62. package/clinkx-workflows/dist/dashboard/auth/credential-store.d.ts +60 -0
  63. package/clinkx-workflows/dist/dashboard/auth/credential-store.js +776 -0
  64. package/clinkx-workflows/dist/dashboard/auth/credential-store.js.map +1 -0
  65. package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.d.ts +9 -0
  66. package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js +44 -0
  67. package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js.map +1 -0
  68. package/clinkx-workflows/dist/dashboard/auth/session-store.d.ts +17 -0
  69. package/clinkx-workflows/dist/dashboard/auth/session-store.js +77 -0
  70. package/clinkx-workflows/dist/dashboard/auth/session-store.js.map +1 -0
  71. package/clinkx-workflows/dist/dashboard/auth/webauthn-service.d.ts +25 -0
  72. package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js +71 -0
  73. package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js.map +1 -0
  74. package/clinkx-workflows/dist/dashboard/context-run-liveness.d.ts +37 -0
  75. package/clinkx-workflows/dist/dashboard/context-run-liveness.js +49 -0
  76. package/clinkx-workflows/dist/dashboard/context-run-liveness.js.map +1 -0
  77. package/clinkx-workflows/dist/dashboard/context-run-reader.d.ts +222 -0
  78. package/clinkx-workflows/dist/dashboard/context-run-reader.js +1178 -0
  79. package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -0
  80. package/clinkx-workflows/dist/dashboard/contracts.d.ts +8936 -0
  81. package/clinkx-workflows/dist/dashboard/contracts.js +1157 -0
  82. package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -0
  83. package/clinkx-workflows/dist/dashboard/generation.d.ts +14 -0
  84. package/clinkx-workflows/dist/dashboard/generation.js +41 -0
  85. package/clinkx-workflows/dist/dashboard/generation.js.map +1 -0
  86. package/clinkx-workflows/dist/dashboard/index.d.ts +28 -0
  87. package/clinkx-workflows/dist/dashboard/index.js +16 -0
  88. package/clinkx-workflows/dist/dashboard/index.js.map +1 -0
  89. package/clinkx-workflows/dist/dashboard/lazy-content.d.ts +101 -0
  90. package/clinkx-workflows/dist/dashboard/lazy-content.js +748 -0
  91. package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -0
  92. package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +303 -0
  93. package/clinkx-workflows/dist/dashboard/progress-report-contract.js +221 -0
  94. package/clinkx-workflows/dist/dashboard/progress-report-contract.js.map +1 -0
  95. package/clinkx-workflows/dist/dashboard/progress-report-reader.d.ts +50 -0
  96. package/clinkx-workflows/dist/dashboard/progress-report-reader.js +581 -0
  97. package/clinkx-workflows/dist/dashboard/progress-report-reader.js.map +1 -0
  98. package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.d.ts +89 -0
  99. package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js +71 -0
  100. package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js.map +1 -0
  101. package/clinkx-workflows/dist/dashboard/progress-report-status-store.d.ts +28 -0
  102. package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +110 -0
  103. package/clinkx-workflows/dist/dashboard/progress-report-status-store.js.map +1 -0
  104. package/clinkx-workflows/dist/dashboard/read-model.d.ts +86 -0
  105. package/clinkx-workflows/dist/dashboard/read-model.js +440 -0
  106. package/clinkx-workflows/dist/dashboard/read-model.js.map +1 -0
  107. package/clinkx-workflows/dist/dashboard/request-admission.d.ts +31 -0
  108. package/clinkx-workflows/dist/dashboard/request-admission.js +126 -0
  109. package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -0
  110. package/clinkx-workflows/dist/dashboard/scanner.d.ts +75 -0
  111. package/clinkx-workflows/dist/dashboard/scanner.js +1110 -0
  112. package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -0
  113. package/clinkx-workflows/dist/dashboard/server-factory.d.ts +50 -0
  114. package/clinkx-workflows/dist/dashboard/server-factory.js +431 -0
  115. package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -0
  116. package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +2611 -0
  117. package/clinkx-workflows/dist/dashboard/session-log-contract.js +480 -0
  118. package/clinkx-workflows/dist/dashboard/session-log-contract.js.map +1 -0
  119. package/clinkx-workflows/dist/dashboard/session-log-identity.d.ts +21 -0
  120. package/clinkx-workflows/dist/dashboard/session-log-identity.js +101 -0
  121. package/clinkx-workflows/dist/dashboard/session-log-identity.js.map +1 -0
  122. package/clinkx-workflows/dist/dashboard/session-log-journal.d.ts +43 -0
  123. package/clinkx-workflows/dist/dashboard/session-log-journal.js +522 -0
  124. package/clinkx-workflows/dist/dashboard/session-log-journal.js.map +1 -0
  125. package/clinkx-workflows/dist/dashboard/session-log-reader.d.ts +61 -0
  126. package/clinkx-workflows/dist/dashboard/session-log-reader.js +378 -0
  127. package/clinkx-workflows/dist/dashboard/session-log-reader.js.map +1 -0
  128. package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +6 -0
  129. package/clinkx-workflows/dist/dashboard/session-log-redaction.js +367 -0
  130. package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -0
  131. package/clinkx-workflows/dist/dashboard/session-log-retention.d.ts +23 -0
  132. package/clinkx-workflows/dist/dashboard/session-log-retention.js +212 -0
  133. package/clinkx-workflows/dist/dashboard/session-log-retention.js.map +1 -0
  134. package/clinkx-workflows/dist/dashboard/static-responses.d.ts +10 -0
  135. package/clinkx-workflows/dist/dashboard/static-responses.js +94 -0
  136. package/clinkx-workflows/dist/dashboard/static-responses.js.map +1 -0
  137. package/clinkx-workflows/dist/dashboard/worker-lock-observer.d.ts +28 -0
  138. package/clinkx-workflows/dist/dashboard/worker-lock-observer.js +241 -0
  139. package/clinkx-workflows/dist/dashboard/worker-lock-observer.js.map +1 -0
  140. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  141. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  142. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  143. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  144. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  145. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  146. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  147. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  148. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  149. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  150. package/clinkx-workflows/dist/dashboard-ui/assets/index-DOkMXTfB.js +25 -0
  151. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  152. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  153. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  154. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  155. package/clinkx-workflows/dist/dashboard-ui/assets/style-ChJsn0KD.css +1 -0
  156. package/clinkx-workflows/dist/dashboard-ui/contracts/council-plan-review-input-v1.json +158 -0
  157. package/clinkx-workflows/dist/dashboard-ui/index.html +15 -0
  158. package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Mono-OFL.txt +93 -0
  159. package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Sans-OFL.txt +93 -0
  160. package/clinkx-workflows/dist/dashboard-ui/licenses/JetBrains-Mono-OFL.txt +93 -0
  161. package/clinkx-workflows/dist/dashboard-ui/licenses/NOTICE.md +32 -0
  162. package/clinkx-workflows/dist/dashboard-ui/theme-init.js +8 -0
  163. package/clinkx-workflows/dist/definition-normalizer.d.ts +19 -0
  164. package/clinkx-workflows/dist/definition-normalizer.js.map +1 -1
  165. package/clinkx-workflows/dist/delegate-validate.d.ts +78 -0
  166. package/clinkx-workflows/dist/delegate-validate.js +97 -0
  167. package/clinkx-workflows/dist/delegate-validate.js.map +1 -0
  168. package/clinkx-workflows/dist/engine.d.ts +141 -0
  169. package/clinkx-workflows/dist/engine.js +882 -28
  170. package/clinkx-workflows/dist/engine.js.map +1 -1
  171. package/clinkx-workflows/dist/errors.d.ts +2 -0
  172. package/clinkx-workflows/dist/errors.js.map +1 -1
  173. package/clinkx-workflows/dist/fingerprint.d.ts +1 -1
  174. package/clinkx-workflows/dist/fingerprint.js +1 -1
  175. package/clinkx-workflows/dist/index.d.ts +19 -0
  176. package/clinkx-workflows/dist/index.js +15 -0
  177. package/clinkx-workflows/dist/index.js.map +1 -1
  178. package/clinkx-workflows/dist/loader.d.ts +9 -3
  179. package/clinkx-workflows/dist/loader.js +92 -21
  180. package/clinkx-workflows/dist/loader.js.map +1 -1
  181. package/clinkx-workflows/dist/plan-review/admission.d.ts +160 -0
  182. package/clinkx-workflows/dist/plan-review/admission.js +290 -0
  183. package/clinkx-workflows/dist/plan-review/admission.js.map +1 -0
  184. package/clinkx-workflows/dist/plan-review/context-policy.d.ts +55 -0
  185. package/clinkx-workflows/dist/plan-review/context-policy.js +146 -0
  186. package/clinkx-workflows/dist/plan-review/context-policy.js.map +1 -0
  187. package/clinkx-workflows/dist/plan-review/contract.d.ts +763 -0
  188. package/clinkx-workflows/dist/plan-review/contract.js +289 -0
  189. package/clinkx-workflows/dist/plan-review/contract.js.map +1 -0
  190. package/clinkx-workflows/dist/plan-review/digest.d.ts +68 -0
  191. package/clinkx-workflows/dist/plan-review/digest.js +241 -0
  192. package/clinkx-workflows/dist/plan-review/digest.js.map +1 -0
  193. package/clinkx-workflows/dist/plan-review/discovery.d.ts +45 -0
  194. package/clinkx-workflows/dist/plan-review/discovery.js +36 -0
  195. package/clinkx-workflows/dist/plan-review/discovery.js.map +1 -0
  196. package/clinkx-workflows/dist/plan-review/execution-contract.d.ts +298 -0
  197. package/clinkx-workflows/dist/plan-review/execution-contract.js +194 -0
  198. package/clinkx-workflows/dist/plan-review/execution-contract.js.map +1 -0
  199. package/clinkx-workflows/dist/plan-review/expander.d.ts +48 -0
  200. package/clinkx-workflows/dist/plan-review/expander.js +243 -0
  201. package/clinkx-workflows/dist/plan-review/expander.js.map +1 -0
  202. package/clinkx-workflows/dist/plan-review/projector.d.ts +126 -0
  203. package/clinkx-workflows/dist/plan-review/projector.js +437 -0
  204. package/clinkx-workflows/dist/plan-review/projector.js.map +1 -0
  205. package/clinkx-workflows/dist/plan-review/publication.d.ts +95 -0
  206. package/clinkx-workflows/dist/plan-review/publication.js +182 -0
  207. package/clinkx-workflows/dist/plan-review/publication.js.map +1 -0
  208. package/clinkx-workflows/dist/plan-review/ranking-reducer.d.ts +34 -0
  209. package/clinkx-workflows/dist/plan-review/ranking-reducer.js +139 -0
  210. package/clinkx-workflows/dist/plan-review/ranking-reducer.js.map +1 -0
  211. package/clinkx-workflows/dist/plan-review/resume.d.ts +38 -0
  212. package/clinkx-workflows/dist/plan-review/resume.js +122 -0
  213. package/clinkx-workflows/dist/plan-review/resume.js.map +1 -0
  214. package/clinkx-workflows/dist/plan-review/rubric.d.ts +7 -0
  215. package/clinkx-workflows/dist/plan-review/rubric.js +24 -0
  216. package/clinkx-workflows/dist/plan-review/rubric.js.map +1 -0
  217. package/clinkx-workflows/dist/plan-review/run-state.d.ts +398 -0
  218. package/clinkx-workflows/dist/plan-review/run-state.js +183 -0
  219. package/clinkx-workflows/dist/plan-review/run-state.js.map +1 -0
  220. package/clinkx-workflows/dist/plan-review/runtime-policy.d.ts +74 -0
  221. package/clinkx-workflows/dist/plan-review/runtime-policy.js +186 -0
  222. package/clinkx-workflows/dist/plan-review/runtime-policy.js.map +1 -0
  223. package/clinkx-workflows/dist/plan-review/snapshot.d.ts +81 -0
  224. package/clinkx-workflows/dist/plan-review/snapshot.js +170 -0
  225. package/clinkx-workflows/dist/plan-review/snapshot.js.map +1 -0
  226. package/clinkx-workflows/dist/plan-review/stage-error.d.ts +19 -0
  227. package/clinkx-workflows/dist/plan-review/stage-error.js +20 -0
  228. package/clinkx-workflows/dist/plan-review/stage-error.js.map +1 -0
  229. package/clinkx-workflows/dist/post-processors/index.d.ts +20 -0
  230. package/clinkx-workflows/dist/post-processors/index.js +21 -0
  231. package/clinkx-workflows/dist/post-processors/index.js.map +1 -0
  232. package/clinkx-workflows/dist/prompt-budget.d.ts +7 -0
  233. package/clinkx-workflows/dist/prompt-budget.js +7 -1
  234. package/clinkx-workflows/dist/prompt-budget.js.map +1 -1
  235. package/clinkx-workflows/dist/reducers/index.js +2 -0
  236. package/clinkx-workflows/dist/reducers/index.js.map +1 -1
  237. package/clinkx-workflows/dist/run-lock-heartbeat.d.ts +2 -0
  238. package/clinkx-workflows/dist/run-lock-heartbeat.js +3 -0
  239. package/clinkx-workflows/dist/run-lock-heartbeat.js.map +1 -0
  240. package/clinkx-workflows/dist/run-summary/cards/council-plan-review.d.ts +34 -0
  241. package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js +74 -0
  242. package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js.map +1 -0
  243. package/clinkx-workflows/dist/run-summary/cards/index.js +5 -0
  244. package/clinkx-workflows/dist/run-summary/cards/index.js.map +1 -1
  245. package/clinkx-workflows/dist/run-summary.d.ts +13 -0
  246. package/clinkx-workflows/dist/run-summary.js +29 -1
  247. package/clinkx-workflows/dist/run-summary.js.map +1 -1
  248. package/clinkx-workflows/dist/schema.d.ts +76 -21
  249. package/clinkx-workflows/dist/schema.js +10 -1
  250. package/clinkx-workflows/dist/schema.js.map +1 -1
  251. package/clinkx-workflows/dist/seed-terms.d.ts +30 -0
  252. package/clinkx-workflows/dist/seed-terms.js +176 -0
  253. package/clinkx-workflows/dist/seed-terms.js.map +1 -0
  254. package/clinkx-workflows/dist/server.d.ts +5 -2
  255. package/clinkx-workflows/dist/server.js +82 -52
  256. package/clinkx-workflows/dist/server.js.map +1 -1
  257. package/clinkx-workflows/dist/shutdown.d.ts +18 -20
  258. package/clinkx-workflows/dist/shutdown.js +43 -61
  259. package/clinkx-workflows/dist/shutdown.js.map +1 -1
  260. package/clinkx-workflows/dist/state-schema.d.ts +38 -0
  261. package/clinkx-workflows/dist/state.d.ts +170 -3
  262. package/clinkx-workflows/dist/state.js +1275 -28
  263. package/clinkx-workflows/dist/state.js.map +1 -1
  264. package/clinkx-workflows/dist/transport.d.ts +15 -0
  265. package/clinkx-workflows/dist/transport.js +19 -10
  266. package/clinkx-workflows/dist/transport.js.map +1 -1
  267. package/clinkx-workflows/dist/types.d.ts +14 -2
  268. package/clinkx-workflows/dist/validators/council.d.ts +318 -85
  269. package/clinkx-workflows/dist/validators/council.js +52 -13
  270. package/clinkx-workflows/dist/validators/council.js.map +1 -1
  271. package/clinkx-workflows/dist/validators/index.d.ts +1 -0
  272. package/clinkx-workflows/dist/validators/index.js +3 -0
  273. package/clinkx-workflows/dist/validators/index.js.map +1 -1
  274. package/clinkx-workflows/dist/validators/plan-review.d.ts +398 -0
  275. package/clinkx-workflows/dist/validators/plan-review.js +316 -0
  276. package/clinkx-workflows/dist/validators/plan-review.js.map +1 -0
  277. package/clinkx-workflows/dist/workflow-receipt.js +188 -4
  278. package/clinkx-workflows/dist/workflow-receipt.js.map +1 -1
  279. package/clinkx-workflows/dist/workflow-tools.d.ts +48 -3
  280. package/clinkx-workflows/dist/workflow-tools.js +1402 -59
  281. package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
  282. package/clinkx-workflows/dist/workspace-signature.d.ts +16 -0
  283. package/clinkx-workflows/dist/workspace-signature.js +388 -0
  284. package/clinkx-workflows/dist/workspace-signature.js.map +1 -0
  285. package/clinkx-workflows/templates/council/answer-specialist.txt +2 -1
  286. package/clinkx-workflows/templates/council/code-review-chairman.txt +13 -0
  287. package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +4 -1
  288. package/clinkx-workflows/templates/council/code-review-reviewer.txt +19 -3
  289. package/clinkx-workflows/templates/council/debug-debugger.txt +2 -1
  290. package/clinkx-workflows/templates/council/discover-analyst.txt +2 -1
  291. package/clinkx-workflows/templates/council/member.txt +2 -1
  292. package/clinkx-workflows/templates/council/plan-review-chairman.txt +68 -0
  293. package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +51 -0
  294. package/clinkx-workflows/templates/council/plan-review-reviewer.txt +60 -0
  295. package/clinkx-workflows/templates/plan-review/chairman.txt +48 -0
  296. package/clinkx-workflows/templates/plan-review/cross-review.txt +52 -0
  297. package/clinkx-workflows/templates/plan-review/ranking.txt +16 -0
  298. package/clinkx-workflows/templates/plan-review/reviewer.txt +57 -0
  299. package/clinkx-workflows/workflows/council-answer.yaml +97 -8
  300. package/clinkx-workflows/workflows/council-code-review.yaml +103 -8
  301. package/clinkx-workflows/workflows/council-debug.yaml +97 -8
  302. package/clinkx-workflows/workflows/council-default.yaml +97 -8
  303. package/clinkx-workflows/workflows/council-discover.yaml +97 -8
  304. package/clinkx-workflows/workflows/council-plan-review.yaml +250 -0
  305. package/clinkx-workflows/workflows/plan-review-receipts.yaml +230 -0
  306. package/conf/adapters/agy.json +49 -0
  307. package/conf/adapters/claude.json +5 -0
  308. package/conf/adapters/codex-app-server.json +44 -0
  309. package/conf/adapters/codex.json +7 -2
  310. package/conf/adapters/glm.json +5 -0
  311. package/conf/adapters/grok.json +60 -0
  312. package/conf/adapters/hapi/agy.json +52 -0
  313. package/conf/adapters/hapi/claude.json +5 -0
  314. package/conf/adapters/hapi/codex.json +7 -2
  315. package/conf/adapters/hapi/glm.json +5 -0
  316. package/dist/activity-journal.d.ts +42 -0
  317. package/dist/activity-journal.js +381 -0
  318. package/dist/activity-journal.js.map +1 -0
  319. package/dist/allowed-roots.d.ts +41 -0
  320. package/dist/allowed-roots.js +125 -3
  321. package/dist/allowed-roots.js.map +1 -1
  322. package/dist/codex/activity-session-log.d.ts +14 -0
  323. package/dist/codex/activity-session-log.js +206 -0
  324. package/dist/codex/activity-session-log.js.map +1 -0
  325. package/dist/codex/agent-tools.d.ts +23 -0
  326. package/dist/codex/agent-tools.js +1270 -0
  327. package/dist/codex/agent-tools.js.map +1 -0
  328. package/dist/codex/app-server-client.d.ts +76 -0
  329. package/dist/codex/app-server-client.js +582 -0
  330. package/dist/codex/app-server-client.js.map +1 -0
  331. package/dist/codex/app-server-registry.d.ts +389 -0
  332. package/dist/codex/app-server-registry.js +1407 -0
  333. package/dist/codex/app-server-registry.js.map +1 -0
  334. package/dist/codex/client-types.d.ts +109 -0
  335. package/dist/codex/client-types.js +2 -0
  336. package/dist/codex/client-types.js.map +1 -0
  337. package/dist/codex/protocol.d.ts +5375 -0
  338. package/dist/codex/protocol.js +614 -0
  339. package/dist/codex/protocol.js.map +1 -0
  340. package/dist/codex/session.d.ts +263 -0
  341. package/dist/codex/session.js +466 -0
  342. package/dist/codex/session.js.map +1 -0
  343. package/dist/config.d.ts +185 -19
  344. package/dist/config.js +82 -16
  345. package/dist/config.js.map +1 -1
  346. package/dist/context-builder/anchors.d.ts +1 -0
  347. package/dist/context-builder/anchors.js +3 -0
  348. package/dist/context-builder/anchors.js.map +1 -1
  349. package/dist/context-builder/bundle.d.ts +35 -2
  350. package/dist/context-builder/bundle.js +342 -2417
  351. package/dist/context-builder/bundle.js.map +1 -1
  352. package/dist/context-builder/codemap.d.ts +5 -0
  353. package/dist/context-builder/codemap.js +387 -13
  354. package/dist/context-builder/codemap.js.map +1 -1
  355. package/dist/context-builder/config.d.ts +1 -0
  356. package/dist/context-builder/config.js +12 -0
  357. package/dist/context-builder/config.js.map +1 -1
  358. package/dist/context-builder/context-run-catalog.d.ts +17 -0
  359. package/dist/context-builder/context-run-catalog.js +99 -0
  360. package/dist/context-builder/context-run-catalog.js.map +1 -0
  361. package/dist/context-builder/delegate-packet.d.ts +37 -0
  362. package/dist/context-builder/delegate-packet.js +359 -67
  363. package/dist/context-builder/delegate-packet.js.map +1 -1
  364. package/dist/context-builder/delegate-validation.d.ts +10 -1
  365. package/dist/context-builder/delegate-validation.js +153 -41
  366. package/dist/context-builder/delegate-validation.js.map +1 -1
  367. package/dist/context-builder/delegate.d.ts +2 -0
  368. package/dist/context-builder/delegate.js +1 -0
  369. package/dist/context-builder/delegate.js.map +1 -1
  370. package/dist/context-builder/diagnostics.d.ts +11 -2
  371. package/dist/context-builder/diagnostics.js +39 -6
  372. package/dist/context-builder/diagnostics.js.map +1 -1
  373. package/dist/context-builder/discovery.d.ts +29 -1
  374. package/dist/context-builder/discovery.js +475 -175
  375. package/dist/context-builder/discovery.js.map +1 -1
  376. package/dist/context-builder/globs.js +10 -10
  377. package/dist/context-builder/globs.js.map +1 -1
  378. package/dist/context-builder/handler.d.ts +26 -2
  379. package/dist/context-builder/handler.js +1158 -2416
  380. package/dist/context-builder/handler.js.map +1 -1
  381. package/dist/context-builder/lifecycle-contract.d.ts +424 -0
  382. package/dist/context-builder/lifecycle-contract.js +523 -0
  383. package/dist/context-builder/lifecycle-contract.js.map +1 -0
  384. package/dist/context-builder/lifecycle-journal.d.ts +62 -0
  385. package/dist/context-builder/lifecycle-journal.js +88 -0
  386. package/dist/context-builder/lifecycle-journal.js.map +1 -0
  387. package/dist/context-builder/lifecycle-observer.d.ts +102 -0
  388. package/dist/context-builder/lifecycle-observer.js +258 -0
  389. package/dist/context-builder/lifecycle-observer.js.map +1 -0
  390. package/dist/context-builder/lifecycle-reconcile.d.ts +32 -0
  391. package/dist/context-builder/lifecycle-reconcile.js +191 -0
  392. package/dist/context-builder/lifecycle-reconcile.js.map +1 -0
  393. package/dist/context-builder/lifecycle-recovery.d.ts +2 -0
  394. package/dist/context-builder/lifecycle-recovery.js +14 -0
  395. package/dist/context-builder/lifecycle-recovery.js.map +1 -0
  396. package/dist/context-builder/path-signals.js +9 -5
  397. package/dist/context-builder/path-signals.js.map +1 -1
  398. package/dist/context-builder/progress.d.ts +3 -1
  399. package/dist/context-builder/progress.js +23 -0
  400. package/dist/context-builder/progress.js.map +1 -1
  401. package/dist/context-builder/receipt.d.ts +6 -4
  402. package/dist/context-builder/receipt.js +12 -1
  403. package/dist/context-builder/receipt.js.map +1 -1
  404. package/dist/context-builder/refine-handler.d.ts +14 -0
  405. package/dist/context-builder/refine-handler.js +790 -0
  406. package/dist/context-builder/refine-handler.js.map +1 -0
  407. package/dist/context-builder/review-coverage.d.ts +84 -0
  408. package/dist/context-builder/review-coverage.js +437 -0
  409. package/dist/context-builder/review-coverage.js.map +1 -0
  410. package/dist/context-builder/schema.d.ts +239 -1
  411. package/dist/context-builder/schema.js +112 -6
  412. package/dist/context-builder/schema.js.map +1 -1
  413. package/dist/context-builder/secrets.d.ts +28 -0
  414. package/dist/context-builder/secrets.js +1473 -44
  415. package/dist/context-builder/secrets.js.map +1 -1
  416. package/dist/context-builder/selection.d.ts +7 -3
  417. package/dist/context-builder/selection.js +200 -738
  418. package/dist/context-builder/selection.js.map +1 -1
  419. package/dist/context-builder/session-log-identity.d.ts +1 -0
  420. package/dist/context-builder/session-log-identity.js +66 -0
  421. package/dist/context-builder/session-log-identity.js.map +1 -0
  422. package/dist/context-builder/slice-range-math.d.ts +16 -0
  423. package/dist/context-builder/slice-range-math.js +137 -0
  424. package/dist/context-builder/slice-range-math.js.map +1 -0
  425. package/dist/context-builder/status.d.ts +22 -1
  426. package/dist/context-builder/status.js +59 -4
  427. package/dist/context-builder/status.js.map +1 -1
  428. package/dist/context-builder/storage.d.ts +35 -2
  429. package/dist/context-builder/storage.js +271 -14
  430. package/dist/context-builder/storage.js.map +1 -1
  431. package/dist/context-builder/token-estimate.d.ts +13 -2
  432. package/dist/context-builder/token-estimate.js +77 -4
  433. package/dist/context-builder/token-estimate.js.map +1 -1
  434. package/dist/context-builder/versions.d.ts +29 -0
  435. package/dist/context-builder/versions.js +30 -0
  436. package/dist/context-builder/versions.js.map +1 -0
  437. package/dist/context-builder/workflow-runner.d.ts +69 -0
  438. package/dist/context-builder/workflow-runner.js +1051 -0
  439. package/dist/context-builder/workflow-runner.js.map +1 -0
  440. package/dist/context-builder/write-watchdog.d.ts +14 -0
  441. package/dist/context-builder/write-watchdog.js +38 -0
  442. package/dist/context-builder/write-watchdog.js.map +1 -0
  443. package/dist/dashboard-adapter-catalog.d.ts +9 -0
  444. package/dist/dashboard-adapter-catalog.js +46 -0
  445. package/dist/dashboard-adapter-catalog.js.map +1 -0
  446. package/dist/dashboard-cli.d.ts +79 -0
  447. package/dist/dashboard-cli.js +585 -0
  448. package/dist/dashboard-cli.js.map +1 -0
  449. package/dist/dashboard-companion/client.d.ts +36 -0
  450. package/dist/dashboard-companion/client.js +210 -0
  451. package/dist/dashboard-companion/client.js.map +1 -0
  452. package/dist/dashboard-companion/config.d.ts +8 -0
  453. package/dist/dashboard-companion/config.js +52 -0
  454. package/dist/dashboard-companion/config.js.map +1 -0
  455. package/dist/dashboard-companion/http-proxy.d.ts +10 -0
  456. package/dist/dashboard-companion/http-proxy.js +234 -0
  457. package/dist/dashboard-companion/http-proxy.js.map +1 -0
  458. package/dist/dashboard-companion/protocol.d.ts +80 -0
  459. package/dist/dashboard-companion/protocol.js +455 -0
  460. package/dist/dashboard-companion/protocol.js.map +1 -0
  461. package/dist/dashboard-companion/standalone-assets.d.ts +12 -0
  462. package/dist/dashboard-companion/standalone-assets.js +72 -0
  463. package/dist/dashboard-companion/standalone-assets.js.map +1 -0
  464. package/dist/dashboard-companion/standalone-entry.d.ts +1 -0
  465. package/dist/dashboard-companion/standalone-entry.js +17 -0
  466. package/dist/dashboard-companion/standalone-entry.js.map +1 -0
  467. package/dist/dashboard-gateway/config.d.ts +9 -0
  468. package/dist/dashboard-gateway/config.js +44 -0
  469. package/dist/dashboard-gateway/config.js.map +1 -0
  470. package/dist/dashboard-gateway/index.d.ts +11 -0
  471. package/dist/dashboard-gateway/index.js +755 -0
  472. package/dist/dashboard-gateway/index.js.map +1 -0
  473. package/dist/dashboard-gateway/main.d.ts +1 -0
  474. package/dist/dashboard-gateway/main.js +25 -0
  475. package/dist/dashboard-gateway/main.js.map +1 -0
  476. package/dist/dashboard-pairing-token.d.ts +1 -0
  477. package/dist/dashboard-pairing-token.js +5 -0
  478. package/dist/dashboard-pairing-token.js.map +1 -0
  479. package/dist/elaris/client.d.ts +73 -0
  480. package/dist/elaris/client.js +825 -0
  481. package/dist/elaris/client.js.map +1 -0
  482. package/dist/elaris/config.d.ts +18 -0
  483. package/dist/elaris/config.js +75 -0
  484. package/dist/elaris/config.js.map +1 -0
  485. package/dist/elaris/errors.d.ts +41 -0
  486. package/dist/elaris/errors.js +205 -0
  487. package/dist/elaris/errors.js.map +1 -0
  488. package/dist/elaris/github-repo.d.ts +18 -0
  489. package/dist/elaris/github-repo.js +89 -0
  490. package/dist/elaris/github-repo.js.map +1 -0
  491. package/dist/elaris/index.d.ts +10 -0
  492. package/dist/elaris/index.js +11 -0
  493. package/dist/elaris/index.js.map +1 -0
  494. package/dist/elaris/redact.d.ts +89 -0
  495. package/dist/elaris/redact.js +423 -0
  496. package/dist/elaris/redact.js.map +1 -0
  497. package/dist/elaris/run-id.d.ts +5 -0
  498. package/dist/elaris/run-id.js +20 -0
  499. package/dist/elaris/run-id.js.map +1 -0
  500. package/dist/elaris/status.d.ts +45 -0
  501. package/dist/elaris/status.js +122 -0
  502. package/dist/elaris/status.js.map +1 -0
  503. package/dist/elaris/tool-schemas.d.ts +314 -0
  504. package/dist/elaris/tool-schemas.js +208 -0
  505. package/dist/elaris/tool-schemas.js.map +1 -0
  506. package/dist/elaris/tools.d.ts +73 -0
  507. package/dist/elaris/tools.js +1065 -0
  508. package/dist/elaris/tools.js.map +1 -0
  509. package/dist/elaris/types.d.ts +1541 -0
  510. package/dist/elaris/types.js +252 -0
  511. package/dist/elaris/types.js.map +1 -0
  512. package/dist/errors.d.ts +16 -0
  513. package/dist/errors.js +19 -0
  514. package/dist/errors.js.map +1 -1
  515. package/dist/handler.d.ts +4 -0
  516. package/dist/handler.js +10 -1
  517. package/dist/handler.js.map +1 -1
  518. package/dist/index.d.ts +2 -1
  519. package/dist/index.js +13 -3
  520. package/dist/index.js.map +1 -1
  521. package/dist/local-clink-backend.d.ts +3 -0
  522. package/dist/local-clink-backend.js +4 -0
  523. package/dist/local-clink-backend.js.map +1 -1
  524. package/dist/package-info.d.ts +2 -0
  525. package/dist/package-info.js +18 -0
  526. package/dist/package-info.js.map +1 -0
  527. package/dist/parsers/codex-jsonl.js +48 -3
  528. package/dist/parsers/codex-jsonl.js.map +1 -1
  529. package/dist/parsers/extract.d.ts +10 -1
  530. package/dist/parsers/extract.js +11 -3
  531. package/dist/parsers/extract.js.map +1 -1
  532. package/dist/pipeline.d.ts +4 -0
  533. package/dist/pipeline.js +129 -8
  534. package/dist/pipeline.js.map +1 -1
  535. package/dist/run-dir.d.ts +24 -0
  536. package/dist/run-dir.js +201 -2
  537. package/dist/run-dir.js.map +1 -1
  538. package/dist/runner.d.ts +13 -0
  539. package/dist/runner.js +184 -15
  540. package/dist/runner.js.map +1 -1
  541. package/dist/safety.d.ts +4 -4
  542. package/dist/safety.js +4 -4
  543. package/dist/schema.d.ts +7 -7
  544. package/dist/server.d.ts +1 -0
  545. package/dist/server.js +359 -17
  546. package/dist/server.js.map +1 -1
  547. package/dist/session-log-stream.d.ts +18 -0
  548. package/dist/session-log-stream.js +319 -0
  549. package/dist/session-log-stream.js.map +1 -0
  550. package/package.json +46 -5
  551. package/conf/adapters/gemini.json +0 -44
  552. package/conf/adapters/hapi/gemini.json +0 -47
  553. package/dist/parsers/gemini-json.d.ts +0 -2
  554. package/dist/parsers/gemini-json.js +0 -72
  555. package/dist/parsers/gemini-json.js.map +0 -1
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Containment-safe, no-follow file reads.
3
+ *
4
+ * Extracted from the private sidecar reader in `workflow-tools.ts` so the
5
+ * plan-review snapshot/contract paths reuse ONE audited implementation instead
6
+ * of hand-rolling a second realpath+O_NOFOLLOW dance.
7
+ *
8
+ * The invariant every caller depends on: a single file handle backs both the
9
+ * stat and the read, so there is no check/use window, and `O_NOFOLLOW` rejects
10
+ * a final-component symlink swapped in after the containment check.
11
+ */
12
+ /**
13
+ * Structured discriminator for the subset of policy violations that callers must
14
+ * be able to branch on WITHOUT matching message wording.
15
+ *
16
+ * Deliberately NOT a taxonomy over every throw site: only the allowed-roots
17
+ * containment failure carries a reason today, because it is the only one a
18
+ * caller (plan-review's snapshot root hint) must distinguish. The other throw
19
+ * sites leave `reason` undefined rather than grow a speculative enum.
20
+ */
21
+ export type ContainedFilePolicyReason = "outside_allowed_roots";
22
+ export interface ContainedFileFingerprint {
23
+ readonly dev: bigint;
24
+ readonly ino: bigint;
25
+ readonly mtimeNs: bigint;
26
+ readonly ctimeNs: bigint;
27
+ readonly size: bigint;
28
+ readonly nlink: bigint;
29
+ }
30
+ export interface ContainedReadBeforeCloseContext {
31
+ readonly fingerprint: ContainedFileFingerprint;
32
+ /** Resolve the authoritative path for the still-open descriptor. */
33
+ readonly resolveRealPath: () => Promise<string | undefined>;
34
+ }
35
+ /**
36
+ * `expectedPath` is OPTIONAL so an injected one-argument verifier (every test
37
+ * seam in this repo) stays assignable. Callers that know the pathname they
38
+ * opened should always pass it: it selects the cheap darwin verification path
39
+ * described on {@link resolveOpenedDescriptorPath}.
40
+ */
41
+ export type OpenedDescriptorPathVerifier = (fd: number, expectedPath?: string) => Promise<string | undefined>;
42
+ /**
43
+ * A containment/policy violation (absolute where relative is required, escape
44
+ * outside the root, symlink, non-regular file). Distinct from a plain Error so
45
+ * callers can tell "you may not read this" from "it is not there right now".
46
+ */
47
+ export declare class ContainedFilePolicyError extends Error {
48
+ readonly reason: ContainedFilePolicyReason | undefined;
49
+ constructor(message: string, reason?: ContainedFilePolicyReason);
50
+ }
51
+ export declare class ContainedFileChangedError extends ContainedFilePolicyError {
52
+ constructor(message: string);
53
+ }
54
+ export declare class ContainedFileTooLargeError extends ContainedFilePolicyError {
55
+ readonly size: number | undefined;
56
+ readonly fingerprint: ContainedFileFingerprint | undefined;
57
+ constructor(message: string, size?: number, fingerprint?: ContainedFileFingerprint);
58
+ }
59
+ export interface ContainedReadOptions {
60
+ /** Return the file body decoded as UTF-8. */
61
+ readonly wantContent?: boolean | undefined;
62
+ /** Return the raw file body. Mutually usable with `wantContent`. */
63
+ readonly wantBytes?: boolean | undefined;
64
+ /** Reject anything that is not a regular file (fstat on the open handle). */
65
+ readonly requireRegular?: boolean | undefined;
66
+ /**
67
+ * Wording for policy-violation messages, e.g. `"run directory"`. Callers with
68
+ * an established message contract pass their own noun so extracting this
69
+ * module did not silently reword their errors.
70
+ */
71
+ readonly containerLabel?: string | undefined;
72
+ /** Wording for the path itself, e.g. `"outputPath"`. */
73
+ readonly pathLabel?: string | undefined;
74
+ /** Reject larger files before allocating their body. */
75
+ readonly maxBytes?: number | undefined;
76
+ /** Deterministic race-injection seam used by containment regression tests. */
77
+ readonly beforeOpen?: (() => void) | undefined;
78
+ /** Deterministic seam after open but before descriptor-path resolution. */
79
+ readonly afterOpen?: (() => void) | undefined;
80
+ /** Verify caller-owned invariants while the admitted descriptor is still open. */
81
+ readonly beforeClose?: (context: ContainedReadBeforeCloseContext) => void | Promise<void>;
82
+ /** Injectable platform adapter for authoritative descriptor-path resolution. */
83
+ readonly descriptorPathVerifier?: OpenedDescriptorPathVerifier | undefined;
84
+ /**
85
+ * Require the opened descriptor to remain at the lexical canonical path
86
+ * requested under `rootDir`, rather than merely somewhere inside that root.
87
+ * Dashboard state/body reads opt in; shared callers retain containment-only
88
+ * behavior.
89
+ */
90
+ readonly requireExactRequestedPath?: boolean | undefined;
91
+ }
92
+ export interface ContainedReadResult {
93
+ readonly size: number;
94
+ readonly content?: string | undefined;
95
+ readonly bytes?: Buffer | undefined;
96
+ /** The canonical path actually opened. */
97
+ readonly realPath: string;
98
+ /** BigInt identity/content tuple captured from the same handle as the body. */
99
+ readonly fingerprint: ContainedFileFingerprint;
100
+ }
101
+ export declare function containedFileFingerprintsEqual(left: ContainedFileFingerprint, right: ContainedFileFingerprint): boolean;
102
+ /**
103
+ * Resolve a verified path for an already-open descriptor. Authoritative on
104
+ * Linux; on darwin the check is probabilistic — see below. Unsupported
105
+ * platforms and helper failures fail closed (`undefined`).
106
+ *
107
+ * WITHOUT an `expectedPath`, Linux reads `/proc/self/fd/<fd>`, which the
108
+ * kernel answers from the descriptor itself. Darwin has no equivalent and
109
+ * falls through to the historical `lsof` helper.
110
+ *
111
+ * WITH an `expectedPath` (linux and darwin) this canonicalizes
112
+ * `P = realpath(expectedPath)`, then `lstat`s `P` and compares `(dev, ino)`
113
+ * against an `fstat` of the descriptor. That pair is NOT atomic: it is two
114
+ * independent walks of a mutable pathname, microseconds apart, and `lstat` is
115
+ * no-follow only on the FINAL component — every intermediate component is
116
+ * resolved again. An ancestor swapped between the two walks can steer the
117
+ * `lstat` onto the same out-of-root inode the descriptor holds, so the
118
+ * comparison passes while the returned string still reads as in-root; the
119
+ * caller's containment re-assert then succeeds on a path that no longer
120
+ * describes the opened bytes. `P` is returned for that re-assert, which is
121
+ * deliberately NOT done here (this helper holds no roots).
122
+ *
123
+ * Ancestor ABA restoration defeats the mitigation outright: restore the
124
+ * original ancestor before the caller's post-read re-verify and both the verify
125
+ * and the re-verify agree — the re-verify's string comparison against the first
126
+ * result then matches by construction.
127
+ *
128
+ * So the darwin guarantee RAISES THE COST of a race rather than eliminating it:
129
+ * three adjacent synchronous syscalls with no `await` between them, and callers
130
+ * verify twice, so the window must be won twice. It is meaningful only under a
131
+ * trusted-local-filesystem threat model — NOT against an adversary with
132
+ * concurrent write access to any ancestor directory.
133
+ *
134
+ * No stronger in-process guarantee exists to reach for: Node exposes no
135
+ * `openat`, no `AT_*` constants, and no dirfd-accepting API, so a
136
+ * descriptor-relative no-follow walk cannot be expressed at all, and darwin's
137
+ * `/dev/fd/<fd>` is not traversable. The residual is accepted deliberately, not
138
+ * overlooked; closing it would require a native addon.
139
+ *
140
+ * `lstatSync`, not `statSync`, on purpose: `realpathSync.native` has already
141
+ * resolved every symlink in `P`, so the final component can only be a symlink
142
+ * if one was swapped in AFTER that resolve. `lstat` fails closed on that swap;
143
+ * `stat` would follow it. Same syscall cost, strictly stronger.
144
+ *
145
+ * What it does NOT prove: that the descriptor has no OTHER name. An out-of-root
146
+ * hardlink alias to the same inode is not detected. `fcntl(F_GETPATH)` would not
147
+ * fix that either — it reads the same darwin vnode name cache, which holds one
148
+ * name per inode and is rewritten by the most recent lookup.
149
+ *
150
+ * WITHOUT an `expectedPath` the historical `lsof` implementation is retained
151
+ * verbatim, so an unmigrated caller keeps today's exact behavior instead of
152
+ * silently downgrading to a weaker check it never opted into.
153
+ */
154
+ export declare function resolveOpenedDescriptorPath(fd: number, expectedPath?: string): Promise<string | undefined>;
155
+ /**
156
+ * Read a run-relative sidecar. `relPath` MUST be relative to `rootDir`.
157
+ *
158
+ * Realpaths BOTH sides: on macOS `/tmp` (and mkdtemp dirs) is itself a symlink
159
+ * to `/private/tmp`, so realpathing only the target would false-reject a
160
+ * legitimate read.
161
+ */
162
+ export declare function readContainedRelativeFile(rootDir: string, relPath: string, options?: ContainedReadOptions): Promise<ContainedReadResult>;
163
+ /**
164
+ * Read an absolute path that must live inside at least one allowed root.
165
+ *
166
+ * Unlike {@link readContainedRelativeFile} this deliberately does NOT realpath
167
+ * the target before opening: resolving the final component would silently
168
+ * follow a symlink and defeat the "reject symlink" requirement. Only the
169
+ * PARENT is canonicalized for the containment check; `O_NOFOLLOW` then rejects
170
+ * a symlinked final component at open time.
171
+ */
172
+ export declare function readContainedAbsoluteFile(allowedRoots: readonly string[], targetPath: string, options?: ContainedReadOptions): Promise<ContainedReadResult>;
@@ -0,0 +1,483 @@
1
+ /**
2
+ * Containment-safe, no-follow file reads.
3
+ *
4
+ * Extracted from the private sidecar reader in `workflow-tools.ts` so the
5
+ * plan-review snapshot/contract paths reuse ONE audited implementation instead
6
+ * of hand-rolling a second realpath+O_NOFOLLOW dance.
7
+ *
8
+ * The invariant every caller depends on: a single file handle backs both the
9
+ * stat and the read, so there is no check/use window, and `O_NOFOLLOW` rejects
10
+ * a final-component symlink swapped in after the containment check.
11
+ */
12
+ import { spawn } from "node:child_process";
13
+ import { constants as fsConstants, fstatSync, lstatSync, realpathSync, } from "node:fs";
14
+ import { open } from "node:fs/promises";
15
+ import { basename, isAbsolute, relative, resolve, sep } from "node:path";
16
+ /**
17
+ * A containment/policy violation (absolute where relative is required, escape
18
+ * outside the root, symlink, non-regular file). Distinct from a plain Error so
19
+ * callers can tell "you may not read this" from "it is not there right now".
20
+ */
21
+ export class ContainedFilePolicyError extends Error {
22
+ // Always assigned (never an optional property): this package enables
23
+ // `exactOptionalPropertyTypes`, under which an `reason?:` declaration would
24
+ // reject an explicit `undefined` assignment.
25
+ reason;
26
+ constructor(message, reason) {
27
+ super(message);
28
+ this.name = "ContainedFilePolicyError";
29
+ this.reason = reason;
30
+ }
31
+ }
32
+ export class ContainedFileChangedError extends ContainedFilePolicyError {
33
+ constructor(message) {
34
+ super(message);
35
+ this.name = "ContainedFileChangedError";
36
+ }
37
+ }
38
+ export class ContainedFileTooLargeError extends ContainedFilePolicyError {
39
+ size;
40
+ fingerprint;
41
+ constructor(message, size, fingerprint) {
42
+ super(message);
43
+ this.name = "ContainedFileTooLargeError";
44
+ this.size = size;
45
+ this.fingerprint = fingerprint;
46
+ }
47
+ }
48
+ function fingerprintFromStats(stats) {
49
+ return {
50
+ dev: stats.dev,
51
+ ino: stats.ino,
52
+ mtimeNs: stats.mtimeNs,
53
+ ctimeNs: stats.ctimeNs,
54
+ size: stats.size,
55
+ nlink: stats.nlink,
56
+ };
57
+ }
58
+ export function containedFileFingerprintsEqual(left, right) {
59
+ return left.dev === right.dev &&
60
+ left.ino === right.ino &&
61
+ left.mtimeNs === right.mtimeNs &&
62
+ left.ctimeNs === right.ctimeNs &&
63
+ left.size === right.size &&
64
+ left.nlink === right.nlink;
65
+ }
66
+ /**
67
+ * Open `realTarget` once with no-follow semantics and serve every requested
68
+ * projection (size / bytes / text) from that SAME handle.
69
+ */
70
+ async function readOpenedContained(realTarget, realRoots, options, exactRequestedPath) {
71
+ // O_NOFOLLOW rejects a final-component symlink swapped in between the
72
+ // containment check and this open — closing the check/use TOCTOU window.
73
+ // When the caller requires a regular file, also admit the descriptor
74
+ // non-blockingly: a FIFO (or other special file) opened O_RDONLY alone blocks
75
+ // until a writer connects, which would hang the reader indefinitely BEFORE the
76
+ // requireRegular fstat can reject it. O_NONBLOCK makes that open return at
77
+ // once so the non-regular file is rejected promptly. It is a no-op for regular
78
+ // files (their reads never return EAGAIN), so gating it on requireRegular
79
+ // preserves byte-identical behavior for every shared caller that does not opt
80
+ // into the regular-file requirement.
81
+ const requireRegular = options.requireRegular === true;
82
+ const flags = fsConstants.O_RDONLY |
83
+ (fsConstants.O_NOFOLLOW ?? 0) |
84
+ (requireRegular ? (fsConstants.O_NONBLOCK ?? 0) : 0);
85
+ const verifyDescriptorPath = options.descriptorPathVerifier ??
86
+ resolveOpenedDescriptorPath;
87
+ options.beforeOpen?.();
88
+ const fh = await open(realTarget, flags);
89
+ try {
90
+ const stats = await fh.stat({ bigint: true });
91
+ const fingerprint = fingerprintFromStats(stats);
92
+ // Reject a non-regular file (FIFO, device, ...) right after the same-handle
93
+ // fstat, BEFORE descriptor-path resolution and reads. For a FIFO this is the
94
+ // case O_NONBLOCK admission exists to make reachable: without it the open
95
+ // above would still be blocked here. Moving the check ahead of the expensive
96
+ // descriptor-path lookup also avoids spawning lsof for a file that will be
97
+ // rejected anyway.
98
+ if (requireRegular && !stats.isFile()) {
99
+ throw new ContainedFilePolicyError(`Path is not a regular file: ${realTarget}`);
100
+ }
101
+ options.afterOpen?.();
102
+ const openedRealPath = await verifyDescriptorPath(fh.fd, realTarget);
103
+ if (openedRealPath === undefined) {
104
+ throw new ContainedFilePolicyError(`Opened file descriptor containment could not be verified: ${realTarget}`);
105
+ }
106
+ if (!realRoots.some((root) => containsPath(root, openedRealPath))) {
107
+ throw new ContainedFilePolicyError(`Opened file escaped its containing root: ${realTarget}`);
108
+ }
109
+ if (exactRequestedPath !== undefined &&
110
+ openedRealPath !== exactRequestedPath) {
111
+ throw new ContainedFilePolicyError(`Opened file did not match its requested path: ${realTarget}`);
112
+ }
113
+ const exceedsMaxBytes = options.maxBytes !== undefined &&
114
+ Number(stats.size) > options.maxBytes;
115
+ // Read into a buffer sized from the checked handle stat. Unlike readFile(),
116
+ // this cannot allocate past the admitted size if the file grows while the
117
+ // read is in progress. A one-byte probe detects growth without widening the
118
+ // allocation bound.
119
+ let bytes;
120
+ let extraByteRead = false;
121
+ if (!exceedsMaxBytes && (options.wantBytes === true || options.wantContent === true)) {
122
+ bytes = Buffer.allocUnsafe(Number(stats.size));
123
+ let offset = 0;
124
+ while (offset < bytes.length) {
125
+ const chunk = await fh.read(bytes, offset, bytes.length - offset, null);
126
+ if (chunk.bytesRead === 0)
127
+ break;
128
+ offset += chunk.bytesRead;
129
+ }
130
+ const probe = Buffer.allocUnsafe(1);
131
+ extraByteRead = (await fh.read(probe, 0, 1, null)).bytesRead > 0;
132
+ if (BigInt(offset) !== stats.size) {
133
+ bytes = bytes.subarray(0, offset);
134
+ }
135
+ }
136
+ const afterStats = await fh.stat({ bigint: true });
137
+ if (bytes !== undefined &&
138
+ (afterStats.size !== stats.size ||
139
+ BigInt(bytes.byteLength) !== stats.size ||
140
+ extraByteRead)) {
141
+ throw new ContainedFileChangedError(`File changed while it was being read: ${realTarget}`);
142
+ }
143
+ const afterFingerprint = fingerprintFromStats(afterStats);
144
+ if (!containedFileFingerprintsEqual(fingerprint, afterFingerprint) ||
145
+ afterFingerprint.nlink === 0n) {
146
+ throw new ContainedFileChangedError(`File changed while it was being read: ${realTarget}`);
147
+ }
148
+ // `openedRealPath` was resolved from this exact live descriptor after open.
149
+ // The caller's before-close hook may need that authoritative admission path,
150
+ // but resolving it a second time is redundant: the unconditional final
151
+ // descriptor lookup below still detects any rename/unlink that occurs before
152
+ // commit. On Darwin this avoids one `lsof` subprocess per admitted file
153
+ // without weakening the final post-hook path check.
154
+ await options.beforeClose?.({
155
+ fingerprint: afterFingerprint,
156
+ resolveRealPath: async () => openedRealPath,
157
+ });
158
+ const finalStats = await fh.stat({ bigint: true });
159
+ const finalFingerprint = fingerprintFromStats(finalStats);
160
+ const finalRealPath = await verifyDescriptorPath(fh.fd, realTarget);
161
+ if (!containedFileFingerprintsEqual(fingerprint, finalFingerprint) ||
162
+ finalFingerprint.nlink === 0n ||
163
+ finalRealPath === undefined ||
164
+ finalRealPath !== openedRealPath ||
165
+ (exactRequestedPath !== undefined &&
166
+ finalRealPath !== exactRequestedPath) ||
167
+ !realRoots.some((root) => containsPath(root, finalRealPath))) {
168
+ throw new ContainedFileChangedError(`File changed while it was being read: ${realTarget}`);
169
+ }
170
+ if (exceedsMaxBytes) {
171
+ throw new ContainedFileTooLargeError(`File exceeds the ${String(options.maxBytes)} byte limit: ${realTarget}`, Number(finalStats.size), finalFingerprint);
172
+ }
173
+ const result = {
174
+ size: Number(finalStats.size),
175
+ realPath: finalRealPath,
176
+ ...(options.wantContent === true
177
+ ? { content: bytes.toString("utf-8") }
178
+ : {}),
179
+ ...(options.wantBytes === true ? { bytes } : {}),
180
+ };
181
+ // Preserve the historical whole-result JSON shape. BigInt cannot be encoded
182
+ // by JSON.stringify, while callers that need cache identity access the
183
+ // fingerprint explicitly through the typed interface.
184
+ Object.defineProperty(result, "fingerprint", {
185
+ configurable: false,
186
+ enumerable: false,
187
+ value: finalFingerprint,
188
+ writable: false,
189
+ });
190
+ return result;
191
+ }
192
+ finally {
193
+ await fh.close();
194
+ }
195
+ }
196
+ /**
197
+ * Proper-descendant containment: `realTarget` must sit strictly INSIDE
198
+ * `realRoot`, never be the root itself.
199
+ *
200
+ * Segment-aware on purpose. A bare `rel.startsWith("..")` test conflates the
201
+ * escape segment `..` with any legal in-root component whose NAME merely begins
202
+ * with two dots — `<root>/..plans` is an ordinary child directory, not an
203
+ * escape. Only `..` alone, or `..` followed by a separator, is an ascent; both
204
+ * `relative()` outputs are matched exactly here.
205
+ */
206
+ function containsPathStrictly(realRoot, realTarget) {
207
+ const rel = relative(realRoot, realTarget);
208
+ return (rel !== "" &&
209
+ rel !== ".." &&
210
+ !rel.startsWith(`..${sep}`) &&
211
+ !isAbsolute(rel));
212
+ }
213
+ /** Containment that additionally accepts the root directory itself. */
214
+ function containsPath(realRoot, realTarget) {
215
+ return relative(realRoot, realTarget) === "" ||
216
+ containsPathStrictly(realRoot, realTarget);
217
+ }
218
+ const DESCRIPTOR_PATH_TIMEOUT_MS = 2_000;
219
+ const DESCRIPTOR_PATH_OUTPUT_LIMIT = 8_192;
220
+ /**
221
+ * Resolve a verified path for an already-open descriptor. Authoritative on
222
+ * Linux; on darwin the check is probabilistic — see below. Unsupported
223
+ * platforms and helper failures fail closed (`undefined`).
224
+ *
225
+ * WITHOUT an `expectedPath`, Linux reads `/proc/self/fd/<fd>`, which the
226
+ * kernel answers from the descriptor itself. Darwin has no equivalent and
227
+ * falls through to the historical `lsof` helper.
228
+ *
229
+ * WITH an `expectedPath` (linux and darwin) this canonicalizes
230
+ * `P = realpath(expectedPath)`, then `lstat`s `P` and compares `(dev, ino)`
231
+ * against an `fstat` of the descriptor. That pair is NOT atomic: it is two
232
+ * independent walks of a mutable pathname, microseconds apart, and `lstat` is
233
+ * no-follow only on the FINAL component — every intermediate component is
234
+ * resolved again. An ancestor swapped between the two walks can steer the
235
+ * `lstat` onto the same out-of-root inode the descriptor holds, so the
236
+ * comparison passes while the returned string still reads as in-root; the
237
+ * caller's containment re-assert then succeeds on a path that no longer
238
+ * describes the opened bytes. `P` is returned for that re-assert, which is
239
+ * deliberately NOT done here (this helper holds no roots).
240
+ *
241
+ * Ancestor ABA restoration defeats the mitigation outright: restore the
242
+ * original ancestor before the caller's post-read re-verify and both the verify
243
+ * and the re-verify agree — the re-verify's string comparison against the first
244
+ * result then matches by construction.
245
+ *
246
+ * So the darwin guarantee RAISES THE COST of a race rather than eliminating it:
247
+ * three adjacent synchronous syscalls with no `await` between them, and callers
248
+ * verify twice, so the window must be won twice. It is meaningful only under a
249
+ * trusted-local-filesystem threat model — NOT against an adversary with
250
+ * concurrent write access to any ancestor directory.
251
+ *
252
+ * No stronger in-process guarantee exists to reach for: Node exposes no
253
+ * `openat`, no `AT_*` constants, and no dirfd-accepting API, so a
254
+ * descriptor-relative no-follow walk cannot be expressed at all, and darwin's
255
+ * `/dev/fd/<fd>` is not traversable. The residual is accepted deliberately, not
256
+ * overlooked; closing it would require a native addon.
257
+ *
258
+ * `lstatSync`, not `statSync`, on purpose: `realpathSync.native` has already
259
+ * resolved every symlink in `P`, so the final component can only be a symlink
260
+ * if one was swapped in AFTER that resolve. `lstat` fails closed on that swap;
261
+ * `stat` would follow it. Same syscall cost, strictly stronger.
262
+ *
263
+ * What it does NOT prove: that the descriptor has no OTHER name. An out-of-root
264
+ * hardlink alias to the same inode is not detected. `fcntl(F_GETPATH)` would not
265
+ * fix that either — it reads the same darwin vnode name cache, which holds one
266
+ * name per inode and is rewritten by the most recent lookup.
267
+ *
268
+ * WITHOUT an `expectedPath` the historical `lsof` implementation is retained
269
+ * verbatim, so an unmigrated caller keeps today's exact behavior instead of
270
+ * silently downgrading to a weaker check it never opted into.
271
+ */
272
+ export async function resolveOpenedDescriptorPath(fd, expectedPath) {
273
+ if (expectedPath !== undefined) {
274
+ if (process.platform !== "linux" && process.platform !== "darwin") {
275
+ return undefined;
276
+ }
277
+ try {
278
+ const canonical = realpathSync.native(expectedPath);
279
+ const opened = fstatSync(fd, { bigint: true });
280
+ const named = lstatSync(canonical, { bigint: true });
281
+ if (opened.dev !== named.dev || opened.ino !== named.ino)
282
+ return undefined;
283
+ return canonical;
284
+ }
285
+ catch {
286
+ return undefined;
287
+ }
288
+ }
289
+ if (process.platform === "linux") {
290
+ try {
291
+ const path = realpathSync(`/proc/self/fd/${String(fd)}`);
292
+ return path.includes(" (deleted)") ? undefined : path;
293
+ }
294
+ catch {
295
+ return undefined;
296
+ }
297
+ }
298
+ if (process.platform !== "darwin")
299
+ return undefined;
300
+ return await new Promise((resolvePath) => {
301
+ let child;
302
+ try {
303
+ child = spawn("/usr/sbin/lsof", ["-b", "-w", "-a", "-p", String(process.pid), "-d", String(fd), "-F0n"], {
304
+ shell: false,
305
+ detached: true,
306
+ env: {
307
+ PATH: "/usr/bin:/bin:/usr/sbin:/sbin",
308
+ LANG: "C",
309
+ },
310
+ stdio: ["ignore", "pipe", "pipe"],
311
+ });
312
+ }
313
+ catch {
314
+ resolvePath(undefined);
315
+ return;
316
+ }
317
+ let stdout = "";
318
+ let stderrBytes = 0;
319
+ let outputExceeded = false;
320
+ let forcedFailure = false;
321
+ let settled = false;
322
+ const terminate = () => {
323
+ try {
324
+ if (child.pid != null)
325
+ process.kill(-child.pid, "SIGKILL");
326
+ }
327
+ catch {
328
+ child.kill("SIGKILL");
329
+ }
330
+ };
331
+ const timer = setTimeout(() => {
332
+ forcedFailure = true;
333
+ terminate();
334
+ }, DESCRIPTOR_PATH_TIMEOUT_MS);
335
+ timer.unref();
336
+ const settle = (path) => {
337
+ if (settled)
338
+ return;
339
+ settled = true;
340
+ clearTimeout(timer);
341
+ resolvePath(path);
342
+ };
343
+ child.stdout?.on("data", (chunk) => {
344
+ if (outputExceeded)
345
+ return;
346
+ if (Buffer.byteLength(stdout) + chunk.byteLength > DESCRIPTOR_PATH_OUTPUT_LIMIT) {
347
+ outputExceeded = true;
348
+ terminate();
349
+ return;
350
+ }
351
+ stdout += chunk.toString("utf-8");
352
+ });
353
+ child.stderr?.on("data", (chunk) => {
354
+ stderrBytes += chunk.byteLength;
355
+ if (stderrBytes > DESCRIPTOR_PATH_OUTPUT_LIMIT) {
356
+ forcedFailure = true;
357
+ terminate();
358
+ }
359
+ });
360
+ child.once("error", () => settle(undefined));
361
+ child.once("close", (status) => {
362
+ if (status !== 0 || outputExceeded || forcedFailure) {
363
+ settle(undefined);
364
+ return;
365
+ }
366
+ const names = stdout
367
+ .split("\0")
368
+ .map((field) => field.replace(/^\n+/, ""))
369
+ .filter((field) => field.startsWith("n"))
370
+ .map((field) => field.slice(1))
371
+ .filter((path) => path !== "" && !path.includes(" (deleted)"));
372
+ settle(names.length === 1 ? names[0] : undefined);
373
+ });
374
+ });
375
+ }
376
+ /**
377
+ * Read a run-relative sidecar. `relPath` MUST be relative to `rootDir`.
378
+ *
379
+ * Realpaths BOTH sides: on macOS `/tmp` (and mkdtemp dirs) is itself a symlink
380
+ * to `/private/tmp`, so realpathing only the target would false-reject a
381
+ * legitimate read.
382
+ */
383
+ export async function readContainedRelativeFile(rootDir, relPath, options = {}) {
384
+ const container = options.containerLabel ?? "containing directory";
385
+ const pathLabel = options.pathLabel ?? "path";
386
+ if (isAbsolute(relPath)) {
387
+ throw new ContainedFilePolicyError(`${pathLabel} must be relative to the ${container}`);
388
+ }
389
+ const realRoot = realpathSync(resolve(rootDir));
390
+ const normalized = resolve(realRoot, relPath);
391
+ if (!containsPath(realRoot, normalized)) {
392
+ throw new ContainedFilePolicyError(`${pathLabel} escapes the ${container}`);
393
+ }
394
+ const base = basename(normalized);
395
+ if (base === "") {
396
+ throw new ContainedFilePolicyError(`${pathLabel} has no file component`);
397
+ }
398
+ // Canonicalize only the parent. Realpathing the final component would follow
399
+ // an in-root symlink before O_NOFOLLOW gets a chance to reject it.
400
+ const realParent = realpathSync(resolve(normalized, ".."));
401
+ const realTarget = resolve(realParent, base);
402
+ if (!containsPath(realRoot, realTarget)) {
403
+ throw new ContainedFilePolicyError(`${pathLabel} escapes the ${container}`);
404
+ }
405
+ try {
406
+ return await readOpenedContained(realTarget, [realRoot], options, options.requireExactRequestedPath === true ? normalized : undefined);
407
+ }
408
+ catch (err) {
409
+ if (err?.code === "ELOOP") {
410
+ throw new ContainedFilePolicyError(`${pathLabel} is a symlink and cannot be read`);
411
+ }
412
+ throw err;
413
+ }
414
+ }
415
+ /**
416
+ * Read an absolute path that must live inside at least one allowed root.
417
+ *
418
+ * Unlike {@link readContainedRelativeFile} this deliberately does NOT realpath
419
+ * the target before opening: resolving the final component would silently
420
+ * follow a symlink and defeat the "reject symlink" requirement. Only the
421
+ * PARENT is canonicalized for the containment check; `O_NOFOLLOW` then rejects
422
+ * a symlinked final component at open time.
423
+ */
424
+ export async function readContainedAbsoluteFile(allowedRoots, targetPath, options = {}) {
425
+ if (!isAbsolute(targetPath)) {
426
+ throw new ContainedFilePolicyError(`path must be absolute: ${targetPath}`);
427
+ }
428
+ if (allowedRoots.length === 0) {
429
+ throw new ContainedFilePolicyError("no allowed roots are configured for path containment");
430
+ }
431
+ const normalized = resolve(targetPath);
432
+ const parent = resolve(normalized, "..");
433
+ const base = basename(normalized);
434
+ if (base === "") {
435
+ throw new ContainedFilePolicyError(`path has no file component: ${targetPath}`);
436
+ }
437
+ let realParent;
438
+ try {
439
+ realParent = realpathSync(parent);
440
+ }
441
+ catch {
442
+ throw new ContainedFilePolicyError(`path parent directory does not exist: ${parent}`);
443
+ }
444
+ // basename(), NOT slice arithmetic against `parent.length`: for a file
445
+ // directly under "/" the parent is "/" (length 1) and the offset math would
446
+ // silently eat the filename's first character.
447
+ const realTarget = resolve(realParent, base);
448
+ const realRoots = allowedRoots.map((root) => {
449
+ let realRoot;
450
+ try {
451
+ realRoot = realpathSync(resolve(root));
452
+ }
453
+ catch {
454
+ realRoot = resolve(root);
455
+ }
456
+ return realRoot;
457
+ });
458
+ const allowed = realRoots.some((realRoot) => containsPathStrictly(realRoot, realTarget));
459
+ if (!allowed) {
460
+ // `rel === ""` means the path IS an allowed root directory, not an escape —
461
+ // a materially different mistake that the shared "outside all allowed roots"
462
+ // wording actively misdescribes. Safe to re-scan: `some()` short-circuits
463
+ // only on true, so `allowed === false` means every root was visited and none
464
+ // matched. Both sub-cases carry the SAME reason: each is a rejection by the
465
+ // allowed-roots check, which is exactly what the reason discriminates.
466
+ const isExactRoot = realRoots.some((realRoot) => relative(realRoot, realTarget) === "");
467
+ if (isExactRoot) {
468
+ throw new ContainedFilePolicyError(`path "${targetPath}" resolves to an allowed root directory; expected a file within an allowed root`, "outside_allowed_roots");
469
+ }
470
+ throw new ContainedFilePolicyError(`path "${targetPath}" is outside all allowed roots [${allowedRoots.join(", ")}]`, "outside_allowed_roots");
471
+ }
472
+ try {
473
+ return await readOpenedContained(realTarget, realRoots, options);
474
+ }
475
+ catch (err) {
476
+ // ELOOP is how O_NOFOLLOW reports "the final component is a symlink".
477
+ if (err?.code === "ELOOP") {
478
+ throw new ContainedFilePolicyError(`path is a symlink and cannot be read: ${targetPath}`);
479
+ }
480
+ throw err;
481
+ }
482
+ }
483
+ //# sourceMappingURL=contained-file.js.map