@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.
- package/LICENSE +21 -0
- package/NOTICE.md +60 -0
- package/PRIVACY.md +46 -0
- package/README.md +311 -0
- package/SECURITY.md +21 -0
- package/apps/desktop/README.md +966 -0
- package/apps/desktop/analysis/protocol.ts +223 -0
- package/apps/desktop/application/capability-binding.ts +594 -0
- package/apps/desktop/application/context.ts +97 -0
- package/apps/desktop/application/creative-iteration.ts +1385 -0
- package/apps/desktop/application/default-registry.ts +146 -0
- package/apps/desktop/application/directing-contract.ts +218 -0
- package/apps/desktop/application/directing-plan.ts +178 -0
- package/apps/desktop/application/errors.ts +18 -0
- package/apps/desktop/application/gateway-port.ts +867 -0
- package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
- package/apps/desktop/application/html-overlay-integrity.ts +337 -0
- package/apps/desktop/application/html-overlay-renderer.ts +45 -0
- package/apps/desktop/application/index.ts +13 -0
- package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
- package/apps/desktop/application/operation-effects.ts +94 -0
- package/apps/desktop/application/operation.ts +224 -0
- package/apps/desktop/application/operations/analysis/faces.ts +244 -0
- package/apps/desktop/application/operations/analysis/music.ts +248 -0
- package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
- package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
- package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
- package/apps/desktop/application/operations/analysis/shared.ts +202 -0
- package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
- package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
- package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
- package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
- package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
- package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
- package/apps/desktop/application/operations/gateway/image.ts +41 -0
- package/apps/desktop/application/operations/gateway/index.ts +17 -0
- package/apps/desktop/application/operations/gateway/shared.ts +122 -0
- package/apps/desktop/application/operations/gateway/speech.ts +41 -0
- package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
- package/apps/desktop/application/operations/gateway/video.ts +41 -0
- package/apps/desktop/application/operations/index.ts +31 -0
- package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
- package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
- package/apps/desktop/application/operations/iteration/select.ts +155 -0
- package/apps/desktop/application/operations/iteration/shared.ts +250 -0
- package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
- package/apps/desktop/application/operations/media/binding.ts +79 -0
- package/apps/desktop/application/operations/media/capabilities.ts +112 -0
- package/apps/desktop/application/operations/media/color-grade.ts +343 -0
- package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
- package/apps/desktop/application/operations/media/index.ts +8 -0
- package/apps/desktop/application/operations/media/ingest.ts +303 -0
- package/apps/desktop/application/operations/media/overlay.ts +729 -0
- package/apps/desktop/application/operations/media/shared.ts +845 -0
- package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
- package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
- package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
- package/apps/desktop/application/operations/project/snapshot.ts +95 -0
- package/apps/desktop/application/operations/recording/index.ts +5 -0
- package/apps/desktop/application/operations/recording/pause.ts +34 -0
- package/apps/desktop/application/operations/recording/resume.ts +34 -0
- package/apps/desktop/application/operations/recording/shared.ts +199 -0
- package/apps/desktop/application/operations/recording/start.ts +169 -0
- package/apps/desktop/application/operations/recording/stop.ts +34 -0
- package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
- package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
- package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
- package/apps/desktop/application/operations/render/project-plan.ts +939 -0
- package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
- package/apps/desktop/application/operations/render/project.ts +1922 -0
- package/apps/desktop/application/operations/shared.ts +67 -0
- package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
- package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
- package/apps/desktop/application/operations/spatial-project.ts +209 -0
- package/apps/desktop/application/operations/spatial-render.ts +415 -0
- package/apps/desktop/application/operations/spatial-scene.ts +97 -0
- package/apps/desktop/application/operations/studio.ts +143 -0
- package/apps/desktop/application/output-publication-lease.ts +163 -0
- package/apps/desktop/application/project-publication-lease.ts +233 -0
- package/apps/desktop/application/project-store.ts +196 -0
- package/apps/desktop/application/receipts.ts +533 -0
- package/apps/desktop/application/registry.ts +182 -0
- package/apps/desktop/application/spatial-assets.ts +675 -0
- package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
- package/apps/desktop/application/spatial-project-authority.ts +35 -0
- package/apps/desktop/application/spatial-project-lease.ts +126 -0
- package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
- package/apps/desktop/application/spatial-project-store.ts +286 -0
- package/apps/desktop/application/spatial-render-projection.ts +201 -0
- package/apps/desktop/application/spatial-render.ts +677 -0
- package/apps/desktop/application/spatial-spz.ts +44 -0
- package/apps/desktop/application/spatial-world-import.ts +135 -0
- package/apps/desktop/application/spatial-world-provenance.ts +46 -0
- package/apps/desktop/application/studio-port.ts +58 -0
- package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
- package/apps/desktop/capture/protocol.ts +689 -0
- package/apps/desktop/cli/analyzer.ts +406 -0
- package/apps/desktop/cli/args.ts +3181 -0
- package/apps/desktop/cli/asset-ingest.ts +747 -0
- package/apps/desktop/cli/atomic-render-effects.ts +210 -0
- package/apps/desktop/cli/atomic-render-platform.ts +205 -0
- package/apps/desktop/cli/atomic-render.ts +50 -0
- package/apps/desktop/cli/audio-analysis.ts +252 -0
- package/apps/desktop/cli/bounded-map.ts +39 -0
- package/apps/desktop/cli/bundle-service.ts +256 -0
- package/apps/desktop/cli/capabilities.ts +212 -0
- package/apps/desktop/cli/capture-bundle.ts +1117 -0
- package/apps/desktop/cli/command-host-resources.ts +251 -0
- package/apps/desktop/cli/commands.ts +6836 -0
- package/apps/desktop/cli/directing-blob.ts +291 -0
- package/apps/desktop/cli/directing-media.ts +517 -0
- package/apps/desktop/cli/directing-quote.ts +89 -0
- package/apps/desktop/cli/directing-service.ts +288 -0
- package/apps/desktop/cli/emoji-assets.ts +538 -0
- package/apps/desktop/cli/errors.ts +29 -0
- package/apps/desktop/cli/face-analysis-service.ts +940 -0
- package/apps/desktop/cli/filter-script.ts +76 -0
- package/apps/desktop/cli/gateway-application-port.ts +1142 -0
- package/apps/desktop/cli/gateway-credential.ts +81 -0
- package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
- package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
- package/apps/desktop/cli/gateway-media-service.ts +3106 -0
- package/apps/desktop/cli/gateway-media-signature.ts +122 -0
- package/apps/desktop/cli/gateway-provider-options.ts +562 -0
- package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
- package/apps/desktop/cli/help.ts +501 -0
- package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
- package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
- package/apps/desktop/cli/io.ts +290 -0
- package/apps/desktop/cli/main.ts +123 -0
- package/apps/desktop/cli/media-effects-service.ts +885 -0
- package/apps/desktop/cli/media-ingest-model.ts +364 -0
- package/apps/desktop/cli/media-ingest-platform.ts +227 -0
- package/apps/desktop/cli/media-ingest-program.ts +200 -0
- package/apps/desktop/cli/media-ingest.ts +30 -0
- package/apps/desktop/cli/music-analysis-service.ts +434 -0
- package/apps/desktop/cli/mutation-lock.ts +530 -0
- package/apps/desktop/cli/native-assets.d.ts +12 -0
- package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
- package/apps/desktop/cli/overlay-playback.ts +177 -0
- package/apps/desktop/cli/paths.ts +265 -0
- package/apps/desktop/cli/portable-surface.ts +242 -0
- package/apps/desktop/cli/project-camera-receipt.ts +112 -0
- package/apps/desktop/cli/project-face-camera.ts +394 -0
- package/apps/desktop/cli/project-inactivity-service.ts +552 -0
- package/apps/desktop/cli/project-media-integrity.ts +121 -0
- package/apps/desktop/cli/project-renderer.ts +1526 -0
- package/apps/desktop/cli/project-service.ts +519 -0
- package/apps/desktop/cli/project-state-transaction.ts +520 -0
- package/apps/desktop/cli/query-limits.ts +1 -0
- package/apps/desktop/cli/recording-controller.ts +1567 -0
- package/apps/desktop/cli/recording-daemon.ts +931 -0
- package/apps/desktop/cli/recording-ref.ts +56 -0
- package/apps/desktop/cli/renamed-environment.ts +6 -0
- package/apps/desktop/cli/renderer.ts +1439 -0
- package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
- package/apps/desktop/cli/scene-analysis-service.ts +879 -0
- package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
- package/apps/desktop/cli/spatial-project-service.ts +90 -0
- package/apps/desktop/cli/spatial-scene-service.ts +105 -0
- package/apps/desktop/cli/spatial-world-service.ts +39 -0
- package/apps/desktop/cli/speech-analysis-service.ts +682 -0
- package/apps/desktop/cli/studio-assemble.ts +58 -0
- package/apps/desktop/cli/studio-command.ts +60 -0
- package/apps/desktop/cli/studio-custody.ts +60 -0
- package/apps/desktop/cli/studio-encode.ts +216 -0
- package/apps/desktop/cli/studio-exr.ts +52 -0
- package/apps/desktop/cli/studio-files.ts +179 -0
- package/apps/desktop/cli/studio-output-validation.ts +162 -0
- package/apps/desktop/cli/studio-process.ts +145 -0
- package/apps/desktop/cli/studio-runtime.ts +77 -0
- package/apps/desktop/cli/studio-scaffold.ts +70 -0
- package/apps/desktop/cli/studio-service.ts +301 -0
- package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
- package/apps/desktop/cli/studio-template-names.ts +3 -0
- package/apps/desktop/cli/studio-workflow.ts +19 -0
- package/apps/desktop/cli/time.ts +25 -0
- package/apps/desktop/cli/workflow-code.ts +584 -0
- package/apps/desktop/cli/workflow-runs.ts +576 -0
- package/apps/desktop/code/advanced.ts +139 -0
- package/apps/desktop/code/application-node-planner.ts +1288 -0
- package/apps/desktop/code/compiler.ts +329 -0
- package/apps/desktop/code/contracts.ts +13 -0
- package/apps/desktop/code/creative-recipes.ts +94 -0
- package/apps/desktop/code/define-workflow.ts +115 -0
- package/apps/desktop/code/editing.ts +284 -0
- package/apps/desktop/code/file-candidate-provenance.ts +420 -0
- package/apps/desktop/code/file-candidate.ts +69 -0
- package/apps/desktop/code/graph-builder.ts +10 -0
- package/apps/desktop/code/host-resource-policy.ts +55 -0
- package/apps/desktop/code/plan-contracts.ts +159 -0
- package/apps/desktop/code/planning.ts +361 -0
- package/apps/desktop/code/public.ts +134 -0
- package/apps/desktop/code/reference-projection.ts +62 -0
- package/apps/desktop/code/run-contracts.ts +466 -0
- package/apps/desktop/code/run-store.ts +2478 -0
- package/apps/desktop/code/runtime-identity.ts +328 -0
- package/apps/desktop/code/scheduler.ts +3067 -0
- package/apps/desktop/code/semantic-builder.ts +1436 -0
- package/apps/desktop/code/source-bundle.ts +1641 -0
- package/apps/desktop/code/source-typecheck.ts +211 -0
- package/apps/desktop/code/testing.ts +79 -0
- package/apps/desktop/code/worker-client.ts +1675 -0
- package/apps/desktop/code/worker-effects.ts +141 -0
- package/apps/desktop/code/worker-entry.ts +435 -0
- package/apps/desktop/code/worker-lease-guardian.ts +179 -0
- package/apps/desktop/code/worker-process-identity.ts +484 -0
- package/apps/desktop/code/worker-protocol.ts +211 -0
- package/apps/desktop/code/workflow-effects.ts +132 -0
- package/apps/desktop/contracts/analysis.ts +949 -0
- package/apps/desktop/contracts/edit.ts +576 -0
- package/apps/desktop/contracts/index.ts +9 -0
- package/apps/desktop/contracts/media-effects.ts +139 -0
- package/apps/desktop/contracts/project-render.ts +644 -0
- package/apps/desktop/contracts/project.ts +740 -0
- package/apps/desktop/contracts/recording.ts +1434 -0
- package/apps/desktop/contracts/render.ts +334 -0
- package/apps/desktop/contracts/runtime.ts +155 -0
- package/apps/desktop/contracts/spatial-compositor.ts +58 -0
- package/apps/desktop/contracts/spatial-project.ts +159 -0
- package/apps/desktop/contracts/spatial-world.ts +48 -0
- package/apps/desktop/contracts/video-effects.ts +120 -0
- package/apps/desktop/core/alignment-apply.ts +400 -0
- package/apps/desktop/core/audio-alignment.ts +466 -0
- package/apps/desktop/core/auto-zoom.ts +209 -0
- package/apps/desktop/core/canonical-json.ts +14 -0
- package/apps/desktop/core/emoji.ts +210 -0
- package/apps/desktop/core/events.ts +409 -0
- package/apps/desktop/core/face-framing.ts +533 -0
- package/apps/desktop/core/face-tracks.ts +326 -0
- package/apps/desktop/core/inactivity.ts +137 -0
- package/apps/desktop/core/index.ts +30 -0
- package/apps/desktop/core/intervals.ts +123 -0
- package/apps/desktop/core/montage.ts +236 -0
- package/apps/desktop/core/music-analysis.ts +518 -0
- package/apps/desktop/core/plan.ts +244 -0
- package/apps/desktop/core/project-auto-zoom.ts +75 -0
- package/apps/desktop/core/project-camera-spatial.ts +676 -0
- package/apps/desktop/core/project-camera.ts +403 -0
- package/apps/desktop/core/project-captions.ts +888 -0
- package/apps/desktop/core/project-inactivity.ts +125 -0
- package/apps/desktop/core/project-metadata.ts +927 -0
- package/apps/desktop/core/project-plan.ts +375 -0
- package/apps/desktop/core/project-render-plan.ts +303 -0
- package/apps/desktop/core/project-time.ts +469 -0
- package/apps/desktop/core/render-plan.ts +442 -0
- package/apps/desktop/core/scene-cache.ts +174 -0
- package/apps/desktop/core/scene-sampling.ts +448 -0
- package/apps/desktop/core/spatial-compositor.ts +22 -0
- package/apps/desktop/core/spatial-durability.ts +99 -0
- package/apps/desktop/core/spatial-project.ts +169 -0
- package/apps/desktop/core/spatial-shot-clock.ts +32 -0
- package/apps/desktop/core/speech-analysis.ts +333 -0
- package/apps/desktop/core/storage.ts +970 -0
- package/apps/desktop/core/time-map.ts +91 -0
- package/apps/desktop/core/time.ts +61 -0
- package/apps/desktop/core/video-effects.ts +660 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
- package/apps/desktop/dist/cli/main.js +5086 -0
- package/apps/desktop/html-overlay/catalog.ts +171 -0
- package/apps/desktop/html-overlay/contracts.ts +372 -0
- package/apps/desktop/html-overlay/execution-profile.ts +77 -0
- package/apps/desktop/html-overlay/index.ts +98 -0
- package/apps/desktop/html-overlay/libraries.ts +263 -0
- package/apps/desktop/html-overlay/random.ts +49 -0
- package/apps/desktop/html-overlay/runtime.ts +813 -0
- package/apps/desktop/html-overlay/scaffolds.ts +792 -0
- package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
- package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
- package/apps/desktop/html-overlay/spatial.ts +720 -0
- package/apps/desktop/studio/assets/README.md +39 -0
- package/apps/desktop/studio/assets/contracts.ts +129 -0
- package/apps/desktop/studio/assets/network.ts +64 -0
- package/apps/desktop/studio/assets/poly-haven.ts +148 -0
- package/apps/desktop/studio/drivers/blender_driver.py +466 -0
- package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
- package/apps/desktop/studio/education/driver.py +308 -0
- package/apps/desktop/studio/education/lesson.py +180 -0
- package/apps/desktop/studio/education/toolkit.py +249 -0
- package/apps/desktop/workflows/chaptered-demo.ts +48 -0
- package/apps/desktop/workflows/creative-iteration.ts +107 -0
- package/apps/desktop/workflows/creative-selection.ts +92 -0
- package/apps/desktop/workflows/fragments.ts +486 -0
- package/apps/desktop/workflows/index.ts +93 -0
- package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
- package/apps/desktop/workflows/scene-direction.ts +12 -0
- package/apps/desktop/workflows/social-variants.ts +75 -0
- package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
- package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
- package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
- package/dist/cli.js +479 -0
- package/dist/code/advanced.js +270 -0
- package/dist/code/index.js +2240 -0
- package/dist/generate.js +24 -0
- package/dist/host-resources.js +26 -0
- package/dist/index-42zsesc1.js +2257 -0
- package/dist/index-7308egqr.js +48 -0
- package/dist/index-8txs6fkn.js +712 -0
- package/dist/index-mcy8z0br.js +192 -0
- package/dist/index-p63wavx0.js +2029 -0
- package/dist/index-r7gdhmsp.js +424 -0
- package/dist/index-sh6xbav6.js +1219 -0
- package/dist/index-z1w83f81.js +4 -0
- package/dist/index-z7239b4h.js +2363 -0
- package/dist/index-zfeg9hmw.js +2013 -0
- package/dist/index.js +241 -0
- package/dist/operations.js +29 -0
- package/dist/skill-install-dh0ntqba.js +10 -0
- package/dist/vectorize/worker.js +142 -0
- package/dist/workflow.js +16 -0
- package/docs/README.md +38 -0
- package/docs/studio.md +215 -0
- package/examples/studio/blender/README.md +26 -0
- package/examples/studio/blender/character.py +152 -0
- package/examples/studio/blender/cloth.py +101 -0
- package/examples/studio/blender/color_chart.py +29 -0
- package/examples/studio/blender/fluid.py +78 -0
- package/examples/studio/blender/import_model.py +74 -0
- package/examples/studio/blender/product.py +53 -0
- package/examples/studio/blender/shaded_street.py +481 -0
- package/examples/studio/blender/studio_scene.py +147 -0
- package/examples/studio/cadquery/README.md +13 -0
- package/examples/studio/cadquery/bracket.py +36 -0
- package/examples/studio/cadquery/import_step.py +12 -0
- package/examples/studio/education/README.md +46 -0
- package/examples/studio/education/lesson.json +99 -0
- package/examples/studio/education/scene.py +7 -0
- package/examples/studio/hybrid-scene.ts +96 -0
- package/examples/studio/native-workflow.ts +14 -0
- package/examples/studio/vgpu/README.md +64 -0
- package/examples/studio/vgpu/heat-field.mjs +290 -0
- package/package.json +304 -0
- package/packages/scene/src/index.ts +1 -0
- package/packages/scene/src/scene.ts +215 -0
- package/schema/diagram.schema.json +334 -0
- package/skills/slopcamera/SKILL.md +45 -0
- package/skills/slopcamera/agents/openai.yaml +4 -0
- package/skills/slopcamera/references/customization.md +77 -0
- package/skills/slopcamera/references/diagrams.md +153 -0
- package/skills/slopcamera/references/directed-scenes.md +42 -0
- package/skills/slopcamera/references/directing-video.md +18 -0
- package/skills/slopcamera/references/educational-video.md +26 -0
- package/skills/slopcamera/references/gateway-media.md +134 -0
- package/skills/slopcamera/references/install.md +62 -0
- package/skills/slopcamera/references/native-studio.md +31 -0
- package/skills/slopcamera/references/reference-led-3d.md +140 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
- package/skills/slopcamera/references/vectorization.md +29 -0
- package/skills/slopcamera/references/video-projects.md +187 -0
- package/skills/slopcamera/references/visual-communication.md +120 -0
- package/skills/slopcamera/references/workflows-sdk.md +45 -0
- package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
- package/src/artifacts.ts +106 -0
- package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
- package/src/assets.d.ts +9 -0
- package/src/cli.ts +601 -0
- package/src/cloud-errors.ts +26 -0
- package/src/code/advanced.ts +10 -0
- package/src/code/boundary.ts +21 -0
- package/src/code/canonical-json.ts +129 -0
- package/src/code/compiler.ts +953 -0
- package/src/code/contracts.ts +576 -0
- package/src/code/define-workflow.ts +334 -0
- package/src/code/errors.ts +41 -0
- package/src/code/graph-builder.ts +945 -0
- package/src/code/index.ts +121 -0
- package/src/code/json-snapshot.ts +899 -0
- package/src/code/json-utf8.ts +88 -0
- package/src/code/portable-builder.ts +136 -0
- package/src/code/projection.ts +378 -0
- package/src/code/public-operations.ts +450 -0
- package/src/code/runtime.ts +580 -0
- package/src/code/sha256.ts +36 -0
- package/src/config.ts +180 -0
- package/src/desktop.ts +253 -0
- package/src/fs.ts +10 -0
- package/src/generate.ts +704 -0
- package/src/host-resource-posix.ts +161 -0
- package/src/host-resources.ts +1749 -0
- package/src/icons.ts +58 -0
- package/src/index.ts +200 -0
- package/src/label-layout.ts +130 -0
- package/src/layout.ts +413 -0
- package/src/lint.ts +127 -0
- package/src/mcp/boundary.ts +354 -0
- package/src/mcp/index.ts +33 -0
- package/src/mcp/server.ts +272 -0
- package/src/mcp/tools.ts +963 -0
- package/src/mcp/types.ts +71 -0
- package/src/operations.ts +775 -0
- package/src/parse.ts +861 -0
- package/src/process-environment.ts +17 -0
- package/src/render.ts +437 -0
- package/src/skill-install.ts +49 -0
- package/src/spatial-scene/authoring.ts +27 -0
- package/src/spatial-scene/camera-track.ts +83 -0
- package/src/spatial-scene/contracts.ts +277 -0
- package/src/spatial-scene/evaluate.ts +145 -0
- package/src/spatial-scene/gltf.ts +558 -0
- package/src/spatial-scene/identity.ts +260 -0
- package/src/spatial-scene/index.ts +10 -0
- package/src/spatial-scene/inspect.ts +68 -0
- package/src/spatial-scene/math.ts +280 -0
- package/src/spatial-scene/patch.ts +131 -0
- package/src/spatial-scene/time.ts +53 -0
- package/src/studio/contracts.ts +129 -0
- package/src/studio/index.ts +2 -0
- package/src/studio/plan.ts +156 -0
- package/src/studio/shared.ts +41 -0
- package/src/theme.ts +68 -0
- package/src/tldr.ts +474 -0
- package/src/types.ts +212 -0
- package/src/vectorize/archive.ts +101 -0
- package/src/vectorize/command.ts +797 -0
- package/src/vectorize/index.ts +27 -0
- package/src/vectorize/limits.ts +61 -0
- package/src/vectorize/metrics.ts +351 -0
- package/src/vectorize/pixels.ts +323 -0
- package/src/vectorize/supervisor.ts +339 -0
- package/src/vectorize/svg.ts +188 -0
- package/src/vectorize/tool.ts +542 -0
- package/src/vectorize/types.ts +113 -0
- package/src/vectorize/vectorize.ts +634 -0
- package/src/vectorize/worker-protocol.ts +40 -0
- package/src/vectorize/worker.ts +209 -0
- package/src/version.ts +1 -0
- package/src/workflow.ts +409 -0
|
@@ -0,0 +1,1288 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { bindStudioRunInput } from "../application/operations/studio";
|
|
3
|
+
import { bindSpatialRenderInput } from "../application/operations/spatial-render";
|
|
4
|
+
|
|
5
|
+
import type { ApplicationContext } from "../application/context";
|
|
6
|
+
import { SpatialProjectBasisSchema } from "../contracts/spatial-project";
|
|
7
|
+
import { SpatialProjectSnapshotOutputSchema, SpatialProjectMutationOutputSchema } from "../application/operations/spatial-project";
|
|
8
|
+
import { bindProjectRenderInputV4, reconcileProjectRenderV4, reconcileProjectRenderEffect, type ProjectRenderReconciliation } from "../application/operations/render/project";
|
|
9
|
+
import {
|
|
10
|
+
bindSlopcameraPortableOperationInputV2,
|
|
11
|
+
slopcameraPortableOutputPublicationParent,
|
|
12
|
+
} from "../application/operations/slopcamera-portable";
|
|
13
|
+
import {
|
|
14
|
+
CreativeBaseV1Schema,
|
|
15
|
+
createCreativeBaseV1,
|
|
16
|
+
} from "../application/creative-iteration";
|
|
17
|
+
import { ApplicationError } from "../application/errors";
|
|
18
|
+
import {
|
|
19
|
+
gatewayPortRequestForOperation,
|
|
20
|
+
gatewayRequestId,
|
|
21
|
+
prepareGatewayOperation,
|
|
22
|
+
reconcileGatewayOperation,
|
|
23
|
+
} from "../application/gateway-port";
|
|
24
|
+
import type { OperationKind } from "../application/operation";
|
|
25
|
+
import {
|
|
26
|
+
openLeasedProjectSnapshot,
|
|
27
|
+
withProjectPublicationLease,
|
|
28
|
+
} from "../application/project-publication-lease";
|
|
29
|
+
import {
|
|
30
|
+
openProjectSnapshot,
|
|
31
|
+
projectGenerationSha256FromHashes,
|
|
32
|
+
projectMatchesEditBasis,
|
|
33
|
+
ProjectEditBasisSchema,
|
|
34
|
+
} from "../application/project-store";
|
|
35
|
+
import {
|
|
36
|
+
bindMediaOperationInput,
|
|
37
|
+
bindSlopcameraVisualOperationInput,
|
|
38
|
+
bindAnalysisCapabilityInput,
|
|
39
|
+
bindCreateCreativeCandidateInput,
|
|
40
|
+
bindCreateCandidateRevisionInput,
|
|
41
|
+
bindMaterializeVariantSelectionInput,
|
|
42
|
+
bindPromoteVariantSelectionInput,
|
|
43
|
+
bindCandidateRenderOutputInput,
|
|
44
|
+
bindProjectCommitEditsInputV2FromSnapshot,
|
|
45
|
+
bindProjectCommitEditsInputV3FromSnapshot,
|
|
46
|
+
bindProjectAutoZoomInput,
|
|
47
|
+
bindProjectInactivityInput,
|
|
48
|
+
bindProjectRenderPlanInput,
|
|
49
|
+
bindProjectRenderPlanInputV2,
|
|
50
|
+
bindProjectRenderInput,
|
|
51
|
+
bindProjectRenderInputV2,
|
|
52
|
+
bindProjectRenderInputV3,
|
|
53
|
+
bindProjectRenderToolchain,
|
|
54
|
+
parseRecordingOperationOutput,
|
|
55
|
+
projectEditCommitReceipt,
|
|
56
|
+
projectEditTransactionId,
|
|
57
|
+
reconcileVariantSelectionPromotion,
|
|
58
|
+
reconcileProjectRender,
|
|
59
|
+
summarizeGatewayOperation,
|
|
60
|
+
CommitProjectEditsInputSchema,
|
|
61
|
+
CommitProjectEditsInputV2Schema,
|
|
62
|
+
CommitProjectEditsInputV3Schema,
|
|
63
|
+
ProjectSnapshotOutputSchema,
|
|
64
|
+
type CommitProjectEditsInput,
|
|
65
|
+
type CommitProjectEditsInputV2,
|
|
66
|
+
type CommitProjectEditsInputV3,
|
|
67
|
+
} from "../application/operations";
|
|
68
|
+
import { ProjectEditCommitReceiptSchema } from "../application/receipts";
|
|
69
|
+
import {
|
|
70
|
+
LOCAL_VERIFIED_RECEIPT_OPERATION_KINDS,
|
|
71
|
+
hasLocalVerifiedReceiptReconciler,
|
|
72
|
+
reconcileLocalVerifiedReceiptOperation,
|
|
73
|
+
} from "../application/verified-receipt-reconciliation";
|
|
74
|
+
import {
|
|
75
|
+
PROJECT_STATE_TRANSACTION_PATH,
|
|
76
|
+
ProjectStateTransactionV1Schema,
|
|
77
|
+
} from "../cli/project-state-transaction";
|
|
78
|
+
import { resolveProjectDirectory } from "../cli/project-service";
|
|
79
|
+
import {
|
|
80
|
+
canonicalJson,
|
|
81
|
+
canonicalJsonFingerprint,
|
|
82
|
+
canonicalJsonSha256,
|
|
83
|
+
sha256Hex,
|
|
84
|
+
} from "../core/canonical-json";
|
|
85
|
+
import {
|
|
86
|
+
createNodeBundleFileSystem,
|
|
87
|
+
loadProjectEditPlan,
|
|
88
|
+
loadVideoProject,
|
|
89
|
+
} from "../core/storage";
|
|
90
|
+
import {
|
|
91
|
+
JsonValueSchema,
|
|
92
|
+
isOperationGraphNode,
|
|
93
|
+
type JsonValue,
|
|
94
|
+
} from "./contracts";
|
|
95
|
+
import { assertOperationFileProvenance } from "./file-candidate-provenance";
|
|
96
|
+
import type {
|
|
97
|
+
NodeExecutionBinding,
|
|
98
|
+
NodeExecutionPlanningRequest,
|
|
99
|
+
NodePreparationBinding,
|
|
100
|
+
NodePreparationRequest,
|
|
101
|
+
NodeReconciliation,
|
|
102
|
+
NodeReconciliationRequest,
|
|
103
|
+
SchedulerNodePlanner,
|
|
104
|
+
} from "./scheduler";
|
|
105
|
+
import { workflowBoundary, workflowValidation, type WorkflowFailure } from "./workflow-effects";
|
|
106
|
+
|
|
107
|
+
const ANALYSIS_ID_DOMAIN = "studio.workflow.analysis-id/v1";
|
|
108
|
+
type RecordingOperationKind = Extract<OperationKind, `recording.${string}`>;
|
|
109
|
+
type GatewayOperationKind = Extract<OperationKind, `gateway.${string}`>;
|
|
110
|
+
|
|
111
|
+
export const APPLICATION_VERIFIED_RECEIPT_RECONCILER_KINDS =
|
|
112
|
+
Object.freeze([
|
|
113
|
+
...LOCAL_VERIFIED_RECEIPT_OPERATION_KINDS,
|
|
114
|
+
"render.project",
|
|
115
|
+
] as const satisfies readonly OperationKind[]);
|
|
116
|
+
|
|
117
|
+
function isRecordingOperationKind(
|
|
118
|
+
kind: OperationKind,
|
|
119
|
+
): kind is RecordingOperationKind {
|
|
120
|
+
return kind === "recording.start"
|
|
121
|
+
|| kind === "recording.pause"
|
|
122
|
+
|| kind === "recording.resume"
|
|
123
|
+
|| kind === "recording.stop";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isGatewayOperationKind(
|
|
127
|
+
kind: OperationKind,
|
|
128
|
+
): kind is GatewayOperationKind {
|
|
129
|
+
return kind === "gateway.image"
|
|
130
|
+
|| kind === "gateway.video"
|
|
131
|
+
|| kind === "gateway.speech"
|
|
132
|
+
|| kind === "gateway.transcription";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function jsonObject(value: JsonValue): value is Readonly<Record<string, JsonValue>> {
|
|
136
|
+
return typeof value === "object"
|
|
137
|
+
&& value !== null
|
|
138
|
+
&& !Array.isArray(value);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function projectReference(input: JsonValue): string | undefined {
|
|
142
|
+
if (!jsonObject(input)) return undefined;
|
|
143
|
+
const project = input.project;
|
|
144
|
+
if (typeof project === "string") return project;
|
|
145
|
+
for (const key of ["revision", "plan"] as const) {
|
|
146
|
+
const nested = input[key];
|
|
147
|
+
if (nested !== undefined && jsonObject(nested)) {
|
|
148
|
+
const projectId = nested.projectId;
|
|
149
|
+
if (typeof projectId === "string") return projectId;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function deterministicAnalysisInput(
|
|
156
|
+
request: NodeExecutionPlanningRequest,
|
|
157
|
+
): JsonValue {
|
|
158
|
+
const input = request.resolvedInput;
|
|
159
|
+
if (
|
|
160
|
+
(
|
|
161
|
+
request.operation.kind !== "analysis.faces"
|
|
162
|
+
&& request.operation.kind !== "analysis.music"
|
|
163
|
+
&& request.operation.kind !== "analysis.project-inactivity"
|
|
164
|
+
)
|
|
165
|
+
|| !jsonObject(input)
|
|
166
|
+
|| input.analysisId !== undefined
|
|
167
|
+
) {
|
|
168
|
+
return input;
|
|
169
|
+
}
|
|
170
|
+
const suffix = sha256Hex(
|
|
171
|
+
`${ANALYSIS_ID_DOMAIN}\0${canonicalJson({
|
|
172
|
+
graphPlanSha256: request.graphPlan.graphPlanSha256,
|
|
173
|
+
nodeKey: request.node.key,
|
|
174
|
+
runId: request.runId,
|
|
175
|
+
})}`,
|
|
176
|
+
).slice(0, 32);
|
|
177
|
+
return JsonValueSchema.parse({
|
|
178
|
+
...input,
|
|
179
|
+
analysisId: `analysis_${suffix}`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function exactOperationInput(
|
|
184
|
+
application: ApplicationContext,
|
|
185
|
+
request: NodeExecutionPlanningRequest,
|
|
186
|
+
): Promise<JsonValue> {
|
|
187
|
+
let deterministic = deterministicAnalysisInput(request);
|
|
188
|
+
if (request.operation.kind === "slopcamera.studio.run") return JsonValueSchema.parse(await bindStudioRunInput(request.application ?? application, deterministic, request.abortSignal, request.beforePublication));
|
|
189
|
+
if (request.operation.kind === "scene.render") return JsonValueSchema.parse(await bindSpatialRenderInput(application, deterministic));
|
|
190
|
+
if (request.operation.kind === "spatial.project.snapshot") {
|
|
191
|
+
const project = projectReference(deterministic);
|
|
192
|
+
const subject = request.graphPlan.staticBindings.initialSubjects.find(item => item.kind === "spatial-project" && item.id === project);
|
|
193
|
+
if (subject?.kind !== "spatial-project" || !jsonObject(deterministic)) throw new ApplicationError("authorization-required", "Spatial snapshots require an exact project binding established during planning.");
|
|
194
|
+
const exact = JsonValueSchema.parse({ ...deterministic, expected: deterministic.expected ?? subject.basis });
|
|
195
|
+
assertSpatialProjectBasisProvenance(request, exact);
|
|
196
|
+
return exact;
|
|
197
|
+
}
|
|
198
|
+
if (request.operation.kind === "edit.create-candidate-revision") {
|
|
199
|
+
return JsonValueSchema.parse(
|
|
200
|
+
await bindCreateCandidateRevisionInput(application, deterministic),
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
if (request.operation.kind === "iteration.create-candidate") {
|
|
204
|
+
return JsonValueSchema.parse(
|
|
205
|
+
await bindCreateCreativeCandidateInput(application, deterministic),
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (request.operation.kind === "project.promote-selection") {
|
|
209
|
+
return JsonValueSchema.parse(
|
|
210
|
+
bindPromoteVariantSelectionInput(application, deterministic),
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (request.operation.kind === "render.materialize-selection") {
|
|
214
|
+
return JsonValueSchema.parse(
|
|
215
|
+
bindMaterializeVariantSelectionInput(application, deterministic),
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
if (request.operation.kind === "render.bind-candidate-output") {
|
|
219
|
+
return JsonValueSchema.parse(bindCandidateRenderOutputInput(
|
|
220
|
+
deterministic,
|
|
221
|
+
await bindProjectRenderToolchain(application),
|
|
222
|
+
));
|
|
223
|
+
}
|
|
224
|
+
if (
|
|
225
|
+
request.operation.kind === "project.commit-edits"
|
|
226
|
+
&& request.operation.version === 2
|
|
227
|
+
) {
|
|
228
|
+
const parsed = CommitProjectEditsInputV2Schema.parse(deterministic);
|
|
229
|
+
const snapshot = await openLeasedProjectSnapshot(
|
|
230
|
+
application,
|
|
231
|
+
parsed.project,
|
|
232
|
+
);
|
|
233
|
+
deterministic = JsonValueSchema.parse(
|
|
234
|
+
await bindProjectCommitEditsInputV2FromSnapshot(
|
|
235
|
+
application,
|
|
236
|
+
parsed,
|
|
237
|
+
snapshot,
|
|
238
|
+
),
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
if (
|
|
242
|
+
request.operation.kind === "project.commit-edits"
|
|
243
|
+
&& request.operation.version === 3
|
|
244
|
+
) {
|
|
245
|
+
const parsed = CommitProjectEditsInputV3Schema.parse(deterministic);
|
|
246
|
+
const snapshot = await openLeasedProjectSnapshot(
|
|
247
|
+
application,
|
|
248
|
+
parsed.project,
|
|
249
|
+
);
|
|
250
|
+
deterministic = JsonValueSchema.parse(
|
|
251
|
+
await bindProjectCommitEditsInputV3FromSnapshot(
|
|
252
|
+
application,
|
|
253
|
+
parsed,
|
|
254
|
+
snapshot,
|
|
255
|
+
),
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
if (
|
|
259
|
+
request.operation.kind === "project.commit-edits"
|
|
260
|
+
&& jsonObject(deterministic)
|
|
261
|
+
&& deterministic.updatedAt === undefined
|
|
262
|
+
) {
|
|
263
|
+
deterministic = JsonValueSchema.parse({
|
|
264
|
+
...deterministic,
|
|
265
|
+
updatedAt: application.clock.now().toISOString(),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
if (request.operation.kind === "analysis.project-auto-zooms") {
|
|
269
|
+
return JsonValueSchema.parse(
|
|
270
|
+
await bindProjectAutoZoomInput(application, deterministic),
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
if (
|
|
274
|
+
request.operation.kind === "analysis.faces"
|
|
275
|
+
|| request.operation.kind === "analysis.music"
|
|
276
|
+
|| request.operation.kind === "analysis.scenes"
|
|
277
|
+
) {
|
|
278
|
+
return JsonValueSchema.parse(
|
|
279
|
+
await bindAnalysisCapabilityInput(
|
|
280
|
+
application,
|
|
281
|
+
request.operation.kind,
|
|
282
|
+
deterministic,
|
|
283
|
+
),
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
if (request.operation.kind === "analysis.project-inactivity") {
|
|
287
|
+
return JsonValueSchema.parse(
|
|
288
|
+
await bindProjectInactivityInput(application, deterministic),
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
if (request.operation.kind === "render.project-plan") {
|
|
292
|
+
if (request.operation.version === 1) {
|
|
293
|
+
return JsonValueSchema.parse(
|
|
294
|
+
await bindProjectRenderPlanInput(application, deterministic),
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
if (request.operation.version === 2) {
|
|
298
|
+
return JsonValueSchema.parse(
|
|
299
|
+
await bindProjectRenderPlanInputV2(application, deterministic),
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
throw new ApplicationError(
|
|
303
|
+
"unsupported-plan",
|
|
304
|
+
`Unsupported render.project-plan binding version: ${String(request.operation.version)}`,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
if (
|
|
308
|
+
request.operation.kind === "media.ingest"
|
|
309
|
+
|| request.operation.kind === "media.overlay"
|
|
310
|
+
|| request.operation.kind === "media.html-overlay"
|
|
311
|
+
|| request.operation.kind === "media.audio-effects"
|
|
312
|
+
|| request.operation.kind === "media.color-grade"
|
|
313
|
+
) {
|
|
314
|
+
return JsonValueSchema.parse(
|
|
315
|
+
await bindMediaOperationInput(
|
|
316
|
+
application,
|
|
317
|
+
request.operation.kind,
|
|
318
|
+
deterministic,
|
|
319
|
+
),
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
if (
|
|
323
|
+
request.operation.version === 1
|
|
324
|
+
&& (
|
|
325
|
+
request.operation.kind === "slopcamera.diagram.check"
|
|
326
|
+
|| request.operation.kind === "slopcamera.diagram.render"
|
|
327
|
+
|| request.operation.kind === "slopcamera.image.vectorize"
|
|
328
|
+
)
|
|
329
|
+
) {
|
|
330
|
+
return JsonValueSchema.parse(
|
|
331
|
+
await bindSlopcameraVisualOperationInput(
|
|
332
|
+
application,
|
|
333
|
+
request.operation.kind,
|
|
334
|
+
deterministic,
|
|
335
|
+
),
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
if (
|
|
339
|
+
request.operation.version === 2
|
|
340
|
+
&& (
|
|
341
|
+
request.operation.kind === "slopcamera.diagram.check"
|
|
342
|
+
|| request.operation.kind === "slopcamera.diagram.render"
|
|
343
|
+
|| request.operation.kind === "slopcamera.image.generate"
|
|
344
|
+
|| request.operation.kind === "slopcamera.image.vectorize"
|
|
345
|
+
)
|
|
346
|
+
) {
|
|
347
|
+
return JsonValueSchema.parse(
|
|
348
|
+
await bindSlopcameraPortableOperationInputV2(
|
|
349
|
+
application,
|
|
350
|
+
request.operation.kind,
|
|
351
|
+
deterministic,
|
|
352
|
+
),
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
if (request.operation.kind === "render.project") {
|
|
356
|
+
if (request.operation.version === 4) return JsonValueSchema.parse(await bindProjectRenderInputV4(application, deterministic));
|
|
357
|
+
if (request.operation.version === 1) {
|
|
358
|
+
return JsonValueSchema.parse(
|
|
359
|
+
await bindProjectRenderInput(application, deterministic),
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
if (request.operation.version === 2) {
|
|
363
|
+
return JsonValueSchema.parse(
|
|
364
|
+
await bindProjectRenderInputV2(application, deterministic),
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
if (request.operation.version === 3) {
|
|
368
|
+
return JsonValueSchema.parse(
|
|
369
|
+
await bindProjectRenderInputV3(application, deterministic),
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
throw new ApplicationError(
|
|
373
|
+
"unsupported-plan",
|
|
374
|
+
`Unsupported render.project binding version: ${String(request.operation.version)}`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
if (isGatewayOperationKind(request.operation.kind)) {
|
|
378
|
+
const prepared = await prepareGatewayOperation(application, {
|
|
379
|
+
request: gatewayPortRequestForOperation(
|
|
380
|
+
request.operation.kind,
|
|
381
|
+
deterministic,
|
|
382
|
+
),
|
|
383
|
+
signal: new AbortController().signal,
|
|
384
|
+
});
|
|
385
|
+
return JsonValueSchema.parse(prepared.request);
|
|
386
|
+
}
|
|
387
|
+
return deterministic;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function inputDescriptor(request: NodePreparationRequest): JsonValue {
|
|
391
|
+
const input = JsonValueSchema.parse(request.resolvedInput);
|
|
392
|
+
const fingerprint = canonicalJsonFingerprint(
|
|
393
|
+
input,
|
|
394
|
+
"studio.workflow.input-descriptor/v1\0",
|
|
395
|
+
);
|
|
396
|
+
const project = projectReference(input);
|
|
397
|
+
return JsonValueSchema.parse({
|
|
398
|
+
bytes: fingerprint.bytes,
|
|
399
|
+
inputSha256: fingerprint.sha256,
|
|
400
|
+
...(project === undefined ? {} : { project }),
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function publishesProject(request: NodeExecutionPlanningRequest): boolean {
|
|
405
|
+
return request.operation.policy.effect === "project-mutation"
|
|
406
|
+
|| request.operation.policy.resources.some(
|
|
407
|
+
claim => claim.resource === "project-publication",
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function publicationKeys(
|
|
412
|
+
request: NodeExecutionPlanningRequest,
|
|
413
|
+
input: JsonValue,
|
|
414
|
+
project: string | undefined,
|
|
415
|
+
): readonly string[] {
|
|
416
|
+
if (request.operation.kind === "slopcamera.studio.run" && jsonObject(input) && input.job !== undefined && jsonObject(input.job) && typeof input.job.jobId === "string") {
|
|
417
|
+
return [`output:studio:${input.job.jobId}`];
|
|
418
|
+
}
|
|
419
|
+
if (request.operation.version === 2 && jsonObject(input)) {
|
|
420
|
+
if (
|
|
421
|
+
request.operation.kind === "slopcamera.diagram.render"
|
|
422
|
+
|| request.operation.kind === "slopcamera.image.generate"
|
|
423
|
+
|| request.operation.kind === "slopcamera.image.vectorize"
|
|
424
|
+
) {
|
|
425
|
+
const publicationParent = slopcameraPortableOutputPublicationParent(
|
|
426
|
+
request.operation.kind,
|
|
427
|
+
input,
|
|
428
|
+
);
|
|
429
|
+
if (publicationParent === undefined) return [];
|
|
430
|
+
return [
|
|
431
|
+
`output:slopcamera:${sha256Hex(
|
|
432
|
+
`studio.workflow.output-publication/v1\0${publicationParent}`,
|
|
433
|
+
)}`,
|
|
434
|
+
];
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (
|
|
438
|
+
request.operation.kind === "render.project"
|
|
439
|
+
&& jsonObject(input)
|
|
440
|
+
&& project !== undefined
|
|
441
|
+
) {
|
|
442
|
+
const output = input.output;
|
|
443
|
+
if (
|
|
444
|
+
output === undefined
|
|
445
|
+
|| !jsonObject(output)
|
|
446
|
+
|| typeof output.path !== "string"
|
|
447
|
+
) return [];
|
|
448
|
+
return [
|
|
449
|
+
`output:${project}:${sha256Hex(
|
|
450
|
+
`studio.workflow.output-publication/v1\0${output.path}`,
|
|
451
|
+
)}`,
|
|
452
|
+
];
|
|
453
|
+
}
|
|
454
|
+
if (
|
|
455
|
+
request.operation.kind === "render.materialize-selection"
|
|
456
|
+
&& jsonObject(input)
|
|
457
|
+
&& project !== undefined
|
|
458
|
+
&& typeof input.destinationPath === "string"
|
|
459
|
+
) {
|
|
460
|
+
return [
|
|
461
|
+
`output:${project}:${sha256Hex(
|
|
462
|
+
`studio.workflow.output-publication/v1\0${input.destinationPath}`,
|
|
463
|
+
)}`,
|
|
464
|
+
];
|
|
465
|
+
}
|
|
466
|
+
if (!publishesProject(request)) return [];
|
|
467
|
+
return project === undefined
|
|
468
|
+
? [`operation:${request.operation.kind}`]
|
|
469
|
+
: [`project:${project}`];
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function assertProjectProvenance(
|
|
473
|
+
request: NodeExecutionPlanningRequest,
|
|
474
|
+
project: string | undefined,
|
|
475
|
+
): void {
|
|
476
|
+
if (project === undefined) return;
|
|
477
|
+
const authorized = request.graphPlan.staticBindings.initialSubjects.some(subject => (
|
|
478
|
+
subject.kind === (request.operation.kind.startsWith("spatial.project.") || (request.operation.kind === "render.project" && request.operation.version === 4) ? "spatial-project" : "project") && subject.id === project
|
|
479
|
+
));
|
|
480
|
+
if (!authorized) {
|
|
481
|
+
throw new ApplicationError(
|
|
482
|
+
"authorization-required",
|
|
483
|
+
`Node ${request.node.key} received a project identity that was not structurally bound during planning.`,
|
|
484
|
+
{
|
|
485
|
+
operation: request.operation.kind,
|
|
486
|
+
project,
|
|
487
|
+
},
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function staticProjectGeneration(
|
|
493
|
+
request: NodeExecutionPlanningRequest,
|
|
494
|
+
project: string,
|
|
495
|
+
): string {
|
|
496
|
+
const subject = request.graphPlan.staticBindings.initialSubjects.find(
|
|
497
|
+
candidate => candidate.kind === "project" && candidate.id === project,
|
|
498
|
+
);
|
|
499
|
+
if (subject === undefined || subject.kind !== "project") {
|
|
500
|
+
throw new ApplicationError(
|
|
501
|
+
"authorization-required",
|
|
502
|
+
`Node ${request.node.key} has no plan-time project generation binding.`,
|
|
503
|
+
{ operation: request.operation.kind, project },
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
return projectGenerationSha256FromHashes({
|
|
507
|
+
currentPlanSha256: subject.planSha256,
|
|
508
|
+
projectSha256: subject.projectSha256,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function nestedGenerationSha256(
|
|
513
|
+
input: JsonValue,
|
|
514
|
+
): string | undefined {
|
|
515
|
+
if (!jsonObject(input)) return undefined;
|
|
516
|
+
const binding = input.binding;
|
|
517
|
+
if (binding !== undefined && jsonObject(binding)) {
|
|
518
|
+
const projectGenerationSha256 = binding.projectGenerationSha256;
|
|
519
|
+
if (typeof projectGenerationSha256 === "string") {
|
|
520
|
+
return projectGenerationSha256;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
for (const key of ["draft", "revision"] as const) {
|
|
524
|
+
const nested = input[key];
|
|
525
|
+
if (nested === undefined || !jsonObject(nested)) continue;
|
|
526
|
+
const baseGeneration = nested.baseGeneration;
|
|
527
|
+
if (baseGeneration === undefined || !jsonObject(baseGeneration)) continue;
|
|
528
|
+
const generationSha256 = baseGeneration.generationSha256;
|
|
529
|
+
if (typeof generationSha256 === "string") return generationSha256;
|
|
530
|
+
}
|
|
531
|
+
return undefined;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function dependencyProjectGeneration(
|
|
535
|
+
request: NodeExecutionPlanningRequest,
|
|
536
|
+
project: string,
|
|
537
|
+
): string | undefined {
|
|
538
|
+
const candidates = new Set<string>();
|
|
539
|
+
const nodes = new Map(
|
|
540
|
+
request.graphPlan.graph.nodes.map(node => [node.key, node]),
|
|
541
|
+
);
|
|
542
|
+
for (const [nodeKey, output] of Object.entries(request.dependencyOutputs)) {
|
|
543
|
+
const node = nodes.get(nodeKey);
|
|
544
|
+
if (node === undefined || !isOperationGraphNode(node)) continue;
|
|
545
|
+
if (node.executor.operation.kind === "project.snapshot") {
|
|
546
|
+
const parsed = ProjectSnapshotOutputSchema.safeParse(output.value);
|
|
547
|
+
if (
|
|
548
|
+
parsed.success
|
|
549
|
+
&& parsed.data.project.projectId === project
|
|
550
|
+
) {
|
|
551
|
+
candidates.add(parsed.data.generation.generationSha256);
|
|
552
|
+
}
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
if (node.executor.operation.kind === "project.commit-edits") {
|
|
556
|
+
const parsed = ProjectEditCommitReceiptSchema.safeParse(output.value);
|
|
557
|
+
if (
|
|
558
|
+
parsed.success
|
|
559
|
+
&& parsed.data.projectId === project
|
|
560
|
+
) {
|
|
561
|
+
candidates.add(parsed.data.generation.generationSha256);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (candidates.size > 1) {
|
|
566
|
+
throw new ApplicationError(
|
|
567
|
+
"conflict",
|
|
568
|
+
`Node ${request.node.key} depends on incompatible project generations.`,
|
|
569
|
+
{ operation: request.operation.kind, project },
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
return [...candidates][0];
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function assertProjectEditBasisProvenance(
|
|
576
|
+
request: NodeExecutionPlanningRequest,
|
|
577
|
+
input: JsonValue,
|
|
578
|
+
project: string,
|
|
579
|
+
): void {
|
|
580
|
+
if (
|
|
581
|
+
request.operation.kind !== "project.commit-edits"
|
|
582
|
+
&& request.operation.kind !== "edit.freeze-revision"
|
|
583
|
+
) return;
|
|
584
|
+
if (!jsonObject(input)) {
|
|
585
|
+
throw new ApplicationError(
|
|
586
|
+
"authorization-required",
|
|
587
|
+
`Node ${request.node.key} has no typed project edit basis.`,
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
const exactBasis = ProjectEditBasisSchema.parse(input.basis);
|
|
591
|
+
const candidates = new Map<string, typeof exactBasis>();
|
|
592
|
+
const nodes = new Map(
|
|
593
|
+
request.graphPlan.graph.nodes.map(node => [node.key, node]),
|
|
594
|
+
);
|
|
595
|
+
for (const [nodeKey, output] of Object.entries(request.dependencyOutputs)) {
|
|
596
|
+
const node = nodes.get(nodeKey);
|
|
597
|
+
if (node === undefined || !isOperationGraphNode(node)) continue;
|
|
598
|
+
if (node.executor.operation.kind === "project.snapshot") {
|
|
599
|
+
const parsed = ProjectSnapshotOutputSchema.safeParse(output.value);
|
|
600
|
+
if (parsed.success && parsed.data.project.projectId === project) {
|
|
601
|
+
candidates.set(canonicalJson(parsed.data.editBasis), parsed.data.editBasis);
|
|
602
|
+
}
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
if (node.executor.operation.kind === "project.commit-edits") {
|
|
606
|
+
const parsed = ProjectEditCommitReceiptSchema.safeParse(output.value);
|
|
607
|
+
if (parsed.success && parsed.data.projectId === project) {
|
|
608
|
+
candidates.set(canonicalJson(parsed.data.editBasis), parsed.data.editBasis);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (
|
|
613
|
+
candidates.size !== 1
|
|
614
|
+
|| !candidates.has(canonicalJson(exactBasis))
|
|
615
|
+
) {
|
|
616
|
+
throw new ApplicationError(
|
|
617
|
+
"authorization-required",
|
|
618
|
+
`Node ${request.node.key} must carry its edit basis directly from one host-owned project snapshot or commit.`,
|
|
619
|
+
{ operation: request.operation.kind, project },
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function assertSpatialProjectBasisProvenance(request: NodeExecutionPlanningRequest, input: JsonValue): void {
|
|
625
|
+
if (!request.operation.kind.startsWith("spatial.project.")
|
|
626
|
+
|| request.operation.kind === "spatial.project.reconcile") return;
|
|
627
|
+
if (!jsonObject(input) || typeof input.project !== "string") throw new ApplicationError("authorization-required", "Spatial mutation requires an exact bound project.");
|
|
628
|
+
const expected = SpatialProjectBasisSchema.parse(input.expected);
|
|
629
|
+
const bases = new Set<string>();
|
|
630
|
+
for (const subject of request.graphPlan.staticBindings.initialSubjects) {
|
|
631
|
+
if (subject.kind === "spatial-project" && subject.id === input.project) bases.add(canonicalJson(subject.basis));
|
|
632
|
+
}
|
|
633
|
+
const nodes = new Map(request.graphPlan.graph.nodes.map(node => [node.key, node]));
|
|
634
|
+
for (const [key, output] of Object.entries(request.dependencyOutputs)) {
|
|
635
|
+
const node = nodes.get(key);
|
|
636
|
+
if (node === undefined || !isOperationGraphNode(node)) continue;
|
|
637
|
+
if (node.executor.operation.kind === "spatial.project.snapshot") {
|
|
638
|
+
const snapshot = SpatialProjectSnapshotOutputSchema.safeParse(output.value);
|
|
639
|
+
if (snapshot.success && snapshot.data.projectId === input.project) bases.add(canonicalJson(snapshot.data.basis));
|
|
640
|
+
} else if (node.executor.operation.kind.startsWith("spatial.project.")) {
|
|
641
|
+
const mutation = SpatialProjectMutationOutputSchema.safeParse(output.value);
|
|
642
|
+
if (mutation.success && mutation.data.projectId === input.project && mutation.data.kind === "completed") {
|
|
643
|
+
bases.add(canonicalJson({ version: 2, sha256: mutation.data.projectRevisionSha256 }));
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (!bases.has(canonicalJson(expected))) throw new ApplicationError("authorization-required", "Spatial mutation basis must come from this project's bound snapshot or a completed typed mutation dependency.");
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function assertCreativeBaseProvenance(
|
|
651
|
+
request: NodeExecutionPlanningRequest,
|
|
652
|
+
input: JsonValue,
|
|
653
|
+
): void {
|
|
654
|
+
if (
|
|
655
|
+
request.operation.kind !== "edit.create-candidate-revision"
|
|
656
|
+
&& request.operation.kind !== "iteration.create-candidate"
|
|
657
|
+
) return;
|
|
658
|
+
if (!jsonObject(input)) {
|
|
659
|
+
throw new ApplicationError(
|
|
660
|
+
"authorization-required",
|
|
661
|
+
`Node ${request.node.key} has no exact creative base.`,
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
const exactBase = CreativeBaseV1Schema.parse(input.base);
|
|
665
|
+
const candidates = new Set<string>();
|
|
666
|
+
const nodes = new Map(
|
|
667
|
+
request.graphPlan.graph.nodes.map(node => [node.key, node]),
|
|
668
|
+
);
|
|
669
|
+
for (const [nodeKey, output] of Object.entries(request.dependencyOutputs)) {
|
|
670
|
+
const node = nodes.get(nodeKey);
|
|
671
|
+
if (
|
|
672
|
+
node === undefined
|
|
673
|
+
|| !isOperationGraphNode(node)
|
|
674
|
+
|| node.executor.operation.kind !== "project.snapshot"
|
|
675
|
+
) continue;
|
|
676
|
+
const snapshot = ProjectSnapshotOutputSchema.safeParse(output.value);
|
|
677
|
+
if (!snapshot.success || snapshot.data.project.projectId !== exactBase.projectId) {
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
candidates.add(canonicalJson(createCreativeBaseV1({
|
|
681
|
+
currentPlan: snapshot.data.currentPlan,
|
|
682
|
+
project: snapshot.data.project,
|
|
683
|
+
})));
|
|
684
|
+
}
|
|
685
|
+
const authoredInput = request.node.input;
|
|
686
|
+
if (
|
|
687
|
+
typeof authoredInput === "object"
|
|
688
|
+
&& authoredInput !== null
|
|
689
|
+
&& !Array.isArray(authoredInput)
|
|
690
|
+
&& "snapshot" in authoredInput
|
|
691
|
+
) {
|
|
692
|
+
const persisted = ProjectSnapshotOutputSchema.safeParse(
|
|
693
|
+
authoredInput.snapshot,
|
|
694
|
+
);
|
|
695
|
+
if (persisted.success) {
|
|
696
|
+
candidates.add(canonicalJson(createCreativeBaseV1({
|
|
697
|
+
currentPlan: persisted.data.currentPlan,
|
|
698
|
+
project: persisted.data.project,
|
|
699
|
+
})));
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (
|
|
703
|
+
candidates.size !== 1
|
|
704
|
+
|| !candidates.has(canonicalJson(exactBase))
|
|
705
|
+
) {
|
|
706
|
+
throw new ApplicationError(
|
|
707
|
+
"authorization-required",
|
|
708
|
+
`Node ${request.node.key} must carry its exact creative base from one host-owned or persisted project snapshot.`,
|
|
709
|
+
{
|
|
710
|
+
operation: request.operation.kind,
|
|
711
|
+
project: exactBase.projectId,
|
|
712
|
+
},
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function resolveExpectedProjectGeneration(
|
|
718
|
+
request: NodeExecutionPlanningRequest,
|
|
719
|
+
input: JsonValue,
|
|
720
|
+
project: string,
|
|
721
|
+
): string | undefined {
|
|
722
|
+
// Spatial operations carry their separately versioned aggregate basis.
|
|
723
|
+
if (request.operation.kind.startsWith("spatial.project.") || (request.operation.kind === "render.project" && request.operation.version === 4)) return undefined;
|
|
724
|
+
if (request.operation.kind === "project.snapshot") {
|
|
725
|
+
return staticProjectGeneration(request, project);
|
|
726
|
+
}
|
|
727
|
+
if (
|
|
728
|
+
request.operation.kind === "project.commit-edits"
|
|
729
|
+
|| request.operation.kind === "edit.freeze-revision"
|
|
730
|
+
|| request.operation.kind === "edit.create-candidate-revision"
|
|
731
|
+
|| request.operation.kind === "iteration.create-candidate"
|
|
732
|
+
|| request.operation.kind === "iteration.create-matrix"
|
|
733
|
+
|| request.operation.kind === "iteration.select"
|
|
734
|
+
|| request.operation.kind === "project.promote-selection"
|
|
735
|
+
|| request.operation.kind === "render.materialize-selection"
|
|
736
|
+
|| request.operation.kind === "render.bind-candidate-output"
|
|
737
|
+
) {
|
|
738
|
+
return undefined;
|
|
739
|
+
}
|
|
740
|
+
if (
|
|
741
|
+
request.operation.kind === "analysis.faces"
|
|
742
|
+
|| request.operation.kind === "analysis.music"
|
|
743
|
+
|| request.operation.kind === "analysis.project-inactivity"
|
|
744
|
+
|| request.operation.kind === "analysis.project-auto-zooms"
|
|
745
|
+
|| request.operation.kind === "analysis.scenes"
|
|
746
|
+
) {
|
|
747
|
+
// Parallel analyses bind a host-owned ProjectEditBasis in their exact
|
|
748
|
+
// input. That basis tolerates only append-only sibling analysis
|
|
749
|
+
// publication; a full generation hash would make a legitimate sibling
|
|
750
|
+
// completion poison deterministic resume.
|
|
751
|
+
return undefined;
|
|
752
|
+
}
|
|
753
|
+
const explicit = nestedGenerationSha256(input);
|
|
754
|
+
const dependency = dependencyProjectGeneration(request, project);
|
|
755
|
+
if (
|
|
756
|
+
explicit !== undefined
|
|
757
|
+
&& dependency !== undefined
|
|
758
|
+
&& explicit !== dependency
|
|
759
|
+
) {
|
|
760
|
+
throw new ApplicationError(
|
|
761
|
+
"conflict",
|
|
762
|
+
`Node ${request.node.key} carries incompatible explicit and dependency project generations.`,
|
|
763
|
+
{ operation: request.operation.kind, project },
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
const resolved = explicit ?? dependency;
|
|
767
|
+
if (
|
|
768
|
+
resolved === undefined
|
|
769
|
+
&& request.operation.policy.preparation.includes("project-state")
|
|
770
|
+
) {
|
|
771
|
+
throw new ApplicationError(
|
|
772
|
+
"authorization-required",
|
|
773
|
+
`Node ${request.node.key} must carry a typed project snapshot or commit dependency.`,
|
|
774
|
+
{ operation: request.operation.kind, project },
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
return resolved;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
export function createApplicationNodePlanner(
|
|
781
|
+
application: ApplicationContext,
|
|
782
|
+
): SchedulerNodePlanner {
|
|
783
|
+
return {
|
|
784
|
+
prepare: (request): Promise<NodePreparationBinding> => {
|
|
785
|
+
const descriptors = inputDescriptor(request);
|
|
786
|
+
const authoredBytes = jsonObject(descriptors)
|
|
787
|
+
&& typeof descriptors.bytes === "number"
|
|
788
|
+
? descriptors.bytes
|
|
789
|
+
: undefined;
|
|
790
|
+
const upperInputBytes = request.operation.policy.maxInputBytes;
|
|
791
|
+
if (
|
|
792
|
+
authoredBytes === undefined
|
|
793
|
+
|| !Number.isSafeInteger(authoredBytes)
|
|
794
|
+
|| authoredBytes < 0
|
|
795
|
+
|| !Number.isSafeInteger(upperInputBytes)
|
|
796
|
+
|| upperInputBytes < 0
|
|
797
|
+
) {
|
|
798
|
+
throw new ApplicationError(
|
|
799
|
+
"internal",
|
|
800
|
+
`Could not derive an input byte bound for node ${request.node.key}.`,
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
if (authoredBytes > upperInputBytes) {
|
|
804
|
+
throw new ApplicationError(
|
|
805
|
+
"invalid-data",
|
|
806
|
+
`Authored input for node ${request.node.key} exceeds the operation's registered input limit.`,
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
return Promise.resolve({
|
|
810
|
+
inputDescriptors: descriptors,
|
|
811
|
+
// Host binding may append timestamps, immutable media identities,
|
|
812
|
+
// capability evidence, or provider-normalized fields. Retain the
|
|
813
|
+
// authored byte count in the descriptors while authorizing no more
|
|
814
|
+
// than the operation's registered hard input ceiling.
|
|
815
|
+
upperInputBytes,
|
|
816
|
+
});
|
|
817
|
+
},
|
|
818
|
+
plan: async (request): Promise<NodeExecutionBinding> => {
|
|
819
|
+
// Reject compute-minted paths before any host code opens, hashes, probes,
|
|
820
|
+
// uploads, or prices the referenced file.
|
|
821
|
+
assertOperationFileProvenance(request);
|
|
822
|
+
assertProjectProvenance(
|
|
823
|
+
request,
|
|
824
|
+
projectReference(request.resolvedInput),
|
|
825
|
+
);
|
|
826
|
+
const exactInput = await exactOperationInput(application, request);
|
|
827
|
+
assertSpatialProjectBasisProvenance(request, exactInput);
|
|
828
|
+
assertCreativeBaseProvenance(request, exactInput);
|
|
829
|
+
const project = projectReference(exactInput);
|
|
830
|
+
assertProjectProvenance(request, project);
|
|
831
|
+
if (project !== undefined) {
|
|
832
|
+
assertProjectEditBasisProvenance(request, exactInput, project);
|
|
833
|
+
}
|
|
834
|
+
const keys = publicationKeys(request, exactInput, project);
|
|
835
|
+
let expectedProjectGeneration: string | undefined;
|
|
836
|
+
if (
|
|
837
|
+
project !== undefined
|
|
838
|
+
&& request.operation.policy.preparation.includes("project-state")
|
|
839
|
+
) {
|
|
840
|
+
expectedProjectGeneration = resolveExpectedProjectGeneration(
|
|
841
|
+
request,
|
|
842
|
+
exactInput,
|
|
843
|
+
project,
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
return {
|
|
847
|
+
exactInput,
|
|
848
|
+
...(expectedProjectGeneration === undefined
|
|
849
|
+
? {}
|
|
850
|
+
: { expectedProjectGeneration }),
|
|
851
|
+
publicationKeys: keys,
|
|
852
|
+
};
|
|
853
|
+
},
|
|
854
|
+
reconcile: async (request): Promise<NodeReconciliation> => (
|
|
855
|
+
await reconcileApplicationNode(request.application, request)
|
|
856
|
+
),
|
|
857
|
+
reconcileEffect: request => request.operation.kind === "render.project"
|
|
858
|
+
? reconcileRenderNodeEffect(request.application, request)
|
|
859
|
+
: workflowBoundary("authority", () => reconcileApplicationNode(request.application, request)),
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function exactReconciliationInput(
|
|
864
|
+
request: NodeReconciliationRequest,
|
|
865
|
+
): JsonValue {
|
|
866
|
+
return request.executionPlan?.exactInput ?? request.resolvedInput;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
async function reconcileRenderNode(
|
|
870
|
+
application: ApplicationContext,
|
|
871
|
+
request: NodeReconciliationRequest,
|
|
872
|
+
): Promise<NodeReconciliation> {
|
|
873
|
+
const executionPlan = request.executionPlan;
|
|
874
|
+
if (executionPlan === undefined) {
|
|
875
|
+
return {
|
|
876
|
+
kind: "incompatible",
|
|
877
|
+
message: "Interrupted render is missing its exact execution plan.",
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
const reconcile = request.operation.version === 4 ? reconcileProjectRenderV4 : reconcileProjectRender;
|
|
881
|
+
const result = await reconcile(
|
|
882
|
+
application,
|
|
883
|
+
executionPlan.exactInput,
|
|
884
|
+
{
|
|
885
|
+
nodeKey: request.node.key,
|
|
886
|
+
nodePlanSha256: executionPlan.nodePlanSha256,
|
|
887
|
+
runId: request.runId,
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
abortSignal: request.abortSignal,
|
|
891
|
+
beforePublication: request.beforePublication,
|
|
892
|
+
},
|
|
893
|
+
);
|
|
894
|
+
return renderNodeReconciliation(result);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function reconcileRenderNodeEffect(
|
|
898
|
+
application: ApplicationContext,
|
|
899
|
+
request: NodeReconciliationRequest,
|
|
900
|
+
): Effect.Effect<NodeReconciliation, WorkflowFailure> {
|
|
901
|
+
if (request.operation.version === 4) return workflowBoundary("authority", () => reconcileRenderNode(application, request));
|
|
902
|
+
return Effect.gen(function*() {
|
|
903
|
+
const executionPlan = request.executionPlan;
|
|
904
|
+
if (executionPlan === undefined) {
|
|
905
|
+
return { kind: "incompatible" as const, message: "Interrupted render is missing its exact execution plan." };
|
|
906
|
+
}
|
|
907
|
+
const result = yield* reconcileProjectRenderEffect(application, executionPlan.exactInput, {
|
|
908
|
+
nodeKey: request.node.key,
|
|
909
|
+
nodePlanSha256: executionPlan.nodePlanSha256,
|
|
910
|
+
runId: request.runId,
|
|
911
|
+
}, { abortSignal: request.abortSignal, beforePublication: request.beforePublication });
|
|
912
|
+
return yield* workflowValidation("authority", () => renderNodeReconciliation(result));
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function renderNodeReconciliation(result: ProjectRenderReconciliation | Awaited<ReturnType<typeof reconcileProjectRenderV4>>): NodeReconciliation {
|
|
917
|
+
if (result.kind === "retry") return result;
|
|
918
|
+
if (result.kind === "conflict") {
|
|
919
|
+
return { kind: "incompatible", message: result.message };
|
|
920
|
+
}
|
|
921
|
+
const output = JsonValueSchema.parse(result.output);
|
|
922
|
+
return {
|
|
923
|
+
kind: "completed",
|
|
924
|
+
output,
|
|
925
|
+
receiptReference: result.output.receipt.path,
|
|
926
|
+
summary: {
|
|
927
|
+
bytes: result.output.output.bytes,
|
|
928
|
+
outputPath: result.output.output.path,
|
|
929
|
+
outputSha256: result.output.output.sha256,
|
|
930
|
+
projectId: result.output.output.projectId,
|
|
931
|
+
receiptPath: result.output.receipt.path,
|
|
932
|
+
revisionSha256: result.output.output.revisionSha256,
|
|
933
|
+
},
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
async function reconcileRecordingNode(
|
|
938
|
+
application: ApplicationContext,
|
|
939
|
+
operationKind: RecordingOperationKind,
|
|
940
|
+
): Promise<NodeReconciliation> {
|
|
941
|
+
const controller = application.recordingController;
|
|
942
|
+
if (controller === undefined) {
|
|
943
|
+
return {
|
|
944
|
+
kind: "ambiguous",
|
|
945
|
+
message: "Recording controller is unavailable for live-state reconciliation.",
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
let output;
|
|
949
|
+
try {
|
|
950
|
+
output = parseRecordingOperationOutput(await controller.status());
|
|
951
|
+
} catch (error) {
|
|
952
|
+
return {
|
|
953
|
+
kind: "ambiguous",
|
|
954
|
+
message: error instanceof Error
|
|
955
|
+
? `Recording state could not be reconciled: ${error.message}`
|
|
956
|
+
: "Recording state could not be reconciled.",
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
return {
|
|
960
|
+
kind: "ambiguous",
|
|
961
|
+
message: `Current controller state ${output.state} for ${
|
|
962
|
+
output.recordingId ?? "no active recording"
|
|
963
|
+
} is not an exact receipt proving ${operationKind} completed; issue a new explicit action.`,
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
async function reconcileGatewayNode(
|
|
968
|
+
application: ApplicationContext,
|
|
969
|
+
request: NodeReconciliationRequest,
|
|
970
|
+
operationKind: GatewayOperationKind,
|
|
971
|
+
): Promise<NodeReconciliation> {
|
|
972
|
+
const executionPlan = request.executionPlan;
|
|
973
|
+
if (executionPlan === undefined) {
|
|
974
|
+
return {
|
|
975
|
+
kind: "incompatible",
|
|
976
|
+
message: "Interrupted Gateway dispatch is missing its exact execution plan.",
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
const gatewayRequest = gatewayPortRequestForOperation(
|
|
980
|
+
operationKind,
|
|
981
|
+
executionPlan.exactInput,
|
|
982
|
+
);
|
|
983
|
+
const requestId = gatewayRequestId({
|
|
984
|
+
nodeKey: request.node.key,
|
|
985
|
+
nodePlanSha256: executionPlan.nodePlanSha256,
|
|
986
|
+
operation: gatewayRequest.operation,
|
|
987
|
+
runId: request.runId,
|
|
988
|
+
});
|
|
989
|
+
const reconciliation = await reconcileGatewayOperation(application, {
|
|
990
|
+
request: gatewayRequest,
|
|
991
|
+
requestId,
|
|
992
|
+
signal: request.abortSignal,
|
|
993
|
+
});
|
|
994
|
+
switch (reconciliation.status) {
|
|
995
|
+
case "completed": {
|
|
996
|
+
const summary = summarizeGatewayOperation(
|
|
997
|
+
operationKind,
|
|
998
|
+
reconciliation.result,
|
|
999
|
+
);
|
|
1000
|
+
return {
|
|
1001
|
+
kind: "completed",
|
|
1002
|
+
output: JsonValueSchema.parse(reconciliation.result),
|
|
1003
|
+
receiptReference: reconciliation.result.receipt.path,
|
|
1004
|
+
summary: summary.fields,
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
case "not-dispatched":
|
|
1008
|
+
return {
|
|
1009
|
+
kind: "ambiguous",
|
|
1010
|
+
message:
|
|
1011
|
+
"The durable Gateway journal proves no paid call crossed dispatch, but ambiguous paid nodes require a fresh exact approval instead of automatic replay.",
|
|
1012
|
+
};
|
|
1013
|
+
case "dispatched":
|
|
1014
|
+
return {
|
|
1015
|
+
kind: "ambiguous",
|
|
1016
|
+
message:
|
|
1017
|
+
"The durable Gateway journal records paid dispatch without a completed authoritative receipt; Slopcamera will not resubmit it.",
|
|
1018
|
+
};
|
|
1019
|
+
case "failed":
|
|
1020
|
+
return {
|
|
1021
|
+
kind: "incompatible",
|
|
1022
|
+
message:
|
|
1023
|
+
`The Gateway request has an authoritative failure receipt at ${reconciliation.failureReceipt.path}.`,
|
|
1024
|
+
};
|
|
1025
|
+
case "conflict":
|
|
1026
|
+
return {
|
|
1027
|
+
kind: "incompatible",
|
|
1028
|
+
message:
|
|
1029
|
+
"The Gateway journal conflicts with this exact workflow request identity.",
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
function missing(error: unknown): boolean {
|
|
1035
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
async function readProjectTransaction(
|
|
1039
|
+
application: ApplicationContext,
|
|
1040
|
+
project: string,
|
|
1041
|
+
) {
|
|
1042
|
+
const directory = await resolveProjectDirectory(
|
|
1043
|
+
application.paths.projectRoot,
|
|
1044
|
+
project,
|
|
1045
|
+
);
|
|
1046
|
+
const fileSystem = createNodeBundleFileSystem(directory.path);
|
|
1047
|
+
try {
|
|
1048
|
+
const text = await fileSystem.readText(PROJECT_STATE_TRANSACTION_PATH);
|
|
1049
|
+
return ProjectStateTransactionV1Schema.parse(
|
|
1050
|
+
JSON.parse(text) as unknown,
|
|
1051
|
+
);
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
if (missing(error)) return null;
|
|
1054
|
+
throw error;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
async function reconcileProjectCommit(
|
|
1059
|
+
application: ApplicationContext,
|
|
1060
|
+
request: NodeReconciliationRequest,
|
|
1061
|
+
): Promise<NodeReconciliation> {
|
|
1062
|
+
const executionPlan = request.executionPlan;
|
|
1063
|
+
const input = exactReconciliationInput(request);
|
|
1064
|
+
const project = projectReference(input);
|
|
1065
|
+
if (
|
|
1066
|
+
executionPlan === undefined
|
|
1067
|
+
|| project === undefined
|
|
1068
|
+
|| !jsonObject(input)
|
|
1069
|
+
) {
|
|
1070
|
+
return {
|
|
1071
|
+
kind: "incompatible",
|
|
1072
|
+
message: "Interrupted project commit is missing its exact project-bound plan.",
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
return await withProjectPublicationLease(
|
|
1076
|
+
application,
|
|
1077
|
+
"project.commit-edits",
|
|
1078
|
+
{ project },
|
|
1079
|
+
async () => {
|
|
1080
|
+
await request.beforePublication();
|
|
1081
|
+
const [transaction, snapshot] = await Promise.all([
|
|
1082
|
+
readProjectTransaction(application, project),
|
|
1083
|
+
openProjectSnapshot(application.paths.projectRoot, project),
|
|
1084
|
+
]);
|
|
1085
|
+
let parsedInput:
|
|
1086
|
+
| CommitProjectEditsInput
|
|
1087
|
+
| CommitProjectEditsInputV2
|
|
1088
|
+
| CommitProjectEditsInputV3;
|
|
1089
|
+
if (request.operation.version === 1) {
|
|
1090
|
+
parsedInput = CommitProjectEditsInputSchema.parse(input);
|
|
1091
|
+
} else if (request.operation.version === 2) {
|
|
1092
|
+
parsedInput = CommitProjectEditsInputV2Schema.parse(input);
|
|
1093
|
+
} else if (request.operation.version === 3) {
|
|
1094
|
+
parsedInput = CommitProjectEditsInputV3Schema.parse(input);
|
|
1095
|
+
} else {
|
|
1096
|
+
return {
|
|
1097
|
+
kind: "incompatible",
|
|
1098
|
+
message:
|
|
1099
|
+
`Interrupted project commit uses unsupported operation version ${request.operation.version}.`,
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
if (projectMatchesEditBasis(parsedInput.basis, snapshot)) {
|
|
1103
|
+
return { kind: "retry" };
|
|
1104
|
+
}
|
|
1105
|
+
const transactionId = projectEditTransactionId(
|
|
1106
|
+
executionPlan.nodePlanSha256,
|
|
1107
|
+
);
|
|
1108
|
+
if (
|
|
1109
|
+
transaction?.transactionId !== transactionId
|
|
1110
|
+
|| transaction.phase !== "settled"
|
|
1111
|
+
|| transaction.active !== "after"
|
|
1112
|
+
) {
|
|
1113
|
+
return {
|
|
1114
|
+
kind: "incompatible",
|
|
1115
|
+
message: "Project state changed without this node's settled transaction receipt.",
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
const directory = await resolveProjectDirectory(
|
|
1119
|
+
application.paths.projectRoot,
|
|
1120
|
+
project,
|
|
1121
|
+
);
|
|
1122
|
+
const fileSystem = createNodeBundleFileSystem(directory.path);
|
|
1123
|
+
const [transactionProject, transactionPlan] = await Promise.all([
|
|
1124
|
+
loadVideoProject(fileSystem, transaction.after.project.path),
|
|
1125
|
+
loadProjectEditPlan(fileSystem, transaction.after.plan.path),
|
|
1126
|
+
]);
|
|
1127
|
+
const output = projectEditCommitReceipt(
|
|
1128
|
+
parsedInput,
|
|
1129
|
+
transactionProject,
|
|
1130
|
+
transactionPlan,
|
|
1131
|
+
);
|
|
1132
|
+
if (
|
|
1133
|
+
transaction.after.plan.sha256 !== output.planHash
|
|
1134
|
+
|| transaction.after.project.sha256
|
|
1135
|
+
!== output.generation.projectSha256
|
|
1136
|
+
|| transaction.after.project.sha256
|
|
1137
|
+
!== canonicalJsonSha256(transactionProject)
|
|
1138
|
+
|| transaction.after.plan.sha256
|
|
1139
|
+
!== canonicalJsonSha256(transactionPlan)
|
|
1140
|
+
|| !projectMatchesEditBasis(output.editBasis, snapshot)
|
|
1141
|
+
) {
|
|
1142
|
+
return {
|
|
1143
|
+
kind: "incompatible",
|
|
1144
|
+
message: "Settled project transaction does not match the current generation.",
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
return {
|
|
1148
|
+
kind: "completed",
|
|
1149
|
+
output: JsonValueSchema.parse(output),
|
|
1150
|
+
summary: {
|
|
1151
|
+
generationSha256: output.generation.generationSha256,
|
|
1152
|
+
operation: output.operation,
|
|
1153
|
+
planHash: output.planHash,
|
|
1154
|
+
planId: output.planId,
|
|
1155
|
+
projectId: output.projectId,
|
|
1156
|
+
},
|
|
1157
|
+
};
|
|
1158
|
+
},
|
|
1159
|
+
request.beforePublication,
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
async function reconcileVariantPromotionNode(
|
|
1164
|
+
application: ApplicationContext,
|
|
1165
|
+
request: NodeReconciliationRequest,
|
|
1166
|
+
): Promise<NodeReconciliation> {
|
|
1167
|
+
const executionPlan = request.executionPlan;
|
|
1168
|
+
if (executionPlan === undefined) {
|
|
1169
|
+
return {
|
|
1170
|
+
kind: "incompatible",
|
|
1171
|
+
message: "Interrupted editorial promotion is missing its exact project-bound plan.",
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
const project = projectReference(executionPlan.exactInput);
|
|
1175
|
+
if (project === undefined) {
|
|
1176
|
+
return {
|
|
1177
|
+
kind: "incompatible",
|
|
1178
|
+
message: "Interrupted editorial promotion has no exact project identity.",
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
return await withProjectPublicationLease(
|
|
1182
|
+
application,
|
|
1183
|
+
"project.promote-selection",
|
|
1184
|
+
{ project },
|
|
1185
|
+
async () => {
|
|
1186
|
+
await request.beforePublication();
|
|
1187
|
+
const reconciliation = await reconcileVariantSelectionPromotion(
|
|
1188
|
+
application,
|
|
1189
|
+
executionPlan.exactInput,
|
|
1190
|
+
);
|
|
1191
|
+
if (reconciliation.kind === "retry") return reconciliation;
|
|
1192
|
+
if (reconciliation.kind === "conflict") {
|
|
1193
|
+
return { kind: "incompatible", message: reconciliation.message };
|
|
1194
|
+
}
|
|
1195
|
+
const output = reconciliation.output;
|
|
1196
|
+
return {
|
|
1197
|
+
kind: "completed",
|
|
1198
|
+
output: JsonValueSchema.parse(output),
|
|
1199
|
+
receiptReference: output.artifact.path,
|
|
1200
|
+
summary: {
|
|
1201
|
+
projectId: output.projectId,
|
|
1202
|
+
promotedPlanSha256: output.promotedPlanSha256,
|
|
1203
|
+
promotionSha256: output.promotionSha256,
|
|
1204
|
+
selectionSha256: output.selectionSha256,
|
|
1205
|
+
},
|
|
1206
|
+
};
|
|
1207
|
+
},
|
|
1208
|
+
request.beforePublication,
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
async function reconcileApplicationNode(
|
|
1213
|
+
application: ApplicationContext,
|
|
1214
|
+
request: NodeReconciliationRequest,
|
|
1215
|
+
): Promise<NodeReconciliation> {
|
|
1216
|
+
if (request.operation.kind === "render.project") {
|
|
1217
|
+
return await reconcileRenderNode(application, request);
|
|
1218
|
+
}
|
|
1219
|
+
if (isGatewayOperationKind(request.operation.kind)) {
|
|
1220
|
+
return await reconcileGatewayNode(
|
|
1221
|
+
application,
|
|
1222
|
+
request,
|
|
1223
|
+
request.operation.kind,
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
if (isRecordingOperationKind(request.operation.kind)) {
|
|
1227
|
+
return await reconcileRecordingNode(
|
|
1228
|
+
application,
|
|
1229
|
+
request.operation.kind,
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
if (request.operation.kind === "project.commit-edits") {
|
|
1233
|
+
return await reconcileProjectCommit(application, request);
|
|
1234
|
+
}
|
|
1235
|
+
if (request.operation.kind === "project.promote-selection") {
|
|
1236
|
+
return await reconcileVariantPromotionNode(application, request);
|
|
1237
|
+
}
|
|
1238
|
+
if (hasLocalVerifiedReceiptReconciler(request.operation.kind)) {
|
|
1239
|
+
const executionPlan = request.executionPlan;
|
|
1240
|
+
const workspaceDirectory = request.workspaceDirectory;
|
|
1241
|
+
if (
|
|
1242
|
+
executionPlan === undefined
|
|
1243
|
+
|| workspaceDirectory === undefined
|
|
1244
|
+
) {
|
|
1245
|
+
return {
|
|
1246
|
+
kind: "incompatible",
|
|
1247
|
+
message:
|
|
1248
|
+
"Interrupted verified-receipt operation is missing its exact private recovery workspace.",
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
const reconciliation = await reconcileLocalVerifiedReceiptOperation(
|
|
1252
|
+
application,
|
|
1253
|
+
{
|
|
1254
|
+
abortSignal: request.abortSignal,
|
|
1255
|
+
beforePublication: request.beforePublication,
|
|
1256
|
+
exactInput: executionPlan.exactInput,
|
|
1257
|
+
...(executionPlan.expectedProjectGeneration === undefined
|
|
1258
|
+
? {}
|
|
1259
|
+
: {
|
|
1260
|
+
expectedProjectGeneration:
|
|
1261
|
+
executionPlan.expectedProjectGeneration,
|
|
1262
|
+
}),
|
|
1263
|
+
identity: {
|
|
1264
|
+
inputSchemaId: request.operation.inputSchemaId,
|
|
1265
|
+
kind: request.operation.kind,
|
|
1266
|
+
nodeKey: request.node.key,
|
|
1267
|
+
nodePlanSha256: executionPlan.nodePlanSha256,
|
|
1268
|
+
outputSchemaId: request.operation.outputSchemaId,
|
|
1269
|
+
runId: request.runId,
|
|
1270
|
+
version: request.operation.version,
|
|
1271
|
+
},
|
|
1272
|
+
workspaceDirectory,
|
|
1273
|
+
},
|
|
1274
|
+
);
|
|
1275
|
+
if (reconciliation.kind !== "completed") return reconciliation;
|
|
1276
|
+
return {
|
|
1277
|
+
...reconciliation,
|
|
1278
|
+
output: JsonValueSchema.parse(reconciliation.output),
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
if (request.resumeClass === "deterministic") {
|
|
1282
|
+
return { kind: "retry" };
|
|
1283
|
+
}
|
|
1284
|
+
return {
|
|
1285
|
+
kind: "ambiguous",
|
|
1286
|
+
message: `Operation ${request.operation.kind} has no application reconciliation boundary.`,
|
|
1287
|
+
};
|
|
1288
|
+
}
|