clinkx 1.0.0 → 1.1.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 (331) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +72 -0
  3. package/clinkx-workflows/dist/chairman-knobs.d.ts +24 -0
  4. package/clinkx-workflows/dist/chairman-knobs.js +27 -0
  5. package/clinkx-workflows/dist/chairman-knobs.js.map +1 -0
  6. package/clinkx-workflows/dist/clink-client.d.ts +10 -0
  7. package/clinkx-workflows/dist/clink-client.js +39 -1
  8. package/clinkx-workflows/dist/clink-client.js.map +1 -1
  9. package/clinkx-workflows/dist/config.d.ts +29 -1
  10. package/clinkx-workflows/dist/config.js +35 -2
  11. package/clinkx-workflows/dist/config.js.map +1 -1
  12. package/clinkx-workflows/dist/context-preflight.d.ts +5 -0
  13. package/clinkx-workflows/dist/context-preflight.js.map +1 -1
  14. package/clinkx-workflows/dist/council-execution-policy.d.ts +68 -0
  15. package/clinkx-workflows/dist/council-execution-policy.js +110 -0
  16. package/clinkx-workflows/dist/council-execution-policy.js.map +1 -0
  17. package/clinkx-workflows/dist/council-expander.d.ts +9 -8
  18. package/clinkx-workflows/dist/council-expander.js +18 -9
  19. package/clinkx-workflows/dist/council-expander.js.map +1 -1
  20. package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +5557 -4
  21. package/clinkx-workflows/dist/dashboard/activity-contract.js +55 -3
  22. package/clinkx-workflows/dist/dashboard/activity-contract.js.map +1 -1
  23. package/clinkx-workflows/dist/dashboard/activity-reader.d.ts +9 -5
  24. package/clinkx-workflows/dist/dashboard/activity-reader.js +15 -2
  25. package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -1
  26. package/clinkx-workflows/dist/dashboard/api-routes.d.ts +4 -0
  27. package/clinkx-workflows/dist/dashboard/api-routes.js +133 -25
  28. package/clinkx-workflows/dist/dashboard/api-routes.js.map +1 -1
  29. package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.d.ts +3865 -0
  30. package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js +69 -0
  31. package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js.map +1 -0
  32. package/clinkx-workflows/dist/dashboard/context-run-reader.d.ts +36 -2
  33. package/clinkx-workflows/dist/dashboard/context-run-reader.js +142 -19
  34. package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -1
  35. package/clinkx-workflows/dist/dashboard/contracts.d.ts +20027 -480
  36. package/clinkx-workflows/dist/dashboard/contracts.js +133 -6
  37. package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -1
  38. package/clinkx-workflows/dist/dashboard/index.d.ts +4 -0
  39. package/clinkx-workflows/dist/dashboard/index.js +4 -0
  40. package/clinkx-workflows/dist/dashboard/index.js.map +1 -1
  41. package/clinkx-workflows/dist/dashboard/invocation-contract.d.ts +786 -0
  42. package/clinkx-workflows/dist/dashboard/invocation-contract.js +201 -0
  43. package/clinkx-workflows/dist/dashboard/invocation-contract.js.map +1 -0
  44. package/clinkx-workflows/dist/dashboard/lazy-content.d.ts +14 -1
  45. package/clinkx-workflows/dist/dashboard/lazy-content.js +53 -2
  46. package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -1
  47. package/clinkx-workflows/dist/dashboard/native-transcript/contract.d.ts +90 -0
  48. package/clinkx-workflows/dist/dashboard/native-transcript/contract.js +31 -0
  49. package/clinkx-workflows/dist/dashboard/native-transcript/contract.js.map +1 -0
  50. package/clinkx-workflows/dist/dashboard/native-transcript/projection.d.ts +12 -0
  51. package/clinkx-workflows/dist/dashboard/native-transcript/projection.js +286 -0
  52. package/clinkx-workflows/dist/dashboard/native-transcript/projection.js.map +1 -0
  53. package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +147 -4
  54. package/clinkx-workflows/dist/dashboard/progress-report-contract.js +11 -11
  55. package/clinkx-workflows/dist/dashboard/progress-report-contract.js.map +1 -1
  56. package/clinkx-workflows/dist/dashboard/progress-report-reader.js +4 -56
  57. package/clinkx-workflows/dist/dashboard/progress-report-reader.js.map +1 -1
  58. package/clinkx-workflows/dist/dashboard/progress-report-status-store.d.ts +2 -1
  59. package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +6 -5
  60. package/clinkx-workflows/dist/dashboard/progress-report-status-store.js.map +1 -1
  61. package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.d.ts +194 -0
  62. package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js +89 -0
  63. package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js.map +1 -0
  64. package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.d.ts +26 -0
  65. package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js +102 -0
  66. package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js.map +1 -0
  67. package/clinkx-workflows/dist/dashboard/request-admission.js +4 -0
  68. package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -1
  69. package/clinkx-workflows/dist/dashboard/scanner.d.ts +3 -0
  70. package/clinkx-workflows/dist/dashboard/scanner.js +52 -8
  71. package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -1
  72. package/clinkx-workflows/dist/dashboard/server-factory.d.ts +14 -1
  73. package/clinkx-workflows/dist/dashboard/server-factory.js +73 -21
  74. package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -1
  75. package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +223 -56
  76. package/clinkx-workflows/dist/dashboard/session-log-contract.js +26 -0
  77. package/clinkx-workflows/dist/dashboard/session-log-contract.js.map +1 -1
  78. package/clinkx-workflows/dist/dashboard/session-log-journal.js +10 -3
  79. package/clinkx-workflows/dist/dashboard/session-log-journal.js.map +1 -1
  80. package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +9 -1
  81. package/clinkx-workflows/dist/dashboard/session-log-redaction.js +793 -59
  82. package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -1
  83. package/clinkx-workflows/dist/dashboard-ui/assets/index-JPHQVtn9.js +106 -0
  84. package/clinkx-workflows/dist/dashboard-ui/assets/style-DOMHk-j6.css +1 -0
  85. package/clinkx-workflows/dist/dashboard-ui/index.html +2 -2
  86. package/clinkx-workflows/dist/delegate-finalizer-policy.d.ts +26 -0
  87. package/clinkx-workflows/dist/delegate-finalizer-policy.js +72 -0
  88. package/clinkx-workflows/dist/delegate-finalizer-policy.js.map +1 -0
  89. package/clinkx-workflows/dist/delegate-signature.d.ts +14 -0
  90. package/clinkx-workflows/dist/delegate-signature.js +14 -0
  91. package/clinkx-workflows/dist/delegate-signature.js.map +1 -0
  92. package/clinkx-workflows/dist/delegate-validate.d.ts +32 -2
  93. package/clinkx-workflows/dist/delegate-validate.js +51 -0
  94. package/clinkx-workflows/dist/delegate-validate.js.map +1 -1
  95. package/clinkx-workflows/dist/engine.d.ts +113 -3
  96. package/clinkx-workflows/dist/engine.js +476 -80
  97. package/clinkx-workflows/dist/engine.js.map +1 -1
  98. package/clinkx-workflows/dist/errors.d.ts +22 -2
  99. package/clinkx-workflows/dist/errors.js +30 -2
  100. package/clinkx-workflows/dist/errors.js.map +1 -1
  101. package/clinkx-workflows/dist/fidelity.d.ts +47 -1
  102. package/clinkx-workflows/dist/fidelity.js +54 -1
  103. package/clinkx-workflows/dist/fidelity.js.map +1 -1
  104. package/clinkx-workflows/dist/fingerprint.d.ts +24 -6
  105. package/clinkx-workflows/dist/fingerprint.js +32 -9
  106. package/clinkx-workflows/dist/fingerprint.js.map +1 -1
  107. package/clinkx-workflows/dist/index.js +53 -1
  108. package/clinkx-workflows/dist/index.js.map +1 -1
  109. package/clinkx-workflows/dist/logger.d.ts +1 -1
  110. package/clinkx-workflows/dist/logger.js +17 -3
  111. package/clinkx-workflows/dist/logger.js.map +1 -1
  112. package/clinkx-workflows/dist/monotonic-clock.d.ts +19 -0
  113. package/clinkx-workflows/dist/monotonic-clock.js +22 -0
  114. package/clinkx-workflows/dist/monotonic-clock.js.map +1 -0
  115. package/clinkx-workflows/dist/plan-review/contract.d.ts +52 -52
  116. package/clinkx-workflows/dist/plan-review/publication.d.ts +4 -4
  117. package/clinkx-workflows/dist/plan-review/run-state.d.ts +16 -16
  118. package/clinkx-workflows/dist/roster-contract.d.ts +889 -0
  119. package/clinkx-workflows/dist/roster-contract.js +180 -0
  120. package/clinkx-workflows/dist/roster-contract.js.map +1 -0
  121. package/clinkx-workflows/dist/rosters.d.ts +14 -0
  122. package/clinkx-workflows/dist/rosters.js +169 -0
  123. package/clinkx-workflows/dist/rosters.js.map +1 -0
  124. package/clinkx-workflows/dist/run-health.d.ts +7 -0
  125. package/clinkx-workflows/dist/run-health.js +25 -0
  126. package/clinkx-workflows/dist/run-health.js.map +1 -0
  127. package/clinkx-workflows/dist/schema.d.ts +64 -0
  128. package/clinkx-workflows/dist/schema.js +24 -0
  129. package/clinkx-workflows/dist/schema.js.map +1 -1
  130. package/clinkx-workflows/dist/server.js +2 -0
  131. package/clinkx-workflows/dist/server.js.map +1 -1
  132. package/clinkx-workflows/dist/shutdown.d.ts +15 -2
  133. package/clinkx-workflows/dist/shutdown.js +60 -16
  134. package/clinkx-workflows/dist/shutdown.js.map +1 -1
  135. package/clinkx-workflows/dist/state-schema.d.ts +23 -0
  136. package/clinkx-workflows/dist/state.d.ts +5 -0
  137. package/clinkx-workflows/dist/state.js +128 -21
  138. package/clinkx-workflows/dist/state.js.map +1 -1
  139. package/clinkx-workflows/dist/stdio-watchdog.d.ts +52 -0
  140. package/clinkx-workflows/dist/stdio-watchdog.js +212 -0
  141. package/clinkx-workflows/dist/stdio-watchdog.js.map +1 -0
  142. package/clinkx-workflows/dist/types.d.ts +6 -0
  143. package/clinkx-workflows/dist/validators/council.d.ts +50 -50
  144. package/clinkx-workflows/dist/validators/plan-review.d.ts +3 -3
  145. package/clinkx-workflows/dist/workflow-tools.d.ts +70 -0
  146. package/clinkx-workflows/dist/workflow-tools.js +760 -168
  147. package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
  148. package/clinkx-workflows/templates/council/answer-chairman.txt +3 -0
  149. package/clinkx-workflows/templates/council/answer-cross-reviewer.txt +3 -0
  150. package/clinkx-workflows/templates/council/answer-specialist.txt +3 -0
  151. package/clinkx-workflows/templates/council/chairman.txt +3 -0
  152. package/clinkx-workflows/templates/council/code-review-chairman.txt +3 -0
  153. package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +3 -0
  154. package/clinkx-workflows/templates/council/code-review-reviewer.txt +3 -0
  155. package/clinkx-workflows/templates/council/debug-chairman.txt +3 -0
  156. package/clinkx-workflows/templates/council/debug-cross-reviewer.txt +3 -0
  157. package/clinkx-workflows/templates/council/debug-debugger.txt +3 -0
  158. package/clinkx-workflows/templates/council/discover-analyst.txt +3 -0
  159. package/clinkx-workflows/templates/council/discover-chairman.txt +3 -0
  160. package/clinkx-workflows/templates/council/discover-cross-reviewer.txt +3 -0
  161. package/clinkx-workflows/templates/council/member.txt +3 -0
  162. package/clinkx-workflows/templates/council/plan-review-chairman.txt +3 -0
  163. package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +3 -0
  164. package/clinkx-workflows/templates/council/plan-review-reviewer.txt +3 -0
  165. package/clinkx-workflows/templates/council/reviewer.txt +3 -0
  166. package/clinkx-workflows/workflows/council-answer.yaml +3 -0
  167. package/clinkx-workflows/workflows/council-code-review.yaml +3 -0
  168. package/clinkx-workflows/workflows/council-debug.yaml +3 -0
  169. package/clinkx-workflows/workflows/council-default.yaml +7 -0
  170. package/clinkx-workflows/workflows/council-discover.yaml +3 -0
  171. package/clinkx-workflows/workflows/council-plan-review.yaml +3 -0
  172. package/conf/adapters/agy.json +9 -2
  173. package/conf/adapters/claude.json +7 -0
  174. package/conf/adapters/codex.json +7 -0
  175. package/conf/adapters/glm.json +19 -3
  176. package/conf/adapters/grok.json +9 -2
  177. package/conf/adapters/hapi/agy.json +9 -2
  178. package/conf/adapters/hapi/claude.json +7 -0
  179. package/conf/adapters/hapi/codex.json +7 -0
  180. package/conf/adapters/hapi/glm.json +20 -4
  181. package/conf/rosters/default-council.json +21 -0
  182. package/dist/activity-journal.d.ts +17 -3
  183. package/dist/activity-journal.js +404 -79
  184. package/dist/activity-journal.js.map +1 -1
  185. package/dist/codex/agent-tools.js +36 -0
  186. package/dist/codex/agent-tools.js.map +1 -1
  187. package/dist/codex/app-server-client.d.ts +3 -0
  188. package/dist/codex/app-server-client.js +11 -0
  189. package/dist/codex/app-server-client.js.map +1 -1
  190. package/dist/codex/app-server-registry.d.ts +18 -0
  191. package/dist/codex/app-server-registry.js +88 -2
  192. package/dist/codex/app-server-registry.js.map +1 -1
  193. package/dist/codex/client-types.d.ts +7 -1
  194. package/dist/codex/protocol.d.ts +4 -4
  195. package/dist/config.d.ts +220 -23
  196. package/dist/config.js +104 -1
  197. package/dist/config.js.map +1 -1
  198. package/dist/context-builder/bundle.d.ts +13 -0
  199. package/dist/context-builder/bundle.js +46 -0
  200. package/dist/context-builder/bundle.js.map +1 -1
  201. package/dist/context-builder/delegate-finalizer-effective.d.ts +33 -0
  202. package/dist/context-builder/delegate-finalizer-effective.js +82 -0
  203. package/dist/context-builder/delegate-finalizer-effective.js.map +1 -0
  204. package/dist/context-builder/delegate-packet.d.ts +18 -0
  205. package/dist/context-builder/delegate-packet.js +73 -9
  206. package/dist/context-builder/delegate-packet.js.map +1 -1
  207. package/dist/context-builder/delegate-reconstruction.d.ts +29 -0
  208. package/dist/context-builder/delegate-reconstruction.js +110 -41
  209. package/dist/context-builder/delegate-reconstruction.js.map +1 -1
  210. package/dist/context-builder/delegate-validation.d.ts +31 -0
  211. package/dist/context-builder/delegate-validation.js +87 -10
  212. package/dist/context-builder/delegate-validation.js.map +1 -1
  213. package/dist/context-builder/delegate.d.ts +9 -0
  214. package/dist/context-builder/delegate.js +210 -59
  215. package/dist/context-builder/delegate.js.map +1 -1
  216. package/dist/context-builder/diagnostics.d.ts +1 -1
  217. package/dist/context-builder/diagnostics.js.map +1 -1
  218. package/dist/context-builder/discovery-progress.d.ts +25 -0
  219. package/dist/context-builder/discovery-progress.js +64 -0
  220. package/dist/context-builder/discovery-progress.js.map +1 -0
  221. package/dist/context-builder/discovery-watchdog.d.ts +15 -0
  222. package/dist/context-builder/discovery-watchdog.js +39 -0
  223. package/dist/context-builder/discovery-watchdog.js.map +1 -0
  224. package/dist/context-builder/discovery.d.ts +7 -0
  225. package/dist/context-builder/discovery.js +119 -7
  226. package/dist/context-builder/discovery.js.map +1 -1
  227. package/dist/context-builder/external-gaps.d.ts +73 -0
  228. package/dist/context-builder/external-gaps.js +248 -0
  229. package/dist/context-builder/external-gaps.js.map +1 -0
  230. package/dist/context-builder/globs.d.ts +1 -0
  231. package/dist/context-builder/globs.js +15 -0
  232. package/dist/context-builder/globs.js.map +1 -1
  233. package/dist/context-builder/handler.d.ts +42 -1
  234. package/dist/context-builder/handler.js +690 -62
  235. package/dist/context-builder/handler.js.map +1 -1
  236. package/dist/context-builder/invocation-sidecar.d.ts +41 -0
  237. package/dist/context-builder/invocation-sidecar.js +189 -0
  238. package/dist/context-builder/invocation-sidecar.js.map +1 -0
  239. package/dist/context-builder/lifecycle-contract.d.ts +51 -51
  240. package/dist/context-builder/mutation-lock.d.ts +32 -0
  241. package/dist/context-builder/mutation-lock.js +321 -0
  242. package/dist/context-builder/mutation-lock.js.map +1 -0
  243. package/dist/context-builder/review-coverage.d.ts +32 -0
  244. package/dist/context-builder/review-coverage.js +130 -23
  245. package/dist/context-builder/review-coverage.js.map +1 -1
  246. package/dist/context-builder/schema.d.ts +76 -1
  247. package/dist/context-builder/schema.js +21 -1
  248. package/dist/context-builder/schema.js.map +1 -1
  249. package/dist/context-builder/secrets.d.ts +31 -0
  250. package/dist/context-builder/secrets.js +865 -83
  251. package/dist/context-builder/secrets.js.map +1 -1
  252. package/dist/context-builder/status.d.ts +1 -1
  253. package/dist/context-builder/status.js +1 -0
  254. package/dist/context-builder/status.js.map +1 -1
  255. package/dist/context-builder/storage.d.ts +29 -5
  256. package/dist/context-builder/storage.js +311 -236
  257. package/dist/context-builder/storage.js.map +1 -1
  258. package/dist/context-builder/versions.d.ts +19 -4
  259. package/dist/context-builder/versions.js +19 -4
  260. package/dist/context-builder/versions.js.map +1 -1
  261. package/dist/context-builder/workflow-runner.d.ts +6 -0
  262. package/dist/context-builder/workflow-runner.js +363 -173
  263. package/dist/context-builder/workflow-runner.js.map +1 -1
  264. package/dist/dashboard-adapter-catalog.js +7 -0
  265. package/dist/dashboard-adapter-catalog.js.map +1 -1
  266. package/dist/dashboard-cli.d.ts +2 -0
  267. package/dist/dashboard-cli.js +40 -2
  268. package/dist/dashboard-cli.js.map +1 -1
  269. package/dist/dashboard-companion/client.d.ts +1 -0
  270. package/dist/dashboard-companion/client.js +22 -3
  271. package/dist/dashboard-companion/client.js.map +1 -1
  272. package/dist/dashboard-companion/protocol.js +4 -1
  273. package/dist/dashboard-companion/protocol.js.map +1 -1
  274. package/dist/dashboard-gateway/index.d.ts +1 -0
  275. package/dist/dashboard-gateway/index.js +33 -2
  276. package/dist/dashboard-gateway/index.js.map +1 -1
  277. package/dist/handler.js +5 -1
  278. package/dist/handler.js.map +1 -1
  279. package/dist/index.d.ts +18 -1
  280. package/dist/index.js +145 -7
  281. package/dist/index.js.map +1 -1
  282. package/dist/logger.d.ts +1 -1
  283. package/dist/logger.js +18 -3
  284. package/dist/logger.js.map +1 -1
  285. package/dist/parsers/agy-stream-json.d.ts +14 -0
  286. package/dist/parsers/agy-stream-json.js +172 -0
  287. package/dist/parsers/agy-stream-json.js.map +1 -0
  288. package/dist/parsers/extract.js +5 -1
  289. package/dist/parsers/extract.js.map +1 -1
  290. package/dist/parsers/observed-invocation.d.ts +20 -0
  291. package/dist/parsers/observed-invocation.js +42 -0
  292. package/dist/parsers/observed-invocation.js.map +1 -0
  293. package/dist/pipeline.d.ts +13 -0
  294. package/dist/pipeline.js +60 -10
  295. package/dist/pipeline.js.map +1 -1
  296. package/dist/progress-report/lock.d.ts +74 -0
  297. package/dist/progress-report/lock.js +343 -0
  298. package/dist/progress-report/lock.js.map +1 -0
  299. package/dist/progress-report/schema.d.ts +808 -0
  300. package/dist/progress-report/schema.js +119 -0
  301. package/dist/progress-report/schema.js.map +1 -0
  302. package/dist/progress-report/store.d.ts +66 -0
  303. package/dist/progress-report/store.js +766 -0
  304. package/dist/progress-report/store.js.map +1 -0
  305. package/dist/progress-report/tools.d.ts +20 -0
  306. package/dist/progress-report/tools.js +141 -0
  307. package/dist/progress-report/tools.js.map +1 -0
  308. package/dist/prompt-mode.d.ts +1 -1
  309. package/dist/prompt-mode.js +7 -2
  310. package/dist/prompt-mode.js.map +1 -1
  311. package/dist/runner.d.ts +7 -0
  312. package/dist/runner.js +10 -3
  313. package/dist/runner.js.map +1 -1
  314. package/dist/safety.d.ts +50 -0
  315. package/dist/safety.js +182 -1
  316. package/dist/safety.js.map +1 -1
  317. package/dist/schema.d.ts +8 -0
  318. package/dist/schema.js +20 -0
  319. package/dist/schema.js.map +1 -1
  320. package/dist/server.d.ts +6 -1
  321. package/dist/server.js +94 -8
  322. package/dist/server.js.map +1 -1
  323. package/dist/session-log-stream.d.ts +1 -1
  324. package/dist/session-log-stream.js +280 -33
  325. package/dist/session-log-stream.js.map +1 -1
  326. package/dist/stdio-activity.d.ts +28 -0
  327. package/dist/stdio-activity.js +72 -0
  328. package/dist/stdio-activity.js.map +1 -0
  329. package/package.json +2 -1
  330. package/clinkx-workflows/dist/dashboard-ui/assets/index-DOkMXTfB.js +0 -25
  331. package/clinkx-workflows/dist/dashboard-ui/assets/style-ChJsn0KD.css +0 -1
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { realpathSync, statSync } from "node:fs";
3
- import { copyFile, mkdir, readFile, realpath, rename, rm, utimes, } from "node:fs/promises";
3
+ import { copyFile, link, lstat, mkdir, open, readFile, realpath, rename, rm, utimes, } from "node:fs/promises";
4
4
  import { isAbsolute, join, relative, resolve } from "node:path";
5
5
  // ---------------------------------------------------------------------------
6
6
  // Deep `clinkx-workflows/dist` imports (one-way dependency, build-order coupled)
@@ -36,11 +36,12 @@ import { CONTEXT_BUILDER_RENDERER_VERSION } from "./bundle.js";
36
36
  import { CONTEXT_DELEGATE_PROMPT_VERSION, CONTEXT_DISCOVERY_ALGORITHM_VERSION, } from "./versions.js";
37
37
  import { getSharedContextBuilderSemaphore, resolveContextBuilderRunsDir, } from "./config.js";
38
38
  import { handleContextBuilder } from "./handler.js";
39
+ import { CONTEXT_BUILDER_INVOCATION_AUDIT_FILENAME, isContextBuilderInvocationAuditSidecarProvenInvalid, readContextBuilderInvocationAuditSidecar, } from "./invocation-sidecar.js";
39
40
  import { CONTEXT_BUILDER_INTENT_PROFILE_DEFAULTS, CONTEXT_BUILDER_INTENT_PROFILES, resolveContextBuilderAdapterBudgetInput, } from "./schema.js";
40
41
  import { assertNoHighConfidenceSecretsInTask, resolveAllowSecrets, } from "./secrets.js";
41
42
  import { CONTEXT_BUILDER_DELEGATE_SCHEMA_VERSION, CONTEXT_BUILDER_NO_DELEGATE_SCHEMA_VERSION, createContextBuilderReceipt, } from "./receipt.js";
42
43
  import { notEvaluatedCoverageReport, } from "./review-coverage.js";
43
- import { isContextBuilderRunCommitted, evictWorkflowContextRuns, promoteContextBuilderRun, resolveContextBuilderRunPaths, withContextBuilderRunMutationLease, } from "./storage.js";
44
+ import { ContextBuilderActiveRunClaimConflictError, ContextBuilderAncillaryPromotionError, ContextBuilderCommittedRunChangedError, ContextBuilderRunClaimConflictError, evictWorkflowContextRuns, isContextBuilderRunCommitted, resolveContextBuilderRunPaths, withCommittedContextBuilderRunMutationLease, withPromotedContextBuilderRun, } from "./storage.js";
44
45
  import { ContextBuilderLifecycleController, ContextBuilderLifecycleObserverError, } from "./lifecycle-observer.js";
45
46
  export function createWorkflowContextBuilderRunner(options = {}) {
46
47
  const cb = options.handleContextBuilder ?? handleContextBuilder;
@@ -57,6 +58,7 @@ export function createWorkflowContextBuilderRunner(options = {}) {
57
58
  const discoveryAlgorithmVersion = options.discoveryAlgorithmVersion ?? CONTEXT_DISCOVERY_ALGORITHM_VERSION;
58
59
  const delegatePromptVersion = options.delegatePromptVersion ?? CONTEXT_DELEGATE_PROMPT_VERSION;
59
60
  const registry = options.registry;
61
+ const syncInvocationSnapshotDirectory = options.syncInvocationSnapshotDirectory ?? syncDirectoryEntry;
60
62
  return {
61
63
  assertRootsAllowed(roots) {
62
64
  assertExplicitRootsWithinAllowed(roots);
@@ -138,31 +140,34 @@ export function createWorkflowContextBuilderRunner(options = {}) {
138
140
  const postSig = await workspaceSignature(mapped.roots);
139
141
  throwIfAborted(opts.signal);
140
142
  if (postSig === preSig) {
141
- const hit = await withContextBuilderRunMutationLease(cache.targetStorage, async () => {
142
- // The unleased probe above is only an optimization. Pruning may
143
- // win before this lease is acquired, so re-check under the lock
144
- // before reading the manifest or materializing any artifact.
145
- if (!await isContextBuilderRunCommitted(cache.targetStorage.runDir)) {
146
- return undefined;
147
- }
148
- // Refresh directory mtime so LRU eviction treats this as recently used.
149
- try {
150
- const now = new Date();
151
- await utimes(cache.targetStorage.runDir, now, now);
152
- }
153
- catch {
154
- // best-effort — a hit must not fail because utimes failed
155
- }
156
- return await synthesizePreflightResultFromCommittedRun({
157
- storage: cache.targetStorage,
158
- runDir: input.runDir,
159
- keyPrefix: cache.keyPrefix,
160
- schemaVersion: effectiveSchemaVersion,
161
- budgetCaps: mapped.budget,
162
- includeSelectedContext,
163
- ...(lifecycle == null ? {} : { lifecycle }),
143
+ let hit;
144
+ try {
145
+ hit = await withCommittedContextBuilderRunMutationLease(cache.targetStorage, async () => {
146
+ // Refresh directory mtime so LRU eviction treats this as recently used.
147
+ try {
148
+ const now = new Date();
149
+ await utimes(cache.targetStorage.runDir, now, now);
150
+ }
151
+ catch {
152
+ // best-effort — a hit must not fail because utimes failed
153
+ }
154
+ return await synthesizePreflightResultFromCommittedRun({
155
+ storage: cache.targetStorage,
156
+ runDir: input.runDir,
157
+ keyPrefix: cache.keyPrefix,
158
+ schemaVersion: effectiveSchemaVersion,
159
+ budgetCaps: mapped.budget,
160
+ includeSelectedContext,
161
+ syncInvocationSnapshotDirectory,
162
+ ...(lifecycle == null ? {} : { lifecycle }),
163
+ });
164
164
  });
165
- });
165
+ }
166
+ catch (error) {
167
+ if (!(error instanceof ContextBuilderCommittedRunChangedError)) {
168
+ throw error;
169
+ }
170
+ }
166
171
  if (hit !== undefined) {
167
172
  await lifecycle?.complete(workflowLifecycleTerminalStatus(hit), {
168
173
  artifactRunId: hit.source_run_id,
@@ -193,152 +198,188 @@ export function createWorkflowContextBuilderRunner(options = {}) {
193
198
  });
194
199
  const tempId = runIdFactory();
195
200
  const tempInput = { ...mapped, run_id: tempId };
196
- const response = await cb(tempInput, {
197
- runsDir,
198
- semaphore,
199
- ...(registry == null ? {} : { registry }),
200
- ...(input.allowRootWideDiscovery == null
201
- ? {}
202
- : { allowRootWideDiscovery: input.allowRootWideDiscovery }),
203
- ...(opts.signal == null ? {} : { signal: opts.signal }),
204
- ...(lifecycle == null ? {} : { lifecycleController: lifecycle }),
205
- ...(preflightSessionLogContext == null
206
- ? {}
207
- : { sessionLogContext: preflightSessionLogContext }),
208
- });
209
- throwIfAborted(opts.signal);
210
- const receipt = parseReceiptResponse(response);
211
201
  const tempStorage = resolveContextBuilderRunPaths(runsDir, tempId);
212
202
  // The id of the run this call will return as its live source. Eviction in
213
203
  // the finally must protect it so small/zero retention bounds cannot delete
214
204
  // the run whose source_artifact_dir we are about to hand back.
215
205
  let liveSourceRunId = tempId;
216
- try {
217
- const tempManifest = await readManifest(tempStorage.manifestJson);
218
- // Degraded fallback selections (delegate failure_policy:"fallback") must
219
- // NEVER enter the shared wfctx cache: a cached fallback would be replayed
220
- // for a later fail-closed failure_policy:"fail" request (silently
221
- // succeeding instead of failing) and would mask genuine delegated
222
- // successes. Not caching also lets a later identical request retry
223
- // delegation rather than reusing a stale degraded bundle. The manifest
224
- // is the authoritative persisted source; fall back to the receipt.
225
- const fallbackUsed = tempManifest.delegate?.fallback_used === true ||
226
- receipt.delegate?.fallback_used === true;
227
- const selectedPaths = await resolveSelectedContextPaths(tempManifest, mapped.roots);
228
- const gate = await evaluatePostCbPromotionGate({
229
- roots: mapped.roots,
230
- selectedPaths,
231
- workspaceSignature,
232
- signatureCoveredPaths,
233
- });
206
+ const consumeCommittedRun = async (response, storageOps) => {
234
207
  throwIfAborted(opts.signal);
235
- if (!fallbackUsed && gate?.selectedFilesCovered === true && gate.postSig === preSig) {
236
- try {
237
- await lifecycle?.transition("materializing");
238
- const promoted = await promoteContextBuilderRun(tempStorage.runDir, cache.wfctxId);
239
- // Promote succeeded and renamed temp → wfctx: protect the wfctx run
240
- // immediately so a later throw (e.g. copyBundleArtifactsIntoRun) can
241
- // never leave the freshly-promoted run unprotected against zero/small
242
- // retention eviction in the finally.
243
- liveSourceRunId = cache.wfctxId;
244
- // Copy from the PROMOTED wfctx run (the temp dir is now deleted) so
245
- // the fanned manifest's run_id matches the returned source_run_id and
246
- // its artifact paths point at live files.
247
- const promotedManifest = await readManifest(promoted.manifestJson);
248
- const resultBeforeMaterialization = buildPreflightResult({
249
- receipt: createReceiptFromManifest(promotedManifest, promoted),
250
- sourceRunId: cache.wfctxId,
251
- sourceArtifactDir: promoted.runDir,
252
- cache: {
253
- hit: false,
254
- write: true,
255
- target_id: cache.wfctxId,
256
- key_prefix: cache.keyPrefix,
257
- },
258
- copiedPaths: [],
259
- schemaVersion: effectiveSchemaVersion,
260
- observability: preflightObservabilityFromManifest(tempManifest, mapped.budget),
261
- coverage: coverageFromManifest(tempManifest),
262
- });
263
- const copiedPaths = await copyBundleArtifactsIntoRun(promotedManifest.artifacts, input.runDir, {
264
- includeSelectedContext,
265
- ...(lifecycle == null
266
- ? {}
267
- : {
268
- beforeManifestCommit: async () => {
269
- await lifecycle?.transition("committing", {
270
- artifactRunId: resultBeforeMaterialization.source_run_id,
271
- cache: {
272
- lookup: "miss",
273
- write: "written",
274
- reason_code: cacheReason,
275
- },
276
- ...workflowResultLifecyclePatch(resultBeforeMaterialization, lifecycle),
277
- });
208
+ const receipt = parseReceiptResponse(response);
209
+ return await withCommittedContextBuilderRunMutationLease(tempStorage, async (sourceOps) => {
210
+ const tempManifest = await readManifest(tempStorage.manifestJson, sourceOps);
211
+ // Degraded fallback selections (delegate failure_policy:"fallback") must
212
+ // NEVER enter the shared wfctx cache: a cached fallback would be replayed
213
+ // for a later fail-closed failure_policy:"fail" request (silently
214
+ // succeeding instead of failing) and would mask genuine delegated
215
+ // successes. Not caching also lets a later identical request retry
216
+ // delegation rather than reusing a stale degraded bundle. The same
217
+ // holds for a delegated selection that proceeded with external
218
+ // evidence gaps (isDelegateDegradedForCache). The manifest is the
219
+ // authoritative persisted source; fall back to the receipt.
220
+ const delegateDegraded = isDelegateDegradedForCache(tempManifest.delegate) ||
221
+ isDelegateDegradedForCache(receipt.delegate);
222
+ const selectedPaths = await resolveSelectedContextPaths(tempManifest, mapped.roots);
223
+ const gate = await evaluatePostCbPromotionGate({
224
+ roots: mapped.roots,
225
+ selectedPaths,
226
+ workspaceSignature,
227
+ signatureCoveredPaths,
228
+ });
229
+ throwIfAborted(opts.signal);
230
+ if (!delegateDegraded
231
+ && gate?.selectedFilesCovered === true
232
+ && gate.postSig === preSig) {
233
+ try {
234
+ await lifecycle?.transition("materializing");
235
+ return await withPromotedContextBuilderRun(tempStorage.runDir, cache.wfctxId, async (promoted, targetOps) => {
236
+ // Promotion completed under source→target locks. Keep the target
237
+ // lease through this authoritative manifest read and final copy.
238
+ liveSourceRunId = cache.wfctxId;
239
+ const promotedManifest = await readManifest(promoted.manifestJson, targetOps);
240
+ const resultBeforeMaterialization = buildPreflightResult({
241
+ receipt: createReceiptFromManifest(promotedManifest, promoted),
242
+ sourceRunId: cache.wfctxId,
243
+ sourceArtifactDir: promoted.runDir,
244
+ cache: {
245
+ hit: false,
246
+ write: true,
247
+ target_id: cache.wfctxId,
248
+ key_prefix: cache.keyPrefix,
278
249
  },
279
- }),
280
- });
281
- const result = {
282
- ...resultBeforeMaterialization,
283
- copied_paths: copiedPaths,
284
- };
285
- await lifecycle?.complete(workflowLifecycleTerminalStatus(result), {
286
- artifactRunId: result.source_run_id,
287
- ...workflowResultLifecyclePatch(result, lifecycle),
288
- });
289
- return result;
290
- }
291
- catch (error) {
292
- if (!isPromoteClaimConflict(error)) {
293
- throw error;
250
+ copiedPaths: [],
251
+ schemaVersion: effectiveSchemaVersion,
252
+ observability: preflightObservabilityFromManifest(tempManifest, mapped.budget),
253
+ coverage: coverageFromManifest(tempManifest),
254
+ });
255
+ const copiedPaths = await materializeWorkflowContext({
256
+ artifacts: promotedManifest.artifacts,
257
+ sourceRunDir: promoted.runDir,
258
+ workflowRunDir: input.runDir,
259
+ includeSelectedContext,
260
+ syncInvocationSnapshotDirectory,
261
+ ...(lifecycle == null
262
+ ? {}
263
+ : { contextRunId: lifecycle.contextRunId }),
264
+ ...(lifecycle == null
265
+ ? {}
266
+ : {
267
+ beforeManifestCommit: async () => {
268
+ await lifecycle?.transition("committing", {
269
+ artifactRunId: resultBeforeMaterialization.source_run_id,
270
+ cache: {
271
+ lookup: "miss",
272
+ write: "written",
273
+ reason_code: cacheReason,
274
+ },
275
+ ...workflowResultLifecyclePatch(resultBeforeMaterialization, lifecycle),
276
+ });
277
+ },
278
+ }),
279
+ });
280
+ const result = {
281
+ ...resultBeforeMaterialization,
282
+ copied_paths: copiedPaths,
283
+ };
284
+ await lifecycle?.complete(workflowLifecycleTerminalStatus(result), {
285
+ artifactRunId: result.source_run_id,
286
+ ...workflowResultLifecyclePatch(result, lifecycle),
287
+ });
288
+ return result;
289
+ }, sourceOps);
290
+ }
291
+ catch (error) {
292
+ if (error instanceof ContextBuilderAncillaryPromotionError) {
293
+ logger.warn({
294
+ reason_code: error.reasonCode,
295
+ source_run_id: tempId,
296
+ }, "workflow context_builder cache promotion skipped because invocation audit ancillary promotion failed");
297
+ }
298
+ else if (!isPromoteClaimConflict(error)) {
299
+ throw error;
300
+ }
294
301
  }
295
302
  }
296
- }
297
- // Non-promote (or promote-claim-conflict) path: copy from the surviving
298
- // temp run, which is the authoritative source for the returned id.
299
- await lifecycle?.transition("materializing");
300
- const resultBeforeMaterialization = buildPreflightResult({
301
- receipt: createReceiptFromManifest(tempManifest, tempStorage),
302
- sourceRunId: tempId,
303
- sourceArtifactDir: tempStorage.runDir,
304
- cache: {
305
- hit: false,
306
- write: false,
307
- target_id: null,
308
- key_prefix: cache.keyPrefix,
309
- },
310
- copiedPaths: [],
311
- schemaVersion: effectiveSchemaVersion,
312
- observability: preflightObservabilityFromManifest(tempManifest, mapped.budget),
313
- coverage: coverageFromManifest(tempManifest),
314
- });
315
- const copiedPaths = await copyBundleArtifactsIntoRun(tempManifest.artifacts, input.runDir, {
316
- includeSelectedContext,
317
- ...(lifecycle == null
303
+ // Non-promote (or promote-claim-conflict) path: copy from the surviving
304
+ // temp run, which is the authoritative source for the returned id.
305
+ await lifecycle?.transition("materializing");
306
+ const resultBeforeMaterialization = buildPreflightResult({
307
+ receipt: createReceiptFromManifest(tempManifest, tempStorage),
308
+ sourceRunId: tempId,
309
+ sourceArtifactDir: tempStorage.runDir,
310
+ cache: {
311
+ hit: false,
312
+ write: false,
313
+ target_id: null,
314
+ key_prefix: cache.keyPrefix,
315
+ },
316
+ copiedPaths: [],
317
+ schemaVersion: effectiveSchemaVersion,
318
+ observability: preflightObservabilityFromManifest(tempManifest, mapped.budget),
319
+ coverage: coverageFromManifest(tempManifest),
320
+ });
321
+ const copiedPaths = await materializeWorkflowContext({
322
+ artifacts: tempManifest.artifacts,
323
+ sourceRunDir: tempStorage.runDir,
324
+ workflowRunDir: input.runDir,
325
+ includeSelectedContext,
326
+ syncInvocationSnapshotDirectory,
327
+ ...(lifecycle == null
328
+ ? {}
329
+ : { contextRunId: lifecycle.contextRunId }),
330
+ ...(lifecycle == null
331
+ ? {}
332
+ : {
333
+ beforeManifestCommit: async () => {
334
+ await lifecycle?.transition("committing", {
335
+ artifactRunId: resultBeforeMaterialization.source_run_id,
336
+ cache: {
337
+ lookup: "miss",
338
+ write: "skipped",
339
+ reason_code: cacheReason,
340
+ },
341
+ ...workflowResultLifecyclePatch(resultBeforeMaterialization, lifecycle),
342
+ });
343
+ },
344
+ }),
345
+ });
346
+ const result = {
347
+ ...resultBeforeMaterialization,
348
+ copied_paths: copiedPaths,
349
+ };
350
+ await lifecycle?.complete(workflowLifecycleTerminalStatus(result), {
351
+ artifactRunId: result.source_run_id,
352
+ ...workflowResultLifecyclePatch(result, lifecycle),
353
+ });
354
+ return result;
355
+ }, storageOps);
356
+ };
357
+ let retainedResult;
358
+ try {
359
+ const response = await cb(tempInput, {
360
+ runsDir,
361
+ semaphore,
362
+ ...(registry == null ? {} : { registry }),
363
+ ...(input.allowRootWideDiscovery == null
318
364
  ? {}
319
- : {
320
- beforeManifestCommit: async () => {
321
- await lifecycle?.transition("committing", {
322
- artifactRunId: resultBeforeMaterialization.source_run_id,
323
- cache: {
324
- lookup: "miss",
325
- write: "skipped",
326
- reason_code: cacheReason,
327
- },
328
- ...workflowResultLifecyclePatch(resultBeforeMaterialization, lifecycle),
329
- });
330
- },
331
- }),
332
- });
333
- const result = {
334
- ...resultBeforeMaterialization,
335
- copied_paths: copiedPaths,
336
- };
337
- await lifecycle?.complete(workflowLifecycleTerminalStatus(result), {
338
- artifactRunId: result.source_run_id,
339
- ...workflowResultLifecyclePatch(result, lifecycle),
365
+ : { allowRootWideDiscovery: input.allowRootWideDiscovery }),
366
+ ...(opts.signal == null ? {} : { signal: opts.signal }),
367
+ ...(lifecycle == null ? {} : { lifecycleController: lifecycle }),
368
+ ...(preflightSessionLogContext == null
369
+ ? {}
370
+ : { sessionLogContext: preflightSessionLogContext }),
371
+ retainCommittedRun: async (committedResponse, committedStorage, leasedOps) => {
372
+ if (committedStorage.runId !== tempStorage.runId
373
+ || committedStorage.runDir !== tempStorage.runDir) {
374
+ throw new ExecutionError("context_builder retained an unexpected workflow temp run");
375
+ }
376
+ retainedResult = await consumeCommittedRun(committedResponse, leasedOps);
377
+ },
340
378
  });
341
- return result;
379
+ if (retainedResult !== undefined) {
380
+ return retainedResult;
381
+ }
382
+ return await consumeCommittedRun(response);
342
383
  }
343
384
  finally {
344
385
  // Best-effort, prefix-scoped/TTL-guarded eviction on every completed
@@ -391,11 +432,141 @@ export function createWorkflowContextBuilderRunner(options = {}) {
391
432
  };
392
433
  }
393
434
  /**
394
- * Subdirectory of the workflow run dir holding the preflight session-log
395
- * generation sidecar. The dashboard reads this back to locate the preflight's
396
- * session-log subtree and to pair it with the persisted lifecycle context_run_id.
435
+ * Subdirectory of the workflow run dir holding operation-scoped preflight
436
+ * metadata: the session-log generation and immutable invocation snapshots.
397
437
  */
398
438
  const PREFLIGHT_SESSION_LOG_DIR = ".context-preflight";
439
+ async function materializeWorkflowContext(input) {
440
+ const copiedPaths = await copyBundleArtifactsIntoRun(input.artifacts, input.workflowRunDir, {
441
+ includeSelectedContext: input.includeSelectedContext,
442
+ ...(input.beforeManifestCommit == null
443
+ ? {}
444
+ : { beforeManifestCommit: input.beforeManifestCommit }),
445
+ });
446
+ if (input.contextRunId !== undefined) {
447
+ await snapshotWorkflowInvocationAudit({
448
+ sourceRunDir: input.sourceRunDir,
449
+ workflowRunDir: input.workflowRunDir,
450
+ contextRunId: input.contextRunId,
451
+ syncInvocationSnapshotDirectory: input.syncInvocationSnapshotDirectory,
452
+ });
453
+ }
454
+ return copiedPaths;
455
+ }
456
+ async function snapshotWorkflowInvocationAudit(input) {
457
+ let source;
458
+ try {
459
+ source = await readContextBuilderInvocationAuditSidecar(input.sourceRunDir);
460
+ }
461
+ catch (error) {
462
+ logger.warn({ err: error, source_artifact_dir: input.sourceRunDir }, "workflow context_builder invocation audit snapshot source rejected");
463
+ return;
464
+ }
465
+ if (source === undefined)
466
+ return;
467
+ const workflowRunRoot = await realpath(input.workflowRunDir);
468
+ const preflightDir = await admitExactWorkflowDirectory(workflowRunRoot, PREFLIGHT_SESSION_LOG_DIR, "workflow context_builder invocation audit snapshot directory");
469
+ const admittedSnapshotDir = await admitExactWorkflowDirectory(preflightDir, input.contextRunId, "workflow context_builder invocation audit snapshot directory");
470
+ assertContainedPath(workflowRunRoot, admittedSnapshotDir, "workflow context_builder invocation audit snapshot escaped the workflow run");
471
+ const target = join(admittedSnapshotDir, CONTEXT_BUILDER_INVOCATION_AUDIT_FILENAME);
472
+ const temp = join(admittedSnapshotDir, `.${CONTEXT_BUILDER_INVOCATION_AUDIT_FILENAME}.${process.pid}.${randomUUID()}.tmp`);
473
+ let ownsTemp = false;
474
+ let publicationAccepted = false;
475
+ try {
476
+ let handle;
477
+ try {
478
+ handle = await open(temp, "wx", 0o600);
479
+ ownsTemp = true;
480
+ await handle.writeFile(source.serialized, "utf8");
481
+ await handle.chmod(0o600);
482
+ await handle.sync();
483
+ }
484
+ finally {
485
+ await handle?.close();
486
+ }
487
+ try {
488
+ await link(temp, target);
489
+ }
490
+ catch (error) {
491
+ if (!isNodeError(error) || error.code !== "EEXIST")
492
+ throw error;
493
+ let incumbent;
494
+ try {
495
+ incumbent = await readContextBuilderInvocationAuditSidecar(admittedSnapshotDir);
496
+ }
497
+ catch (error) {
498
+ if (!isContextBuilderInvocationAuditSidecarProvenInvalid(error)) {
499
+ throw error;
500
+ }
501
+ logger.warn({ err: error, incumbent_path: target }, "workflow context_builder invocation audit snapshot incumbent rejected; preserving existing entry");
502
+ return;
503
+ }
504
+ if (incumbent?.serialized !== source.serialized) {
505
+ throw new ExecutionError("workflow context_builder invocation audit snapshot conflicts with existing bytes");
506
+ }
507
+ }
508
+ publicationAccepted = true;
509
+ }
510
+ finally {
511
+ if (ownsTemp) {
512
+ try {
513
+ await rm(temp, { force: true });
514
+ }
515
+ catch (error) {
516
+ logger.warn({ err: error, temporary_path: temp }, "workflow context_builder invocation audit snapshot temp cleanup failed");
517
+ }
518
+ }
519
+ if (publicationAccepted) {
520
+ await syncSnapshotDirectoriesBestEffort([admittedSnapshotDir, preflightDir, workflowRunRoot], input.syncInvocationSnapshotDirectory);
521
+ }
522
+ }
523
+ }
524
+ async function syncDirectoryEntry(path) {
525
+ const handle = await open(path, "r");
526
+ try {
527
+ await handle.sync();
528
+ }
529
+ finally {
530
+ await handle.close();
531
+ }
532
+ }
533
+ async function syncSnapshotDirectoriesBestEffort(directories, syncDirectory) {
534
+ for (const directory of directories) {
535
+ try {
536
+ await syncDirectory(directory);
537
+ }
538
+ catch (error) {
539
+ logger.warn({ err: error, directory }, "workflow context_builder invocation audit snapshot directory sync failed");
540
+ }
541
+ }
542
+ }
543
+ async function admitExactWorkflowDirectory(parent, name, label) {
544
+ const path = join(parent, name);
545
+ try {
546
+ await mkdir(path, { mode: 0o700 });
547
+ }
548
+ catch (error) {
549
+ if (!isNodeError(error) || error.code !== "EEXIST")
550
+ throw error;
551
+ }
552
+ try {
553
+ const [stats, canonicalPath] = await Promise.all([
554
+ lstat(path),
555
+ realpath(path),
556
+ ]);
557
+ if (stats.isSymbolicLink() ||
558
+ !stats.isDirectory() ||
559
+ canonicalPath !== path) {
560
+ throw new ExecutionError(`${label} is unsafe`);
561
+ }
562
+ return path;
563
+ }
564
+ catch (error) {
565
+ if (error instanceof ExecutionError)
566
+ throw error;
567
+ throw new ExecutionError(`${label} is unsafe`);
568
+ }
569
+ }
399
570
  /**
400
571
  * Create-or-read the preflight session-log generation sidecar under the workflow
401
572
  * run dir, then pair it with the controller-minted operation id. Create-or-read
@@ -405,8 +576,8 @@ const PREFLIGHT_SESSION_LOG_DIR = ".context-preflight";
405
576
  */
406
577
  async function resolvePreflightSessionLogContext(runDir, contextRunId) {
407
578
  try {
408
- const sidecarDir = join(runDir, PREFLIGHT_SESSION_LOG_DIR);
409
- await mkdir(sidecarDir, { recursive: true, mode: 0o700 });
579
+ const workflowRunRoot = await realpath(runDir);
580
+ const sidecarDir = await admitExactWorkflowDirectory(workflowRunRoot, PREFLIGHT_SESSION_LOG_DIR, "workflow context_builder preflight metadata directory");
410
581
  const existing = await readSessionLogGeneration(sidecarDir);
411
582
  const contextGeneration = existing.kind === "available"
412
583
  ? existing.sessionLogGeneration
@@ -433,6 +604,17 @@ function workflowLifecycleExecutionConfig(input) {
433
604
  finalizer: { configured: true, enabled: finalizerEnabled },
434
605
  };
435
606
  }
607
+ /**
608
+ * A delegate bundle that is degraded — a deterministic fallback, or a
609
+ * delegated selection that proceeded with known external evidence gaps
610
+ * (status "partial", fallback_used false, external_gap_policy "degrade") — is
611
+ * never promoted into or served from the wfctx cache. The gap policy is not
612
+ * in the cache key, so a cached degraded bundle could otherwise satisfy a later
613
+ * fail-closed request.
614
+ */
615
+ function isDelegateDegradedForCache(delegate) {
616
+ return delegate?.fallback_used === true || delegate?.status === "partial";
617
+ }
436
618
  function workflowLifecycleTerminalStatus(result) {
437
619
  return result.delegate?.fallback_used === true || result.status === "partial"
438
620
  ? "partial"
@@ -624,7 +806,9 @@ async function synthesizePreflightResultFromCommittedRun(input) {
624
806
  // artifact, or a future code path. Treat it as a cache MISS (return undefined,
625
807
  // the same value the ENOENT/not-usable case returns) so runOrReuse falls
626
808
  // through and re-runs the builder; a real "fail" policy then fails closed.
627
- if (manifest.delegate?.fallback_used === true) {
809
+ // A degraded external-gap bundle (status "partial" without fallback) is
810
+ // refused the same way.
811
+ if (isDelegateDegradedForCache(manifest.delegate)) {
628
812
  return undefined;
629
813
  }
630
814
  const receipt = createReceiptFromManifest(manifest, input.storage);
@@ -649,8 +833,15 @@ async function synthesizePreflightResultFromCommittedRun(input) {
649
833
  ...workflowResultLifecyclePatch(resultBeforeMaterialization, input.lifecycle, true),
650
834
  });
651
835
  await input.lifecycle?.transition("materializing");
652
- const copiedPaths = await copyBundleArtifactsIntoRun(manifest.artifacts, input.runDir, {
836
+ const copiedPaths = await materializeWorkflowContext({
837
+ artifacts: manifest.artifacts,
838
+ sourceRunDir: input.storage.runDir,
839
+ workflowRunDir: input.runDir,
653
840
  includeSelectedContext: input.includeSelectedContext,
841
+ syncInvocationSnapshotDirectory: input.syncInvocationSnapshotDirectory,
842
+ ...(input.lifecycle == null
843
+ ? {}
844
+ : { contextRunId: input.lifecycle.contextRunId }),
654
845
  ...(input.lifecycle == null
655
846
  ? {}
656
847
  : {
@@ -853,8 +1044,8 @@ function nonNegativeInteger(value) {
853
1044
  ? value
854
1045
  : 0;
855
1046
  }
856
- async function readManifest(manifestPath) {
857
- return JSON.parse(await readFile(manifestPath, "utf8"));
1047
+ async function readManifest(manifestPath, ops) {
1048
+ return JSON.parse(await (ops?.readFile ?? readFile)(manifestPath, "utf8"));
858
1049
  }
859
1050
  async function resolveSelectedContextPaths(manifest, roots) {
860
1051
  const selected = Array.isArray(manifest["selected_context"])
@@ -917,9 +1108,8 @@ async function normalizeExistingPath(path) {
917
1108
  }
918
1109
  }
919
1110
  function isPromoteClaimConflict(error) {
920
- return (error instanceof InvalidParamsError
921
- && error.message.startsWith("context_builder run_id already exists:")) || (error instanceof ExecutionError
922
- && error.message.startsWith("context_builder run_id conflicts with an incomplete or active run:"));
1111
+ return error instanceof ContextBuilderRunClaimConflictError
1112
+ || error instanceof ContextBuilderActiveRunClaimConflictError;
923
1113
  }
924
1114
  function throwIfAborted(signal) {
925
1115
  if (signal?.aborted === true) {