@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,1385 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
IsoTimestampSchema,
|
|
5
|
+
ProjectEditPlanV1Schema,
|
|
6
|
+
ProjectRenderOutputPathSchema,
|
|
7
|
+
RepositoryRelativePathSchema,
|
|
8
|
+
Sha256Schema,
|
|
9
|
+
VideoProjectIdSchema,
|
|
10
|
+
VideoProjectV1Schema,
|
|
11
|
+
type ProjectEditPlanV1,
|
|
12
|
+
type VideoProjectV1,
|
|
13
|
+
} from "../contracts";
|
|
14
|
+
import {
|
|
15
|
+
canonicalJson,
|
|
16
|
+
canonicalJsonSha256,
|
|
17
|
+
hashProjectEditPlan,
|
|
18
|
+
hashProjectStructure,
|
|
19
|
+
} from "../core";
|
|
20
|
+
import {
|
|
21
|
+
OrderedProjectEditV3Schema,
|
|
22
|
+
ProjectEditBatchV3Schema,
|
|
23
|
+
} from "./operations/derive/edit-batch";
|
|
24
|
+
import {
|
|
25
|
+
ProjectCommitManualZoomBindingV3Schema,
|
|
26
|
+
ProjectCommitMetadataBindingV2Schema,
|
|
27
|
+
} from "./operations/project/commit-edits";
|
|
28
|
+
import {
|
|
29
|
+
hashProjectGeneration,
|
|
30
|
+
projectEditBasis,
|
|
31
|
+
projectGenerationSha256FromHashes,
|
|
32
|
+
ProjectEditBasisSchema,
|
|
33
|
+
ProjectGenerationHashesSchema,
|
|
34
|
+
} from "./project-store";
|
|
35
|
+
import {
|
|
36
|
+
ProjectEditRevisionArtifactSchema,
|
|
37
|
+
ProjectRenderOutputReferenceSchema,
|
|
38
|
+
ProjectRenderReceiptReferenceSchema,
|
|
39
|
+
} from "./receipts";
|
|
40
|
+
|
|
41
|
+
const MAXIMUM_CREATIVE_DOCUMENT_BYTES = 256 * 1024 * 1024;
|
|
42
|
+
const CREATIVE_CANDIDATE_ID_PREFIX = "candidate_";
|
|
43
|
+
|
|
44
|
+
export const CreativeVariantKeySchema = z.string()
|
|
45
|
+
.min(1)
|
|
46
|
+
.max(64)
|
|
47
|
+
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u);
|
|
48
|
+
|
|
49
|
+
export const CreativeRenderNameSchema = z.string()
|
|
50
|
+
.min(1)
|
|
51
|
+
.max(64)
|
|
52
|
+
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u);
|
|
53
|
+
|
|
54
|
+
export const CreativeCandidateIdSchema = z.string()
|
|
55
|
+
.regex(/^candidate_[a-f0-9]{32}$/u);
|
|
56
|
+
|
|
57
|
+
export const CreativeCandidateNamespaceSchema = RepositoryRelativePathSchema
|
|
58
|
+
.refine(
|
|
59
|
+
value => /^iterations\/[a-f0-9]{64}\/candidates\/candidate_[a-f0-9]{32}$/u
|
|
60
|
+
.test(value),
|
|
61
|
+
"Creative candidate namespaces must be deterministic iteration paths.",
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
export const CreativeImmutableArtifactSchema = z.strictObject({
|
|
65
|
+
bytes: z.number().int().safe().positive().max(MAXIMUM_CREATIVE_DOCUMENT_BYTES),
|
|
66
|
+
path: RepositoryRelativePathSchema,
|
|
67
|
+
sha256: Sha256Schema,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const CreativeBaseV1BodySchema = z.strictObject({
|
|
71
|
+
currentPlan: ProjectEditPlanV1Schema,
|
|
72
|
+
editBasis: ProjectEditBasisSchema,
|
|
73
|
+
generation: ProjectGenerationHashesSchema,
|
|
74
|
+
kind: z.union([
|
|
75
|
+
z.literal("slopcamera.creative-base"),
|
|
76
|
+
z.literal("studio.creative-base"),
|
|
77
|
+
]),
|
|
78
|
+
project: VideoProjectV1Schema,
|
|
79
|
+
projectId: VideoProjectIdSchema,
|
|
80
|
+
schemaVersion: z.literal(1),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
function creativeBaseDomainHash(
|
|
84
|
+
body: z.infer<typeof CreativeBaseV1BodySchema>,
|
|
85
|
+
): string {
|
|
86
|
+
return canonicalJsonSha256({
|
|
87
|
+
domain: "studio.creative-base/v1",
|
|
88
|
+
...body,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function validateCreativeBase(
|
|
93
|
+
base: z.infer<typeof CreativeBaseV1BodySchema> & {
|
|
94
|
+
readonly baseSha256: string;
|
|
95
|
+
},
|
|
96
|
+
context: z.RefinementCtx,
|
|
97
|
+
): void {
|
|
98
|
+
const generation = hashProjectGeneration(base.project, base.currentPlan);
|
|
99
|
+
const basis = projectEditBasis(base.project, base.currentPlan);
|
|
100
|
+
if (
|
|
101
|
+
base.projectId !== base.project.projectId
|
|
102
|
+
|| base.projectId !== base.editBasis.projectId
|
|
103
|
+
|| base.currentPlan.projectId !== base.projectId
|
|
104
|
+
) {
|
|
105
|
+
context.addIssue({
|
|
106
|
+
code: "custom",
|
|
107
|
+
message: "Creative base project identities disagree.",
|
|
108
|
+
path: ["projectId"],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
base.currentPlan.projectStructureSha256
|
|
113
|
+
!== hashProjectStructure(base.project)
|
|
114
|
+
) {
|
|
115
|
+
context.addIssue({
|
|
116
|
+
code: "custom",
|
|
117
|
+
message: "Creative base plan is not bound to its exact frozen project.",
|
|
118
|
+
path: ["currentPlan", "projectStructureSha256"],
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (canonicalJson(generation) !== canonicalJson(base.generation)) {
|
|
122
|
+
context.addIssue({
|
|
123
|
+
code: "custom",
|
|
124
|
+
message: "Creative base generation does not match its frozen documents.",
|
|
125
|
+
path: ["generation"],
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (canonicalJson(basis) !== canonicalJson(base.editBasis)) {
|
|
129
|
+
context.addIssue({
|
|
130
|
+
code: "custom",
|
|
131
|
+
message: "Creative base edit basis does not match its frozen documents.",
|
|
132
|
+
path: ["editBasis"],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (
|
|
136
|
+
base.generation.currentPlanSha256 !== base.editBasis.currentPlanSha256
|
|
137
|
+
|| base.generation.generationSha256 !== projectGenerationSha256FromHashes(
|
|
138
|
+
base.generation,
|
|
139
|
+
)
|
|
140
|
+
) {
|
|
141
|
+
context.addIssue({
|
|
142
|
+
code: "custom",
|
|
143
|
+
message: "Creative base exact generation and append-tolerant basis disagree.",
|
|
144
|
+
path: ["generation"],
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const body = CreativeBaseV1BodySchema.parse({
|
|
148
|
+
currentPlan: base.currentPlan,
|
|
149
|
+
editBasis: base.editBasis,
|
|
150
|
+
generation: base.generation,
|
|
151
|
+
kind: base.kind,
|
|
152
|
+
project: base.project,
|
|
153
|
+
projectId: base.projectId,
|
|
154
|
+
schemaVersion: base.schemaVersion,
|
|
155
|
+
});
|
|
156
|
+
if (creativeBaseDomainHash(body) !== base.baseSha256) {
|
|
157
|
+
context.addIssue({
|
|
158
|
+
code: "custom",
|
|
159
|
+
message: "Creative base hash does not match its exact frozen documents.",
|
|
160
|
+
path: ["baseSha256"],
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const CreativeBaseV1Schema = CreativeBaseV1BodySchema.extend({
|
|
166
|
+
baseSha256: Sha256Schema,
|
|
167
|
+
}).strict().superRefine(validateCreativeBase);
|
|
168
|
+
|
|
169
|
+
export type CreativeBaseV1 = z.infer<typeof CreativeBaseV1Schema>;
|
|
170
|
+
|
|
171
|
+
export function createCreativeBaseV1(input: {
|
|
172
|
+
readonly currentPlan: ProjectEditPlanV1;
|
|
173
|
+
readonly project: VideoProjectV1;
|
|
174
|
+
}): CreativeBaseV1 {
|
|
175
|
+
const project = VideoProjectV1Schema.parse(input.project);
|
|
176
|
+
const currentPlan = ProjectEditPlanV1Schema.parse(input.currentPlan);
|
|
177
|
+
const body = CreativeBaseV1BodySchema.parse({
|
|
178
|
+
currentPlan,
|
|
179
|
+
editBasis: projectEditBasis(project, currentPlan),
|
|
180
|
+
generation: hashProjectGeneration(project, currentPlan),
|
|
181
|
+
kind: "slopcamera.creative-base",
|
|
182
|
+
project,
|
|
183
|
+
projectId: project.projectId,
|
|
184
|
+
schemaVersion: 1,
|
|
185
|
+
});
|
|
186
|
+
return CreativeBaseV1Schema.parse({
|
|
187
|
+
...body,
|
|
188
|
+
baseSha256: creativeBaseDomainHash(body),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export const CreativeBaseIdentityV1Schema = z.strictObject({
|
|
193
|
+
baseSha256: Sha256Schema,
|
|
194
|
+
editBasis: ProjectEditBasisSchema,
|
|
195
|
+
generation: ProjectGenerationHashesSchema,
|
|
196
|
+
projectId: VideoProjectIdSchema,
|
|
197
|
+
schemaVersion: z.literal(1),
|
|
198
|
+
}).superRefine((base, context) => {
|
|
199
|
+
if (
|
|
200
|
+
base.projectId !== base.editBasis.projectId
|
|
201
|
+
|| base.generation.currentPlanSha256 !== base.editBasis.currentPlanSha256
|
|
202
|
+
|| base.generation.generationSha256
|
|
203
|
+
!== projectGenerationSha256FromHashes(base.generation)
|
|
204
|
+
) {
|
|
205
|
+
context.addIssue({
|
|
206
|
+
code: "custom",
|
|
207
|
+
message: "Creative base identity does not describe one project generation.",
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
export type CreativeBaseIdentityV1 = z.infer<
|
|
213
|
+
typeof CreativeBaseIdentityV1Schema
|
|
214
|
+
>;
|
|
215
|
+
|
|
216
|
+
export function creativeBaseIdentityV1(
|
|
217
|
+
baseInput: CreativeBaseV1,
|
|
218
|
+
): CreativeBaseIdentityV1 {
|
|
219
|
+
const base = CreativeBaseV1Schema.parse(baseInput);
|
|
220
|
+
return CreativeBaseIdentityV1Schema.parse({
|
|
221
|
+
baseSha256: base.baseSha256,
|
|
222
|
+
editBasis: base.editBasis,
|
|
223
|
+
generation: base.generation,
|
|
224
|
+
projectId: base.projectId,
|
|
225
|
+
schemaVersion: 1,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function creativeCandidateId(input: {
|
|
230
|
+
readonly baseSha256: string;
|
|
231
|
+
readonly variantKey: string;
|
|
232
|
+
}): string {
|
|
233
|
+
const baseSha256 = Sha256Schema.parse(input.baseSha256);
|
|
234
|
+
const variantKey = CreativeVariantKeySchema.parse(input.variantKey);
|
|
235
|
+
return CreativeCandidateIdSchema.parse(
|
|
236
|
+
`${CREATIVE_CANDIDATE_ID_PREFIX}${canonicalJsonSha256({
|
|
237
|
+
baseSha256,
|
|
238
|
+
domain: "studio.creative-candidate-id/v1",
|
|
239
|
+
variantKey,
|
|
240
|
+
}).slice(0, 32)}`,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function creativeCandidateNamespace(input: {
|
|
245
|
+
readonly baseSha256: string;
|
|
246
|
+
readonly candidateId: string;
|
|
247
|
+
}): string {
|
|
248
|
+
const baseSha256 = Sha256Schema.parse(input.baseSha256);
|
|
249
|
+
const candidateId = CreativeCandidateIdSchema.parse(input.candidateId);
|
|
250
|
+
return CreativeCandidateNamespaceSchema.parse(
|
|
251
|
+
`iterations/${baseSha256}/candidates/${candidateId}`,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export const CreativeCandidateIdentityV1Schema = z.strictObject({
|
|
256
|
+
candidateId: CreativeCandidateIdSchema,
|
|
257
|
+
namespace: CreativeCandidateNamespaceSchema,
|
|
258
|
+
variantKey: CreativeVariantKeySchema,
|
|
259
|
+
}).superRefine((candidate, context) => {
|
|
260
|
+
const parts = candidate.namespace.split("/");
|
|
261
|
+
const baseSha256 = parts[1];
|
|
262
|
+
if (
|
|
263
|
+
baseSha256 === undefined
|
|
264
|
+
|| creativeCandidateId({
|
|
265
|
+
baseSha256,
|
|
266
|
+
variantKey: candidate.variantKey,
|
|
267
|
+
}) !== candidate.candidateId
|
|
268
|
+
|| parts.at(-1) !== candidate.candidateId
|
|
269
|
+
) {
|
|
270
|
+
context.addIssue({
|
|
271
|
+
code: "custom",
|
|
272
|
+
message: "Creative candidate identity is not stable within its namespace.",
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
export type CreativeCandidateIdentityV1 = z.infer<
|
|
278
|
+
typeof CreativeCandidateIdentityV1Schema
|
|
279
|
+
>;
|
|
280
|
+
|
|
281
|
+
export function createCreativeCandidateIdentityV1(input: {
|
|
282
|
+
readonly base: CreativeBaseV1 | CreativeBaseIdentityV1;
|
|
283
|
+
readonly variantKey: string;
|
|
284
|
+
}): CreativeCandidateIdentityV1 {
|
|
285
|
+
const base = "currentPlan" in input.base
|
|
286
|
+
? creativeBaseIdentityV1(input.base)
|
|
287
|
+
: CreativeBaseIdentityV1Schema.parse(input.base);
|
|
288
|
+
const candidateId = creativeCandidateId({
|
|
289
|
+
baseSha256: base.baseSha256,
|
|
290
|
+
variantKey: input.variantKey,
|
|
291
|
+
});
|
|
292
|
+
return CreativeCandidateIdentityV1Schema.parse({
|
|
293
|
+
candidateId,
|
|
294
|
+
namespace: creativeCandidateNamespace({
|
|
295
|
+
baseSha256: base.baseSha256,
|
|
296
|
+
candidateId,
|
|
297
|
+
}),
|
|
298
|
+
variantKey: input.variantKey,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export const CandidateRevisionHostBindingsV1Schema = z.strictObject({
|
|
303
|
+
manualZoomBindings: z.array(
|
|
304
|
+
ProjectCommitManualZoomBindingV3Schema,
|
|
305
|
+
).max(10_000),
|
|
306
|
+
metadataBinding: ProjectCommitMetadataBindingV2Schema.nullable(),
|
|
307
|
+
}).superRefine((bindings, context) => {
|
|
308
|
+
const zoomIds = bindings.manualZoomBindings.map(binding => binding.zoomId);
|
|
309
|
+
if (new Set(zoomIds).size !== zoomIds.length) {
|
|
310
|
+
context.addIssue({
|
|
311
|
+
code: "custom",
|
|
312
|
+
message: "Candidate revision manual zoom bindings must have unique zoom IDs.",
|
|
313
|
+
path: ["manualZoomBindings"],
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
for (let index = 1; index < zoomIds.length; index += 1) {
|
|
317
|
+
if (zoomIds[index - 1]!.localeCompare(zoomIds[index]!) >= 0) {
|
|
318
|
+
context.addIssue({
|
|
319
|
+
code: "custom",
|
|
320
|
+
message: "Candidate revision manual zoom bindings must be sorted by zoom ID.",
|
|
321
|
+
path: ["manualZoomBindings", index, "zoomId"],
|
|
322
|
+
});
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
export type CandidateRevisionHostBindingsV1 = z.infer<
|
|
329
|
+
typeof CandidateRevisionHostBindingsV1Schema
|
|
330
|
+
>;
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Creative comparison includes the frozen baseline. It therefore uses the
|
|
334
|
+
* complete V3 edit language while admitting the one canonical empty batch.
|
|
335
|
+
*/
|
|
336
|
+
export const CandidateProjectEditBatchV3Schema = z.strictObject({
|
|
337
|
+
kind: z.union([
|
|
338
|
+
z.literal("slopcamera.project-edit-batch"),
|
|
339
|
+
z.literal("studio.project-edit-batch"),
|
|
340
|
+
]),
|
|
341
|
+
ordered: z.array(OrderedProjectEditV3Schema).max(10_000),
|
|
342
|
+
schemaVersion: z.literal(3),
|
|
343
|
+
sha256: Sha256Schema,
|
|
344
|
+
}).superRefine((batch, context) => {
|
|
345
|
+
if (
|
|
346
|
+
canonicalJsonSha256({
|
|
347
|
+
kind: batch.kind,
|
|
348
|
+
ordered: batch.ordered,
|
|
349
|
+
schemaVersion: batch.schemaVersion,
|
|
350
|
+
}) !== batch.sha256
|
|
351
|
+
) {
|
|
352
|
+
context.addIssue({
|
|
353
|
+
code: "custom",
|
|
354
|
+
message: "Candidate V3 edit batch hash does not match its ordered edits.",
|
|
355
|
+
path: ["sha256"],
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
if (batch.ordered.length > 0 && !ProjectEditBatchV3Schema.safeParse(batch).success) {
|
|
359
|
+
context.addIssue({
|
|
360
|
+
code: "custom",
|
|
361
|
+
message: "Candidate edits must satisfy the complete V3 batch contract.",
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
export type CandidateProjectEditBatchV3 = z.infer<
|
|
367
|
+
typeof CandidateProjectEditBatchV3Schema
|
|
368
|
+
>;
|
|
369
|
+
|
|
370
|
+
export function createEmptyCandidateProjectEditBatchV3(): CandidateProjectEditBatchV3 {
|
|
371
|
+
const body = {
|
|
372
|
+
kind: "slopcamera.project-edit-batch" as const,
|
|
373
|
+
ordered: [],
|
|
374
|
+
schemaVersion: 3 as const,
|
|
375
|
+
};
|
|
376
|
+
return CandidateProjectEditBatchV3Schema.parse({
|
|
377
|
+
...body,
|
|
378
|
+
sha256: canonicalJsonSha256(body),
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function canonicalCandidateRevisionHostBindingsV1(input: {
|
|
383
|
+
readonly manualZoomBindings?: readonly z.infer<
|
|
384
|
+
typeof ProjectCommitManualZoomBindingV3Schema
|
|
385
|
+
>[];
|
|
386
|
+
readonly metadataBinding?: z.infer<
|
|
387
|
+
typeof ProjectCommitMetadataBindingV2Schema
|
|
388
|
+
> | null;
|
|
389
|
+
}): CandidateRevisionHostBindingsV1 {
|
|
390
|
+
return CandidateRevisionHostBindingsV1Schema.parse({
|
|
391
|
+
manualZoomBindings: [...(input.manualZoomBindings ?? [])]
|
|
392
|
+
.sort((left, right) => left.zoomId.localeCompare(right.zoomId)),
|
|
393
|
+
metadataBinding: input.metadataBinding ?? null,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function candidateRevisionDerivationSha256(input: {
|
|
398
|
+
readonly baseSha256: string;
|
|
399
|
+
readonly batch: CandidateProjectEditBatchV3;
|
|
400
|
+
readonly bindings: CandidateRevisionHostBindingsV1;
|
|
401
|
+
readonly candidate: CreativeCandidateIdentityV1;
|
|
402
|
+
readonly projectEditPlanSha256: string;
|
|
403
|
+
readonly revisionSha256: string;
|
|
404
|
+
readonly updatedAt: string;
|
|
405
|
+
}): string {
|
|
406
|
+
return canonicalJsonSha256({
|
|
407
|
+
baseSha256: Sha256Schema.parse(input.baseSha256),
|
|
408
|
+
batch: CandidateProjectEditBatchV3Schema.parse(input.batch),
|
|
409
|
+
bindings: CandidateRevisionHostBindingsV1Schema.parse(input.bindings),
|
|
410
|
+
candidate: CreativeCandidateIdentityV1Schema.parse(input.candidate),
|
|
411
|
+
domain: "studio.creative-candidate-revision-derivation/v1",
|
|
412
|
+
projectEditPlanSha256: Sha256Schema.parse(input.projectEditPlanSha256),
|
|
413
|
+
revisionSha256: Sha256Schema.parse(input.revisionSha256),
|
|
414
|
+
updatedAt: IsoTimestampSchema.parse(input.updatedAt),
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export const CreativeCandidateRevisionReferenceV1Schema = z.strictObject({
|
|
419
|
+
artifact: ProjectEditRevisionArtifactSchema,
|
|
420
|
+
base: CreativeBaseIdentityV1Schema,
|
|
421
|
+
batchSha256: Sha256Schema,
|
|
422
|
+
bindingsSha256: Sha256Schema,
|
|
423
|
+
candidate: CreativeCandidateIdentityV1Schema,
|
|
424
|
+
derivationSha256: Sha256Schema,
|
|
425
|
+
kind: z.union([
|
|
426
|
+
z.literal("slopcamera.creative-candidate-revision-reference"),
|
|
427
|
+
z.literal("studio.creative-candidate-revision-reference"),
|
|
428
|
+
]),
|
|
429
|
+
planId: ProjectEditPlanV1Schema.shape.planId,
|
|
430
|
+
projectEditPlanSha256: Sha256Schema,
|
|
431
|
+
projectId: VideoProjectIdSchema,
|
|
432
|
+
projectSha256: Sha256Schema,
|
|
433
|
+
projectStructureSha256: Sha256Schema,
|
|
434
|
+
revisionSha256: Sha256Schema,
|
|
435
|
+
schemaVersion: z.literal(1),
|
|
436
|
+
updatedAt: IsoTimestampSchema,
|
|
437
|
+
}).superRefine((revision, context) => {
|
|
438
|
+
if (
|
|
439
|
+
revision.artifact.path
|
|
440
|
+
!== `edits/revisions/${revision.artifact.sha256}.json`
|
|
441
|
+
|| revision.base.projectId !== revision.projectId
|
|
442
|
+
|| revision.base.generation.projectSha256 !== revision.projectSha256
|
|
443
|
+
|| !revision.candidate.namespace.startsWith(
|
|
444
|
+
`iterations/${revision.base.baseSha256}/`,
|
|
445
|
+
)
|
|
446
|
+
) {
|
|
447
|
+
context.addIssue({
|
|
448
|
+
code: "custom",
|
|
449
|
+
message: "Candidate revision reference identities or artifact path disagree.",
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
export type CreativeCandidateRevisionReferenceV1 = z.infer<
|
|
455
|
+
typeof CreativeCandidateRevisionReferenceV1Schema
|
|
456
|
+
>;
|
|
457
|
+
|
|
458
|
+
export const CreativeCandidateRenderV1Schema = z.strictObject({
|
|
459
|
+
name: CreativeRenderNameSchema,
|
|
460
|
+
output: ProjectRenderOutputReferenceSchema,
|
|
461
|
+
receipt: ProjectRenderReceiptReferenceSchema,
|
|
462
|
+
}).superRefine((render, context) => {
|
|
463
|
+
if (
|
|
464
|
+
render.output.projectId !== render.receipt.projectId
|
|
465
|
+
|| render.output.revisionSha256 !== render.receipt.revisionSha256
|
|
466
|
+
|| render.output.sha256 !== render.receipt.outputSha256
|
|
467
|
+
) {
|
|
468
|
+
context.addIssue({
|
|
469
|
+
code: "custom",
|
|
470
|
+
message: "Creative candidate render output and verified receipt disagree.",
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
export type CreativeCandidateRenderV1 = z.infer<
|
|
476
|
+
typeof CreativeCandidateRenderV1Schema
|
|
477
|
+
>;
|
|
478
|
+
|
|
479
|
+
function validateCanonicalRenders(
|
|
480
|
+
renders: readonly CreativeCandidateRenderV1[],
|
|
481
|
+
context: z.RefinementCtx,
|
|
482
|
+
path: readonly (number | string)[],
|
|
483
|
+
): void {
|
|
484
|
+
for (let index = 1; index < renders.length; index += 1) {
|
|
485
|
+
if (renders[index - 1]!.name.localeCompare(renders[index]!.name) >= 0) {
|
|
486
|
+
context.addIssue({
|
|
487
|
+
code: "custom",
|
|
488
|
+
message: "Creative candidate render names must be unique and sorted.",
|
|
489
|
+
path: [...path, index, "name"],
|
|
490
|
+
});
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const CreativeCandidateV1BodySchema = z.strictObject({
|
|
497
|
+
base: CreativeBaseV1Schema,
|
|
498
|
+
batch: CandidateProjectEditBatchV3Schema,
|
|
499
|
+
bindings: CandidateRevisionHostBindingsV1Schema,
|
|
500
|
+
candidate: CreativeCandidateIdentityV1Schema,
|
|
501
|
+
kind: z.union([
|
|
502
|
+
z.literal("slopcamera.creative-candidate"),
|
|
503
|
+
z.literal("studio.creative-candidate"),
|
|
504
|
+
]),
|
|
505
|
+
renders: z.array(CreativeCandidateRenderV1Schema).max(16),
|
|
506
|
+
revision: CreativeCandidateRevisionReferenceV1Schema,
|
|
507
|
+
schemaVersion: z.literal(1),
|
|
508
|
+
updatedAt: IsoTimestampSchema,
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
function creativeCandidateDomainHash(
|
|
512
|
+
body: z.infer<typeof CreativeCandidateV1BodySchema>,
|
|
513
|
+
): string {
|
|
514
|
+
return canonicalJsonSha256({
|
|
515
|
+
domain: "studio.creative-candidate/v1",
|
|
516
|
+
...body,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
export const CreativeCandidateV1Schema = CreativeCandidateV1BodySchema.extend({
|
|
521
|
+
candidateSha256: Sha256Schema,
|
|
522
|
+
}).strict().superRefine((candidate, context) => {
|
|
523
|
+
validateCanonicalRenders(candidate.renders, context, ["renders"]);
|
|
524
|
+
const base = creativeBaseIdentityV1(candidate.base);
|
|
525
|
+
if (
|
|
526
|
+
canonicalJson(base) !== canonicalJson(candidate.revision.base)
|
|
527
|
+
|| canonicalJson(candidate.candidate)
|
|
528
|
+
!== canonicalJson(candidate.revision.candidate)
|
|
529
|
+
|| candidate.batch.sha256 !== candidate.revision.batchSha256
|
|
530
|
+
|| canonicalJsonSha256(candidate.bindings)
|
|
531
|
+
!== candidate.revision.bindingsSha256
|
|
532
|
+
|| candidate.updatedAt !== candidate.revision.updatedAt
|
|
533
|
+
) {
|
|
534
|
+
context.addIssue({
|
|
535
|
+
code: "custom",
|
|
536
|
+
message: "Creative candidate derivation does not match its revision reference.",
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
const expectedDerivation = candidateRevisionDerivationSha256({
|
|
540
|
+
baseSha256: candidate.base.baseSha256,
|
|
541
|
+
batch: candidate.batch,
|
|
542
|
+
bindings: candidate.bindings,
|
|
543
|
+
candidate: candidate.candidate,
|
|
544
|
+
projectEditPlanSha256: candidate.revision.projectEditPlanSha256,
|
|
545
|
+
revisionSha256: candidate.revision.revisionSha256,
|
|
546
|
+
updatedAt: candidate.updatedAt,
|
|
547
|
+
});
|
|
548
|
+
if (expectedDerivation !== candidate.revision.derivationSha256) {
|
|
549
|
+
context.addIssue({
|
|
550
|
+
code: "custom",
|
|
551
|
+
message: "Creative candidate revision derivation hash is invalid.",
|
|
552
|
+
path: ["revision", "derivationSha256"],
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
if (candidate.renders.some(render => (
|
|
556
|
+
render.output.projectId !== candidate.base.projectId
|
|
557
|
+
|| render.output.revisionSha256 !== candidate.revision.revisionSha256
|
|
558
|
+
))) {
|
|
559
|
+
context.addIssue({
|
|
560
|
+
code: "custom",
|
|
561
|
+
message: "Creative candidate renders must belong to its one revision.",
|
|
562
|
+
path: ["renders"],
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
const body = CreativeCandidateV1BodySchema.parse({
|
|
566
|
+
base: candidate.base,
|
|
567
|
+
batch: candidate.batch,
|
|
568
|
+
bindings: candidate.bindings,
|
|
569
|
+
candidate: candidate.candidate,
|
|
570
|
+
kind: candidate.kind,
|
|
571
|
+
renders: candidate.renders,
|
|
572
|
+
revision: candidate.revision,
|
|
573
|
+
schemaVersion: candidate.schemaVersion,
|
|
574
|
+
updatedAt: candidate.updatedAt,
|
|
575
|
+
});
|
|
576
|
+
if (creativeCandidateDomainHash(body) !== candidate.candidateSha256) {
|
|
577
|
+
context.addIssue({
|
|
578
|
+
code: "custom",
|
|
579
|
+
message: "Creative candidate hash does not match its complete derivation.",
|
|
580
|
+
path: ["candidateSha256"],
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
export type CreativeCandidateV1 = z.infer<typeof CreativeCandidateV1Schema>;
|
|
586
|
+
|
|
587
|
+
export function createCreativeCandidateV1(input: {
|
|
588
|
+
readonly base: CreativeBaseV1;
|
|
589
|
+
readonly batch: CandidateProjectEditBatchV3;
|
|
590
|
+
readonly bindings: CandidateRevisionHostBindingsV1;
|
|
591
|
+
readonly candidate: CreativeCandidateIdentityV1;
|
|
592
|
+
readonly renders: readonly CreativeCandidateRenderV1[];
|
|
593
|
+
readonly revision: CreativeCandidateRevisionReferenceV1;
|
|
594
|
+
readonly updatedAt: string;
|
|
595
|
+
}): CreativeCandidateV1 {
|
|
596
|
+
const body = CreativeCandidateV1BodySchema.parse({
|
|
597
|
+
...input,
|
|
598
|
+
kind: "slopcamera.creative-candidate",
|
|
599
|
+
renders: [...input.renders]
|
|
600
|
+
.sort((left, right) => left.name.localeCompare(right.name)),
|
|
601
|
+
schemaVersion: 1,
|
|
602
|
+
});
|
|
603
|
+
return CreativeCandidateV1Schema.parse({
|
|
604
|
+
...body,
|
|
605
|
+
candidateSha256: creativeCandidateDomainHash(body),
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
export function creativeCandidatePath(input: {
|
|
610
|
+
readonly baseSha256: string;
|
|
611
|
+
readonly candidateId: string;
|
|
612
|
+
readonly candidateSha256: string;
|
|
613
|
+
}): string {
|
|
614
|
+
return RepositoryRelativePathSchema.parse(
|
|
615
|
+
`${creativeCandidateNamespace(input)}/${Sha256Schema.parse(
|
|
616
|
+
input.candidateSha256,
|
|
617
|
+
)}.json`,
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export const CreativeCandidateReferenceV1Schema = z.strictObject({
|
|
622
|
+
artifact: CreativeImmutableArtifactSchema,
|
|
623
|
+
base: CreativeBaseIdentityV1Schema,
|
|
624
|
+
candidate: CreativeCandidateIdentityV1Schema,
|
|
625
|
+
candidateSha256: Sha256Schema,
|
|
626
|
+
kind: z.union([
|
|
627
|
+
z.literal("slopcamera.creative-candidate-reference"),
|
|
628
|
+
z.literal("studio.creative-candidate-reference"),
|
|
629
|
+
]),
|
|
630
|
+
renderSetSha256: Sha256Schema,
|
|
631
|
+
revisionSha256: Sha256Schema,
|
|
632
|
+
schemaVersion: z.literal(1),
|
|
633
|
+
}).superRefine((reference, context) => {
|
|
634
|
+
const expectedPath = creativeCandidatePath({
|
|
635
|
+
baseSha256: reference.base.baseSha256,
|
|
636
|
+
candidateId: reference.candidate.candidateId,
|
|
637
|
+
candidateSha256: reference.candidateSha256,
|
|
638
|
+
});
|
|
639
|
+
if (
|
|
640
|
+
reference.artifact.path !== expectedPath
|
|
641
|
+
|| !reference.candidate.namespace.startsWith(
|
|
642
|
+
`iterations/${reference.base.baseSha256}/`,
|
|
643
|
+
)
|
|
644
|
+
) {
|
|
645
|
+
context.addIssue({
|
|
646
|
+
code: "custom",
|
|
647
|
+
message: "Creative candidate reference path or namespace is invalid.",
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
export type CreativeCandidateReferenceV1 = z.infer<
|
|
653
|
+
typeof CreativeCandidateReferenceV1Schema
|
|
654
|
+
>;
|
|
655
|
+
|
|
656
|
+
export function creativeCandidateRenderSetSha256(
|
|
657
|
+
rendersInput: readonly CreativeCandidateRenderV1[],
|
|
658
|
+
): string {
|
|
659
|
+
const renders = [...rendersInput]
|
|
660
|
+
.map(render => CreativeCandidateRenderV1Schema.parse(render))
|
|
661
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
662
|
+
return creativeCandidateRenderSummarySetSha256(
|
|
663
|
+
renders.map(render => ({
|
|
664
|
+
name: render.name,
|
|
665
|
+
outputSha256: render.output.sha256,
|
|
666
|
+
receiptSha256: render.receipt.receiptSha256,
|
|
667
|
+
})),
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
export function creativeCandidateRenderSummarySetSha256(
|
|
672
|
+
rendersInput: readonly {
|
|
673
|
+
readonly name: string;
|
|
674
|
+
readonly outputSha256: string;
|
|
675
|
+
readonly receiptSha256: string;
|
|
676
|
+
}[],
|
|
677
|
+
): string {
|
|
678
|
+
const renders = [...rendersInput].map(render => ({
|
|
679
|
+
name: CreativeRenderNameSchema.parse(render.name),
|
|
680
|
+
outputSha256: Sha256Schema.parse(render.outputSha256),
|
|
681
|
+
receiptSha256: Sha256Schema.parse(render.receiptSha256),
|
|
682
|
+
}))
|
|
683
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
684
|
+
return canonicalJsonSha256({
|
|
685
|
+
domain: "studio.creative-candidate-render-set/v1",
|
|
686
|
+
renders,
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
export function creativeCandidateReferenceV1(input: {
|
|
691
|
+
readonly artifact: z.infer<typeof CreativeImmutableArtifactSchema>;
|
|
692
|
+
readonly candidate: CreativeCandidateV1;
|
|
693
|
+
}): CreativeCandidateReferenceV1 {
|
|
694
|
+
const candidate = CreativeCandidateV1Schema.parse(input.candidate);
|
|
695
|
+
return CreativeCandidateReferenceV1Schema.parse({
|
|
696
|
+
artifact: input.artifact,
|
|
697
|
+
base: creativeBaseIdentityV1(candidate.base),
|
|
698
|
+
candidate: candidate.candidate,
|
|
699
|
+
candidateSha256: candidate.candidateSha256,
|
|
700
|
+
kind: "slopcamera.creative-candidate-reference",
|
|
701
|
+
renderSetSha256: creativeCandidateRenderSetSha256(candidate.renders),
|
|
702
|
+
revisionSha256: candidate.revision.revisionSha256,
|
|
703
|
+
schemaVersion: 1,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function compareCandidateReferences(
|
|
708
|
+
left: CreativeCandidateReferenceV1,
|
|
709
|
+
right: CreativeCandidateReferenceV1,
|
|
710
|
+
): number {
|
|
711
|
+
return left.candidate.candidateId.localeCompare(right.candidate.candidateId);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
export function variantCandidateSetSha256(
|
|
715
|
+
candidatesInput: readonly CreativeCandidateReferenceV1[],
|
|
716
|
+
): string {
|
|
717
|
+
const candidates = [...candidatesInput]
|
|
718
|
+
.map(candidate => CreativeCandidateReferenceV1Schema.parse(candidate))
|
|
719
|
+
.sort(compareCandidateReferences);
|
|
720
|
+
return canonicalJsonSha256({
|
|
721
|
+
candidates,
|
|
722
|
+
domain: "studio.variant-candidate-set/v1",
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const VariantMatrixV1BodySchema = z.strictObject({
|
|
727
|
+
base: CreativeBaseIdentityV1Schema,
|
|
728
|
+
candidateSetSha256: Sha256Schema,
|
|
729
|
+
candidates: z.array(CreativeCandidateReferenceV1Schema).min(1).max(16),
|
|
730
|
+
kind: z.union([
|
|
731
|
+
z.literal("slopcamera.variant-matrix"),
|
|
732
|
+
z.literal("studio.variant-matrix"),
|
|
733
|
+
]),
|
|
734
|
+
schemaVersion: z.literal(1),
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
function variantMatrixDomainHash(
|
|
738
|
+
body: z.infer<typeof VariantMatrixV1BodySchema>,
|
|
739
|
+
): string {
|
|
740
|
+
return canonicalJsonSha256({
|
|
741
|
+
domain: "studio.variant-matrix/v1",
|
|
742
|
+
...body,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
export const VariantMatrixV1Schema = VariantMatrixV1BodySchema.extend({
|
|
747
|
+
matrixSha256: Sha256Schema,
|
|
748
|
+
}).strict().superRefine((matrix, context) => {
|
|
749
|
+
for (let index = 0; index < matrix.candidates.length; index += 1) {
|
|
750
|
+
const candidate = matrix.candidates[index]!;
|
|
751
|
+
if (canonicalJson(candidate.base) !== canonicalJson(matrix.base)) {
|
|
752
|
+
context.addIssue({
|
|
753
|
+
code: "custom",
|
|
754
|
+
message: "Variant matrix candidates must share the matrix base.",
|
|
755
|
+
path: ["candidates", index, "base"],
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
const previous = matrix.candidates[index - 1];
|
|
759
|
+
if (
|
|
760
|
+
previous !== undefined
|
|
761
|
+
&& compareCandidateReferences(previous, candidate) >= 0
|
|
762
|
+
) {
|
|
763
|
+
context.addIssue({
|
|
764
|
+
code: "custom",
|
|
765
|
+
message: "Variant matrix candidate IDs must be unique and sorted.",
|
|
766
|
+
path: ["candidates", index, "candidate", "candidateId"],
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (variantCandidateSetSha256(matrix.candidates) !== matrix.candidateSetSha256) {
|
|
771
|
+
context.addIssue({
|
|
772
|
+
code: "custom",
|
|
773
|
+
message: "Variant matrix candidate-set hash is invalid.",
|
|
774
|
+
path: ["candidateSetSha256"],
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
const body = VariantMatrixV1BodySchema.parse({
|
|
778
|
+
base: matrix.base,
|
|
779
|
+
candidateSetSha256: matrix.candidateSetSha256,
|
|
780
|
+
candidates: matrix.candidates,
|
|
781
|
+
kind: matrix.kind,
|
|
782
|
+
schemaVersion: matrix.schemaVersion,
|
|
783
|
+
});
|
|
784
|
+
if (variantMatrixDomainHash(body) !== matrix.matrixSha256) {
|
|
785
|
+
context.addIssue({
|
|
786
|
+
code: "custom",
|
|
787
|
+
message: "Variant matrix hash does not match its closed candidate set.",
|
|
788
|
+
path: ["matrixSha256"],
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
export type VariantMatrixV1 = z.infer<typeof VariantMatrixV1Schema>;
|
|
794
|
+
|
|
795
|
+
export function createVariantMatrixV1(input: {
|
|
796
|
+
readonly base: CreativeBaseIdentityV1;
|
|
797
|
+
readonly candidates: readonly CreativeCandidateReferenceV1[];
|
|
798
|
+
}): VariantMatrixV1 {
|
|
799
|
+
const candidates = [...input.candidates]
|
|
800
|
+
.map(candidate => CreativeCandidateReferenceV1Schema.parse(candidate))
|
|
801
|
+
.sort(compareCandidateReferences);
|
|
802
|
+
const body = VariantMatrixV1BodySchema.parse({
|
|
803
|
+
base: input.base,
|
|
804
|
+
candidateSetSha256: variantCandidateSetSha256(candidates),
|
|
805
|
+
candidates,
|
|
806
|
+
kind: "slopcamera.variant-matrix",
|
|
807
|
+
schemaVersion: 1,
|
|
808
|
+
});
|
|
809
|
+
return VariantMatrixV1Schema.parse({
|
|
810
|
+
...body,
|
|
811
|
+
matrixSha256: variantMatrixDomainHash(body),
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
export function variantMatrixPath(input: {
|
|
816
|
+
readonly baseSha256: string;
|
|
817
|
+
readonly matrixSha256: string;
|
|
818
|
+
}): string {
|
|
819
|
+
return RepositoryRelativePathSchema.parse(
|
|
820
|
+
`iterations/${Sha256Schema.parse(input.baseSha256)}/matrices/${
|
|
821
|
+
Sha256Schema.parse(input.matrixSha256)
|
|
822
|
+
}.json`,
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
export const VariantMatrixReferenceV1Schema = z.strictObject({
|
|
827
|
+
artifact: CreativeImmutableArtifactSchema,
|
|
828
|
+
base: CreativeBaseIdentityV1Schema,
|
|
829
|
+
candidateCount: z.number().int().safe().min(1).max(16),
|
|
830
|
+
candidateSetSha256: Sha256Schema,
|
|
831
|
+
kind: z.union([
|
|
832
|
+
z.literal("slopcamera.variant-matrix-reference"),
|
|
833
|
+
z.literal("studio.variant-matrix-reference"),
|
|
834
|
+
]),
|
|
835
|
+
matrixSha256: Sha256Schema,
|
|
836
|
+
schemaVersion: z.literal(1),
|
|
837
|
+
}).superRefine((reference, context) => {
|
|
838
|
+
if (reference.artifact.path !== variantMatrixPath({
|
|
839
|
+
baseSha256: reference.base.baseSha256,
|
|
840
|
+
matrixSha256: reference.matrixSha256,
|
|
841
|
+
})) {
|
|
842
|
+
context.addIssue({
|
|
843
|
+
code: "custom",
|
|
844
|
+
message: "Variant matrix reference path is not content-addressed.",
|
|
845
|
+
path: ["artifact", "path"],
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
export type VariantMatrixReferenceV1 = z.infer<
|
|
851
|
+
typeof VariantMatrixReferenceV1Schema
|
|
852
|
+
>;
|
|
853
|
+
|
|
854
|
+
export function variantMatrixReferenceV1(input: {
|
|
855
|
+
readonly artifact: z.infer<typeof CreativeImmutableArtifactSchema>;
|
|
856
|
+
readonly matrix: VariantMatrixV1;
|
|
857
|
+
}): VariantMatrixReferenceV1 {
|
|
858
|
+
const matrix = VariantMatrixV1Schema.parse(input.matrix);
|
|
859
|
+
return VariantMatrixReferenceV1Schema.parse({
|
|
860
|
+
artifact: input.artifact,
|
|
861
|
+
base: matrix.base,
|
|
862
|
+
candidateCount: matrix.candidates.length,
|
|
863
|
+
candidateSetSha256: matrix.candidateSetSha256,
|
|
864
|
+
kind: "slopcamera.variant-matrix-reference",
|
|
865
|
+
matrixSha256: matrix.matrixSha256,
|
|
866
|
+
schemaVersion: 1,
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
export const VariantSelectionEvidenceScoreV1Schema = z.strictObject({
|
|
871
|
+
criterion: z.string().min(1).max(128),
|
|
872
|
+
score: z.number().finite().min(-1_000_000).max(1_000_000),
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
export const VariantSelectionEvidenceV1Schema = z.strictObject({
|
|
876
|
+
rationale: z.string().min(1).max(8_192),
|
|
877
|
+
scores: z.array(VariantSelectionEvidenceScoreV1Schema).max(64).optional(),
|
|
878
|
+
}).superRefine((evidence, context) => {
|
|
879
|
+
const scores = evidence.scores ?? [];
|
|
880
|
+
for (let index = 1; index < scores.length; index += 1) {
|
|
881
|
+
if (scores[index - 1]!.criterion.localeCompare(scores[index]!.criterion) >= 0) {
|
|
882
|
+
context.addIssue({
|
|
883
|
+
code: "custom",
|
|
884
|
+
message: "Selection evidence criteria must be unique and sorted.",
|
|
885
|
+
path: ["scores", index, "criterion"],
|
|
886
|
+
});
|
|
887
|
+
break;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
export const VariantSelectionResultV1Schema = z.strictObject({
|
|
893
|
+
candidateSha256: Sha256Schema,
|
|
894
|
+
renders: z.array(z.strictObject({
|
|
895
|
+
name: CreativeRenderNameSchema,
|
|
896
|
+
outputSha256: Sha256Schema,
|
|
897
|
+
receiptSha256: Sha256Schema,
|
|
898
|
+
})).max(16),
|
|
899
|
+
revisionSha256: Sha256Schema,
|
|
900
|
+
}).superRefine((result, context) => {
|
|
901
|
+
for (let index = 1; index < result.renders.length; index += 1) {
|
|
902
|
+
if (
|
|
903
|
+
result.renders[index - 1]!.name.localeCompare(
|
|
904
|
+
result.renders[index]!.name,
|
|
905
|
+
) >= 0
|
|
906
|
+
) {
|
|
907
|
+
context.addIssue({
|
|
908
|
+
code: "custom",
|
|
909
|
+
message: "Selection result render names must be unique and sorted.",
|
|
910
|
+
path: ["renders", index, "name"],
|
|
911
|
+
});
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
const VariantSelectionV1BodySchema = z.strictObject({
|
|
918
|
+
base: CreativeBaseIdentityV1Schema,
|
|
919
|
+
chosen: CreativeCandidateReferenceV1Schema,
|
|
920
|
+
evidence: VariantSelectionEvidenceV1Schema.optional(),
|
|
921
|
+
kind: z.union([
|
|
922
|
+
z.literal("slopcamera.variant-selection"),
|
|
923
|
+
z.literal("studio.variant-selection"),
|
|
924
|
+
]),
|
|
925
|
+
matrix: VariantMatrixReferenceV1Schema,
|
|
926
|
+
result: VariantSelectionResultV1Schema,
|
|
927
|
+
schemaVersion: z.literal(1),
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
function variantSelectionDomainHash(
|
|
931
|
+
body: z.infer<typeof VariantSelectionV1BodySchema>,
|
|
932
|
+
): string {
|
|
933
|
+
return canonicalJsonSha256({
|
|
934
|
+
domain: "studio.variant-selection/v1",
|
|
935
|
+
...body,
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
export const VariantSelectionV1Schema = VariantSelectionV1BodySchema.extend({
|
|
940
|
+
selectionSha256: Sha256Schema,
|
|
941
|
+
}).strict().superRefine((selection, context) => {
|
|
942
|
+
if (
|
|
943
|
+
canonicalJson(selection.base) !== canonicalJson(selection.matrix.base)
|
|
944
|
+
|| canonicalJson(selection.base) !== canonicalJson(selection.chosen.base)
|
|
945
|
+
|| selection.result.candidateSha256 !== selection.chosen.candidateSha256
|
|
946
|
+
|| selection.result.revisionSha256 !== selection.chosen.revisionSha256
|
|
947
|
+
|| creativeCandidateRenderSummarySetSha256(selection.result.renders)
|
|
948
|
+
!== selection.chosen.renderSetSha256
|
|
949
|
+
) {
|
|
950
|
+
context.addIssue({
|
|
951
|
+
code: "custom",
|
|
952
|
+
message: "Variant selection base, chosen candidate, and result disagree.",
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
const body = VariantSelectionV1BodySchema.parse({
|
|
956
|
+
base: selection.base,
|
|
957
|
+
chosen: selection.chosen,
|
|
958
|
+
...(selection.evidence === undefined
|
|
959
|
+
? {}
|
|
960
|
+
: { evidence: selection.evidence }),
|
|
961
|
+
kind: selection.kind,
|
|
962
|
+
matrix: selection.matrix,
|
|
963
|
+
result: selection.result,
|
|
964
|
+
schemaVersion: selection.schemaVersion,
|
|
965
|
+
});
|
|
966
|
+
if (variantSelectionDomainHash(body) !== selection.selectionSha256) {
|
|
967
|
+
context.addIssue({
|
|
968
|
+
code: "custom",
|
|
969
|
+
message: "Variant selection hash does not match its exact decision.",
|
|
970
|
+
path: ["selectionSha256"],
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
export type VariantSelectionV1 = z.infer<typeof VariantSelectionV1Schema>;
|
|
976
|
+
|
|
977
|
+
export function createVariantSelectionV1(input: {
|
|
978
|
+
readonly candidate: CreativeCandidateV1;
|
|
979
|
+
readonly chosen: CreativeCandidateReferenceV1;
|
|
980
|
+
readonly evidence?: z.infer<typeof VariantSelectionEvidenceV1Schema>;
|
|
981
|
+
readonly matrix: VariantMatrixReferenceV1;
|
|
982
|
+
}): VariantSelectionV1 {
|
|
983
|
+
const candidate = CreativeCandidateV1Schema.parse(input.candidate);
|
|
984
|
+
const renders = candidate.renders.map(render => ({
|
|
985
|
+
name: render.name,
|
|
986
|
+
outputSha256: render.output.sha256,
|
|
987
|
+
receiptSha256: render.receipt.receiptSha256,
|
|
988
|
+
}));
|
|
989
|
+
const evidence = input.evidence === undefined
|
|
990
|
+
? undefined
|
|
991
|
+
: VariantSelectionEvidenceV1Schema.parse({
|
|
992
|
+
...input.evidence,
|
|
993
|
+
...(input.evidence.scores === undefined
|
|
994
|
+
? {}
|
|
995
|
+
: {
|
|
996
|
+
scores: [...input.evidence.scores].sort((left, right) => (
|
|
997
|
+
left.criterion.localeCompare(right.criterion)
|
|
998
|
+
)),
|
|
999
|
+
}),
|
|
1000
|
+
});
|
|
1001
|
+
const body = VariantSelectionV1BodySchema.parse({
|
|
1002
|
+
base: input.matrix.base,
|
|
1003
|
+
chosen: input.chosen,
|
|
1004
|
+
...(evidence === undefined ? {} : { evidence }),
|
|
1005
|
+
kind: "slopcamera.variant-selection",
|
|
1006
|
+
matrix: input.matrix,
|
|
1007
|
+
result: {
|
|
1008
|
+
candidateSha256: candidate.candidateSha256,
|
|
1009
|
+
renders,
|
|
1010
|
+
revisionSha256: candidate.revision.revisionSha256,
|
|
1011
|
+
},
|
|
1012
|
+
schemaVersion: 1,
|
|
1013
|
+
});
|
|
1014
|
+
return VariantSelectionV1Schema.parse({
|
|
1015
|
+
...body,
|
|
1016
|
+
selectionSha256: variantSelectionDomainHash(body),
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
export function variantSelectionPath(input: {
|
|
1021
|
+
readonly baseSha256: string;
|
|
1022
|
+
readonly selectionSha256: string;
|
|
1023
|
+
}): string {
|
|
1024
|
+
return RepositoryRelativePathSchema.parse(
|
|
1025
|
+
`iterations/${Sha256Schema.parse(input.baseSha256)}/selections/${
|
|
1026
|
+
Sha256Schema.parse(input.selectionSha256)
|
|
1027
|
+
}.json`,
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
export const VariantSelectionReferenceV1Schema = z.strictObject({
|
|
1032
|
+
artifact: CreativeImmutableArtifactSchema,
|
|
1033
|
+
base: CreativeBaseIdentityV1Schema,
|
|
1034
|
+
chosenCandidateId: CreativeCandidateIdSchema,
|
|
1035
|
+
chosenCandidateSha256: Sha256Schema,
|
|
1036
|
+
kind: z.union([
|
|
1037
|
+
z.literal("slopcamera.variant-selection-reference"),
|
|
1038
|
+
z.literal("studio.variant-selection-reference"),
|
|
1039
|
+
]),
|
|
1040
|
+
matrixSha256: Sha256Schema,
|
|
1041
|
+
revisionSha256: Sha256Schema,
|
|
1042
|
+
schemaVersion: z.literal(1),
|
|
1043
|
+
selectionSha256: Sha256Schema,
|
|
1044
|
+
}).superRefine((reference, context) => {
|
|
1045
|
+
if (reference.artifact.path !== variantSelectionPath({
|
|
1046
|
+
baseSha256: reference.base.baseSha256,
|
|
1047
|
+
selectionSha256: reference.selectionSha256,
|
|
1048
|
+
})) {
|
|
1049
|
+
context.addIssue({
|
|
1050
|
+
code: "custom",
|
|
1051
|
+
message: "Variant selection reference path is not content-addressed.",
|
|
1052
|
+
path: ["artifact", "path"],
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
export type VariantSelectionReferenceV1 = z.infer<
|
|
1058
|
+
typeof VariantSelectionReferenceV1Schema
|
|
1059
|
+
>;
|
|
1060
|
+
|
|
1061
|
+
export function variantSelectionReferenceV1(input: {
|
|
1062
|
+
readonly artifact: z.infer<typeof CreativeImmutableArtifactSchema>;
|
|
1063
|
+
readonly selection: VariantSelectionV1;
|
|
1064
|
+
}): VariantSelectionReferenceV1 {
|
|
1065
|
+
const selection = VariantSelectionV1Schema.parse(input.selection);
|
|
1066
|
+
return VariantSelectionReferenceV1Schema.parse({
|
|
1067
|
+
artifact: input.artifact,
|
|
1068
|
+
base: selection.base,
|
|
1069
|
+
chosenCandidateId: selection.chosen.candidate.candidateId,
|
|
1070
|
+
chosenCandidateSha256: selection.chosen.candidateSha256,
|
|
1071
|
+
kind: "slopcamera.variant-selection-reference",
|
|
1072
|
+
matrixSha256: selection.matrix.matrixSha256,
|
|
1073
|
+
revisionSha256: selection.result.revisionSha256,
|
|
1074
|
+
schemaVersion: 1,
|
|
1075
|
+
selectionSha256: selection.selectionSha256,
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
const EditorialPromotionReceiptV1BodySchema = z.strictObject({
|
|
1080
|
+
base: CreativeBaseIdentityV1Schema,
|
|
1081
|
+
candidate: CreativeCandidateReferenceV1Schema,
|
|
1082
|
+
kind: z.union([
|
|
1083
|
+
z.literal("slopcamera.editorial-promotion-receipt"),
|
|
1084
|
+
z.literal("studio.editorial-promotion-receipt"),
|
|
1085
|
+
]),
|
|
1086
|
+
promotedBasis: ProjectEditBasisSchema,
|
|
1087
|
+
promotedPlanId: ProjectEditPlanV1Schema.shape.planId,
|
|
1088
|
+
promotedPlanSha256: Sha256Schema,
|
|
1089
|
+
schemaVersion: z.literal(1),
|
|
1090
|
+
selection: VariantSelectionReferenceV1Schema,
|
|
1091
|
+
transactionId: z.string().regex(/^transaction_[a-f0-9]{32}$/u),
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
function editorialPromotionDomainHash(
|
|
1095
|
+
body: z.infer<typeof EditorialPromotionReceiptV1BodySchema>,
|
|
1096
|
+
): string {
|
|
1097
|
+
return canonicalJsonSha256({
|
|
1098
|
+
domain: "studio.editorial-promotion-receipt/v1",
|
|
1099
|
+
...body,
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
export const EditorialPromotionReceiptV1Schema =
|
|
1104
|
+
EditorialPromotionReceiptV1BodySchema.extend({
|
|
1105
|
+
promotionSha256: Sha256Schema,
|
|
1106
|
+
}).strict().superRefine((receipt, context) => {
|
|
1107
|
+
if (
|
|
1108
|
+
canonicalJson(receipt.base) !== canonicalJson(receipt.selection.base)
|
|
1109
|
+
|| canonicalJson(receipt.base) !== canonicalJson(receipt.candidate.base)
|
|
1110
|
+
|| receipt.selection.chosenCandidateSha256
|
|
1111
|
+
!== receipt.candidate.candidateSha256
|
|
1112
|
+
|| receipt.promotedBasis.currentPlanSha256
|
|
1113
|
+
!== receipt.promotedPlanSha256
|
|
1114
|
+
) {
|
|
1115
|
+
context.addIssue({
|
|
1116
|
+
code: "custom",
|
|
1117
|
+
message: "Editorial promotion receipt identities disagree.",
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
const body = EditorialPromotionReceiptV1BodySchema.parse({
|
|
1121
|
+
base: receipt.base,
|
|
1122
|
+
candidate: receipt.candidate,
|
|
1123
|
+
kind: receipt.kind,
|
|
1124
|
+
promotedBasis: receipt.promotedBasis,
|
|
1125
|
+
promotedPlanId: receipt.promotedPlanId,
|
|
1126
|
+
promotedPlanSha256: receipt.promotedPlanSha256,
|
|
1127
|
+
schemaVersion: receipt.schemaVersion,
|
|
1128
|
+
selection: receipt.selection,
|
|
1129
|
+
transactionId: receipt.transactionId,
|
|
1130
|
+
});
|
|
1131
|
+
if (editorialPromotionDomainHash(body) !== receipt.promotionSha256) {
|
|
1132
|
+
context.addIssue({
|
|
1133
|
+
code: "custom",
|
|
1134
|
+
message: "Editorial promotion receipt hash is invalid.",
|
|
1135
|
+
path: ["promotionSha256"],
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
export type EditorialPromotionReceiptV1 = z.infer<
|
|
1141
|
+
typeof EditorialPromotionReceiptV1Schema
|
|
1142
|
+
>;
|
|
1143
|
+
|
|
1144
|
+
export function editorialPromotionTransactionId(selectionSha256: string): string {
|
|
1145
|
+
return `transaction_${canonicalJsonSha256({
|
|
1146
|
+
domain: "studio.editorial-promotion-transaction/v1",
|
|
1147
|
+
selectionSha256: Sha256Schema.parse(selectionSha256),
|
|
1148
|
+
}).slice(0, 32)}`;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
export function createEditorialPromotionReceiptV1(input: {
|
|
1152
|
+
readonly base: CreativeBaseIdentityV1;
|
|
1153
|
+
readonly candidate: CreativeCandidateReferenceV1;
|
|
1154
|
+
readonly promotedPlan: ProjectEditPlanV1;
|
|
1155
|
+
readonly selection: VariantSelectionReferenceV1;
|
|
1156
|
+
readonly frozenProject: VideoProjectV1;
|
|
1157
|
+
}): EditorialPromotionReceiptV1 {
|
|
1158
|
+
const promotedPlan = ProjectEditPlanV1Schema.parse(input.promotedPlan);
|
|
1159
|
+
const frozenProject = VideoProjectV1Schema.parse(input.frozenProject);
|
|
1160
|
+
const body = EditorialPromotionReceiptV1BodySchema.parse({
|
|
1161
|
+
base: input.base,
|
|
1162
|
+
candidate: input.candidate,
|
|
1163
|
+
kind: "slopcamera.editorial-promotion-receipt",
|
|
1164
|
+
promotedBasis: projectEditBasis(frozenProject, promotedPlan),
|
|
1165
|
+
promotedPlanId: promotedPlan.planId,
|
|
1166
|
+
promotedPlanSha256: hashProjectEditPlan(promotedPlan),
|
|
1167
|
+
schemaVersion: 1,
|
|
1168
|
+
selection: input.selection,
|
|
1169
|
+
transactionId: editorialPromotionTransactionId(
|
|
1170
|
+
input.selection.selectionSha256,
|
|
1171
|
+
),
|
|
1172
|
+
});
|
|
1173
|
+
return EditorialPromotionReceiptV1Schema.parse({
|
|
1174
|
+
...body,
|
|
1175
|
+
promotionSha256: editorialPromotionDomainHash(body),
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
export function editorialPromotionReceiptPath(input: {
|
|
1180
|
+
readonly baseSha256: string;
|
|
1181
|
+
}): string {
|
|
1182
|
+
return RepositoryRelativePathSchema.parse(
|
|
1183
|
+
`iterations/${Sha256Schema.parse(input.baseSha256)}/promotions/editorial.json`,
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
export const EditorialPromotionReceiptReferenceV1Schema = z.strictObject({
|
|
1188
|
+
artifact: CreativeImmutableArtifactSchema,
|
|
1189
|
+
kind: z.union([
|
|
1190
|
+
z.literal("slopcamera.editorial-promotion-receipt-reference"),
|
|
1191
|
+
z.literal("studio.editorial-promotion-receipt-reference"),
|
|
1192
|
+
]),
|
|
1193
|
+
projectId: VideoProjectIdSchema,
|
|
1194
|
+
promotedPlanSha256: Sha256Schema,
|
|
1195
|
+
promotionSha256: Sha256Schema,
|
|
1196
|
+
schemaVersion: z.literal(1),
|
|
1197
|
+
selectionSha256: Sha256Schema,
|
|
1198
|
+
}).superRefine((reference, context) => {
|
|
1199
|
+
const segments = reference.artifact.path.split("/");
|
|
1200
|
+
if (
|
|
1201
|
+
segments[0] !== "iterations"
|
|
1202
|
+
|| segments[2] !== "promotions"
|
|
1203
|
+
|| segments[3] !== "editorial.json"
|
|
1204
|
+
) {
|
|
1205
|
+
context.addIssue({
|
|
1206
|
+
code: "custom",
|
|
1207
|
+
message: "Editorial promotion receipt reference path is invalid.",
|
|
1208
|
+
path: ["artifact", "path"],
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
export type EditorialPromotionReceiptReferenceV1 = z.infer<
|
|
1214
|
+
typeof EditorialPromotionReceiptReferenceV1Schema
|
|
1215
|
+
>;
|
|
1216
|
+
|
|
1217
|
+
export function editorialPromotionReceiptReferenceV1(input: {
|
|
1218
|
+
readonly artifact: z.infer<typeof CreativeImmutableArtifactSchema>;
|
|
1219
|
+
readonly receipt: EditorialPromotionReceiptV1;
|
|
1220
|
+
}): EditorialPromotionReceiptReferenceV1 {
|
|
1221
|
+
const receipt = EditorialPromotionReceiptV1Schema.parse(input.receipt);
|
|
1222
|
+
return EditorialPromotionReceiptReferenceV1Schema.parse({
|
|
1223
|
+
artifact: input.artifact,
|
|
1224
|
+
kind: "slopcamera.editorial-promotion-receipt-reference",
|
|
1225
|
+
projectId: receipt.base.projectId,
|
|
1226
|
+
promotedPlanSha256: receipt.promotedPlanSha256,
|
|
1227
|
+
promotionSha256: receipt.promotionSha256,
|
|
1228
|
+
schemaVersion: 1,
|
|
1229
|
+
selectionSha256: receipt.selection.selectionSha256,
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
const DeliveryMaterializationReceiptV1BodySchema = z.strictObject({
|
|
1234
|
+
candidate: CreativeCandidateReferenceV1Schema,
|
|
1235
|
+
destination: ProjectRenderOutputReferenceSchema,
|
|
1236
|
+
kind: z.union([
|
|
1237
|
+
z.literal("slopcamera.delivery-materialization-receipt"),
|
|
1238
|
+
z.literal("studio.delivery-materialization-receipt"),
|
|
1239
|
+
]),
|
|
1240
|
+
renderName: CreativeRenderNameSchema,
|
|
1241
|
+
schemaVersion: z.literal(1),
|
|
1242
|
+
selection: VariantSelectionReferenceV1Schema,
|
|
1243
|
+
source: ProjectRenderOutputReferenceSchema,
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
function deliveryMaterializationDomainHash(
|
|
1247
|
+
body: z.infer<typeof DeliveryMaterializationReceiptV1BodySchema>,
|
|
1248
|
+
): string {
|
|
1249
|
+
return canonicalJsonSha256({
|
|
1250
|
+
domain: "studio.delivery-materialization-receipt/v1",
|
|
1251
|
+
...body,
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
export const DeliveryMaterializationReceiptV1Schema =
|
|
1256
|
+
DeliveryMaterializationReceiptV1BodySchema.extend({
|
|
1257
|
+
materializationSha256: Sha256Schema,
|
|
1258
|
+
}).strict().superRefine((receipt, context) => {
|
|
1259
|
+
if (
|
|
1260
|
+
receipt.source.path === receipt.destination.path
|
|
1261
|
+
|| receipt.source.bytes !== receipt.destination.bytes
|
|
1262
|
+
|| receipt.source.sha256 !== receipt.destination.sha256
|
|
1263
|
+
|| receipt.source.projectId !== receipt.destination.projectId
|
|
1264
|
+
|| receipt.source.revisionSha256 !== receipt.destination.revisionSha256
|
|
1265
|
+
|| receipt.source.planArtifactSha256
|
|
1266
|
+
!== receipt.destination.planArtifactSha256
|
|
1267
|
+
|| canonicalJson(receipt.candidate.base)
|
|
1268
|
+
!== canonicalJson(receipt.selection.base)
|
|
1269
|
+
|| receipt.candidate.candidate.candidateId
|
|
1270
|
+
!== receipt.selection.chosenCandidateId
|
|
1271
|
+
|| receipt.candidate.candidateSha256
|
|
1272
|
+
!== receipt.selection.chosenCandidateSha256
|
|
1273
|
+
|| receipt.candidate.revisionSha256
|
|
1274
|
+
!== receipt.selection.revisionSha256
|
|
1275
|
+
|| receipt.source.projectId !== receipt.selection.base.projectId
|
|
1276
|
+
|| receipt.source.revisionSha256 !== receipt.selection.revisionSha256
|
|
1277
|
+
) {
|
|
1278
|
+
context.addIssue({
|
|
1279
|
+
code: "custom",
|
|
1280
|
+
message: "Delivery materialization does not preserve the selected render provenance and bytes.",
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
const body = DeliveryMaterializationReceiptV1BodySchema.parse({
|
|
1284
|
+
candidate: receipt.candidate,
|
|
1285
|
+
destination: receipt.destination,
|
|
1286
|
+
kind: receipt.kind,
|
|
1287
|
+
renderName: receipt.renderName,
|
|
1288
|
+
schemaVersion: receipt.schemaVersion,
|
|
1289
|
+
selection: receipt.selection,
|
|
1290
|
+
source: receipt.source,
|
|
1291
|
+
});
|
|
1292
|
+
if (
|
|
1293
|
+
deliveryMaterializationDomainHash(body)
|
|
1294
|
+
!== receipt.materializationSha256
|
|
1295
|
+
) {
|
|
1296
|
+
context.addIssue({
|
|
1297
|
+
code: "custom",
|
|
1298
|
+
message: "Delivery materialization receipt hash is invalid.",
|
|
1299
|
+
path: ["materializationSha256"],
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
|
|
1304
|
+
export type DeliveryMaterializationReceiptV1 = z.infer<
|
|
1305
|
+
typeof DeliveryMaterializationReceiptV1Schema
|
|
1306
|
+
>;
|
|
1307
|
+
|
|
1308
|
+
export function createDeliveryMaterializationReceiptV1(input: {
|
|
1309
|
+
readonly candidate: CreativeCandidateReferenceV1;
|
|
1310
|
+
readonly destinationPath: string;
|
|
1311
|
+
readonly renderName: string;
|
|
1312
|
+
readonly selection: VariantSelectionReferenceV1;
|
|
1313
|
+
readonly source: z.infer<typeof ProjectRenderOutputReferenceSchema>;
|
|
1314
|
+
}): DeliveryMaterializationReceiptV1 {
|
|
1315
|
+
const source = ProjectRenderOutputReferenceSchema.parse(input.source);
|
|
1316
|
+
const body = DeliveryMaterializationReceiptV1BodySchema.parse({
|
|
1317
|
+
candidate: input.candidate,
|
|
1318
|
+
destination: {
|
|
1319
|
+
...source,
|
|
1320
|
+
path: ProjectRenderOutputPathSchema.parse(input.destinationPath),
|
|
1321
|
+
},
|
|
1322
|
+
kind: "slopcamera.delivery-materialization-receipt",
|
|
1323
|
+
renderName: input.renderName,
|
|
1324
|
+
schemaVersion: 1,
|
|
1325
|
+
selection: input.selection,
|
|
1326
|
+
source,
|
|
1327
|
+
});
|
|
1328
|
+
return DeliveryMaterializationReceiptV1Schema.parse({
|
|
1329
|
+
...body,
|
|
1330
|
+
materializationSha256: deliveryMaterializationDomainHash(body),
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
export function deliveryMaterializationReceiptPath(
|
|
1335
|
+
materializationSha256Input: string,
|
|
1336
|
+
): string {
|
|
1337
|
+
const materializationSha256 = Sha256Schema.parse(
|
|
1338
|
+
materializationSha256Input,
|
|
1339
|
+
);
|
|
1340
|
+
return RepositoryRelativePathSchema.parse(
|
|
1341
|
+
`renders/materializations/${materializationSha256}.json`,
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
export const DeliveryMaterializationReceiptReferenceV1Schema = z.strictObject({
|
|
1346
|
+
artifact: CreativeImmutableArtifactSchema,
|
|
1347
|
+
destination: ProjectRenderOutputReferenceSchema,
|
|
1348
|
+
kind: z.union([
|
|
1349
|
+
z.literal("slopcamera.delivery-materialization-receipt-reference"),
|
|
1350
|
+
z.literal("studio.delivery-materialization-receipt-reference"),
|
|
1351
|
+
]),
|
|
1352
|
+
materializationSha256: Sha256Schema,
|
|
1353
|
+
schemaVersion: z.literal(1),
|
|
1354
|
+
selectionSha256: Sha256Schema,
|
|
1355
|
+
}).superRefine((reference, context) => {
|
|
1356
|
+
if (
|
|
1357
|
+
reference.artifact.path
|
|
1358
|
+
!== deliveryMaterializationReceiptPath(reference.materializationSha256)
|
|
1359
|
+
) {
|
|
1360
|
+
context.addIssue({
|
|
1361
|
+
code: "custom",
|
|
1362
|
+
message: "Delivery materialization receipt path is not content-addressed.",
|
|
1363
|
+
path: ["artifact", "path"],
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
|
|
1368
|
+
export type DeliveryMaterializationReceiptReferenceV1 = z.infer<
|
|
1369
|
+
typeof DeliveryMaterializationReceiptReferenceV1Schema
|
|
1370
|
+
>;
|
|
1371
|
+
|
|
1372
|
+
export function deliveryMaterializationReceiptReferenceV1(input: {
|
|
1373
|
+
readonly artifact: z.infer<typeof CreativeImmutableArtifactSchema>;
|
|
1374
|
+
readonly receipt: DeliveryMaterializationReceiptV1;
|
|
1375
|
+
}): DeliveryMaterializationReceiptReferenceV1 {
|
|
1376
|
+
const receipt = DeliveryMaterializationReceiptV1Schema.parse(input.receipt);
|
|
1377
|
+
return DeliveryMaterializationReceiptReferenceV1Schema.parse({
|
|
1378
|
+
artifact: input.artifact,
|
|
1379
|
+
destination: receipt.destination,
|
|
1380
|
+
kind: "slopcamera.delivery-materialization-receipt-reference",
|
|
1381
|
+
materializationSha256: receipt.materializationSha256,
|
|
1382
|
+
schemaVersion: 1,
|
|
1383
|
+
selectionSha256: receipt.selection.selectionSha256,
|
|
1384
|
+
});
|
|
1385
|
+
}
|