@hraness/slopcamera 3.2.4

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 (441) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +60 -0
  3. package/PRIVACY.md +46 -0
  4. package/README.md +311 -0
  5. package/SECURITY.md +21 -0
  6. package/apps/desktop/README.md +966 -0
  7. package/apps/desktop/analysis/protocol.ts +223 -0
  8. package/apps/desktop/application/capability-binding.ts +594 -0
  9. package/apps/desktop/application/context.ts +97 -0
  10. package/apps/desktop/application/creative-iteration.ts +1385 -0
  11. package/apps/desktop/application/default-registry.ts +146 -0
  12. package/apps/desktop/application/directing-contract.ts +218 -0
  13. package/apps/desktop/application/directing-plan.ts +178 -0
  14. package/apps/desktop/application/errors.ts +18 -0
  15. package/apps/desktop/application/gateway-port.ts +867 -0
  16. package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
  17. package/apps/desktop/application/html-overlay-integrity.ts +337 -0
  18. package/apps/desktop/application/html-overlay-renderer.ts +45 -0
  19. package/apps/desktop/application/index.ts +13 -0
  20. package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
  21. package/apps/desktop/application/operation-effects.ts +94 -0
  22. package/apps/desktop/application/operation.ts +224 -0
  23. package/apps/desktop/application/operations/analysis/faces.ts +244 -0
  24. package/apps/desktop/application/operations/analysis/music.ts +248 -0
  25. package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
  26. package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
  27. package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
  28. package/apps/desktop/application/operations/analysis/shared.ts +202 -0
  29. package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
  30. package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
  31. package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
  32. package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
  33. package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
  34. package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
  35. package/apps/desktop/application/operations/gateway/image.ts +41 -0
  36. package/apps/desktop/application/operations/gateway/index.ts +17 -0
  37. package/apps/desktop/application/operations/gateway/shared.ts +122 -0
  38. package/apps/desktop/application/operations/gateway/speech.ts +41 -0
  39. package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
  40. package/apps/desktop/application/operations/gateway/video.ts +41 -0
  41. package/apps/desktop/application/operations/index.ts +31 -0
  42. package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
  43. package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
  44. package/apps/desktop/application/operations/iteration/select.ts +155 -0
  45. package/apps/desktop/application/operations/iteration/shared.ts +250 -0
  46. package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
  47. package/apps/desktop/application/operations/media/binding.ts +79 -0
  48. package/apps/desktop/application/operations/media/capabilities.ts +112 -0
  49. package/apps/desktop/application/operations/media/color-grade.ts +343 -0
  50. package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
  51. package/apps/desktop/application/operations/media/index.ts +8 -0
  52. package/apps/desktop/application/operations/media/ingest.ts +303 -0
  53. package/apps/desktop/application/operations/media/overlay.ts +729 -0
  54. package/apps/desktop/application/operations/media/shared.ts +845 -0
  55. package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
  56. package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
  57. package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
  58. package/apps/desktop/application/operations/project/snapshot.ts +95 -0
  59. package/apps/desktop/application/operations/recording/index.ts +5 -0
  60. package/apps/desktop/application/operations/recording/pause.ts +34 -0
  61. package/apps/desktop/application/operations/recording/resume.ts +34 -0
  62. package/apps/desktop/application/operations/recording/shared.ts +199 -0
  63. package/apps/desktop/application/operations/recording/start.ts +169 -0
  64. package/apps/desktop/application/operations/recording/stop.ts +34 -0
  65. package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
  66. package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
  67. package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
  68. package/apps/desktop/application/operations/render/project-plan.ts +939 -0
  69. package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
  70. package/apps/desktop/application/operations/render/project.ts +1922 -0
  71. package/apps/desktop/application/operations/shared.ts +67 -0
  72. package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
  73. package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
  74. package/apps/desktop/application/operations/spatial-project.ts +209 -0
  75. package/apps/desktop/application/operations/spatial-render.ts +415 -0
  76. package/apps/desktop/application/operations/spatial-scene.ts +97 -0
  77. package/apps/desktop/application/operations/studio.ts +143 -0
  78. package/apps/desktop/application/output-publication-lease.ts +163 -0
  79. package/apps/desktop/application/project-publication-lease.ts +233 -0
  80. package/apps/desktop/application/project-store.ts +196 -0
  81. package/apps/desktop/application/receipts.ts +533 -0
  82. package/apps/desktop/application/registry.ts +182 -0
  83. package/apps/desktop/application/spatial-assets.ts +675 -0
  84. package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
  85. package/apps/desktop/application/spatial-project-authority.ts +35 -0
  86. package/apps/desktop/application/spatial-project-lease.ts +126 -0
  87. package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
  88. package/apps/desktop/application/spatial-project-store.ts +286 -0
  89. package/apps/desktop/application/spatial-render-projection.ts +201 -0
  90. package/apps/desktop/application/spatial-render.ts +677 -0
  91. package/apps/desktop/application/spatial-spz.ts +44 -0
  92. package/apps/desktop/application/spatial-world-import.ts +135 -0
  93. package/apps/desktop/application/spatial-world-provenance.ts +46 -0
  94. package/apps/desktop/application/studio-port.ts +58 -0
  95. package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
  96. package/apps/desktop/capture/protocol.ts +689 -0
  97. package/apps/desktop/cli/analyzer.ts +406 -0
  98. package/apps/desktop/cli/args.ts +3181 -0
  99. package/apps/desktop/cli/asset-ingest.ts +747 -0
  100. package/apps/desktop/cli/atomic-render-effects.ts +210 -0
  101. package/apps/desktop/cli/atomic-render-platform.ts +205 -0
  102. package/apps/desktop/cli/atomic-render.ts +50 -0
  103. package/apps/desktop/cli/audio-analysis.ts +252 -0
  104. package/apps/desktop/cli/bounded-map.ts +39 -0
  105. package/apps/desktop/cli/bundle-service.ts +256 -0
  106. package/apps/desktop/cli/capabilities.ts +212 -0
  107. package/apps/desktop/cli/capture-bundle.ts +1117 -0
  108. package/apps/desktop/cli/command-host-resources.ts +251 -0
  109. package/apps/desktop/cli/commands.ts +6836 -0
  110. package/apps/desktop/cli/directing-blob.ts +291 -0
  111. package/apps/desktop/cli/directing-media.ts +517 -0
  112. package/apps/desktop/cli/directing-quote.ts +89 -0
  113. package/apps/desktop/cli/directing-service.ts +288 -0
  114. package/apps/desktop/cli/emoji-assets.ts +538 -0
  115. package/apps/desktop/cli/errors.ts +29 -0
  116. package/apps/desktop/cli/face-analysis-service.ts +940 -0
  117. package/apps/desktop/cli/filter-script.ts +76 -0
  118. package/apps/desktop/cli/gateway-application-port.ts +1142 -0
  119. package/apps/desktop/cli/gateway-credential.ts +81 -0
  120. package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
  121. package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
  122. package/apps/desktop/cli/gateway-media-service.ts +3106 -0
  123. package/apps/desktop/cli/gateway-media-signature.ts +122 -0
  124. package/apps/desktop/cli/gateway-provider-options.ts +562 -0
  125. package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
  126. package/apps/desktop/cli/help.ts +501 -0
  127. package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
  128. package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
  129. package/apps/desktop/cli/io.ts +290 -0
  130. package/apps/desktop/cli/main.ts +123 -0
  131. package/apps/desktop/cli/media-effects-service.ts +885 -0
  132. package/apps/desktop/cli/media-ingest-model.ts +364 -0
  133. package/apps/desktop/cli/media-ingest-platform.ts +227 -0
  134. package/apps/desktop/cli/media-ingest-program.ts +200 -0
  135. package/apps/desktop/cli/media-ingest.ts +30 -0
  136. package/apps/desktop/cli/music-analysis-service.ts +434 -0
  137. package/apps/desktop/cli/mutation-lock.ts +530 -0
  138. package/apps/desktop/cli/native-assets.d.ts +12 -0
  139. package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
  140. package/apps/desktop/cli/overlay-playback.ts +177 -0
  141. package/apps/desktop/cli/paths.ts +265 -0
  142. package/apps/desktop/cli/portable-surface.ts +242 -0
  143. package/apps/desktop/cli/project-camera-receipt.ts +112 -0
  144. package/apps/desktop/cli/project-face-camera.ts +394 -0
  145. package/apps/desktop/cli/project-inactivity-service.ts +552 -0
  146. package/apps/desktop/cli/project-media-integrity.ts +121 -0
  147. package/apps/desktop/cli/project-renderer.ts +1526 -0
  148. package/apps/desktop/cli/project-service.ts +519 -0
  149. package/apps/desktop/cli/project-state-transaction.ts +520 -0
  150. package/apps/desktop/cli/query-limits.ts +1 -0
  151. package/apps/desktop/cli/recording-controller.ts +1567 -0
  152. package/apps/desktop/cli/recording-daemon.ts +931 -0
  153. package/apps/desktop/cli/recording-ref.ts +56 -0
  154. package/apps/desktop/cli/renamed-environment.ts +6 -0
  155. package/apps/desktop/cli/renderer.ts +1439 -0
  156. package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
  157. package/apps/desktop/cli/scene-analysis-service.ts +879 -0
  158. package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
  159. package/apps/desktop/cli/spatial-project-service.ts +90 -0
  160. package/apps/desktop/cli/spatial-scene-service.ts +105 -0
  161. package/apps/desktop/cli/spatial-world-service.ts +39 -0
  162. package/apps/desktop/cli/speech-analysis-service.ts +682 -0
  163. package/apps/desktop/cli/studio-assemble.ts +58 -0
  164. package/apps/desktop/cli/studio-command.ts +60 -0
  165. package/apps/desktop/cli/studio-custody.ts +60 -0
  166. package/apps/desktop/cli/studio-encode.ts +216 -0
  167. package/apps/desktop/cli/studio-exr.ts +52 -0
  168. package/apps/desktop/cli/studio-files.ts +179 -0
  169. package/apps/desktop/cli/studio-output-validation.ts +162 -0
  170. package/apps/desktop/cli/studio-process.ts +145 -0
  171. package/apps/desktop/cli/studio-runtime.ts +77 -0
  172. package/apps/desktop/cli/studio-scaffold.ts +70 -0
  173. package/apps/desktop/cli/studio-service.ts +301 -0
  174. package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
  175. package/apps/desktop/cli/studio-template-names.ts +3 -0
  176. package/apps/desktop/cli/studio-workflow.ts +19 -0
  177. package/apps/desktop/cli/time.ts +25 -0
  178. package/apps/desktop/cli/workflow-code.ts +584 -0
  179. package/apps/desktop/cli/workflow-runs.ts +576 -0
  180. package/apps/desktop/code/advanced.ts +139 -0
  181. package/apps/desktop/code/application-node-planner.ts +1288 -0
  182. package/apps/desktop/code/compiler.ts +329 -0
  183. package/apps/desktop/code/contracts.ts +13 -0
  184. package/apps/desktop/code/creative-recipes.ts +94 -0
  185. package/apps/desktop/code/define-workflow.ts +115 -0
  186. package/apps/desktop/code/editing.ts +284 -0
  187. package/apps/desktop/code/file-candidate-provenance.ts +420 -0
  188. package/apps/desktop/code/file-candidate.ts +69 -0
  189. package/apps/desktop/code/graph-builder.ts +10 -0
  190. package/apps/desktop/code/host-resource-policy.ts +55 -0
  191. package/apps/desktop/code/plan-contracts.ts +159 -0
  192. package/apps/desktop/code/planning.ts +361 -0
  193. package/apps/desktop/code/public.ts +134 -0
  194. package/apps/desktop/code/reference-projection.ts +62 -0
  195. package/apps/desktop/code/run-contracts.ts +466 -0
  196. package/apps/desktop/code/run-store.ts +2478 -0
  197. package/apps/desktop/code/runtime-identity.ts +328 -0
  198. package/apps/desktop/code/scheduler.ts +3067 -0
  199. package/apps/desktop/code/semantic-builder.ts +1436 -0
  200. package/apps/desktop/code/source-bundle.ts +1641 -0
  201. package/apps/desktop/code/source-typecheck.ts +211 -0
  202. package/apps/desktop/code/testing.ts +79 -0
  203. package/apps/desktop/code/worker-client.ts +1675 -0
  204. package/apps/desktop/code/worker-effects.ts +141 -0
  205. package/apps/desktop/code/worker-entry.ts +435 -0
  206. package/apps/desktop/code/worker-lease-guardian.ts +179 -0
  207. package/apps/desktop/code/worker-process-identity.ts +484 -0
  208. package/apps/desktop/code/worker-protocol.ts +211 -0
  209. package/apps/desktop/code/workflow-effects.ts +132 -0
  210. package/apps/desktop/contracts/analysis.ts +949 -0
  211. package/apps/desktop/contracts/edit.ts +576 -0
  212. package/apps/desktop/contracts/index.ts +9 -0
  213. package/apps/desktop/contracts/media-effects.ts +139 -0
  214. package/apps/desktop/contracts/project-render.ts +644 -0
  215. package/apps/desktop/contracts/project.ts +740 -0
  216. package/apps/desktop/contracts/recording.ts +1434 -0
  217. package/apps/desktop/contracts/render.ts +334 -0
  218. package/apps/desktop/contracts/runtime.ts +155 -0
  219. package/apps/desktop/contracts/spatial-compositor.ts +58 -0
  220. package/apps/desktop/contracts/spatial-project.ts +159 -0
  221. package/apps/desktop/contracts/spatial-world.ts +48 -0
  222. package/apps/desktop/contracts/video-effects.ts +120 -0
  223. package/apps/desktop/core/alignment-apply.ts +400 -0
  224. package/apps/desktop/core/audio-alignment.ts +466 -0
  225. package/apps/desktop/core/auto-zoom.ts +209 -0
  226. package/apps/desktop/core/canonical-json.ts +14 -0
  227. package/apps/desktop/core/emoji.ts +210 -0
  228. package/apps/desktop/core/events.ts +409 -0
  229. package/apps/desktop/core/face-framing.ts +533 -0
  230. package/apps/desktop/core/face-tracks.ts +326 -0
  231. package/apps/desktop/core/inactivity.ts +137 -0
  232. package/apps/desktop/core/index.ts +30 -0
  233. package/apps/desktop/core/intervals.ts +123 -0
  234. package/apps/desktop/core/montage.ts +236 -0
  235. package/apps/desktop/core/music-analysis.ts +518 -0
  236. package/apps/desktop/core/plan.ts +244 -0
  237. package/apps/desktop/core/project-auto-zoom.ts +75 -0
  238. package/apps/desktop/core/project-camera-spatial.ts +676 -0
  239. package/apps/desktop/core/project-camera.ts +403 -0
  240. package/apps/desktop/core/project-captions.ts +888 -0
  241. package/apps/desktop/core/project-inactivity.ts +125 -0
  242. package/apps/desktop/core/project-metadata.ts +927 -0
  243. package/apps/desktop/core/project-plan.ts +375 -0
  244. package/apps/desktop/core/project-render-plan.ts +303 -0
  245. package/apps/desktop/core/project-time.ts +469 -0
  246. package/apps/desktop/core/render-plan.ts +442 -0
  247. package/apps/desktop/core/scene-cache.ts +174 -0
  248. package/apps/desktop/core/scene-sampling.ts +448 -0
  249. package/apps/desktop/core/spatial-compositor.ts +22 -0
  250. package/apps/desktop/core/spatial-durability.ts +99 -0
  251. package/apps/desktop/core/spatial-project.ts +169 -0
  252. package/apps/desktop/core/spatial-shot-clock.ts +32 -0
  253. package/apps/desktop/core/speech-analysis.ts +333 -0
  254. package/apps/desktop/core/storage.ts +970 -0
  255. package/apps/desktop/core/time-map.ts +91 -0
  256. package/apps/desktop/core/time.ts +61 -0
  257. package/apps/desktop/core/video-effects.ts +660 -0
  258. package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
  259. package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  260. package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  261. package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
  262. package/apps/desktop/dist/cli/main.js +5086 -0
  263. package/apps/desktop/html-overlay/catalog.ts +171 -0
  264. package/apps/desktop/html-overlay/contracts.ts +372 -0
  265. package/apps/desktop/html-overlay/execution-profile.ts +77 -0
  266. package/apps/desktop/html-overlay/index.ts +98 -0
  267. package/apps/desktop/html-overlay/libraries.ts +263 -0
  268. package/apps/desktop/html-overlay/random.ts +49 -0
  269. package/apps/desktop/html-overlay/runtime.ts +813 -0
  270. package/apps/desktop/html-overlay/scaffolds.ts +792 -0
  271. package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
  272. package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
  273. package/apps/desktop/html-overlay/spatial.ts +720 -0
  274. package/apps/desktop/studio/assets/README.md +39 -0
  275. package/apps/desktop/studio/assets/contracts.ts +129 -0
  276. package/apps/desktop/studio/assets/network.ts +64 -0
  277. package/apps/desktop/studio/assets/poly-haven.ts +148 -0
  278. package/apps/desktop/studio/drivers/blender_driver.py +466 -0
  279. package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
  280. package/apps/desktop/studio/education/driver.py +308 -0
  281. package/apps/desktop/studio/education/lesson.py +180 -0
  282. package/apps/desktop/studio/education/toolkit.py +249 -0
  283. package/apps/desktop/workflows/chaptered-demo.ts +48 -0
  284. package/apps/desktop/workflows/creative-iteration.ts +107 -0
  285. package/apps/desktop/workflows/creative-selection.ts +92 -0
  286. package/apps/desktop/workflows/fragments.ts +486 -0
  287. package/apps/desktop/workflows/index.ts +93 -0
  288. package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
  289. package/apps/desktop/workflows/scene-direction.ts +12 -0
  290. package/apps/desktop/workflows/social-variants.ts +75 -0
  291. package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
  292. package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
  293. package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  294. package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  295. package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
  296. package/dist/cli.js +479 -0
  297. package/dist/code/advanced.js +270 -0
  298. package/dist/code/index.js +2240 -0
  299. package/dist/generate.js +24 -0
  300. package/dist/host-resources.js +26 -0
  301. package/dist/index-42zsesc1.js +2257 -0
  302. package/dist/index-7308egqr.js +48 -0
  303. package/dist/index-8txs6fkn.js +712 -0
  304. package/dist/index-mcy8z0br.js +192 -0
  305. package/dist/index-p63wavx0.js +2029 -0
  306. package/dist/index-r7gdhmsp.js +424 -0
  307. package/dist/index-sh6xbav6.js +1219 -0
  308. package/dist/index-z1w83f81.js +4 -0
  309. package/dist/index-z7239b4h.js +2363 -0
  310. package/dist/index-zfeg9hmw.js +2013 -0
  311. package/dist/index.js +241 -0
  312. package/dist/operations.js +29 -0
  313. package/dist/skill-install-dh0ntqba.js +10 -0
  314. package/dist/vectorize/worker.js +142 -0
  315. package/dist/workflow.js +16 -0
  316. package/docs/README.md +38 -0
  317. package/docs/studio.md +215 -0
  318. package/examples/studio/blender/README.md +26 -0
  319. package/examples/studio/blender/character.py +152 -0
  320. package/examples/studio/blender/cloth.py +101 -0
  321. package/examples/studio/blender/color_chart.py +29 -0
  322. package/examples/studio/blender/fluid.py +78 -0
  323. package/examples/studio/blender/import_model.py +74 -0
  324. package/examples/studio/blender/product.py +53 -0
  325. package/examples/studio/blender/shaded_street.py +481 -0
  326. package/examples/studio/blender/studio_scene.py +147 -0
  327. package/examples/studio/cadquery/README.md +13 -0
  328. package/examples/studio/cadquery/bracket.py +36 -0
  329. package/examples/studio/cadquery/import_step.py +12 -0
  330. package/examples/studio/education/README.md +46 -0
  331. package/examples/studio/education/lesson.json +99 -0
  332. package/examples/studio/education/scene.py +7 -0
  333. package/examples/studio/hybrid-scene.ts +96 -0
  334. package/examples/studio/native-workflow.ts +14 -0
  335. package/examples/studio/vgpu/README.md +64 -0
  336. package/examples/studio/vgpu/heat-field.mjs +290 -0
  337. package/package.json +304 -0
  338. package/packages/scene/src/index.ts +1 -0
  339. package/packages/scene/src/scene.ts +215 -0
  340. package/schema/diagram.schema.json +334 -0
  341. package/skills/slopcamera/SKILL.md +45 -0
  342. package/skills/slopcamera/agents/openai.yaml +4 -0
  343. package/skills/slopcamera/references/customization.md +77 -0
  344. package/skills/slopcamera/references/diagrams.md +153 -0
  345. package/skills/slopcamera/references/directed-scenes.md +42 -0
  346. package/skills/slopcamera/references/directing-video.md +18 -0
  347. package/skills/slopcamera/references/educational-video.md +26 -0
  348. package/skills/slopcamera/references/gateway-media.md +134 -0
  349. package/skills/slopcamera/references/install.md +62 -0
  350. package/skills/slopcamera/references/native-studio.md +31 -0
  351. package/skills/slopcamera/references/reference-led-3d.md +140 -0
  352. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
  353. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
  354. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
  355. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
  356. package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
  357. package/skills/slopcamera/references/vectorization.md +29 -0
  358. package/skills/slopcamera/references/video-projects.md +187 -0
  359. package/skills/slopcamera/references/visual-communication.md +120 -0
  360. package/skills/slopcamera/references/workflows-sdk.md +45 -0
  361. package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
  362. package/src/artifacts.ts +106 -0
  363. package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
  364. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
  365. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
  366. package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
  367. package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  368. package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
  369. package/src/assets.d.ts +9 -0
  370. package/src/cli.ts +601 -0
  371. package/src/cloud-errors.ts +26 -0
  372. package/src/code/advanced.ts +10 -0
  373. package/src/code/boundary.ts +21 -0
  374. package/src/code/canonical-json.ts +129 -0
  375. package/src/code/compiler.ts +953 -0
  376. package/src/code/contracts.ts +576 -0
  377. package/src/code/define-workflow.ts +334 -0
  378. package/src/code/errors.ts +41 -0
  379. package/src/code/graph-builder.ts +945 -0
  380. package/src/code/index.ts +121 -0
  381. package/src/code/json-snapshot.ts +899 -0
  382. package/src/code/json-utf8.ts +88 -0
  383. package/src/code/portable-builder.ts +136 -0
  384. package/src/code/projection.ts +378 -0
  385. package/src/code/public-operations.ts +450 -0
  386. package/src/code/runtime.ts +580 -0
  387. package/src/code/sha256.ts +36 -0
  388. package/src/config.ts +180 -0
  389. package/src/desktop.ts +253 -0
  390. package/src/fs.ts +10 -0
  391. package/src/generate.ts +704 -0
  392. package/src/host-resource-posix.ts +161 -0
  393. package/src/host-resources.ts +1749 -0
  394. package/src/icons.ts +58 -0
  395. package/src/index.ts +200 -0
  396. package/src/label-layout.ts +130 -0
  397. package/src/layout.ts +413 -0
  398. package/src/lint.ts +127 -0
  399. package/src/mcp/boundary.ts +354 -0
  400. package/src/mcp/index.ts +33 -0
  401. package/src/mcp/server.ts +272 -0
  402. package/src/mcp/tools.ts +963 -0
  403. package/src/mcp/types.ts +71 -0
  404. package/src/operations.ts +775 -0
  405. package/src/parse.ts +861 -0
  406. package/src/process-environment.ts +17 -0
  407. package/src/render.ts +437 -0
  408. package/src/skill-install.ts +49 -0
  409. package/src/spatial-scene/authoring.ts +27 -0
  410. package/src/spatial-scene/camera-track.ts +83 -0
  411. package/src/spatial-scene/contracts.ts +277 -0
  412. package/src/spatial-scene/evaluate.ts +145 -0
  413. package/src/spatial-scene/gltf.ts +558 -0
  414. package/src/spatial-scene/identity.ts +260 -0
  415. package/src/spatial-scene/index.ts +10 -0
  416. package/src/spatial-scene/inspect.ts +68 -0
  417. package/src/spatial-scene/math.ts +280 -0
  418. package/src/spatial-scene/patch.ts +131 -0
  419. package/src/spatial-scene/time.ts +53 -0
  420. package/src/studio/contracts.ts +129 -0
  421. package/src/studio/index.ts +2 -0
  422. package/src/studio/plan.ts +156 -0
  423. package/src/studio/shared.ts +41 -0
  424. package/src/theme.ts +68 -0
  425. package/src/tldr.ts +474 -0
  426. package/src/types.ts +212 -0
  427. package/src/vectorize/archive.ts +101 -0
  428. package/src/vectorize/command.ts +797 -0
  429. package/src/vectorize/index.ts +27 -0
  430. package/src/vectorize/limits.ts +61 -0
  431. package/src/vectorize/metrics.ts +351 -0
  432. package/src/vectorize/pixels.ts +323 -0
  433. package/src/vectorize/supervisor.ts +339 -0
  434. package/src/vectorize/svg.ts +188 -0
  435. package/src/vectorize/tool.ts +542 -0
  436. package/src/vectorize/types.ts +113 -0
  437. package/src/vectorize/vectorize.ts +634 -0
  438. package/src/vectorize/worker-protocol.ts +40 -0
  439. package/src/vectorize/worker.ts +209 -0
  440. package/src/version.ts +1 -0
  441. package/src/workflow.ts +409 -0
@@ -0,0 +1,1922 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { constants } from "node:fs";
3
+ import {
4
+ link,
5
+ lstat,
6
+ open,
7
+ realpath,
8
+ unlink,
9
+ } from "node:fs/promises";
10
+ import {
11
+ dirname,
12
+ isAbsolute,
13
+ join,
14
+ relative,
15
+ sep,
16
+ } from "node:path";
17
+
18
+ import { Cause, Context, Effect, Exit, Option, Ref } from "effect";
19
+ import { z } from "zod";
20
+
21
+ import { createBoundedJsonValueSnapshot } from "../../../../../src/code/json-snapshot";
22
+ import { parseSpatialScene, spatialSceneSha256 } from "../../../../../src/spatial-scene/identity";
23
+ import { SPATIAL_PROJECT_LIMITS, SpatialProjectRevisionV2Schema } from "../../../contracts/spatial-project";
24
+ import { createNodeSpatialDurability } from "../../../core/spatial-durability";
25
+ import { spatialProjectContents, spatialProjectDocumentText, spatialProjectRevisionSha256 } from "../../../core/spatial-project";
26
+ import { verifySpatialCompositorOutput } from "../../../cli/spatial-compositor-verifier";
27
+ import { createSpatialCompositorCadence } from "../../spatial-compositor-cadence";
28
+ import { createSpatialRenderProjection } from "../../spatial-render-projection";
29
+ import { planSpatialRender, SpatialRenderReceiptSchema, spatialShotRenderRequest } from "../../spatial-render";
30
+ import { loadRepositoryMedia } from "../media/shared";
31
+ import {
32
+ PROJECT_SPATIAL_RECEIPT_MAXIMUM_BYTES, ProjectRenderSpatialBindingV1Schema, ProjectRenderOutputSchemaV4,
33
+ ProjectSpatialRenderReceiptV1Schema, createProjectSpatialRenderReceipt,
34
+ type ProjectRenderOutputV4, type ProjectSpatialRenderReceiptV1,
35
+ } from "./project-spatial-receipt";
36
+
37
+ import {
38
+ ProjectRenderTargetSchema,
39
+ ProjectRenderOutputRequestSchema,
40
+ ProjectRenderSyncPolicySchema,
41
+ ProjectRenderToolIdentitySchema,
42
+ ProjectRenderToolchainSchema,
43
+ resolveProjectRenderTarget,
44
+ resolveProjectRenderEncoderRecipe,
45
+ type ProjectRenderToolIdentity,
46
+ type ProjectRenderToolchain,
47
+ type ProjectRenderPlanV1,
48
+ } from "../../../contracts";
49
+ import {
50
+ canonicalJson,
51
+ canonicalJsonSha256,
52
+ createNodeBundleFileSystem,
53
+ sha256Hex,
54
+ } from "../../../core";
55
+ import { AtomicRenderPlatform, executeAtomicRenderEffect } from "../../../cli/atomic-render-effects";
56
+ import { AtomicRenderPlatformLive } from "../../../cli/atomic-render-platform";
57
+ import {
58
+ buildProjectFfmpegInvocation,
59
+ reverifyProjectRenderInputs,
60
+ } from "../../../cli/project-renderer";
61
+ import {
62
+ resolveVerifiedProjectMedia,
63
+ } from "../../../cli/project-media-integrity";
64
+ import {
65
+ ensurePhysicalPrivateDirectoryWithin,
66
+ } from "../../../cli/paths";
67
+ import type {
68
+ ApplicationCapability,
69
+ ApplicationContext,
70
+ ApplicationProcessRunner,
71
+ } from "../../context";
72
+ import {
73
+ ExactCapabilityApplicationRunner,
74
+ bindExactCapability,
75
+ } from "../../capability-binding";
76
+ import { ApplicationError, errorMessage } from "../../errors";
77
+ import type { OperationDefinition, OperationExecutionContext } from "../../operation";
78
+ import {
79
+ operationBoundary,
80
+ operationExitValue,
81
+ operationFinally,
82
+ operationValidation,
83
+ runStandaloneOperation,
84
+ type OperationEffectFailure,
85
+ type OperationFailurePhase,
86
+ } from "../../operation-effects";
87
+ import { withOutputPublicationLeaseEffect } from "../../output-publication-lease";
88
+ import {
89
+ ProjectRenderOutputReferenceSchema,
90
+ ProjectRenderPlanReferenceSchema,
91
+ ProjectRenderReceiptReferenceSchema,
92
+ ProjectRenderReceiptV2Schema,
93
+ ProjectEditRevisionDocumentSchema,
94
+ createProjectRenderReceiptV2,
95
+ type ProjectRenderOutputReference,
96
+ type ProjectRenderReceiptReference,
97
+ } from "../../receipts";
98
+ import { throwIfAborted } from "../shared";
99
+ import {
100
+ CandidateProjectRendererAbiSchema,
101
+ CandidateProjectRenderInputSchema,
102
+ assertCurrentCandidateProjectRendererAbi,
103
+ type CandidateProjectRenderInput,
104
+ } from "./bind-candidate-output";
105
+ import {
106
+ exactProjectDirectory,
107
+ loadExactProjectRenderPlan,
108
+ } from "./project-plan";
109
+
110
+ const PROJECT_RENDER_MAX_DURATION_MS = 6 * 60 * 60_000;
111
+ const PROJECT_RENDER_PRECOMMIT_FILE =
112
+ "project-render-publication-precommit.v1.json";
113
+ const PROJECT_RENDER_PRECOMMIT_MAXIMUM_BYTES = 256 * 1_024;
114
+ const PROJECT_RENDER_PRECOMMIT_DOMAIN =
115
+ "studio.project-render-publication-precommit/v1";
116
+ const CANDIDATE_RENDER_REUSE_RECORD_DOMAIN =
117
+ "slopcamera.candidate-render-reuse-record/v1";
118
+
119
+ class ProjectRenderServices extends Context.Tag("@slopcamera/local/ProjectRenderServices")<
120
+ ProjectRenderServices, ApplicationContext
121
+ >() { }
122
+
123
+ /** Finite native verification/publication must settle before its enclosing lease can close. */
124
+ function renderBoundary<A>(
125
+ phase: OperationFailurePhase,
126
+ execute: () => A | Promise<A>,
127
+ ): Effect.Effect<A, OperationEffectFailure> {
128
+ return Effect.uninterruptible(operationBoundary(phase, execute));
129
+ }
130
+
131
+ function renderValidation<A>(evaluate: () => A): Effect.Effect<A, OperationEffectFailure> {
132
+ return operationValidation("project", evaluate);
133
+ }
134
+
135
+ /** Join every admitted native read while retaining Promise.all's first rejection identity. */
136
+ function renderPathsExist(paths: readonly string[]): Effect.Effect<readonly boolean[], OperationEffectFailure> {
137
+ return Effect.uninterruptible(Effect.gen(function*() {
138
+ const firstFailure = yield* Ref.make(Option.none<number>());
139
+ const results = yield* Effect.forEach(paths, (path, index) => Effect.tap(
140
+ Effect.exit(renderBoundary("publication", () => pathExists(path))),
141
+ exit => Exit.isSuccess(exit) ? Effect.void : Ref.update(firstFailure, current =>
142
+ Option.isNone(current) ? Option.some(index) : current),
143
+ ), { concurrency: "unbounded" });
144
+ const selected = yield* Ref.get(firstFailure);
145
+ if (Option.isSome(selected)) {
146
+ const first = results[selected.value];
147
+ if (first !== undefined && Exit.isFailure(first)) {
148
+ const otherCauses = results.reduce<Cause.Cause<OperationEffectFailure>>((cause, result, index) =>
149
+ index === selected.value || Exit.isSuccess(result) ? cause : Cause.parallel(cause, result.cause), Cause.empty);
150
+ // Select only the first native rejection at the public boundary. Later
151
+ // failures are retained privately by the same native-finally envelope.
152
+ if (Cause.isEmpty(otherCauses)) return yield* Effect.failCause(first.cause);
153
+ return yield* operationFinally(Effect.failCause(otherCauses), Effect.asVoid(first));
154
+ }
155
+ }
156
+ return yield* Effect.forEach(results, result => result);
157
+ }));
158
+ }
159
+
160
+ const ProjectRenderExecutionIdentitySchema = z.strictObject({
161
+ nodeKey: z.string()
162
+ .min(1)
163
+ .max(255)
164
+ .regex(
165
+ /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*(?:\/[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*)*$/u,
166
+ ),
167
+ nodePlanSha256: z.string().regex(/^[a-f0-9]{64}$/u),
168
+ runId: z.string().regex(/^run_[a-z0-9][a-z0-9_-]{5,95}$/u),
169
+ });
170
+
171
+ const ProjectRenderPublicationPrecommitBodySchema = z.strictObject({
172
+ kind: z.union([
173
+ z.literal("slopcamera.project-render-publication-precommit"),
174
+ z.literal("studio.project-render-publication-precommit"),
175
+ ]),
176
+ receipt: ProjectRenderReceiptV2Schema,
177
+ receiptContentsSha256: z.string().regex(/^[a-f0-9]{64}$/u),
178
+ schemaVersion: z.literal(1),
179
+ });
180
+
181
+ const ProjectRenderPublicationPrecommitSchema =
182
+ ProjectRenderPublicationPrecommitBodySchema.extend({
183
+ precommitSha256: z.string().regex(/^[a-f0-9]{64}$/u),
184
+ }).strict().superRefine((precommit, context) => {
185
+ const receiptContents = `${canonicalJson(precommit.receipt)}\n`;
186
+ if (sha256Hex(receiptContents) !== precommit.receiptContentsSha256) {
187
+ context.addIssue({
188
+ code: "custom",
189
+ message: "Render publication precommit receipt digest is invalid.",
190
+ path: ["receiptContentsSha256"],
191
+ });
192
+ }
193
+ const body = ProjectRenderPublicationPrecommitBodySchema.parse({
194
+ kind: precommit.kind,
195
+ receipt: precommit.receipt,
196
+ receiptContentsSha256: precommit.receiptContentsSha256,
197
+ schemaVersion: precommit.schemaVersion,
198
+ });
199
+ if (
200
+ canonicalJsonSha256({
201
+ domain: PROJECT_RENDER_PRECOMMIT_DOMAIN,
202
+ ...body,
203
+ }) !== precommit.precommitSha256
204
+ ) {
205
+ context.addIssue({
206
+ code: "custom",
207
+ message: "Render publication precommit domain digest is invalid.",
208
+ path: ["precommitSha256"],
209
+ });
210
+ }
211
+ });
212
+
213
+ type ProjectRenderPublicationPrecommit = z.infer<
214
+ typeof ProjectRenderPublicationPrecommitSchema
215
+ >;
216
+
217
+ const CandidateRenderReuseRecordBodyV1Schema = z.strictObject({
218
+ derivationSha256: z.string().regex(/^[a-f0-9]{64}$/u),
219
+ inputSha256: z.string().regex(/^[a-f0-9]{64}$/u),
220
+ kind: z.union([
221
+ z.literal("slopcamera.candidate-render-reuse-record"),
222
+ ]),
223
+ output: ProjectRenderOutputReferenceSchema,
224
+ rendererAbi: CandidateProjectRendererAbiSchema,
225
+ schemaVersion: z.literal(1),
226
+ sourceReceipt: ProjectRenderReceiptReferenceSchema,
227
+ });
228
+
229
+ const CandidateRenderReuseRecordV1Schema =
230
+ CandidateRenderReuseRecordBodyV1Schema.extend({
231
+ recordSha256: z.string().regex(/^[a-f0-9]{64}$/u),
232
+ }).strict().superRefine((record, context) => {
233
+ const body = CandidateRenderReuseRecordBodyV1Schema.parse({
234
+ derivationSha256: record.derivationSha256,
235
+ inputSha256: record.inputSha256,
236
+ kind: record.kind,
237
+ output: record.output,
238
+ rendererAbi: record.rendererAbi,
239
+ schemaVersion: record.schemaVersion,
240
+ sourceReceipt: record.sourceReceipt,
241
+ });
242
+ if (
243
+ record.output.projectId !== record.sourceReceipt.projectId
244
+ || record.output.revisionSha256 !== record.sourceReceipt.revisionSha256
245
+ || record.output.sha256 !== record.sourceReceipt.outputSha256
246
+ || canonicalJsonSha256({
247
+ domain: CANDIDATE_RENDER_REUSE_RECORD_DOMAIN,
248
+ ...body,
249
+ }) !== record.recordSha256
250
+ ) {
251
+ context.addIssue({
252
+ code: "custom",
253
+ message: "Candidate render reuse record is not bound to its exact output and source receipt.",
254
+ });
255
+ }
256
+ });
257
+
258
+ type CandidateRenderReuseRecordV1 = z.infer<
259
+ typeof CandidateRenderReuseRecordV1Schema
260
+ >;
261
+
262
+ const ProjectRenderUnboundInputSchema = z.strictObject({
263
+ binding: ProjectRenderToolchainSchema.optional(),
264
+ output: ProjectRenderOutputRequestSchema,
265
+ plan: ProjectRenderPlanReferenceSchema,
266
+ syncPolicy: ProjectRenderSyncPolicySchema,
267
+ });
268
+
269
+ const ProjectRenderUnboundInputSchemaV2 = z.strictObject({
270
+ binding: ProjectRenderToolchainSchema.optional(),
271
+ output: ProjectRenderOutputRequestSchema,
272
+ plan: ProjectRenderPlanReferenceSchema,
273
+ syncPolicy: ProjectRenderSyncPolicySchema,
274
+ target: ProjectRenderTargetSchema,
275
+ });
276
+
277
+ export const ProjectRenderInputSchema = z.strictObject({
278
+ binding: ProjectRenderToolchainSchema,
279
+ output: ProjectRenderOutputRequestSchema,
280
+ plan: ProjectRenderPlanReferenceSchema,
281
+ syncPolicy: ProjectRenderSyncPolicySchema,
282
+ });
283
+
284
+ export const ProjectRenderInputSchemaV2 = z.strictObject({
285
+ binding: ProjectRenderToolchainSchema,
286
+ output: ProjectRenderOutputRequestSchema,
287
+ plan: ProjectRenderPlanReferenceSchema,
288
+ syncPolicy: ProjectRenderSyncPolicySchema,
289
+ target: ProjectRenderTargetSchema,
290
+ });
291
+
292
+ export const ProjectRenderInputSchemaV3 = CandidateProjectRenderInputSchema;
293
+
294
+ export const ProjectRenderOutputSchema = z.strictObject({
295
+ output: ProjectRenderOutputReferenceSchema,
296
+ receipt: ProjectRenderReceiptReferenceSchema,
297
+ }).superRefine((output, context) => {
298
+ if (
299
+ output.output.projectId !== output.receipt.projectId
300
+ || output.output.revisionSha256 !== output.receipt.revisionSha256
301
+ || output.output.sha256 !== output.receipt.outputSha256
302
+ ) {
303
+ context.addIssue({
304
+ code: "custom",
305
+ message: "Project render output and receipt references disagree.",
306
+ });
307
+ }
308
+ });
309
+
310
+ export type ProjectRenderInput = z.infer<typeof ProjectRenderInputSchema>;
311
+ export type ProjectRenderInputV2 = z.infer<typeof ProjectRenderInputSchemaV2>;
312
+ export type ProjectRenderInputV3 = z.infer<typeof ProjectRenderInputSchemaV3>;
313
+ export type ProjectRenderOutput = z.infer<typeof ProjectRenderOutputSchema>;
314
+
315
+ type AnyProjectRenderInput =
316
+ | ProjectRenderInput
317
+ | ProjectRenderInputV2
318
+ | ProjectRenderInputV3;
319
+
320
+ export interface ProjectRenderExecutionIdentity {
321
+ readonly nodeKey: string;
322
+ readonly nodePlanSha256: string;
323
+ readonly runId: string;
324
+ }
325
+
326
+ export interface ProjectRenderReconciliationControl {
327
+ readonly abortSignal: AbortSignal;
328
+ readonly beforePublication: () => Promise<void>;
329
+ }
330
+
331
+ export type ProjectRenderReconciliation =
332
+ | {
333
+ readonly kind: "completed";
334
+ readonly output: ProjectRenderOutput;
335
+ }
336
+ | {
337
+ readonly kind: "retry";
338
+ }
339
+ | {
340
+ readonly kind: "conflict";
341
+ readonly message: string;
342
+ };
343
+
344
+ async function exactToolIdentity(
345
+ capability: ApplicationCapability,
346
+ name: ProjectRenderToolIdentity["name"],
347
+ required: boolean,
348
+ ): Promise<ProjectRenderToolIdentity | null> {
349
+ if (
350
+ !capability.available
351
+ || capability.command === undefined
352
+ || capability.command === ""
353
+ ) {
354
+ if (!required) return null;
355
+ throw new ApplicationError(
356
+ "unavailable",
357
+ `${name} is unavailable: ${capability?.reason ?? "capability was not probed"}`,
358
+ { capability: name },
359
+ );
360
+ }
361
+ if (capability.version === undefined || capability.version.trim() === "") {
362
+ throw new ApplicationError(
363
+ "unavailable",
364
+ `${name} did not provide an exact probed version identity.`,
365
+ { capability: name },
366
+ );
367
+ }
368
+ return ProjectRenderToolIdentitySchema.parse(
369
+ await bindExactCapability(capability),
370
+ );
371
+ }
372
+
373
+ export async function bindProjectRenderToolchain(
374
+ application: ApplicationContext,
375
+ ): Promise<ProjectRenderToolchain> {
376
+ const [ffmpeg, ffprobe, rsvgConvert] = await Promise.all([
377
+ application.capability("ffmpeg").then(capability => (
378
+ exactToolIdentity(capability, "ffmpeg", true)
379
+ )),
380
+ application.capability("ffprobe").then(capability => (
381
+ exactToolIdentity(capability, "ffprobe", true)
382
+ )),
383
+ application.capability("rsvg-convert").then(capability => (
384
+ exactToolIdentity(capability, "rsvg-convert", false)
385
+ )),
386
+ ]);
387
+ return ProjectRenderToolchainSchema.parse({
388
+ ffmpeg,
389
+ ffprobe,
390
+ rsvgConvert,
391
+ });
392
+ }
393
+
394
+ /**
395
+ * Adds the host-probed toolchain to exact operation input before the scheduler
396
+ * hashes its node plan. Authored workflows cannot substitute another binary.
397
+ */
398
+ export async function bindProjectRenderInput(
399
+ application: ApplicationContext,
400
+ resolvedInput: unknown,
401
+ ): Promise<ProjectRenderInput> {
402
+ const requested = ProjectRenderUnboundInputSchema.parse(resolvedInput);
403
+ const binding = await bindProjectRenderToolchain(application);
404
+ if (
405
+ requested.binding !== undefined
406
+ && canonicalJson(requested.binding) !== canonicalJson(binding)
407
+ ) {
408
+ throw new ApplicationError(
409
+ "conflict",
410
+ "Project render tool binding changed after exact node planning.",
411
+ );
412
+ }
413
+ return ProjectRenderInputSchema.parse({
414
+ ...requested,
415
+ binding,
416
+ });
417
+ }
418
+
419
+ export async function bindProjectRenderInputV2(
420
+ application: ApplicationContext,
421
+ resolvedInput: unknown,
422
+ ): Promise<ProjectRenderInputV2> {
423
+ const requested = ProjectRenderUnboundInputSchemaV2.parse(resolvedInput);
424
+ const binding = await bindProjectRenderToolchain(application);
425
+ if (
426
+ requested.binding !== undefined
427
+ && canonicalJson(requested.binding) !== canonicalJson(binding)
428
+ ) {
429
+ throw new ApplicationError(
430
+ "conflict",
431
+ "Project render tool binding changed after exact node planning.",
432
+ );
433
+ }
434
+ return ProjectRenderInputSchemaV2.parse({
435
+ ...requested,
436
+ binding,
437
+ });
438
+ }
439
+
440
+ export async function bindProjectRenderInputV3(
441
+ application: ApplicationContext,
442
+ resolvedInput: unknown,
443
+ ): Promise<ProjectRenderInputV3> {
444
+ const requested = CandidateProjectRenderInputSchema.parse(resolvedInput);
445
+ assertCurrentCandidateProjectRendererAbi(requested.derivation.rendererAbi);
446
+ const binding = await bindProjectRenderToolchain(application);
447
+ if (canonicalJson(requested.binding) !== canonicalJson(binding)) {
448
+ throw new ApplicationError(
449
+ "conflict",
450
+ "Candidate render tool binding changed after exact node planning.",
451
+ );
452
+ }
453
+ return ProjectRenderInputSchemaV3.parse({
454
+ ...requested,
455
+ binding,
456
+ derivation: {
457
+ ...requested.derivation,
458
+ binding,
459
+ },
460
+ });
461
+ }
462
+
463
+ async function bindAnyProjectRenderInput(
464
+ application: ApplicationContext,
465
+ resolvedInput: unknown,
466
+ ): Promise<AnyProjectRenderInput> {
467
+ if (
468
+ typeof resolvedInput === "object"
469
+ && resolvedInput !== null
470
+ && "derivation" in resolvedInput
471
+ ) {
472
+ return await bindProjectRenderInputV3(application, resolvedInput);
473
+ }
474
+ if (
475
+ typeof resolvedInput === "object"
476
+ && resolvedInput !== null
477
+ && "target" in resolvedInput
478
+ ) {
479
+ return await bindProjectRenderInputV2(application, resolvedInput);
480
+ }
481
+ return await bindProjectRenderInput(application, resolvedInput);
482
+ }
483
+
484
+ function assertProjectRenderTarget(
485
+ input: AnyProjectRenderInput,
486
+ plan: ProjectRenderPlanV1,
487
+ ): void {
488
+ if (!("target" in input)) return;
489
+ const expected = resolveProjectRenderTarget(input.target);
490
+ if (
491
+ plan.output.frameRate !== expected.frameRate
492
+ || plan.output.pixelHeight !== expected.pixelHeight
493
+ || plan.output.pixelWidth !== expected.pixelWidth
494
+ ) {
495
+ throw new ApplicationError(
496
+ "conflict",
497
+ "Project render plan geometry does not match its exact canvas target.",
498
+ {
499
+ actual: {
500
+ frameRate: plan.output.frameRate,
501
+ pixelHeight: plan.output.pixelHeight,
502
+ pixelWidth: plan.output.pixelWidth,
503
+ },
504
+ expected,
505
+ },
506
+ );
507
+ }
508
+ }
509
+
510
+ function isWithin(root: string, candidate: string): boolean {
511
+ const pathFromRoot = relative(root, candidate);
512
+ return pathFromRoot === "" || (
513
+ pathFromRoot !== ".."
514
+ && !pathFromRoot.startsWith(`..${sep}`)
515
+ && !isAbsolute(pathFromRoot)
516
+ );
517
+ }
518
+
519
+ async function exactWorkflowWorkspace(
520
+ application: ApplicationContext,
521
+ requested: string,
522
+ ): Promise<string> {
523
+ const [privateRoot, details] = await Promise.all([
524
+ realpath(application.paths.privateRoot),
525
+ lstat(requested),
526
+ ]);
527
+ if (
528
+ details.isSymbolicLink()
529
+ || !details.isDirectory()
530
+ || (details.mode & 0o077) !== 0
531
+ ) {
532
+ throw new ApplicationError(
533
+ "unsafe-path",
534
+ "Workflow render workspace must be a private physical directory.",
535
+ );
536
+ }
537
+ const workspace = await realpath(requested);
538
+ if (!isWithin(privateRoot, workspace) || workspace === privateRoot) {
539
+ throw new ApplicationError(
540
+ "unsafe-path",
541
+ "Workflow render workspace must remain beneath the application private root.",
542
+ );
543
+ }
544
+ return workspace;
545
+ }
546
+
547
+ function expectedWorkflowWorkspacePath(
548
+ application: ApplicationContext,
549
+ executionValue: ProjectRenderExecutionIdentity,
550
+ ): string {
551
+ const execution = ProjectRenderExecutionIdentitySchema.parse(executionValue);
552
+ return join(
553
+ application.paths.privateRoot,
554
+ "workflow-runs",
555
+ execution.runId,
556
+ "staging",
557
+ sha256Hex(execution.nodeKey),
558
+ execution.nodePlanSha256,
559
+ );
560
+ }
561
+
562
+ async function exactRunWorkflowWorkspace(
563
+ application: ApplicationContext,
564
+ execution: ProjectRenderExecutionIdentity,
565
+ requested: string,
566
+ ): Promise<string> {
567
+ const workspace = await exactWorkflowWorkspace(application, requested);
568
+ const expected = await realpath(expectedWorkflowWorkspacePath(
569
+ application,
570
+ execution,
571
+ ));
572
+ if (workspace !== expected) {
573
+ throw new ApplicationError(
574
+ "unsafe-path",
575
+ "Workflow render workspace does not match its exact run and node plan.",
576
+ );
577
+ }
578
+ return workspace;
579
+ }
580
+
581
+ function workflowRunner(
582
+ runner: ApplicationProcessRunner,
583
+ workspaceDirectory: string,
584
+ ): ApplicationProcessRunner {
585
+ return {
586
+ run: async (argv, options = {}) => await runner.run(argv, {
587
+ ...options,
588
+ cwd: workspaceDirectory,
589
+ }),
590
+ };
591
+ }
592
+
593
+ async function syncDirectory(path: string): Promise<void> {
594
+ const directory = await open(path, "r");
595
+ try {
596
+ await directory.sync();
597
+ } finally {
598
+ await directory.close();
599
+ }
600
+ }
601
+
602
+ function isFileSystemError(error: unknown, code: string): boolean {
603
+ return error instanceof Error && "code" in error && error.code === code;
604
+ }
605
+
606
+ async function pathExists(path: string): Promise<boolean> {
607
+ try {
608
+ await lstat(path);
609
+ return true;
610
+ } catch (error) {
611
+ if (isFileSystemError(error, "ENOENT")) return false;
612
+ throw error;
613
+ }
614
+ }
615
+
616
+ function createPublicationPrecommit(
617
+ receipt: z.infer<typeof ProjectRenderReceiptV2Schema>,
618
+ ): ProjectRenderPublicationPrecommit {
619
+ const receiptContentsSha256 = sha256Hex(`${canonicalJson(receipt)}\n`);
620
+ const body = ProjectRenderPublicationPrecommitBodySchema.parse({
621
+ kind: "slopcamera.project-render-publication-precommit",
622
+ receipt,
623
+ receiptContentsSha256,
624
+ schemaVersion: 1,
625
+ });
626
+ return ProjectRenderPublicationPrecommitSchema.parse({
627
+ ...body,
628
+ precommitSha256: canonicalJsonSha256({
629
+ domain: PROJECT_RENDER_PRECOMMIT_DOMAIN,
630
+ ...body,
631
+ }),
632
+ });
633
+ }
634
+
635
+ async function readPublicationPrecommit(
636
+ workspace: string,
637
+ ): Promise<ProjectRenderPublicationPrecommit | null> {
638
+ const path = join(workspace, PROJECT_RENDER_PRECOMMIT_FILE);
639
+ let handle;
640
+ try {
641
+ handle = await open(
642
+ path,
643
+ constants.O_RDONLY
644
+ | (constants.O_NOFOLLOW ?? 0)
645
+ | (constants.O_NONBLOCK ?? 0),
646
+ );
647
+ } catch (error) {
648
+ if (isFileSystemError(error, "ENOENT")) return null;
649
+ throw error;
650
+ }
651
+ try {
652
+ const before = await handle.stat();
653
+ if (
654
+ !before.isFile()
655
+ || before.nlink !== 1
656
+ || before.size < 1
657
+ || before.size > PROJECT_RENDER_PRECOMMIT_MAXIMUM_BYTES
658
+ || (before.mode & 0o777) !== 0o600
659
+ ) {
660
+ throw new ApplicationError(
661
+ "invalid-data",
662
+ "Project render publication precommit is unsafe or exceeds its size bound.",
663
+ );
664
+ }
665
+ const contents = await handle.readFile("utf8");
666
+ const after = await handle.stat();
667
+ if (
668
+ before.dev !== after.dev
669
+ || before.ino !== after.ino
670
+ || before.size !== after.size
671
+ || before.mtimeMs !== after.mtimeMs
672
+ || before.ctimeMs !== after.ctimeMs
673
+ ) {
674
+ throw new ApplicationError(
675
+ "conflict",
676
+ "Project render publication precommit changed while it was read.",
677
+ );
678
+ }
679
+ let value: unknown;
680
+ try {
681
+ value = JSON.parse(contents) as unknown;
682
+ } catch {
683
+ throw new ApplicationError(
684
+ "invalid-data",
685
+ "Project render publication precommit is not valid JSON.",
686
+ );
687
+ }
688
+ const precommit = ProjectRenderPublicationPrecommitSchema.parse(value);
689
+ if (contents !== `${canonicalJson(precommit)}\n`) {
690
+ throw new ApplicationError(
691
+ "invalid-data",
692
+ "Project render publication precommit is not canonical immutable JSON.",
693
+ );
694
+ }
695
+ return precommit;
696
+ } finally {
697
+ await handle.close();
698
+ }
699
+ }
700
+
701
+ async function publishPublicationPrecommit(
702
+ workspace: string,
703
+ precommit: ProjectRenderPublicationPrecommit,
704
+ ): Promise<ProjectRenderPublicationPrecommit> {
705
+ const target = join(workspace, PROJECT_RENDER_PRECOMMIT_FILE);
706
+ const contents = `${canonicalJson(precommit)}\n`;
707
+ const temporary = join(
708
+ workspace,
709
+ `.project-render-precommit-${randomUUID()}.tmp`,
710
+ );
711
+ let created = false;
712
+ const handle = await open(
713
+ temporary,
714
+ constants.O_CREAT
715
+ | constants.O_EXCL
716
+ | constants.O_WRONLY
717
+ | (constants.O_NOFOLLOW ?? 0),
718
+ 0o600,
719
+ );
720
+ try {
721
+ await handle.writeFile(contents, "utf8");
722
+ await handle.sync();
723
+ } finally {
724
+ await handle.close();
725
+ }
726
+ try {
727
+ try {
728
+ await link(temporary, target);
729
+ created = true;
730
+ } catch (error) {
731
+ if (!isFileSystemError(error, "EEXIST")) throw error;
732
+ }
733
+ } finally {
734
+ await unlink(temporary).catch((error: unknown) => {
735
+ if (!isFileSystemError(error, "ENOENT")) throw error;
736
+ });
737
+ }
738
+ if (created) await syncDirectory(workspace);
739
+ const published = await readPublicationPrecommit(workspace);
740
+ if (published === null) {
741
+ throw new ApplicationError(
742
+ "conflict",
743
+ "Project render publication precommit disappeared after publication.",
744
+ );
745
+ }
746
+ return published;
747
+ }
748
+
749
+ async function requireFreshPublicationTargets(
750
+ outputPath: string,
751
+ receiptPath: string,
752
+ ): Promise<void> {
753
+ const [outputExists, receiptExists] = await Promise.all([
754
+ pathExists(outputPath),
755
+ pathExists(receiptPath),
756
+ ]);
757
+ if (outputExists || receiptExists) {
758
+ throw new ApplicationError(
759
+ "conflict",
760
+ "Project render output or exact-run receipt already exists; immutable publication never replaces either path.",
761
+ { outputExists, receiptExists },
762
+ );
763
+ }
764
+ }
765
+
766
+ function isCandidateProjectRenderInput(
767
+ input: AnyProjectRenderInput,
768
+ ): input is CandidateProjectRenderInput {
769
+ return "derivation" in input;
770
+ }
771
+
772
+ function candidateRenderReuseRecordPath(
773
+ input: CandidateProjectRenderInput,
774
+ ): string {
775
+ return `renders/receipts/derivations/${input.derivation.derivationSha256}.json`;
776
+ }
777
+
778
+ function receiptPath(nodePlanSha256: string): string {
779
+ return `renders/receipts/${nodePlanSha256}.json`;
780
+ }
781
+
782
+ function receiptReference(
783
+ contents: string,
784
+ path: string,
785
+ output: ProjectRenderOutputReference,
786
+ receiptSha256: string,
787
+ nodePlanSha256: string,
788
+ ): ProjectRenderReceiptReference {
789
+ return ProjectRenderReceiptReferenceSchema.parse({
790
+ bytes: new TextEncoder().encode(contents).byteLength,
791
+ kind: "slopcamera.project-render-receipt-reference",
792
+ nodePlanSha256,
793
+ outputSha256: output.sha256,
794
+ path,
795
+ projectId: output.projectId,
796
+ receiptSha256,
797
+ revisionSha256: output.revisionSha256,
798
+ schemaVersion: 2,
799
+ sha256: sha256Hex(contents),
800
+ });
801
+ }
802
+
803
+ function invocationMatchesExactRender(
804
+ invocation: z.infer<typeof ProjectRenderReceiptV2Schema>["invocation"],
805
+ outputAbsolute: string,
806
+ renderPlanSha256: string,
807
+ ): boolean {
808
+ const filterIndex = invocation.arguments.indexOf("-filter_complex_script");
809
+ const filterArgument = filterIndex < 0
810
+ ? undefined
811
+ : invocation.arguments[filterIndex + 1];
812
+ const normalizedFilterPath = invocation.filterGraph.path.split("/").join(sep);
813
+ return invocation.arguments.at(-1) === outputAbsolute
814
+ && invocation.outputPath !== ""
815
+ && invocation.renderPlanSha256 === renderPlanSha256
816
+ && filterArgument !== undefined
817
+ && filterArgument.endsWith(`${sep}${normalizedFilterPath}`);
818
+ }
819
+
820
+ function assertReceiptMatchesReusableRender(options: {
821
+ readonly input: AnyProjectRenderInput;
822
+ readonly outputAbsolute: string;
823
+ readonly receipt: z.infer<typeof ProjectRenderReceiptV2Schema>;
824
+ readonly renderPlanSha256: string;
825
+ }): void {
826
+ const receipt = options.receipt;
827
+ if (
828
+ receipt.inputSha256 !== canonicalJsonSha256(options.input)
829
+ || canonicalJson(receipt.plan) !== canonicalJson(options.input.plan)
830
+ || receipt.syncPolicy !== options.input.syncPolicy
831
+ || canonicalJson(receipt.toolchain)
832
+ !== canonicalJson(options.input.binding)
833
+ || receipt.output.path !== options.input.output.path
834
+ || receipt.output.bytes > options.input.output.maximumBytes
835
+ || receipt.invocation.outputPath !== options.input.output.path
836
+ || !invocationMatchesExactRender(
837
+ receipt.invocation,
838
+ options.outputAbsolute,
839
+ options.renderPlanSha256,
840
+ )
841
+ ) {
842
+ throw new ApplicationError(
843
+ "conflict",
844
+ "Project render receipt is not reusable for the exact input, plan, toolchain, and invocation.",
845
+ );
846
+ }
847
+ }
848
+
849
+ function assertReceiptMatchesExactRender(options: {
850
+ readonly execution: ProjectRenderExecutionIdentity;
851
+ readonly input: AnyProjectRenderInput;
852
+ readonly outputAbsolute: string;
853
+ readonly receipt: z.infer<typeof ProjectRenderReceiptV2Schema>;
854
+ readonly renderPlanSha256: string;
855
+ }): void {
856
+ assertReceiptMatchesReusableRender(options);
857
+ const execution = ProjectRenderExecutionIdentitySchema.parse(
858
+ options.execution,
859
+ );
860
+ if (
861
+ options.receipt.run.runId !== execution.runId
862
+ || options.receipt.run.nodeKey !== execution.nodeKey
863
+ || options.receipt.run.nodePlanSha256 !== execution.nodePlanSha256
864
+ ) {
865
+ throw new ApplicationError(
866
+ "conflict",
867
+ "Project render receipt is not bound to the exact input, run, plan, toolchain, and invocation.",
868
+ );
869
+ }
870
+ }
871
+
872
+ async function expectedWorkflowWorkspaceIfPresent(
873
+ application: ApplicationContext,
874
+ execution: ProjectRenderExecutionIdentity,
875
+ ): Promise<string | null> {
876
+ const expected = expectedWorkflowWorkspacePath(application, execution);
877
+ try {
878
+ return await exactWorkflowWorkspace(application, expected);
879
+ } catch (error) {
880
+ if (isFileSystemError(error, "ENOENT")) return null;
881
+ throw error;
882
+ }
883
+ }
884
+
885
+ async function publishReceiptNoReplace(options: {
886
+ readonly application: ApplicationContext;
887
+ readonly beforePublication?: () => Promise<void>;
888
+ readonly contents: string;
889
+ readonly execution: ProjectRenderExecutionIdentity;
890
+ readonly input: AnyProjectRenderInput;
891
+ readonly signal?: AbortSignal;
892
+ }): Promise<{
893
+ readonly contents: string;
894
+ readonly receipt: z.infer<typeof ProjectRenderReceiptV2Schema>;
895
+ }> {
896
+ const plan = await loadExactProjectRenderPlan(
897
+ options.application,
898
+ options.input.plan,
899
+ );
900
+ if (plan.fileSystem.writeTextNoReplace === undefined) {
901
+ throw new ApplicationError(
902
+ "internal",
903
+ "Project storage does not support immutable render-receipt publication.",
904
+ );
905
+ }
906
+ const path = receiptPath(options.execution.nodePlanSha256);
907
+ if (options.signal !== undefined) throwIfAborted(options.signal);
908
+ await options.beforePublication?.();
909
+ if (options.signal !== undefined) throwIfAborted(options.signal);
910
+ const disposition = await plan.fileSystem.writeTextNoReplace(
911
+ path,
912
+ options.contents,
913
+ );
914
+ const published = await readReceipt(
915
+ options.application,
916
+ options.input,
917
+ options.execution,
918
+ );
919
+ if (published.contents !== options.contents) {
920
+ throw new ApplicationError(
921
+ "conflict",
922
+ disposition === "exists"
923
+ ? "Project render receipt path already contains different bytes."
924
+ : "Published project render receipt contains different bytes.",
925
+ );
926
+ }
927
+ return published;
928
+ }
929
+
930
+ async function readReceipt(
931
+ application: ApplicationContext,
932
+ input: AnyProjectRenderInput,
933
+ execution: ProjectRenderExecutionIdentity,
934
+ ): Promise<{
935
+ readonly contents: string;
936
+ readonly receipt: z.infer<typeof ProjectRenderReceiptV2Schema>;
937
+ }> {
938
+ const plan = await loadExactProjectRenderPlan(application, input.plan);
939
+ const path = receiptPath(execution.nodePlanSha256);
940
+ const contents = await plan.fileSystem.readText(path);
941
+ let parsed: unknown;
942
+ try {
943
+ parsed = JSON.parse(contents) as unknown;
944
+ } catch {
945
+ throw new ApplicationError(
946
+ "conflict",
947
+ "Existing project render receipt is not valid JSON.",
948
+ );
949
+ }
950
+ const receipt = ProjectRenderReceiptV2Schema.parse(parsed);
951
+ if (contents !== `${canonicalJson(receipt)}\n`) {
952
+ throw new ApplicationError(
953
+ "conflict",
954
+ "Existing project render receipt is not its canonical V2 document.",
955
+ );
956
+ }
957
+ return { contents, receipt };
958
+ }
959
+
960
+ function createCandidateRenderReuseRecord(options: {
961
+ readonly input: CandidateProjectRenderInput;
962
+ readonly receipt: z.infer<typeof ProjectRenderReceiptV2Schema>;
963
+ readonly receiptContents: string;
964
+ }): CandidateRenderReuseRecordV1 {
965
+ const sourceReceipt = receiptReference(
966
+ options.receiptContents,
967
+ receiptPath(options.receipt.run.nodePlanSha256),
968
+ options.receipt.output,
969
+ options.receipt.receiptSha256,
970
+ options.receipt.run.nodePlanSha256,
971
+ );
972
+ const body = CandidateRenderReuseRecordBodyV1Schema.parse({
973
+ derivationSha256: options.input.derivation.derivationSha256,
974
+ inputSha256: canonicalJsonSha256(options.input),
975
+ kind: "slopcamera.candidate-render-reuse-record",
976
+ output: options.receipt.output,
977
+ rendererAbi: options.input.derivation.rendererAbi,
978
+ schemaVersion: 1,
979
+ sourceReceipt,
980
+ });
981
+ return CandidateRenderReuseRecordV1Schema.parse({
982
+ ...body,
983
+ recordSha256: canonicalJsonSha256({
984
+ domain: CANDIDATE_RENDER_REUSE_RECORD_DOMAIN,
985
+ ...body,
986
+ }),
987
+ });
988
+ }
989
+
990
+ async function readReceiptReference(options: {
991
+ readonly fileSystem: Awaited<ReturnType<
992
+ typeof loadExactProjectRenderPlan
993
+ >>["fileSystem"];
994
+ readonly reference: ProjectRenderReceiptReference;
995
+ }): Promise<{
996
+ readonly contents: string;
997
+ readonly receipt: z.infer<typeof ProjectRenderReceiptV2Schema>;
998
+ }> {
999
+ const contents = await options.fileSystem.readText(options.reference.path);
1000
+ let parsed: unknown;
1001
+ try {
1002
+ parsed = JSON.parse(contents) as unknown;
1003
+ } catch {
1004
+ throw new ApplicationError(
1005
+ "conflict",
1006
+ "Candidate render source receipt is not valid JSON.",
1007
+ );
1008
+ }
1009
+ const receipt = ProjectRenderReceiptV2Schema.parse(parsed);
1010
+ if (
1011
+ contents !== `${canonicalJson(receipt)}\n`
1012
+ || new TextEncoder().encode(contents).byteLength !== options.reference.bytes
1013
+ || sha256Hex(contents) !== options.reference.sha256
1014
+ || receipt.receiptSha256 !== options.reference.receiptSha256
1015
+ || receipt.run.nodePlanSha256 !== options.reference.nodePlanSha256
1016
+ || receipt.output.projectId !== options.reference.projectId
1017
+ || receipt.output.revisionSha256 !== options.reference.revisionSha256
1018
+ || receipt.output.sha256 !== options.reference.outputSha256
1019
+ ) {
1020
+ throw new ApplicationError(
1021
+ "conflict",
1022
+ "Candidate render source receipt does not match its immutable reference.",
1023
+ );
1024
+ }
1025
+ return { contents, receipt };
1026
+ }
1027
+
1028
+ async function readCandidateRenderReuseRecord(options: {
1029
+ readonly exactPlan: Awaited<ReturnType<typeof loadExactProjectRenderPlan>>;
1030
+ readonly input: CandidateProjectRenderInput;
1031
+ readonly outputAbsolute: string;
1032
+ }): Promise<{
1033
+ readonly record: CandidateRenderReuseRecordV1;
1034
+ readonly receipt: z.infer<typeof ProjectRenderReceiptV2Schema>;
1035
+ readonly receiptContents: string;
1036
+ }> {
1037
+ const path = candidateRenderReuseRecordPath(options.input);
1038
+ const contents = await options.exactPlan.fileSystem.readText(path);
1039
+ let parsed: unknown;
1040
+ try {
1041
+ parsed = JSON.parse(contents) as unknown;
1042
+ } catch {
1043
+ throw new ApplicationError(
1044
+ "conflict",
1045
+ "Candidate render reuse record is not valid JSON.",
1046
+ );
1047
+ }
1048
+ const record = CandidateRenderReuseRecordV1Schema.parse(parsed);
1049
+ if (
1050
+ contents !== `${canonicalJson(record)}\n`
1051
+ || record.derivationSha256 !== options.input.derivation.derivationSha256
1052
+ || record.inputSha256 !== canonicalJsonSha256(options.input)
1053
+ || record.rendererAbi !== options.input.derivation.rendererAbi
1054
+ ) {
1055
+ throw new ApplicationError(
1056
+ "conflict",
1057
+ "Candidate render reuse record does not match the exact derivation.",
1058
+ );
1059
+ }
1060
+ const source = await readReceiptReference({
1061
+ fileSystem: options.exactPlan.fileSystem,
1062
+ reference: record.sourceReceipt,
1063
+ });
1064
+ assertReceiptMatchesReusableRender({
1065
+ input: options.input,
1066
+ outputAbsolute: options.outputAbsolute,
1067
+ receipt: source.receipt,
1068
+ renderPlanSha256: options.exactPlan.document.renderPlanSha256,
1069
+ });
1070
+ if (canonicalJson(record.output) !== canonicalJson(source.receipt.output)) {
1071
+ throw new ApplicationError(
1072
+ "conflict",
1073
+ "Candidate render reuse record output differs from its source receipt.",
1074
+ );
1075
+ }
1076
+ await resolveVerifiedProjectMedia({
1077
+ expected: record.output,
1078
+ label: "Reusable candidate render output",
1079
+ path: options.input.output.path,
1080
+ repositoryRoot: options.exactPlan.directory,
1081
+ });
1082
+ return {
1083
+ record,
1084
+ receipt: source.receipt,
1085
+ receiptContents: source.contents,
1086
+ };
1087
+ }
1088
+
1089
+ async function publishCandidateRenderReuseRecord(options: {
1090
+ readonly exactPlan: Awaited<ReturnType<typeof loadExactProjectRenderPlan>>;
1091
+ readonly input: CandidateProjectRenderInput;
1092
+ readonly outputAbsolute: string;
1093
+ readonly receipt: z.infer<typeof ProjectRenderReceiptV2Schema>;
1094
+ readonly receiptContents: string;
1095
+ }): Promise<CandidateRenderReuseRecordV1> {
1096
+ if (options.exactPlan.fileSystem.writeTextNoReplace === undefined) {
1097
+ throw new ApplicationError(
1098
+ "internal",
1099
+ "Project storage does not support immutable candidate render reuse records.",
1100
+ );
1101
+ }
1102
+ const record = createCandidateRenderReuseRecord(options);
1103
+ const contents = `${canonicalJson(record)}\n`;
1104
+ await ensurePhysicalPrivateDirectoryWithin(
1105
+ options.exactPlan.directory,
1106
+ dirname(candidateRenderReuseRecordPath(options.input)),
1107
+ );
1108
+ await options.exactPlan.fileSystem.writeTextNoReplace(
1109
+ candidateRenderReuseRecordPath(options.input),
1110
+ contents,
1111
+ );
1112
+ const published = await readCandidateRenderReuseRecord({
1113
+ exactPlan: options.exactPlan,
1114
+ input: options.input,
1115
+ outputAbsolute: options.outputAbsolute,
1116
+ });
1117
+ return published.record;
1118
+ }
1119
+
1120
+ function assertReceiptMatchesReusableNodePlan(options: {
1121
+ readonly execution: ProjectRenderExecutionIdentity;
1122
+ readonly input: CandidateProjectRenderInput;
1123
+ readonly outputAbsolute: string;
1124
+ readonly receipt: z.infer<typeof ProjectRenderReceiptV2Schema>;
1125
+ readonly renderPlanSha256: string;
1126
+ }): void {
1127
+ assertReceiptMatchesReusableRender(options);
1128
+ const execution = ProjectRenderExecutionIdentitySchema.parse(
1129
+ options.execution,
1130
+ );
1131
+ if (
1132
+ options.receipt.run.nodeKey !== execution.nodeKey
1133
+ || options.receipt.run.nodePlanSha256 !== execution.nodePlanSha256
1134
+ ) {
1135
+ throw new ApplicationError(
1136
+ "conflict",
1137
+ "Reusable candidate render receipt path is not bound to the current node plan.",
1138
+ );
1139
+ }
1140
+ }
1141
+
1142
+ function adoptCandidateRenderIfPresent(options: {
1143
+ readonly application: ApplicationContext;
1144
+ readonly beforePublication: () => Promise<void>;
1145
+ readonly exactPlan: Awaited<ReturnType<typeof loadExactProjectRenderPlan>>;
1146
+ readonly execution: ProjectRenderExecutionIdentity;
1147
+ readonly input: CandidateProjectRenderInput;
1148
+ readonly outputAbsolute: string;
1149
+ readonly publicationPrecommit?: ProjectRenderPublicationPrecommit;
1150
+ readonly signal: AbortSignal;
1151
+ }): Effect.Effect<ProjectRenderOutput | null, OperationEffectFailure> {
1152
+ return Effect.gen(function*() {
1153
+ const currentReceiptRelative = receiptPath(options.execution.nodePlanSha256);
1154
+ const currentReceiptAbsolute = join(options.exactPlan.directory, currentReceiptRelative);
1155
+ const reuseRecordAbsolute = join(options.exactPlan.directory, candidateRenderReuseRecordPath(options.input));
1156
+ const [outputExists, currentReceiptExists, reuseRecordExists] = yield* renderPathsExist([
1157
+ options.outputAbsolute, currentReceiptAbsolute, reuseRecordAbsolute,
1158
+ ]);
1159
+ if (!outputExists) {
1160
+ yield* renderValidation(() => {
1161
+ if (currentReceiptExists || reuseRecordExists) {
1162
+ throw new ApplicationError("conflict", "Candidate render receipt or reuse record exists without its immutable output.");
1163
+ }
1164
+ });
1165
+ return null;
1166
+ }
1167
+ if (!currentReceiptExists && !reuseRecordExists) {
1168
+ const precommit = yield* renderValidation(() => {
1169
+ if (options.publicationPrecommit === undefined) {
1170
+ throw new ApplicationError("conflict", "Candidate render output exists without an exact receipt or reuse record.");
1171
+ }
1172
+ assertReceiptMatchesExactRender({
1173
+ execution: options.execution, input: options.input, outputAbsolute: options.outputAbsolute,
1174
+ receipt: options.publicationPrecommit.receipt,
1175
+ renderPlanSha256: options.exactPlan.document.renderPlanSha256,
1176
+ });
1177
+ return options.publicationPrecommit;
1178
+ });
1179
+ yield* renderBoundary("publication", () => resolveVerifiedProjectMedia({
1180
+ expected: precommit.receipt.output,
1181
+ label: "Interrupted candidate render output",
1182
+ path: options.input.output.path,
1183
+ repositoryRoot: options.exactPlan.directory,
1184
+ }));
1185
+ const expectedContents = `${canonicalJson(precommit.receipt)}\n`;
1186
+ const published = yield* renderBoundary("publication", () => publishReceiptNoReplace({
1187
+ application: options.application, beforePublication: options.beforePublication,
1188
+ contents: expectedContents, execution: options.execution, input: options.input, signal: options.signal,
1189
+ }));
1190
+ yield* renderValidation(() => {
1191
+ if (published.contents !== expectedContents) {
1192
+ throw new ApplicationError("conflict", "Recovered candidate render receipt differs from its exact run-private publication precommit.");
1193
+ }
1194
+ assertReceiptMatchesExactRender({
1195
+ execution: options.execution, input: options.input, outputAbsolute: options.outputAbsolute,
1196
+ receipt: published.receipt, renderPlanSha256: options.exactPlan.document.renderPlanSha256,
1197
+ });
1198
+ });
1199
+ yield* renderBoundary("publication", () => resolveVerifiedProjectMedia({
1200
+ expected: published.receipt.output, label: "Recovered candidate render output",
1201
+ path: options.input.output.path, repositoryRoot: options.exactPlan.directory,
1202
+ }));
1203
+ yield* renderBoundary("publication", () => publishCandidateRenderReuseRecord({
1204
+ exactPlan: options.exactPlan, input: options.input, outputAbsolute: options.outputAbsolute,
1205
+ receipt: published.receipt, receiptContents: published.contents,
1206
+ }));
1207
+ return yield* renderValidation(() => ProjectRenderOutputSchema.parse({
1208
+ output: published.receipt.output,
1209
+ receipt: receiptReference(published.contents, currentReceiptRelative, published.receipt.output,
1210
+ published.receipt.receiptSha256, options.execution.nodePlanSha256),
1211
+ }));
1212
+ }
1213
+
1214
+ const source = yield* Effect.gen(function*() {
1215
+ if (reuseRecordExists) {
1216
+ const reusable = yield* renderBoundary("publication", () => readCandidateRenderReuseRecord({
1217
+ exactPlan: options.exactPlan, input: options.input, outputAbsolute: options.outputAbsolute,
1218
+ }));
1219
+ return { contents: reusable.receiptContents, reference: reusable.record.sourceReceipt, receipt: reusable.receipt };
1220
+ }
1221
+ const current = yield* renderBoundary("publication", () => readReceipt(options.application, options.input, options.execution));
1222
+ yield* renderValidation(() => assertReceiptMatchesReusableNodePlan({
1223
+ execution: options.execution, input: options.input, outputAbsolute: options.outputAbsolute,
1224
+ receipt: current.receipt, renderPlanSha256: options.exactPlan.document.renderPlanSha256,
1225
+ }));
1226
+ yield* renderBoundary("publication", () => resolveVerifiedProjectMedia({
1227
+ expected: current.receipt.output, label: "Candidate render awaiting reuse record",
1228
+ path: options.input.output.path, repositoryRoot: options.exactPlan.directory,
1229
+ }));
1230
+ return {
1231
+ ...current,
1232
+ reference: receiptReference(current.contents, currentReceiptRelative, current.receipt.output,
1233
+ current.receipt.receiptSha256, options.execution.nodePlanSha256),
1234
+ };
1235
+ });
1236
+
1237
+ // Adoption retains its two cancellation checks; it has no fresh-encoding final-fence exception.
1238
+ yield* renderValidation(() => throwIfAborted(options.signal));
1239
+ yield* renderBoundary("publication", options.beforePublication);
1240
+ yield* renderValidation(() => throwIfAborted(options.signal));
1241
+ yield* renderBoundary("publication", () => resolveVerifiedProjectMedia({
1242
+ expected: source.receipt.output, label: "Adopted candidate render output",
1243
+ path: options.input.output.path, repositoryRoot: options.exactPlan.directory,
1244
+ }));
1245
+ if (currentReceiptExists) {
1246
+ const current = yield* renderBoundary("publication", () => readReceipt(options.application, options.input, options.execution));
1247
+ yield* renderValidation(() => {
1248
+ assertReceiptMatchesReusableNodePlan({
1249
+ execution: options.execution, input: options.input, outputAbsolute: options.outputAbsolute,
1250
+ receipt: current.receipt, renderPlanSha256: options.exactPlan.document.renderPlanSha256,
1251
+ });
1252
+ if (canonicalJson(current.receipt.output) !== canonicalJson(source.receipt.output)) {
1253
+ throw new ApplicationError("conflict", "Current candidate render receipt disagrees with reusable output evidence.");
1254
+ }
1255
+ });
1256
+ }
1257
+ if (!reuseRecordExists) {
1258
+ yield* renderBoundary("publication", () => publishCandidateRenderReuseRecord({
1259
+ exactPlan: options.exactPlan, input: options.input, outputAbsolute: options.outputAbsolute,
1260
+ receipt: source.receipt, receiptContents: source.contents,
1261
+ }));
1262
+ }
1263
+ return yield* renderValidation(() => ProjectRenderOutputSchema.parse({ output: source.receipt.output, receipt: source.reference }));
1264
+ });
1265
+ }
1266
+
1267
+ /**
1268
+ * Verified-receipt reconciliation for a previously dispatched exact render
1269
+ * node. If the public output was durably committed immediately before a
1270
+ * crash, the immutable run-private precommit authorizes only the matching
1271
+ * receipt to be finalized.
1272
+ */
1273
+ function reconcileProjectRenderProgram(
1274
+ inputValue: unknown,
1275
+ execution: ProjectRenderExecutionIdentity,
1276
+ control: ProjectRenderReconciliationControl,
1277
+ ): Effect.Effect<ProjectRenderReconciliation, OperationEffectFailure, ProjectRenderServices> {
1278
+ return Effect.gen(function*() {
1279
+ const application = yield* ProjectRenderServices;
1280
+ yield* renderValidation(() => throwIfAborted(control.abortSignal));
1281
+ const input = yield* renderBoundary("input", () => bindAnyProjectRenderInput(application, inputValue));
1282
+ const directory = yield* renderBoundary("project", () => exactProjectDirectory(application, input.plan.projectId));
1283
+ const outputAbsolute = join(directory, input.output.path);
1284
+ if (isCandidateProjectRenderInput(input)) {
1285
+ return yield* withOutputPublicationLeaseEffect(application, {
1286
+ outputPath: input.output.path, projectId: input.plan.projectId,
1287
+ }, Effect.gen(function*() {
1288
+ const exactPlan = yield* renderBoundary("project", () => loadExactProjectRenderPlan(application, input.plan));
1289
+ yield* renderValidation(() => assertProjectRenderTarget(input, exactPlan.document.plan));
1290
+ const workspace = yield* renderBoundary("workspace", () => expectedWorkflowWorkspaceIfPresent(application, execution));
1291
+ const precommit = workspace === null ? null : yield* renderBoundary("publication", () => readPublicationPrecommit(workspace));
1292
+ if (precommit !== null) {
1293
+ yield* renderValidation(() => assertReceiptMatchesExactRender({
1294
+ execution, input, outputAbsolute, receipt: precommit.receipt,
1295
+ renderPlanSha256: exactPlan.document.renderPlanSha256,
1296
+ }));
1297
+ }
1298
+ const adopted = yield* adoptCandidateRenderIfPresent({
1299
+ application, beforePublication: control.beforePublication, exactPlan, execution, input, outputAbsolute,
1300
+ ...(precommit === null ? {} : { publicationPrecommit: precommit }),
1301
+ signal: control.abortSignal,
1302
+ });
1303
+ return adopted === null ? { kind: "retry" as const } : { kind: "completed" as const, output: adopted };
1304
+ }));
1305
+ }
1306
+
1307
+ // Legacy exact-receipt recovery retains its separate idempotent/no-replace path.
1308
+ const receiptRelative = receiptPath(execution.nodePlanSha256);
1309
+ const receiptAbsolute = join(directory, receiptRelative);
1310
+ const [outputExists, receiptExists] = yield* renderPathsExist([outputAbsolute, receiptAbsolute]);
1311
+ const workspace = yield* renderBoundary("workspace", () => expectedWorkflowWorkspaceIfPresent(application, execution));
1312
+ const precommit = workspace === null ? null : yield* renderBoundary("publication", () => readPublicationPrecommit(workspace));
1313
+ if (!outputExists && !receiptExists && precommit === null) return { kind: "retry" };
1314
+ if (!outputExists && receiptExists) {
1315
+ return { kind: "conflict", message: "Project render has a partial output/receipt publication." };
1316
+ }
1317
+ const exactPlan = yield* renderBoundary("project", () => loadExactProjectRenderPlan(application, input.plan));
1318
+ yield* renderValidation(() => {
1319
+ assertProjectRenderTarget(input, exactPlan.document.plan);
1320
+ throwIfAborted(control.abortSignal);
1321
+ if (precommit !== null) {
1322
+ assertReceiptMatchesExactRender({
1323
+ execution, input, outputAbsolute, receipt: precommit.receipt,
1324
+ renderPlanSha256: exactPlan.document.renderPlanSha256,
1325
+ });
1326
+ }
1327
+ });
1328
+ if (!outputExists) return { kind: "retry" };
1329
+ if (!receiptExists && precommit === null) {
1330
+ return { kind: "conflict", message: "Project render output exists without its exact run-private publication precommit." };
1331
+ }
1332
+ const published = yield* Effect.gen(function*() {
1333
+ if (receiptExists) return yield* renderBoundary("publication", () => readReceipt(application, input, execution));
1334
+ const exactPrecommit = yield* renderValidation(() => {
1335
+ if (precommit === null) throw new ApplicationError("conflict", "Project render output exists without its exact run-private publication precommit.");
1336
+ return precommit;
1337
+ });
1338
+ yield* renderBoundary("publication", () => resolveVerifiedProjectMedia({
1339
+ expected: exactPrecommit.receipt.output, label: "Interrupted project render output",
1340
+ path: input.output.path, repositoryRoot: directory,
1341
+ }));
1342
+ return yield* renderBoundary("publication", () => publishReceiptNoReplace({
1343
+ application, beforePublication: control.beforePublication,
1344
+ contents: `${canonicalJson(exactPrecommit.receipt)}\n`, execution, input, signal: control.abortSignal,
1345
+ }));
1346
+ });
1347
+ const { contents, receipt } = published;
1348
+ yield* renderValidation(() => assertReceiptMatchesExactRender({
1349
+ execution, input, outputAbsolute, receipt, renderPlanSha256: exactPlan.document.renderPlanSha256,
1350
+ }));
1351
+ if (precommit !== null && canonicalJson(precommit.receipt) !== canonicalJson(receipt)) {
1352
+ return { kind: "conflict", message: "Project render receipt differs from its exact run-private publication precommit." };
1353
+ }
1354
+ yield* renderBoundary("publication", () => resolveVerifiedProjectMedia({
1355
+ expected: receipt.output, label: "Reconciled project render output",
1356
+ path: input.output.path, repositoryRoot: directory,
1357
+ }));
1358
+ return yield* renderValidation(() => ({
1359
+ kind: "completed" as const,
1360
+ output: ProjectRenderOutputSchema.parse({
1361
+ output: receipt.output,
1362
+ receipt: receiptReference(contents, receiptRelative, receipt.output, receipt.receiptSha256, execution.nodePlanSha256),
1363
+ }),
1364
+ }));
1365
+ });
1366
+ }
1367
+
1368
+ /** Closed local composition boundary; expected native failures retain the existing conflict projection. */
1369
+ export function reconcileProjectRenderEffect(
1370
+ application: ApplicationContext,
1371
+ inputValue: unknown,
1372
+ execution: ProjectRenderExecutionIdentity,
1373
+ control: ProjectRenderReconciliationControl,
1374
+ ): Effect.Effect<ProjectRenderReconciliation> {
1375
+ const program = reconcileProjectRenderProgram(inputValue, execution, control)
1376
+ .pipe(Effect.provideService(ProjectRenderServices, application));
1377
+ return Effect.map(Effect.exit(program), exit => {
1378
+ try {
1379
+ return operationExitValue(exit);
1380
+ } catch (error) {
1381
+ return { kind: "conflict" as const, message: errorMessage(error) };
1382
+ }
1383
+ });
1384
+ }
1385
+
1386
+ export async function reconcileProjectRender(
1387
+ application: ApplicationContext,
1388
+ inputValue: unknown,
1389
+ execution: ProjectRenderExecutionIdentity,
1390
+ control: ProjectRenderReconciliationControl,
1391
+ ): Promise<ProjectRenderReconciliation> {
1392
+ return await runStandaloneOperation(reconcileProjectRenderEffect(application, inputValue, execution, control));
1393
+ }
1394
+
1395
+ function projectRenderProgram(
1396
+ context: OperationExecutionContext,
1397
+ parsedInput: unknown,
1398
+ ): Effect.Effect<ProjectRenderOutput, OperationEffectFailure, ProjectRenderServices | AtomicRenderPlatform> {
1399
+ return Effect.gen(function*() {
1400
+ const application = yield* ProjectRenderServices;
1401
+ const workflow = yield* renderValidation(() => {
1402
+ if (context.workflow === undefined) {
1403
+ throw new ApplicationError("conflict", "Workflow project rendering requires an exact run-private execution context.");
1404
+ }
1405
+ throwIfAborted(context.abortSignal);
1406
+ return context.workflow;
1407
+ });
1408
+ const input = yield* renderBoundary("input", () => bindAnyProjectRenderInput(application, parsedInput));
1409
+ const exactPlan = yield* renderBoundary("project", () => loadExactProjectRenderPlan(application, input.plan));
1410
+ yield* renderValidation(() => {
1411
+ assertProjectRenderTarget(input, exactPlan.document.plan);
1412
+ if (input.syncPolicy === "require-verified" && exactPlan.document.plan.warnings.some(warning => warning.code === "unverified-sync")) {
1413
+ throw new ApplicationError("conflict", "Project render plan contains unverified placement synchronization.");
1414
+ }
1415
+ });
1416
+ const execution = { nodeKey: workflow.nodeKey, nodePlanSha256: workflow.nodePlanSha256, runId: workflow.runId };
1417
+ const workspace = yield* renderBoundary("workspace", () => exactRunWorkflowWorkspace(application, execution, workflow.workspaceDirectory));
1418
+ const runner = yield* renderValidation(() => workflowRunner(new ExactCapabilityApplicationRunner(
1419
+ application.runner,
1420
+ [input.binding.ffmpeg, input.binding.ffprobe, ...(input.binding.rsvgConvert === null ? [] : [input.binding.rsvgConvert])],
1421
+ application.paths.privateRoot,
1422
+ ), workspace));
1423
+ const outputParent = yield* renderBoundary("workspace", () => ensurePhysicalPrivateDirectoryWithin(exactPlan.directory, dirname(input.output.path)));
1424
+ const outputAbsolute = join(exactPlan.directory, input.output.path);
1425
+ yield* renderValidation(() => {
1426
+ if (dirname(outputAbsolute) !== outputParent) {
1427
+ throw new ApplicationError("unsafe-path", "Project render output parent did not resolve to its physical directory.");
1428
+ }
1429
+ });
1430
+ const receiptRelative = receiptPath(workflow.nodePlanSha256);
1431
+ const receiptAbsolute = join(exactPlan.directory, receiptRelative);
1432
+ yield* renderBoundary("workspace", () => ensurePhysicalPrivateDirectoryWithin(exactPlan.directory, dirname(receiptRelative)));
1433
+ yield* renderValidation(() => {
1434
+ if (exactPlan.fileSystem.writeTextNoReplace === undefined) {
1435
+ throw new ApplicationError("internal", "Project storage does not support immutable render-receipt publication.");
1436
+ }
1437
+ });
1438
+ if (!isCandidateProjectRenderInput(input)) {
1439
+ yield* renderBoundary("publication", () => requireFreshPublicationTargets(outputAbsolute, receiptAbsolute));
1440
+ }
1441
+ return yield* withOutputPublicationLeaseEffect(application, {
1442
+ outputPath: input.output.path, projectId: input.plan.projectId,
1443
+ }, Effect.gen(function*() {
1444
+ if (isCandidateProjectRenderInput(input)) {
1445
+ const adopted = yield* adoptCandidateRenderIfPresent({
1446
+ application, beforePublication: () => workflow.beforePublication(), exactPlan, execution, input, outputAbsolute,
1447
+ signal: context.abortSignal,
1448
+ });
1449
+ if (adopted !== null) return adopted;
1450
+ }
1451
+ yield* renderBoundary("publication", () => requireFreshPublicationTargets(outputAbsolute, receiptAbsolute));
1452
+ const built = yield* renderBoundary("media", () => buildProjectFfmpegInvocation(exactPlan.document.plan, {
1453
+ ffmpeg: input.binding.ffmpeg.executablePath,
1454
+ ffprobe: input.binding.ffprobe.executablePath,
1455
+ outputPath: outputAbsolute,
1456
+ projectDirectory: exactPlan.directory,
1457
+ ...("target" in input ? { renderTier: input.target.tier } : {}),
1458
+ repositoryRoot: application.paths.repositoryRoot,
1459
+ ...(input.binding.rsvgConvert === null ? {} : {
1460
+ rsvgConvert: input.binding.rsvgConvert.executablePath,
1461
+ rsvgConvertVersion: input.binding.rsvgConvert.version,
1462
+ }),
1463
+ runner, workspaceDirectory: workspace,
1464
+ }));
1465
+ yield* renderValidation(() => throwIfAborted(context.abortSignal));
1466
+ const rendered = yield* executeAtomicRenderEffect({
1467
+ abortSignal: context.abortSignal,
1468
+ argv: built.argv,
1469
+ failureLabel: "FFmpeg workflow project render failed",
1470
+ finalOutputPath: outputAbsolute,
1471
+ maximumOutputBytes: input.output.maximumBytes,
1472
+ requireFreshOutput: true,
1473
+ runner,
1474
+ stagingDirectory: workspace,
1475
+ timeoutMs: PROJECT_RENDER_MAX_DURATION_MS,
1476
+ }, {
1477
+ prepare: outputIntegrity => Effect.gen(function*() {
1478
+ yield* renderBoundary("publication", () => reverifyProjectRenderInputs(built.pinnedInputs));
1479
+ yield* renderValidation(() => throwIfAborted(context.abortSignal));
1480
+ // The atomic owner masks before this final fence. A successful fence
1481
+ // starts the recoverable precommit/output/receipt sequence.
1482
+ yield* renderBoundary("publication", () => workflow.beforePublication());
1483
+ const candidate = yield* renderValidation(() => {
1484
+ const output = ProjectRenderOutputReferenceSchema.parse({
1485
+ ...outputIntegrity,
1486
+ kind: "slopcamera.project-render-output-reference",
1487
+ path: input.output.path,
1488
+ planArtifactSha256: input.plan.artifact.sha256,
1489
+ projectId: input.plan.projectId,
1490
+ revisionSha256: input.plan.revisionSha256,
1491
+ schemaVersion: 1,
1492
+ });
1493
+ return createPublicationPrecommit(createProjectRenderReceiptV2({
1494
+ createdAt: application.clock.now().toISOString(),
1495
+ inputSha256: canonicalJsonSha256(input),
1496
+ invocation: built.invocation,
1497
+ output, plan: input.plan, run: execution,
1498
+ syncPolicy: input.syncPolicy, toolchain: input.binding,
1499
+ }));
1500
+ });
1501
+ const precommit = yield* renderBoundary("publication", () => publishPublicationPrecommit(workspace, candidate));
1502
+ return yield* renderValidation(() => {
1503
+ assertReceiptMatchesExactRender({
1504
+ execution, input, outputAbsolute, receipt: precommit.receipt,
1505
+ renderPlanSha256: exactPlan.document.renderPlanSha256,
1506
+ });
1507
+ if (precommit.receipt.output.bytes !== outputIntegrity.bytes || precommit.receipt.output.sha256 !== outputIntegrity.sha256) {
1508
+ throw new ApplicationError("conflict", "Existing project render precommit describes different rendered bytes.");
1509
+ }
1510
+ return { contents: `${canonicalJson(precommit.receipt)}\n`, output: precommit.receipt.output, precommit };
1511
+ });
1512
+ }),
1513
+ companion: {
1514
+ finalPath: receiptAbsolute,
1515
+ publish: (prepared, outputIntegrity) => Effect.gen(function*() {
1516
+ // No new cancellation/fence after the public link. Recovery relies
1517
+ // on this callback or reconciliation finalizing the exact receipt.
1518
+ yield* renderValidation(() => {
1519
+ if (prepared.output.bytes !== outputIntegrity.bytes || prepared.output.sha256 !== outputIntegrity.sha256) {
1520
+ throw new ApplicationError("internal", "Project render output committed without its exact prepared receipt.");
1521
+ }
1522
+ });
1523
+ yield* renderBoundary("publication", () => publishReceiptNoReplace({
1524
+ application, contents: prepared.contents, execution, input,
1525
+ }));
1526
+ }),
1527
+ },
1528
+ });
1529
+ const prepared = rendered.prepared;
1530
+ const published = yield* renderBoundary("publication", () => readReceipt(application, input, execution));
1531
+ yield* renderValidation(() => {
1532
+ if (published.contents !== prepared.contents) {
1533
+ throw new ApplicationError("conflict", "Published project render receipt contains different bytes.");
1534
+ }
1535
+ });
1536
+ if (isCandidateProjectRenderInput(input)) {
1537
+ yield* renderBoundary("publication", () => publishCandidateRenderReuseRecord({
1538
+ exactPlan, input, outputAbsolute, receipt: published.receipt, receiptContents: published.contents,
1539
+ }));
1540
+ }
1541
+ return yield* renderValidation(() => ProjectRenderOutputSchema.parse({
1542
+ output: prepared.output,
1543
+ receipt: receiptReference(prepared.contents, receiptRelative, prepared.output,
1544
+ prepared.precommit.receipt.receiptSha256, workflow.nodePlanSha256),
1545
+ }));
1546
+ }));
1547
+ });
1548
+ }
1549
+
1550
+ function projectRenderEffect(
1551
+ context: OperationExecutionContext,
1552
+ input: unknown,
1553
+ ): Effect.Effect<ProjectRenderOutput, OperationEffectFailure> {
1554
+ return projectRenderProgram(context, input).pipe(
1555
+ Effect.provideService(ProjectRenderServices, context.application),
1556
+ Effect.provide(AtomicRenderPlatformLive),
1557
+ );
1558
+ }
1559
+
1560
+ const projectRenderLifecycle = {
1561
+ kind: "local-artifact",
1562
+ execute: async (context, input) => await runStandaloneOperation(projectRenderEffect(context, input)),
1563
+ executeEffect: projectRenderEffect,
1564
+ } satisfies OperationDefinition<"render.project", unknown, ProjectRenderOutput>["lifecycle"];
1565
+
1566
+ export const projectRenderOperationDefinition = {
1567
+ inputSchema: ProjectRenderInputSchema,
1568
+ inputSchemaId: "slopcamera.operation.render.project.input/v1",
1569
+ kind: "render.project",
1570
+ lifecycle: projectRenderLifecycle,
1571
+ outputSchema: ProjectRenderOutputSchema,
1572
+ outputSchemaId: "slopcamera.operation.render.project.output/v1",
1573
+ policy: {
1574
+ cache: "exact-run",
1575
+ cancellable: true,
1576
+ effect: "local-derived-write",
1577
+ maxDurationMs: PROJECT_RENDER_MAX_DURATION_MS,
1578
+ maxFanOut: 0,
1579
+ maxInputBytes: 32 * 1_024,
1580
+ maxOutputBytes: 16 * 1_024,
1581
+ preparation: [],
1582
+ resources: [
1583
+ { amount: 1, resource: "cpu" },
1584
+ { amount: 1, resource: "local-io" },
1585
+ { amount: 1, resource: "ffmpeg" },
1586
+ { amount: 1, resource: "output-publication" },
1587
+ ],
1588
+ resume: "verified-receipt",
1589
+ },
1590
+ receiptReference: output => output.receipt.path,
1591
+ summarize: output => ({
1592
+ fields: {
1593
+ bytes: output.output.bytes,
1594
+ outputPath: output.output.path,
1595
+ outputSha256: output.output.sha256,
1596
+ projectId: output.output.projectId,
1597
+ receiptPath: output.receipt.path,
1598
+ revisionSha256: output.output.revisionSha256,
1599
+ },
1600
+ kind: "render.project",
1601
+ }),
1602
+ version: 1,
1603
+ } satisfies OperationDefinition<
1604
+ "render.project",
1605
+ ProjectRenderInput,
1606
+ ProjectRenderOutput
1607
+ >;
1608
+
1609
+ export const projectRenderOperationDefinitionV2 = {
1610
+ ...projectRenderOperationDefinition,
1611
+ inputSchema: ProjectRenderInputSchemaV2,
1612
+ inputSchemaId: "slopcamera.operation.render.project.input/v2",
1613
+ lifecycle: projectRenderLifecycle,
1614
+ outputSchemaId: "slopcamera.operation.render.project.output/v2",
1615
+ policy: {
1616
+ ...projectRenderOperationDefinition.policy,
1617
+ resources: [
1618
+ { amount: 1, resource: "cpu" },
1619
+ { amount: 1, resource: "local-io" },
1620
+ { amount: 1, resource: "ffmpeg" },
1621
+ { amount: 1, resource: "output-publication" },
1622
+ // FFmpeg owns one decoder pool per distinct input in addition to its
1623
+ // filter and encoder pools. Until the host can bind one execution-wide
1624
+ // thread budget into both the recipe and scheduler claim, this explicit
1625
+ // capacity-one resource serializes v2 project renders independently of
1626
+ // the host's ordinary CPU and FFmpeg ceilings.
1627
+ { amount: 1, resource: "project-render" },
1628
+ ],
1629
+ },
1630
+ version: 2,
1631
+ } satisfies OperationDefinition<
1632
+ "render.project",
1633
+ ProjectRenderInputV2,
1634
+ ProjectRenderOutput
1635
+ >;
1636
+
1637
+ export const projectRenderOperationDefinitionV3 = {
1638
+ ...projectRenderOperationDefinitionV2,
1639
+ inputSchema: ProjectRenderInputSchemaV3,
1640
+ inputSchemaId: "slopcamera.operation.render.project.input/v3",
1641
+ lifecycle: projectRenderLifecycle,
1642
+ outputSchemaId: "slopcamera.operation.render.project.output/v3",
1643
+ version: 3,
1644
+ } satisfies OperationDefinition<
1645
+ "render.project",
1646
+ ProjectRenderInputV3,
1647
+ ProjectRenderOutput
1648
+ >;
1649
+
1650
+ // V4 is an explicit spatial authority envelope. Existing V1–3 input, output,
1651
+ // receipt and recovery paths retain their original schemas and numeric clocks.
1652
+ const captureSpatialProjectRender = (input: unknown) => createBoundedJsonValueSnapshot(input, 16 * 1024 * 1024, "spatial project render input", { maximumDepth: 64, maximumValues: 1_000_000 }).value;
1653
+ const spatialInputShape = ProjectRenderInputSchemaV2.extend({ spatial: ProjectRenderSpatialBindingV1Schema }).superRefine((input, context) => {
1654
+ const projection = input.spatial.projection;
1655
+ const target = resolveProjectRenderTarget(input.target);
1656
+ if (input.output.maximumBytes > 256 * 1024 * 1024 || input.plan.projectId !== projection.source.projectId
1657
+ || input.plan.revisionSha256 !== projection.derivedV1RevisionSha256 || input.plan.planSha256 !== projection.compositionPlanSha256
1658
+ || target.frameRate !== projection.legacyFrameRateAdapter.value || target.pixelWidth !== projection.output.pixelWidth || target.pixelHeight !== projection.output.pixelHeight) {
1659
+ context.addIssue({ code: "custom", message: "Spatial project rendering requires an exact projection-bound plan/target and at most 256 MiB of output." });
1660
+ }
1661
+ });
1662
+ export const ProjectRenderInputSchemaV4 = z.preprocess(captureSpatialProjectRender, spatialInputShape);
1663
+ export type ProjectRenderInputV4 = z.infer<typeof ProjectRenderInputSchemaV4>;
1664
+ const SPATIAL_RENDER_PRECOMMIT = "spatial-project-render-precommit.v1.json";
1665
+ export const SPATIAL_PROJECT_RENDER_INPUT_LIMITS = Object.freeze({ materializedBytes: 1024 * 1024 * 1024, sceneDocumentBytes: 32 * 1024 * 1024, receiptBytes: 16 * 1024 * 1024 });
1666
+
1667
+ function equalSpatialRender(left: unknown, right: unknown, message: string): void {
1668
+ if (canonicalJson(left) !== canonicalJson(right)) throw new ApplicationError("conflict", message);
1669
+ }
1670
+ async function loadSpatialRenderJson(application: ApplicationContext, absolutePath: string, maximumBytes: number, signal: AbortSignal,
1671
+ expected?: { readonly sha256: string; readonly bytes: number }) {
1672
+ const request = { path: relative(application.paths.repositoryRoot, absolutePath), ...(expected === undefined ? {} : { bytes: expected.bytes, sha256: expected.sha256 }) };
1673
+ const loaded = await loadRepositoryMedia(application, request, signal, maximumBytes);
1674
+ let value: unknown;
1675
+ try { value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(loaded.data)); }
1676
+ catch { throw new ApplicationError("invalid-data", "Spatial compositor artifact is not valid UTF-8 JSON."); }
1677
+ return { value, artifact: loaded.artifact, text: new TextDecoder().decode(loaded.data) };
1678
+ }
1679
+
1680
+ /** Re-derive from immutable source authority, never from mutable V1 project files. */
1681
+ async function assertSpatialProjectRenderSource(application: ApplicationContext, input: ProjectRenderInputV4, signal: AbortSignal) {
1682
+ const exactPlan = await loadExactProjectRenderPlan(application, input.plan);
1683
+ const spatial = input.spatial, projection = spatial.projection, head = projection.source;
1684
+ const original = await loadSpatialRenderJson(application, join(exactPlan.directory, head.revision.path), SPATIAL_PROJECT_LIMITS.documentBytes, signal, head.revision);
1685
+ const revision = SpatialProjectRevisionV2Schema.parse(original.value);
1686
+ if (revision.projectId !== input.plan.projectId || revision.transactionId !== head.transactionId
1687
+ || spatialProjectRevisionSha256(revision) !== head.projectRevisionSha256 || original.text !== spatialProjectDocumentText(revision)) {
1688
+ throw new ApplicationError("conflict", "Spatial projection source revision differs from its exact retained authority.");
1689
+ }
1690
+ if (revision.scenes.reduce((sum, scene) => sum + scene.artifact.bytes, 0) > SPATIAL_PROJECT_RENDER_INPUT_LIMITS.sceneDocumentBytes) {
1691
+ throw new ApplicationError("invalid-data", "Spatial compositor retained scene documents exceed their total byte budget.");
1692
+ }
1693
+ const materializedPayloads = new Map<string, number>();
1694
+ for (const { materialized } of projection.shots) {
1695
+ const prior = materializedPayloads.get(materialized.artifact.sha256);
1696
+ if (prior !== undefined && prior !== materialized.artifact.bytes) throw new ApplicationError("conflict", "Materialized shots disagree on one content-addressed payload size.");
1697
+ materializedPayloads.set(materialized.artifact.sha256, materialized.artifact.bytes);
1698
+ }
1699
+ if ([...materializedPayloads.values()].reduce((sum, bytes) => sum + bytes, 0) > SPATIAL_PROJECT_RENDER_INPUT_LIMITS.materializedBytes) {
1700
+ throw new ApplicationError("invalid-data", "Spatial compositor materialized source videos exceed their total byte budget.");
1701
+ }
1702
+ const scenes = [];
1703
+ for (const source of revision.scenes) {
1704
+ const loaded = await loadSpatialRenderJson(application, join(exactPlan.directory, source.artifact.path), 2_097_153, signal, source.artifact);
1705
+ const document = parseSpatialScene(loaded.value);
1706
+ if (spatialSceneSha256(document) !== source.sceneSha256 || loaded.text !== spatialProjectDocumentText(document)) throw new ApplicationError("conflict", "Spatial source document differs from its retained scene reference.");
1707
+ scenes.push({ document, sceneSha256: source.sceneSha256 });
1708
+ }
1709
+ const recomputed = createSpatialRenderProjection({
1710
+ snapshot: { version: 2, head, revision, headText: spatialProjectDocumentText(head), basis: { version: 2, sha256: head.projectRevisionSha256 }, contents: spatialProjectContents(revision, scenes) },
1711
+ materializedShots: projection.shots.map(item => item.materialized), policy: projection.policy, output: projection.output,
1712
+ });
1713
+ equalSpatialRender(recomputed.projection, projection, "Spatial render projection cannot be derived from its exact source revision.");
1714
+ equalSpatialRender(recomputed.projectionSha256, spatial.projectionSha256, "Spatial projection digest differs from its recomputed identity.");
1715
+ equalSpatialRender(recomputed.renderPlan, exactPlan.document.plan, "Spatial composition plan differs from the exact projection output.");
1716
+ const derived = await loadSpatialRenderJson(application, join(exactPlan.directory, projection.derivedV1Revision.path), SPATIAL_PROJECT_LIMITS.documentBytes, signal, projection.derivedV1Revision);
1717
+ const derivedRevision = ProjectEditRevisionDocumentSchema.parse(derived.value);
1718
+ equalSpatialRender(derivedRevision, recomputed.revision, "Retained V1 composition revision differs from the spatial derivation.");
1719
+ if (derived.text !== spatialProjectDocumentText(derivedRevision) || exactPlan.document.projectSha256 !== derivedRevision.projectSha256
1720
+ || exactPlan.document.projectEditPlanSha256 !== derivedRevision.projectEditPlanSha256 || exactPlan.document.revisionSha256 !== derivedRevision.revisionSha256) {
1721
+ throw new ApplicationError("conflict", "Spatial plan document does not bind its exact derived revision.");
1722
+ }
1723
+ equalSpatialRender(createSpatialCompositorCadence({ projection, projectionSha256: spatial.projectionSha256, plan: exactPlan.document.plan }), spatial.cadence, "Spatial compositor cadence differs from the exact projection and plan.");
1724
+ const receiptCache = new Map<string, z.infer<typeof SpatialRenderReceiptSchema>>(), verifiedPayloads = new Set<string>();
1725
+ let receiptBytes = 0;
1726
+ for (const { shot, materialized } of projection.shots) {
1727
+ let receipt = receiptCache.get(materialized.receiptSha256);
1728
+ if (receipt === undefined) {
1729
+ const loaded = await loadSpatialRenderJson(application, join(exactPlan.directory, `spatial/receipts/${materialized.receiptSha256}.json`), 1024 * 1024, signal);
1730
+ receiptBytes += loaded.artifact.bytes;
1731
+ if (loaded.artifact.sha256 !== materialized.receiptSha256) throw new ApplicationError("conflict", "Materialized shot receipt failed its physical content address.");
1732
+ if (receiptBytes > SPATIAL_PROJECT_RENDER_INPUT_LIMITS.receiptBytes) throw new ApplicationError("invalid-data", "Spatial compositor source receipts exceed their total byte budget.");
1733
+ receipt = SpatialRenderReceiptSchema.parse(loaded.value);
1734
+ receiptCache.set(materialized.receiptSha256, receipt);
1735
+ }
1736
+ const source = scenes.find(scene => scene.sceneSha256 === shot.sceneSha256);
1737
+ if (source === undefined) throw new ApplicationError("conflict", "Materialized shot source is absent.");
1738
+ const requested = planSpatialRender(source.document, spatialShotRenderRequest(shot, projection.output.frameRate, projection.output.executionProfile));
1739
+ equalSpatialRender(receipt.request, requested.request, "Materialized shot receipt uses a different camera, overrides or exact source clock.");
1740
+ equalSpatialRender(receipt.requestSha256, requested.requestSha256, "Materialized shot request digest differs from its exact shot.");
1741
+ equalSpatialRender(receipt.samples.map(sample => sample.sample), requested.samples, "Materialized shot receipt samples differ from its exact source-clock mapping.");
1742
+ const encoded = receipt.render.encodedEvidence;
1743
+ if (receipt.sceneSha256 !== shot.sceneSha256 || receipt.render.kind !== "video" || receipt.render.width !== materialized.pixelWidth || receipt.render.height !== materialized.pixelHeight
1744
+ || encoded === undefined || encoded.frameCount !== materialized.frameCount || encoded.codec !== materialized.codec || encoded.container !== materialized.container
1745
+ || encoded.streamIndex !== materialized.streamIndex || encoded.alpha !== materialized.alpha || encoded.colorSpace !== materialized.colorSpace
1746
+ || receipt.output.sha256 !== materialized.artifact.sha256 || receipt.output.bytes !== materialized.artifact.bytes) {
1747
+ throw new ApplicationError("conflict", "Materialized shot video differs from its qualified source render receipt.");
1748
+ }
1749
+ equalSpatialRender(encoded.frameRate, materialized.frameRate, "Materialized shot encoded rate differs from its retained receipt.");
1750
+ if (!verifiedPayloads.has(materialized.artifact.sha256)) {
1751
+ await resolveVerifiedProjectMedia({ expected: materialized.artifact, path: materialized.artifact.path, label: "Materialized spatial shot", repositoryRoot: exactPlan.directory });
1752
+ verifiedPayloads.add(materialized.artifact.sha256);
1753
+ }
1754
+ }
1755
+ return exactPlan;
1756
+ }
1757
+
1758
+ export async function bindProjectRenderInputV4(application: ApplicationContext, resolvedInput: unknown, signal = new AbortController().signal): Promise<ProjectRenderInputV4> {
1759
+ // A V4 author already has a projection/cadence. Only the host toolchain may be filled here.
1760
+ const requested = z.preprocess(captureSpatialProjectRender, ProjectRenderUnboundInputSchemaV2.extend({ spatial: ProjectRenderSpatialBindingV1Schema })).parse(resolvedInput);
1761
+ const binding = await bindProjectRenderToolchain(application);
1762
+ if (requested.binding !== undefined) equalSpatialRender(requested.binding, binding, "Spatial project compositor toolchain changed after planning.");
1763
+ const input = ProjectRenderInputSchemaV4.parse({ ...requested, binding });
1764
+ await assertSpatialProjectRenderSource(application, input, signal);
1765
+ return input;
1766
+ }
1767
+
1768
+ function spatialReceiptPath(nodePlanSha256: string): string { return `renders/receipts/${nodePlanSha256}.spatial.json`; }
1769
+ function spatialProjectRenderOutput(receipt: ProjectSpatialRenderReceiptV1): ProjectRenderOutputV4 {
1770
+ const text = `${canonicalJson(receipt)}\n`, execution = receipt.execution;
1771
+ return ProjectRenderOutputSchemaV4.parse({ output: execution.output, receipt: {
1772
+ kind: "slopcamera.spatial-project-render-receipt-reference", schemaVersion: 1,
1773
+ bytes: Buffer.byteLength(text), sha256: sha256Hex(text), receiptSha256: receipt.receiptSha256,
1774
+ path: spatialReceiptPath(execution.run.nodePlanSha256), projectId: execution.projectId, revisionSha256: execution.revisionSha256,
1775
+ projectRevisionSha256: receipt.spatial.projection.source.projectRevisionSha256, projectionSha256: receipt.spatial.projectionSha256,
1776
+ cadenceSha256: receipt.spatial.cadence.cadenceSha256, outputSha256: execution.output.sha256, nodePlanSha256: execution.run.nodePlanSha256,
1777
+ } });
1778
+ }
1779
+ function assertSpatialReceiptMatches(input: ProjectRenderInputV4, receipt: ProjectSpatialRenderReceiptV1, execution: ProjectRenderExecutionIdentity, outputAbsolute: string): void {
1780
+ assertReceiptMatchesExactRender({ input, receipt: receipt.execution, execution, outputAbsolute, renderPlanSha256: input.plan.renderPlanSha256 });
1781
+ equalSpatialRender(input.spatial, receipt.spatial, "Spatial compositor receipt belongs to a different source authority or cadence.");
1782
+ }
1783
+ async function readSpatialProjectRenderReceipt(application: ApplicationContext, absolutePath: string, signal: AbortSignal): Promise<ProjectSpatialRenderReceiptV1> {
1784
+ const read = await loadSpatialRenderJson(application, absolutePath, PROJECT_SPATIAL_RECEIPT_MAXIMUM_BYTES, signal);
1785
+ const receipt = ProjectSpatialRenderReceiptV1Schema.parse(read.value);
1786
+ if (read.text !== `${canonicalJson(receipt)}\n`) throw new ApplicationError("conflict", "Spatial compositor receipt is not its exact canonical document.");
1787
+ return receipt;
1788
+ }
1789
+ async function publishSpatialProjectRenderReceipt(application: ApplicationContext, root: string, path: string, receipt: ProjectSpatialRenderReceiptV1, beforePublication?: () => Promise<void>): Promise<void> {
1790
+ if (!isWithin(application.paths.repositoryRoot, root)) throw new ApplicationError("unsafe-path", "Spatial receipt root is outside the application repository.");
1791
+ const text = `${canonicalJson(receipt)}\n`;
1792
+ if (Buffer.byteLength(text) > PROJECT_SPATIAL_RECEIPT_MAXIMUM_BYTES) throw new ApplicationError("invalid-data", "Spatial compositor receipt exceeds its byte budget.");
1793
+ const fs = createNodeBundleFileSystem(root);
1794
+ if (fs.writeTextNoReplace === undefined) throw new ApplicationError("internal", "Spatial render storage lacks immutable publication.");
1795
+ await fs.writeTextNoReplace(path, text, beforePublication);
1796
+ const published = await fs.readText(path, PROJECT_SPATIAL_RECEIPT_MAXIMUM_BYTES);
1797
+ if (published !== text) throw new ApplicationError("conflict", "Spatial compositor publication target contains different receipt bytes.");
1798
+ await createNodeSpatialDurability(root).syncExactFile(path, { bytes: Buffer.byteLength(text), sha256: sha256Hex(text) });
1799
+ }
1800
+
1801
+ function projectRenderProgramV4(context: OperationExecutionContext, value: unknown): Effect.Effect<ProjectRenderOutputV4, OperationEffectFailure, ProjectRenderServices | AtomicRenderPlatform> {
1802
+ return Effect.gen(function*() {
1803
+ const application = yield* ProjectRenderServices;
1804
+ const workflow = yield* renderValidation(() => {
1805
+ if (context.workflow === undefined) throw new ApplicationError("conflict", "Spatial project rendering requires an exact run-private execution context.");
1806
+ throwIfAborted(context.abortSignal); return context.workflow;
1807
+ });
1808
+ const input = yield* renderBoundary("input", () => bindProjectRenderInputV4(application, value, context.abortSignal));
1809
+ const exactPlan = yield* renderBoundary("project", () => loadExactProjectRenderPlan(application, input.plan));
1810
+ yield* renderValidation(() => {
1811
+ assertProjectRenderTarget(input, exactPlan.document.plan);
1812
+ if (input.syncPolicy === "require-verified" && exactPlan.document.plan.warnings.some(warning => warning.code === "unverified-sync")) throw new ApplicationError("conflict", "Spatial composition includes unverified placement synchronization.");
1813
+ });
1814
+ const execution = { nodeKey: workflow.nodeKey, nodePlanSha256: workflow.nodePlanSha256, runId: workflow.runId };
1815
+ const workspace = yield* renderBoundary("workspace", () => exactRunWorkflowWorkspace(application, execution, workflow.workspaceDirectory));
1816
+ const runner = workflowRunner(new ExactCapabilityApplicationRunner(application.runner, [input.binding.ffmpeg, input.binding.ffprobe,
1817
+ ...(input.binding.rsvgConvert === null ? [] : [input.binding.rsvgConvert])], application.paths.privateRoot), workspace);
1818
+ const outputAbsolute = join(exactPlan.directory, input.output.path), receiptRelative = spatialReceiptPath(workflow.nodePlanSha256), receiptAbsolute = join(exactPlan.directory, receiptRelative);
1819
+ yield* renderBoundary("workspace", async () => {
1820
+ const outputParent = await ensurePhysicalPrivateDirectoryWithin(exactPlan.directory, dirname(input.output.path));
1821
+ if (dirname(outputAbsolute) !== outputParent) throw new ApplicationError("unsafe-path", "Spatial compositor output parent changed.");
1822
+ await ensurePhysicalPrivateDirectoryWithin(exactPlan.directory, dirname(receiptRelative));
1823
+ });
1824
+ return yield* withOutputPublicationLeaseEffect(application, { outputPath: input.output.path, projectId: input.plan.projectId }, Effect.gen(function*() {
1825
+ yield* renderBoundary("publication", () => requireFreshPublicationTargets(outputAbsolute, receiptAbsolute));
1826
+ const built = yield* renderBoundary("media", () => buildProjectFfmpegInvocation(exactPlan.document.plan, {
1827
+ ffmpeg: input.binding.ffmpeg.executablePath, ffprobe: input.binding.ffprobe.executablePath,
1828
+ outputPath: outputAbsolute, projectDirectory: exactPlan.directory, renderTier: input.target.tier, repositoryRoot: application.paths.repositoryRoot,
1829
+ ...(input.binding.rsvgConvert === null ? {} : { rsvgConvert: input.binding.rsvgConvert.executablePath, rsvgConvertVersion: input.binding.rsvgConvert.version }),
1830
+ runner, workspaceDirectory: workspace, spatialCadence: input.spatial.cadence,
1831
+ }));
1832
+ const publicationFence = async () => { await workflow.beforePublication(); await application.hostResourceLease?.assertOwned(); throwIfAborted(context.abortSignal); };
1833
+ let preparedIntegrity: { readonly bytes: number; readonly sha256: string } | undefined;
1834
+ const rendered = yield* executeAtomicRenderEffect({ abortSignal: context.abortSignal, argv: built.argv, failureLabel: "FFmpeg spatial project render failed",
1835
+ finalOutputPath: outputAbsolute, maximumOutputBytes: input.output.maximumBytes, requireFreshOutput: true, runner, stagingDirectory: workspace, timeoutMs: PROJECT_RENDER_MAX_DURATION_MS,
1836
+ beforeNativePublish: async stagedPath => {
1837
+ if (preparedIntegrity === undefined) throw new ApplicationError("conflict", "Spatial publication requires its exact verified precommit integrity.");
1838
+ await resolveVerifiedProjectMedia({ expected: preparedIntegrity, path: relative(application.paths.repositoryRoot, stagedPath), label: "Spatial output at final publication fence", repositoryRoot: application.paths.repositoryRoot });
1839
+ await reverifyProjectRenderInputs(built.pinnedInputs);
1840
+ await publicationFence();
1841
+ },
1842
+ }, {
1843
+ prepare: (integrity, stagedPath) => Effect.gen(function*() {
1844
+ const timing = yield* renderBoundary("media", () => verifySpatialCompositorOutput({ cadence: input.spatial.cadence, outputPath: stagedPath,
1845
+ expectedVideo: { pixelWidth: exactPlan.document.plan.output.pixelWidth, pixelHeight: exactPlan.document.plan.output.pixelHeight, pixelFormat: resolveProjectRenderEncoderRecipe(input.target.tier).video.pixelFormat },
1846
+ maximumBytes: input.output.maximumBytes, ffprobe: input.binding.ffprobe.executablePath, runner, signal: context.abortSignal }));
1847
+ yield* renderBoundary("media", () => resolveVerifiedProjectMedia({ expected: integrity, path: relative(application.paths.repositoryRoot, stagedPath),
1848
+ label: "Spatial output after timing verification", repositoryRoot: application.paths.repositoryRoot }));
1849
+ yield* renderBoundary("publication", () => reverifyProjectRenderInputs(built.pinnedInputs));
1850
+ yield* renderBoundary("publication", publicationFence);
1851
+ const receipt = yield* renderValidation(() => createProjectSpatialRenderReceipt({
1852
+ execution: createProjectRenderReceiptV2({ createdAt: application.clock.now().toISOString(), inputSha256: canonicalJsonSha256(input), invocation: built.invocation,
1853
+ output: ProjectRenderOutputReferenceSchema.parse({ ...integrity, kind: "slopcamera.project-render-output-reference", schemaVersion: 1, path: input.output.path,
1854
+ planArtifactSha256: input.plan.artifact.sha256, projectId: input.plan.projectId, revisionSha256: input.plan.revisionSha256 }),
1855
+ plan: input.plan, run: execution, syncPolicy: input.syncPolicy, toolchain: input.binding }), spatial: input.spatial, timing,
1856
+ }));
1857
+ yield* renderValidation(() => assertSpatialReceiptMatches(input, receipt, execution, outputAbsolute));
1858
+ yield* renderBoundary("publication", () => publishSpatialProjectRenderReceipt(application, workspace, SPATIAL_RENDER_PRECOMMIT, receipt, publicationFence));
1859
+ preparedIntegrity = integrity;
1860
+ return receipt;
1861
+ }),
1862
+ companion: { finalPath: receiptAbsolute, publish: (receipt, integrity) => Effect.gen(function*() {
1863
+ yield* renderValidation(() => {
1864
+ if (receipt.execution.output.bytes !== integrity.bytes || receipt.execution.output.sha256 !== integrity.sha256) throw new ApplicationError("conflict", "Published spatial compositor bytes differ from the exact precommit.");
1865
+ });
1866
+ // After the public link, finish the bounded durable companion even if cancellation arrives.
1867
+ yield* renderBoundary("publication", () => createNodeSpatialDurability(exactPlan.directory).syncExactFile(input.output.path, integrity));
1868
+ yield* renderBoundary("publication", () => publishSpatialProjectRenderReceipt(application, exactPlan.directory, receiptRelative, receipt));
1869
+ }) },
1870
+ });
1871
+ return yield* renderValidation(() => spatialProjectRenderOutput(rendered.prepared));
1872
+ }));
1873
+ });
1874
+ }
1875
+
1876
+ export function projectRenderEffectV4(context: OperationExecutionContext, input: unknown): Effect.Effect<ProjectRenderOutputV4, OperationEffectFailure> {
1877
+ return projectRenderProgramV4(context, input).pipe(Effect.provideService(ProjectRenderServices, context.application), Effect.provide(AtomicRenderPlatformLive));
1878
+ }
1879
+ export const projectRenderOperationDefinitionV4 = {
1880
+ ...projectRenderOperationDefinitionV2, version: 4,
1881
+ inputSchema: ProjectRenderInputSchemaV4, inputSchemaId: "slopcamera.operation.render.project.input/v4",
1882
+ outputSchema: ProjectRenderOutputSchemaV4, outputSchemaId: "slopcamera.operation.render.project.output/v4",
1883
+ lifecycle: { kind: "local-artifact", execute: async (context, input) => await runStandaloneOperation(projectRenderEffectV4(context, input)), executeEffect: projectRenderEffectV4 },
1884
+ policy: { ...projectRenderOperationDefinitionV2.policy, maxInputBytes: 16 * 1024 * 1024, maxOutputBytes: 32 * 1024 },
1885
+ receiptReference: output => output.receipt.path,
1886
+ summarize: output => ({ kind: "render.project", fields: { outputSha256: output.output.sha256, projectRevisionSha256: output.receipt.projectRevisionSha256,
1887
+ projectionSha256: output.receipt.projectionSha256, cadenceSha256: output.receipt.cadenceSha256 } }),
1888
+ } satisfies OperationDefinition<"render.project", ProjectRenderInputV4, ProjectRenderOutputV4>;
1889
+
1890
+ export async function reconcileProjectRenderV4(application: ApplicationContext, value: unknown, execution: ProjectRenderExecutionIdentity, control: ProjectRenderReconciliationControl): Promise<
1891
+ { readonly kind: "completed"; readonly output: ProjectRenderOutputV4 } | { readonly kind: "retry" } | { readonly kind: "conflict"; readonly message: string }
1892
+ > {
1893
+ try {
1894
+ const input = ProjectRenderInputSchemaV4.parse(value);
1895
+ const exactPlan = await assertSpatialProjectRenderSource(application, input, control.abortSignal);
1896
+ const outputAbsolute = join(exactPlan.directory, input.output.path), receiptRelative = spatialReceiptPath(execution.nodePlanSha256), receiptAbsolute = join(exactPlan.directory, receiptRelative);
1897
+ const workspace = await expectedWorkflowWorkspaceIfPresent(application, execution);
1898
+ const precommitPath = workspace === null ? undefined : join(workspace, SPATIAL_RENDER_PRECOMMIT);
1899
+ const [hasOutput, hasReceipt, hasPrecommit] = await Promise.all([pathExists(outputAbsolute), pathExists(receiptAbsolute), precommitPath === undefined ? false : pathExists(precommitPath)]);
1900
+ if (!hasOutput && !hasReceipt && !hasPrecommit) return { kind: "retry" };
1901
+ if (!hasOutput) return { kind: "conflict", message: "Spatial compositor publication has receipt/precommit evidence without its output; reconcile this attempt before retrying." };
1902
+ if (!hasReceipt && !hasPrecommit) return { kind: "conflict", message: "Spatial compositor output lacks its exact durable publication precommit." };
1903
+ const receipt = await readSpatialProjectRenderReceipt(application, hasReceipt ? receiptAbsolute : precommitPath!, control.abortSignal);
1904
+ if (hasReceipt && hasPrecommit) equalSpatialRender(receipt, await readSpatialProjectRenderReceipt(application, precommitPath!, control.abortSignal), "Spatial public receipt differs from its exact publication precommit.");
1905
+ assertSpatialReceiptMatches(input, receipt, execution, outputAbsolute);
1906
+ await resolveVerifiedProjectMedia({ expected: receipt.execution.output, path: input.output.path, label: "Reconciled spatial compositor output", repositoryRoot: exactPlan.directory });
1907
+ const runner = new ExactCapabilityApplicationRunner(application.runner, [input.binding.ffprobe], application.paths.privateRoot);
1908
+ const timing = await verifySpatialCompositorOutput({ cadence: input.spatial.cadence, outputPath: outputAbsolute, maximumBytes: input.output.maximumBytes,
1909
+ expectedVideo: { pixelWidth: exactPlan.document.plan.output.pixelWidth, pixelHeight: exactPlan.document.plan.output.pixelHeight, pixelFormat: resolveProjectRenderEncoderRecipe(input.target.tier).video.pixelFormat },
1910
+ ffprobe: input.binding.ffprobe.executablePath, runner, signal: control.abortSignal });
1911
+ equalSpatialRender(timing, receipt.timing, "Retained spatial compositor timing differs from the actual encoded bytes.");
1912
+ const publicationFence = async () => { await control.beforePublication(); await application.hostResourceLease?.assertOwned(); throwIfAborted(control.abortSignal); };
1913
+ await publicationFence();
1914
+ await createNodeSpatialDurability(exactPlan.directory).syncExactFile(input.output.path, receipt.execution.output);
1915
+ if (!hasReceipt) await publishSpatialProjectRenderReceipt(application, exactPlan.directory, receiptRelative, receipt, publicationFence);
1916
+ else await createNodeSpatialDurability(exactPlan.directory).syncExactFile(receiptRelative, { bytes: Buffer.byteLength(`${canonicalJson(receipt)}\n`), sha256: sha256Hex(`${canonicalJson(receipt)}\n`) });
1917
+ return { kind: "completed", output: spatialProjectRenderOutput(receipt) };
1918
+ } catch (error) { return { kind: "conflict", message: errorMessage(error) }; }
1919
+ }
1920
+
1921
+ export { ProjectRenderOutputSchemaV4 };
1922
+ export type { ProjectRenderOutputV4 };