@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,1574 @@
|
|
|
1
|
+
import { reconcileStudioRun } from "./operations/studio";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { recoverSpatialRenderAttempt, recoverSpatialRenderOutput } from "./operations/spatial-render";
|
|
4
|
+
import { constants } from "node:fs";
|
|
5
|
+
import { open } from "node:fs/promises";
|
|
6
|
+
import { join, relative } from "node:path";
|
|
7
|
+
|
|
8
|
+
import type { z } from "zod";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
FaceAnalysisV1Schema,
|
|
12
|
+
MusicAnalysisV1Schema,
|
|
13
|
+
ProjectAnalysisReferenceSchema,
|
|
14
|
+
ProjectInactivityAnalysisV1Schema,
|
|
15
|
+
type VideoProjectV1,
|
|
16
|
+
} from "../contracts";
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_FACE_ANALYSIS_CONFIG,
|
|
19
|
+
buildFaceAnalysisReference,
|
|
20
|
+
} from "../cli/face-analysis-service";
|
|
21
|
+
import { resolveAudioAnalysisSubject } from "../cli/audio-analysis";
|
|
22
|
+
import {
|
|
23
|
+
DEFAULT_MUSIC_ANALYSIS_CONFIG,
|
|
24
|
+
buildMusicAnalysisReference,
|
|
25
|
+
} from "../cli/music-analysis-service";
|
|
26
|
+
import {
|
|
27
|
+
DEFAULT_PROJECT_INACTIVITY_CONFIG,
|
|
28
|
+
buildProjectInactivityAnalysisReference,
|
|
29
|
+
loadProjectInactivityReferenceEvidence,
|
|
30
|
+
} from "../cli/project-inactivity-service";
|
|
31
|
+
import {
|
|
32
|
+
openProject,
|
|
33
|
+
projectAnalysisPath,
|
|
34
|
+
type OpenProject,
|
|
35
|
+
} from "../cli/project-service";
|
|
36
|
+
import { resolveVideoAnalysisSubject } from "../cli/scene-analysis-service";
|
|
37
|
+
import {
|
|
38
|
+
canonicalJson,
|
|
39
|
+
canonicalJsonSha256,
|
|
40
|
+
sha256Hex,
|
|
41
|
+
} from "../core/canonical-json";
|
|
42
|
+
import { hashProjectStructure } from "../core/project-plan";
|
|
43
|
+
import { htmlOverlayFrameCount } from "../html-overlay";
|
|
44
|
+
import type { ApplicationContext } from "./context";
|
|
45
|
+
import { ApplicationError } from "./errors";
|
|
46
|
+
import {
|
|
47
|
+
mergeProjectAnalysisReference,
|
|
48
|
+
openLeasedProjectSnapshot,
|
|
49
|
+
} from "./project-publication-lease";
|
|
50
|
+
import {
|
|
51
|
+
ProjectEditBasisSchema,
|
|
52
|
+
assertProjectEditBasis,
|
|
53
|
+
type OpenProjectSnapshot,
|
|
54
|
+
} from "./project-store";
|
|
55
|
+
import {
|
|
56
|
+
readOperationCompletionCheckpoint,
|
|
57
|
+
type OperationCheckpointExecutionIdentity,
|
|
58
|
+
} from "./operation-completion-checkpoint";
|
|
59
|
+
import type { OperationKind } from "./operation";
|
|
60
|
+
import {
|
|
61
|
+
FacesOperationInputSchema,
|
|
62
|
+
FacesOperationOutputSchema,
|
|
63
|
+
MusicOperationInputSchema,
|
|
64
|
+
MusicOperationOutputSchema,
|
|
65
|
+
ProjectInactivityOperationInputSchema,
|
|
66
|
+
ProjectInactivityOperationOutputSchema,
|
|
67
|
+
projectInactivityRecordingBinding,
|
|
68
|
+
BoundMediaAudioEffectsInputSchema,
|
|
69
|
+
MediaAudioEffectsOutputSchema,
|
|
70
|
+
MediaAudioEffectsReceiptSchema,
|
|
71
|
+
BoundMediaColorGradeInputSchema,
|
|
72
|
+
MediaColorGradeOutputSchema,
|
|
73
|
+
MediaColorGradeReceiptSchema,
|
|
74
|
+
BoundHtmlOverlayInputSchema,
|
|
75
|
+
HtmlOverlayOutputSchema,
|
|
76
|
+
HtmlOverlayReceiptSchema,
|
|
77
|
+
MAXIMUM_HTML_OVERLAY_DOCUMENT_BYTES,
|
|
78
|
+
MAXIMUM_HTML_OVERLAY_OUTPUT_BYTES,
|
|
79
|
+
MAXIMUM_HTML_OVERLAY_RESOURCE_BYTES,
|
|
80
|
+
BoundMediaIngestInputSchema,
|
|
81
|
+
MediaIngestOutputSchema,
|
|
82
|
+
MediaIngestReceiptSchema,
|
|
83
|
+
BoundMediaOverlayInputSchema,
|
|
84
|
+
MAXIMUM_MEDIA_OVERLAY_INPUT_BYTES,
|
|
85
|
+
MediaOverlayOutputSchema,
|
|
86
|
+
MediaOverlayReceiptSchema,
|
|
87
|
+
MAXIMUM_DIAGRAM_ARTIFACT_BYTES,
|
|
88
|
+
MAXIMUM_DIAGRAM_SOURCE_BYTES,
|
|
89
|
+
MAXIMUM_VECTOR_ARTIFACT_BYTES,
|
|
90
|
+
BoundSlopcameraDiagramRenderInputSchema,
|
|
91
|
+
BoundSlopcameraImageVectorizeInputSchema,
|
|
92
|
+
SlopcameraDiagramRenderOutputSchema,
|
|
93
|
+
SlopcameraDiagramRenderReceiptSchema,
|
|
94
|
+
SlopcameraImageVectorizeOutputSchema,
|
|
95
|
+
SlopcameraImageVectorizeReceiptSchema,
|
|
96
|
+
} from "./operations";
|
|
97
|
+
import {
|
|
98
|
+
MAXIMUM_MEDIA_EFFECT_INPUT_BYTES,
|
|
99
|
+
MAXIMUM_MEDIA_INGEST_INPUT_BYTES,
|
|
100
|
+
MediaArtifactReferenceSchema,
|
|
101
|
+
bindRepositoryMedia,
|
|
102
|
+
type MediaArtifactReference,
|
|
103
|
+
} from "./operations/media/shared";
|
|
104
|
+
import {
|
|
105
|
+
MAXIMUM_LOCAL_MEDIA_EFFECT_OUTPUT_BYTES,
|
|
106
|
+
} from "../cli/media-effects-service";
|
|
107
|
+
|
|
108
|
+
export const LOCAL_VERIFIED_RECEIPT_OPERATION_KINDS = Object.freeze([
|
|
109
|
+
"scene.render",
|
|
110
|
+
"slopcamera.studio.run",
|
|
111
|
+
"analysis.faces",
|
|
112
|
+
"analysis.music",
|
|
113
|
+
"analysis.project-inactivity",
|
|
114
|
+
"media.audio-effects",
|
|
115
|
+
"media.color-grade",
|
|
116
|
+
"media.html-overlay",
|
|
117
|
+
"media.ingest",
|
|
118
|
+
"media.overlay",
|
|
119
|
+
"slopcamera.diagram.render",
|
|
120
|
+
"slopcamera.image.vectorize",
|
|
121
|
+
] as const satisfies readonly OperationKind[]);
|
|
122
|
+
|
|
123
|
+
type LocalVerifiedReceiptOperationKind =
|
|
124
|
+
typeof LOCAL_VERIFIED_RECEIPT_OPERATION_KINDS[number];
|
|
125
|
+
|
|
126
|
+
export interface VerifiedReceiptReconciliationRequest {
|
|
127
|
+
readonly abortSignal: AbortSignal;
|
|
128
|
+
readonly beforePublication: () => Promise<void>;
|
|
129
|
+
readonly exactInput: unknown;
|
|
130
|
+
readonly expectedProjectGeneration?: string;
|
|
131
|
+
readonly identity: OperationCheckpointExecutionIdentity;
|
|
132
|
+
readonly workspaceDirectory: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export type VerifiedReceiptReconciliation =
|
|
136
|
+
| {
|
|
137
|
+
readonly kind: "completed";
|
|
138
|
+
readonly output: unknown;
|
|
139
|
+
readonly receiptReference?: string;
|
|
140
|
+
readonly summary: Readonly<Record<
|
|
141
|
+
string,
|
|
142
|
+
boolean | null | number | string
|
|
143
|
+
>>;
|
|
144
|
+
}
|
|
145
|
+
| { readonly kind: "retry" }
|
|
146
|
+
| { readonly kind: "ambiguous" | "incompatible"; readonly message: string };
|
|
147
|
+
|
|
148
|
+
type ProjectAnalysisReference = VideoProjectV1["analyses"][number];
|
|
149
|
+
|
|
150
|
+
function isMissing(error: unknown): boolean {
|
|
151
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function incompatible(error: unknown): VerifiedReceiptReconciliation {
|
|
155
|
+
const message = error instanceof Error
|
|
156
|
+
? error.message
|
|
157
|
+
: "Authoritative operation evidence could not be verified.";
|
|
158
|
+
return {
|
|
159
|
+
kind: "incompatible",
|
|
160
|
+
message: `Interrupted operation recovery failed closed: ${message}`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function requireAnalysisId(analysisId: string | undefined): string {
|
|
165
|
+
if (analysisId === undefined) {
|
|
166
|
+
throw new ApplicationError(
|
|
167
|
+
"incompatible",
|
|
168
|
+
"Exact analysis input is missing its deterministic analysis identity.",
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return analysisId;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function throwIfReconciliationAborted(signal: AbortSignal): void {
|
|
175
|
+
if (signal.aborted) {
|
|
176
|
+
throw new ApplicationError(
|
|
177
|
+
"cancelled",
|
|
178
|
+
"Workflow reconciliation was cancelled.",
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function readCanonicalAnalysisAt<Analysis>(
|
|
184
|
+
project: OpenProject,
|
|
185
|
+
path: string,
|
|
186
|
+
schema: z.ZodType<Analysis>,
|
|
187
|
+
expectedSha256?: string,
|
|
188
|
+
): Promise<Analysis | undefined> {
|
|
189
|
+
let text: string;
|
|
190
|
+
try {
|
|
191
|
+
text = await project.fileSystem.readText(path);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (isMissing(error)) return undefined;
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
if (expectedSha256 !== undefined && sha256Hex(text) !== expectedSha256) {
|
|
197
|
+
throw new ApplicationError(
|
|
198
|
+
"incompatible",
|
|
199
|
+
`Authoritative analysis sidecar failed its SHA-256 check: ${path}`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
let input: unknown;
|
|
203
|
+
try {
|
|
204
|
+
input = JSON.parse(text) as unknown;
|
|
205
|
+
} catch {
|
|
206
|
+
throw new ApplicationError(
|
|
207
|
+
"incompatible",
|
|
208
|
+
`Authoritative analysis sidecar is not JSON: ${path}`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
const analysis = schema.parse(input);
|
|
212
|
+
if (`${canonicalJson(analysis)}\n` !== text) {
|
|
213
|
+
throw new ApplicationError(
|
|
214
|
+
"incompatible",
|
|
215
|
+
`Authoritative analysis sidecar is not canonical JSON: ${path}`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return analysis;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function readCanonicalAnalysis<Analysis>(
|
|
222
|
+
project: OpenProject,
|
|
223
|
+
reference: ProjectAnalysisReference,
|
|
224
|
+
schema: z.ZodType<Analysis>,
|
|
225
|
+
): Promise<Analysis> {
|
|
226
|
+
const analysis = await readCanonicalAnalysisAt(
|
|
227
|
+
project,
|
|
228
|
+
reference.path,
|
|
229
|
+
schema,
|
|
230
|
+
reference.sha256,
|
|
231
|
+
);
|
|
232
|
+
if (analysis === undefined) {
|
|
233
|
+
throw new ApplicationError(
|
|
234
|
+
"incompatible",
|
|
235
|
+
`Authoritative analysis sidecar is missing: ${reference.path}`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
return analysis;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function authoritativeAnalysis<Analysis>(options: {
|
|
242
|
+
readonly analysisId: string;
|
|
243
|
+
readonly expectedKind: ProjectAnalysisReference["kind"];
|
|
244
|
+
readonly expectedPath: string;
|
|
245
|
+
readonly project: OpenProject;
|
|
246
|
+
readonly schema: z.ZodType<Analysis>;
|
|
247
|
+
}): Promise<
|
|
248
|
+
| { readonly kind: "missing" }
|
|
249
|
+
| {
|
|
250
|
+
readonly analysis: Analysis;
|
|
251
|
+
readonly kind: "orphan";
|
|
252
|
+
}
|
|
253
|
+
| {
|
|
254
|
+
readonly analysis: Analysis;
|
|
255
|
+
readonly kind: "published";
|
|
256
|
+
readonly reference: ProjectAnalysisReference;
|
|
257
|
+
}
|
|
258
|
+
> {
|
|
259
|
+
const reference = options.project.project.analyses.find(
|
|
260
|
+
candidate => candidate.analysisId === options.analysisId,
|
|
261
|
+
);
|
|
262
|
+
if (reference === undefined) {
|
|
263
|
+
const analysis = await readCanonicalAnalysisAt(
|
|
264
|
+
options.project,
|
|
265
|
+
options.expectedPath,
|
|
266
|
+
options.schema,
|
|
267
|
+
);
|
|
268
|
+
return analysis === undefined
|
|
269
|
+
? { kind: "missing" }
|
|
270
|
+
: { analysis, kind: "orphan" };
|
|
271
|
+
}
|
|
272
|
+
if (
|
|
273
|
+
reference.kind !== options.expectedKind
|
|
274
|
+
|| reference.path !== options.expectedPath
|
|
275
|
+
) {
|
|
276
|
+
throw new ApplicationError(
|
|
277
|
+
"incompatible",
|
|
278
|
+
`Project analysis ${options.analysisId} has incompatible authority.`,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
analysis: await readCanonicalAnalysis(
|
|
283
|
+
options.project,
|
|
284
|
+
reference,
|
|
285
|
+
options.schema,
|
|
286
|
+
),
|
|
287
|
+
kind: "published",
|
|
288
|
+
reference,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function adoptOrphanAnalysisReference(options: {
|
|
293
|
+
readonly abortSignal: AbortSignal;
|
|
294
|
+
readonly application: ApplicationContext;
|
|
295
|
+
readonly beforePublication: () => Promise<void>;
|
|
296
|
+
readonly operation: Extract<OperationKind, `analysis.${string}`>;
|
|
297
|
+
readonly project: string;
|
|
298
|
+
readonly projectBinding: unknown;
|
|
299
|
+
readonly reference: ProjectAnalysisReference;
|
|
300
|
+
readonly snapshot: OpenProjectSnapshot;
|
|
301
|
+
}): Promise<{
|
|
302
|
+
readonly project: VideoProjectV1;
|
|
303
|
+
readonly reference: ProjectAnalysisReference;
|
|
304
|
+
}> {
|
|
305
|
+
assertExactAnalysisProjectBinding(
|
|
306
|
+
options.projectBinding,
|
|
307
|
+
options.snapshot,
|
|
308
|
+
);
|
|
309
|
+
return await mergeProjectAnalysisReference({
|
|
310
|
+
application: options.application,
|
|
311
|
+
basis: ProjectEditBasisSchema.parse(options.projectBinding),
|
|
312
|
+
beforePublication: async () => {
|
|
313
|
+
throwIfReconciliationAborted(options.abortSignal);
|
|
314
|
+
await options.beforePublication();
|
|
315
|
+
throwIfReconciliationAborted(options.abortSignal);
|
|
316
|
+
},
|
|
317
|
+
operation: options.operation,
|
|
318
|
+
project: options.project,
|
|
319
|
+
reference: ProjectAnalysisReferenceSchema.parse(options.reference),
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function assertExactAnalysisProjectBinding(
|
|
324
|
+
projectBinding: unknown,
|
|
325
|
+
snapshot: OpenProjectSnapshot,
|
|
326
|
+
): void {
|
|
327
|
+
if (projectBinding === undefined) {
|
|
328
|
+
throw new ApplicationError(
|
|
329
|
+
"incompatible",
|
|
330
|
+
"Interrupted analysis recovery requires its exact project edit basis.",
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
assertProjectEditBasis(
|
|
334
|
+
ProjectEditBasisSchema.parse(projectBinding),
|
|
335
|
+
snapshot,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function assertEqual(
|
|
340
|
+
actual: unknown,
|
|
341
|
+
expected: unknown,
|
|
342
|
+
message: string,
|
|
343
|
+
): void {
|
|
344
|
+
if (canonicalJson(actual) !== canonicalJson(expected)) {
|
|
345
|
+
throw new ApplicationError("incompatible", message);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function completedAnalysis(
|
|
350
|
+
output: unknown,
|
|
351
|
+
summary: Readonly<Record<string, boolean | null | number | string>>,
|
|
352
|
+
): VerifiedReceiptReconciliation {
|
|
353
|
+
return { kind: "completed", output, summary };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function recoverFaces(
|
|
357
|
+
application: ApplicationContext,
|
|
358
|
+
inputValue: unknown,
|
|
359
|
+
control: Pick<
|
|
360
|
+
VerifiedReceiptReconciliationRequest,
|
|
361
|
+
"abortSignal" | "beforePublication"
|
|
362
|
+
>,
|
|
363
|
+
): Promise<VerifiedReceiptReconciliation> {
|
|
364
|
+
throwIfReconciliationAborted(control.abortSignal);
|
|
365
|
+
const input = FacesOperationInputSchema.parse(inputValue);
|
|
366
|
+
const analysisId = requireAnalysisId(input.analysisId);
|
|
367
|
+
const snapshot = await openLeasedProjectSnapshot(
|
|
368
|
+
application,
|
|
369
|
+
input.project,
|
|
370
|
+
);
|
|
371
|
+
assertExactAnalysisProjectBinding(input.projectBinding, snapshot);
|
|
372
|
+
const authority = await authoritativeAnalysis({
|
|
373
|
+
analysisId,
|
|
374
|
+
expectedKind: "faces",
|
|
375
|
+
expectedPath: projectAnalysisPath("faces", analysisId),
|
|
376
|
+
project: snapshot.openProject,
|
|
377
|
+
schema: FaceAnalysisV1Schema,
|
|
378
|
+
});
|
|
379
|
+
if (authority.kind === "missing") return { kind: "retry" };
|
|
380
|
+
const expectedSubject = resolveVideoAnalysisSubject(
|
|
381
|
+
snapshot.project,
|
|
382
|
+
input.source,
|
|
383
|
+
).subject;
|
|
384
|
+
assertEqual(
|
|
385
|
+
authority.analysis.subject,
|
|
386
|
+
expectedSubject,
|
|
387
|
+
"Face analysis subject no longer matches its exact operation input.",
|
|
388
|
+
);
|
|
389
|
+
assertEqual(
|
|
390
|
+
authority.analysis.config,
|
|
391
|
+
input.config ?? DEFAULT_FACE_ANALYSIS_CONFIG,
|
|
392
|
+
"Face analysis config no longer matches its exact operation input.",
|
|
393
|
+
);
|
|
394
|
+
const initialReference = authority.kind === "published"
|
|
395
|
+
? authority.reference
|
|
396
|
+
: buildFaceAnalysisReference(
|
|
397
|
+
authority.analysis,
|
|
398
|
+
projectAnalysisPath("faces", analysisId),
|
|
399
|
+
);
|
|
400
|
+
assertEqual(
|
|
401
|
+
initialReference,
|
|
402
|
+
buildFaceAnalysisReference(
|
|
403
|
+
authority.analysis,
|
|
404
|
+
initialReference.path,
|
|
405
|
+
),
|
|
406
|
+
"Face analysis reference disagrees with its immutable sidecar.",
|
|
407
|
+
);
|
|
408
|
+
const adopted = authority.kind === "orphan"
|
|
409
|
+
? await adoptOrphanAnalysisReference({
|
|
410
|
+
abortSignal: control.abortSignal,
|
|
411
|
+
application,
|
|
412
|
+
beforePublication: control.beforePublication,
|
|
413
|
+
operation: "analysis.faces",
|
|
414
|
+
project: input.project,
|
|
415
|
+
projectBinding: input.projectBinding,
|
|
416
|
+
reference: initialReference,
|
|
417
|
+
snapshot,
|
|
418
|
+
})
|
|
419
|
+
: {
|
|
420
|
+
project: snapshot.project,
|
|
421
|
+
reference: initialReference,
|
|
422
|
+
};
|
|
423
|
+
const output = FacesOperationOutputSchema.parse({
|
|
424
|
+
analysisId: authority.analysis.analysisId,
|
|
425
|
+
analyzedFrames: authority.analysis.coverage.analyzedFrames,
|
|
426
|
+
backend: authority.analysis.backend,
|
|
427
|
+
localOnly: true,
|
|
428
|
+
path: adopted.reference.path,
|
|
429
|
+
privacy: authority.analysis.privacy,
|
|
430
|
+
projectId: adopted.project.projectId,
|
|
431
|
+
reference: adopted.reference,
|
|
432
|
+
source: input.source,
|
|
433
|
+
tracks: authority.analysis.tracks.length,
|
|
434
|
+
});
|
|
435
|
+
return completedAnalysis(output, {
|
|
436
|
+
analysisId: output.analysisId,
|
|
437
|
+
analyzedFrames: output.analyzedFrames,
|
|
438
|
+
localOnly: output.localOnly,
|
|
439
|
+
path: output.path,
|
|
440
|
+
projectId: output.projectId,
|
|
441
|
+
tracks: output.tracks,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async function recoverMusic(
|
|
446
|
+
application: ApplicationContext,
|
|
447
|
+
inputValue: unknown,
|
|
448
|
+
control: Pick<
|
|
449
|
+
VerifiedReceiptReconciliationRequest,
|
|
450
|
+
"abortSignal" | "beforePublication"
|
|
451
|
+
>,
|
|
452
|
+
): Promise<VerifiedReceiptReconciliation> {
|
|
453
|
+
throwIfReconciliationAborted(control.abortSignal);
|
|
454
|
+
const input = MusicOperationInputSchema.parse(inputValue);
|
|
455
|
+
const analysisId = requireAnalysisId(input.analysisId);
|
|
456
|
+
const snapshot = await openLeasedProjectSnapshot(
|
|
457
|
+
application,
|
|
458
|
+
input.project,
|
|
459
|
+
);
|
|
460
|
+
assertExactAnalysisProjectBinding(input.projectBinding, snapshot);
|
|
461
|
+
const authority = await authoritativeAnalysis({
|
|
462
|
+
analysisId,
|
|
463
|
+
expectedKind: "music",
|
|
464
|
+
expectedPath: projectAnalysisPath("music", analysisId),
|
|
465
|
+
project: snapshot.openProject,
|
|
466
|
+
schema: MusicAnalysisV1Schema,
|
|
467
|
+
});
|
|
468
|
+
if (authority.kind === "missing") return { kind: "retry" };
|
|
469
|
+
const expectedSubject = resolveAudioAnalysisSubject(
|
|
470
|
+
snapshot.project,
|
|
471
|
+
input.source,
|
|
472
|
+
).subject;
|
|
473
|
+
assertEqual(
|
|
474
|
+
authority.analysis.subject,
|
|
475
|
+
expectedSubject,
|
|
476
|
+
"Music analysis subject no longer matches its exact operation input.",
|
|
477
|
+
);
|
|
478
|
+
assertEqual(
|
|
479
|
+
authority.analysis.config,
|
|
480
|
+
input.config ?? DEFAULT_MUSIC_ANALYSIS_CONFIG,
|
|
481
|
+
"Music analysis config no longer matches its exact operation input.",
|
|
482
|
+
);
|
|
483
|
+
const initialReference = authority.kind === "published"
|
|
484
|
+
? authority.reference
|
|
485
|
+
: buildMusicAnalysisReference(
|
|
486
|
+
authority.analysis,
|
|
487
|
+
projectAnalysisPath("music", analysisId),
|
|
488
|
+
);
|
|
489
|
+
assertEqual(
|
|
490
|
+
initialReference,
|
|
491
|
+
buildMusicAnalysisReference(
|
|
492
|
+
authority.analysis,
|
|
493
|
+
initialReference.path,
|
|
494
|
+
),
|
|
495
|
+
"Music analysis reference disagrees with its immutable sidecar.",
|
|
496
|
+
);
|
|
497
|
+
const adopted = authority.kind === "orphan"
|
|
498
|
+
? await adoptOrphanAnalysisReference({
|
|
499
|
+
abortSignal: control.abortSignal,
|
|
500
|
+
application,
|
|
501
|
+
beforePublication: control.beforePublication,
|
|
502
|
+
operation: "analysis.music",
|
|
503
|
+
project: input.project,
|
|
504
|
+
projectBinding: input.projectBinding,
|
|
505
|
+
reference: initialReference,
|
|
506
|
+
snapshot,
|
|
507
|
+
})
|
|
508
|
+
: {
|
|
509
|
+
project: snapshot.project,
|
|
510
|
+
reference: initialReference,
|
|
511
|
+
};
|
|
512
|
+
const output = MusicOperationOutputSchema.parse({
|
|
513
|
+
analysisId: authority.analysis.analysisId,
|
|
514
|
+
keyChanges: authority.analysis.keyRegions.filter(
|
|
515
|
+
region => region.changeConfidence !== null,
|
|
516
|
+
).length,
|
|
517
|
+
keyRegions: authority.analysis.keyRegions.length,
|
|
518
|
+
musicRegions: authority.analysis.musicRegions.length,
|
|
519
|
+
path: adopted.reference.path,
|
|
520
|
+
projectId: adopted.project.projectId,
|
|
521
|
+
reference: adopted.reference,
|
|
522
|
+
tempoChanges: authority.analysis.tempoRegions.filter(
|
|
523
|
+
region => region.changeFromPrevious !== null,
|
|
524
|
+
).length,
|
|
525
|
+
tempoRegions: authority.analysis.tempoRegions.length,
|
|
526
|
+
});
|
|
527
|
+
return completedAnalysis(output, {
|
|
528
|
+
analysisId: output.analysisId,
|
|
529
|
+
keyRegions: output.keyRegions,
|
|
530
|
+
musicRegions: output.musicRegions,
|
|
531
|
+
path: output.path,
|
|
532
|
+
projectId: output.projectId,
|
|
533
|
+
tempoRegions: output.tempoRegions,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function recoverProjectInactivity(
|
|
538
|
+
application: ApplicationContext,
|
|
539
|
+
inputValue: unknown,
|
|
540
|
+
control: Pick<
|
|
541
|
+
VerifiedReceiptReconciliationRequest,
|
|
542
|
+
"abortSignal" | "beforePublication"
|
|
543
|
+
>,
|
|
544
|
+
): Promise<VerifiedReceiptReconciliation> {
|
|
545
|
+
throwIfReconciliationAborted(control.abortSignal);
|
|
546
|
+
const input = ProjectInactivityOperationInputSchema.parse(inputValue);
|
|
547
|
+
const analysisId = requireAnalysisId(input.analysisId);
|
|
548
|
+
const snapshot = await openLeasedProjectSnapshot(
|
|
549
|
+
application,
|
|
550
|
+
input.project,
|
|
551
|
+
);
|
|
552
|
+
assertExactAnalysisProjectBinding(input.projectBinding, snapshot);
|
|
553
|
+
if (input.recordingBinding === undefined) {
|
|
554
|
+
throw new ApplicationError(
|
|
555
|
+
"incompatible",
|
|
556
|
+
"Interrupted inactivity analysis recovery requires exact recording evidence.",
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
const config = input.config ?? DEFAULT_PROJECT_INACTIVITY_CONFIG;
|
|
560
|
+
const referenceEvidence = await loadProjectInactivityReferenceEvidence(
|
|
561
|
+
snapshot.project,
|
|
562
|
+
application.paths.artifactRoot,
|
|
563
|
+
config.cursorMovementThresholdPx,
|
|
564
|
+
);
|
|
565
|
+
assertEqual(
|
|
566
|
+
projectInactivityRecordingBinding(referenceEvidence),
|
|
567
|
+
input.recordingBinding,
|
|
568
|
+
"Inactivity recording evidence no longer matches its exact operation input.",
|
|
569
|
+
);
|
|
570
|
+
const authority = await authoritativeAnalysis({
|
|
571
|
+
analysisId,
|
|
572
|
+
expectedKind: "inactivity",
|
|
573
|
+
expectedPath: projectAnalysisPath("inactivity", analysisId),
|
|
574
|
+
project: snapshot.openProject,
|
|
575
|
+
schema: ProjectInactivityAnalysisV1Schema,
|
|
576
|
+
});
|
|
577
|
+
if (authority.kind === "missing") return { kind: "retry" };
|
|
578
|
+
if (
|
|
579
|
+
authority.analysis.projectId !== snapshot.project.projectId
|
|
580
|
+
|| authority.analysis.projectStructureSha256
|
|
581
|
+
!== hashProjectStructure(snapshot.project)
|
|
582
|
+
) {
|
|
583
|
+
throw new ApplicationError(
|
|
584
|
+
"incompatible",
|
|
585
|
+
"Inactivity analysis no longer matches the current project structure.",
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
assertEqual(
|
|
589
|
+
authority.analysis.config,
|
|
590
|
+
config,
|
|
591
|
+
"Inactivity analysis config no longer matches its exact operation input.",
|
|
592
|
+
);
|
|
593
|
+
const initialReference = authority.kind === "published"
|
|
594
|
+
? authority.reference
|
|
595
|
+
: buildProjectInactivityAnalysisReference(
|
|
596
|
+
authority.analysis,
|
|
597
|
+
projectAnalysisPath("inactivity", analysisId),
|
|
598
|
+
);
|
|
599
|
+
assertEqual(
|
|
600
|
+
initialReference,
|
|
601
|
+
buildProjectInactivityAnalysisReference(
|
|
602
|
+
authority.analysis,
|
|
603
|
+
initialReference.path,
|
|
604
|
+
),
|
|
605
|
+
"Inactivity analysis reference disagrees with its immutable sidecar.",
|
|
606
|
+
);
|
|
607
|
+
const adopted = authority.kind === "orphan"
|
|
608
|
+
? await adoptOrphanAnalysisReference({
|
|
609
|
+
abortSignal: control.abortSignal,
|
|
610
|
+
application,
|
|
611
|
+
beforePublication: control.beforePublication,
|
|
612
|
+
operation: "analysis.project-inactivity",
|
|
613
|
+
project: input.project,
|
|
614
|
+
projectBinding: input.projectBinding,
|
|
615
|
+
reference: initialReference,
|
|
616
|
+
snapshot,
|
|
617
|
+
})
|
|
618
|
+
: {
|
|
619
|
+
project: snapshot.project,
|
|
620
|
+
reference: initialReference,
|
|
621
|
+
};
|
|
622
|
+
const output = ProjectInactivityOperationOutputSchema.parse({
|
|
623
|
+
analysisId: authority.analysis.analysisId,
|
|
624
|
+
candidateCount: authority.analysis.result.candidateCount,
|
|
625
|
+
cuts: authority.analysis.result.recommendedRanges,
|
|
626
|
+
evidencePath: adopted.reference.path,
|
|
627
|
+
projectId: authority.analysis.projectId,
|
|
628
|
+
protectedInteractionCount:
|
|
629
|
+
authority.analysis.result.protectedInteractionCount,
|
|
630
|
+
reference: adopted.reference,
|
|
631
|
+
referenceRecording:
|
|
632
|
+
authority.analysis.referenceRecording?.recordingId ?? null,
|
|
633
|
+
});
|
|
634
|
+
return completedAnalysis(output, {
|
|
635
|
+
analysisId: output.analysisId,
|
|
636
|
+
candidateCount: output.candidateCount,
|
|
637
|
+
cuts: output.cuts.length,
|
|
638
|
+
evidencePath: output.evidencePath,
|
|
639
|
+
projectId: output.projectId,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
async function readCanonicalReceipt<Receipt>(
|
|
644
|
+
application: ApplicationContext,
|
|
645
|
+
referenceInput: MediaArtifactReference,
|
|
646
|
+
schema: z.ZodType<Receipt>,
|
|
647
|
+
signal: AbortSignal,
|
|
648
|
+
): Promise<Receipt> {
|
|
649
|
+
throwIfReconciliationAborted(signal);
|
|
650
|
+
const reference = MediaArtifactReferenceSchema.parse(referenceInput);
|
|
651
|
+
const bound = await bindRepositoryMedia(
|
|
652
|
+
application,
|
|
653
|
+
reference,
|
|
654
|
+
signal,
|
|
655
|
+
1024 * 1024,
|
|
656
|
+
);
|
|
657
|
+
const handle = await open(
|
|
658
|
+
bound.absolutePath,
|
|
659
|
+
constants.O_RDONLY
|
|
660
|
+
| (constants.O_NOFOLLOW ?? 0)
|
|
661
|
+
| (constants.O_NONBLOCK ?? 0),
|
|
662
|
+
);
|
|
663
|
+
try {
|
|
664
|
+
const before = await handle.stat();
|
|
665
|
+
const text = await handle.readFile("utf8");
|
|
666
|
+
const after = await handle.stat();
|
|
667
|
+
if (
|
|
668
|
+
before.dev !== after.dev
|
|
669
|
+
|| before.ino !== after.ino
|
|
670
|
+
|| before.size !== after.size
|
|
671
|
+
|| before.mtimeMs !== after.mtimeMs
|
|
672
|
+
|| before.ctimeMs !== after.ctimeMs
|
|
673
|
+
|| createHash("sha256").update(text).digest("hex") !== reference.sha256
|
|
674
|
+
) {
|
|
675
|
+
throw new ApplicationError(
|
|
676
|
+
"incompatible",
|
|
677
|
+
"Media receipt changed while it was being verified.",
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
let input: unknown;
|
|
681
|
+
try {
|
|
682
|
+
input = JSON.parse(text) as unknown;
|
|
683
|
+
} catch {
|
|
684
|
+
throw new ApplicationError(
|
|
685
|
+
"incompatible",
|
|
686
|
+
"Media receipt is not valid JSON.",
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
const receipt = schema.parse(input);
|
|
690
|
+
if (`${canonicalJson(receipt)}\n` !== text) {
|
|
691
|
+
throw new ApplicationError(
|
|
692
|
+
"incompatible",
|
|
693
|
+
"Media receipt is not canonical immutable JSON.",
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
return receipt;
|
|
697
|
+
} finally {
|
|
698
|
+
await handle.close();
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
async function verifyMediaArtifact(
|
|
703
|
+
application: ApplicationContext,
|
|
704
|
+
reference: MediaArtifactReference,
|
|
705
|
+
maximumBytes: number,
|
|
706
|
+
signal: AbortSignal,
|
|
707
|
+
): Promise<void> {
|
|
708
|
+
await bindRepositoryMedia(
|
|
709
|
+
application,
|
|
710
|
+
reference,
|
|
711
|
+
signal,
|
|
712
|
+
maximumBytes,
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function ingestArtifactFromAsset(
|
|
717
|
+
output: z.infer<typeof MediaIngestOutputSchema>,
|
|
718
|
+
): MediaArtifactReference {
|
|
719
|
+
const segments = output.asset.streams.flatMap(stream => stream.segments);
|
|
720
|
+
const first = segments[0];
|
|
721
|
+
if (
|
|
722
|
+
first === undefined
|
|
723
|
+
|| segments.some(segment => (
|
|
724
|
+
segment.bytes !== first.bytes
|
|
725
|
+
|| segment.path !== first.path
|
|
726
|
+
|| segment.sha256 !== first.sha256
|
|
727
|
+
))
|
|
728
|
+
) {
|
|
729
|
+
throw new ApplicationError(
|
|
730
|
+
"incompatible",
|
|
731
|
+
"Recovered media ingest asset has inconsistent artifact identities.",
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
return MediaArtifactReferenceSchema.parse({
|
|
735
|
+
bytes: first.bytes,
|
|
736
|
+
path: first.path,
|
|
737
|
+
sha256: first.sha256,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async function recoverMediaIngest(
|
|
742
|
+
application: ApplicationContext,
|
|
743
|
+
exactInput: unknown,
|
|
744
|
+
outputValue: unknown,
|
|
745
|
+
expectedProjectGeneration: string | undefined,
|
|
746
|
+
signal: AbortSignal,
|
|
747
|
+
): Promise<VerifiedReceiptReconciliation> {
|
|
748
|
+
throwIfReconciliationAborted(signal);
|
|
749
|
+
const input = BoundMediaIngestInputSchema.parse(exactInput);
|
|
750
|
+
const output = MediaIngestOutputSchema.parse(outputValue);
|
|
751
|
+
if (expectedProjectGeneration === undefined) {
|
|
752
|
+
throw new ApplicationError(
|
|
753
|
+
"incompatible",
|
|
754
|
+
"Recovered media ingest is missing its exact project generation.",
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
await Promise.all([
|
|
758
|
+
verifyMediaArtifact(
|
|
759
|
+
application,
|
|
760
|
+
input.source,
|
|
761
|
+
MAXIMUM_MEDIA_INGEST_INPUT_BYTES,
|
|
762
|
+
signal,
|
|
763
|
+
),
|
|
764
|
+
verifyMediaArtifact(
|
|
765
|
+
application,
|
|
766
|
+
output.artifact,
|
|
767
|
+
MAXIMUM_MEDIA_INGEST_INPUT_BYTES,
|
|
768
|
+
signal,
|
|
769
|
+
),
|
|
770
|
+
]);
|
|
771
|
+
assertEqual(
|
|
772
|
+
ingestArtifactFromAsset(output),
|
|
773
|
+
output.artifact,
|
|
774
|
+
"Recovered media ingest asset disagrees with its published artifact.",
|
|
775
|
+
);
|
|
776
|
+
const receipt = await readCanonicalReceipt(
|
|
777
|
+
application,
|
|
778
|
+
output.receipt,
|
|
779
|
+
MediaIngestReceiptSchema,
|
|
780
|
+
signal,
|
|
781
|
+
);
|
|
782
|
+
if (
|
|
783
|
+
receipt.projectGenerationSha256 !== expectedProjectGeneration
|
|
784
|
+
|| receipt.role !== input.role
|
|
785
|
+
|| receipt.assetSha256 !== canonicalJsonSha256(output.asset)
|
|
786
|
+
) {
|
|
787
|
+
throw new ApplicationError(
|
|
788
|
+
"incompatible",
|
|
789
|
+
"Recovered media ingest receipt disagrees with its exact project-bound operation.",
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
assertEqual(
|
|
793
|
+
receipt.input,
|
|
794
|
+
input.source,
|
|
795
|
+
"Recovered media ingest receipt disagrees with its exact source.",
|
|
796
|
+
);
|
|
797
|
+
assertEqual(
|
|
798
|
+
receipt.output,
|
|
799
|
+
output.artifact,
|
|
800
|
+
"Recovered media ingest receipt disagrees with its published artifact.",
|
|
801
|
+
);
|
|
802
|
+
return {
|
|
803
|
+
kind: "completed",
|
|
804
|
+
output,
|
|
805
|
+
receiptReference: output.receipt.path,
|
|
806
|
+
summary: {
|
|
807
|
+
assetId: output.asset.assetId,
|
|
808
|
+
bytes: output.artifact.bytes,
|
|
809
|
+
created: output.created,
|
|
810
|
+
path: output.artifact.path,
|
|
811
|
+
receipt: output.receipt.path,
|
|
812
|
+
sha256: output.artifact.sha256,
|
|
813
|
+
},
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
async function recoverMediaOverlay(
|
|
818
|
+
application: ApplicationContext,
|
|
819
|
+
exactInput: unknown,
|
|
820
|
+
outputValue: unknown,
|
|
821
|
+
expectedProjectGeneration: string | undefined,
|
|
822
|
+
signal: AbortSignal,
|
|
823
|
+
): Promise<VerifiedReceiptReconciliation> {
|
|
824
|
+
throwIfReconciliationAborted(signal);
|
|
825
|
+
const input = BoundMediaOverlayInputSchema.parse(exactInput);
|
|
826
|
+
const output = MediaOverlayOutputSchema.parse(outputValue);
|
|
827
|
+
if (expectedProjectGeneration === undefined) {
|
|
828
|
+
throw new ApplicationError(
|
|
829
|
+
"incompatible",
|
|
830
|
+
"Recovered overlay preparation is missing its exact project generation.",
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
const inputArtifact = input.source.kind === "emoji"
|
|
834
|
+
? input.source.resolved.artifact
|
|
835
|
+
: input.source.artifact;
|
|
836
|
+
await Promise.all([
|
|
837
|
+
verifyMediaArtifact(
|
|
838
|
+
application,
|
|
839
|
+
inputArtifact,
|
|
840
|
+
MAXIMUM_MEDIA_OVERLAY_INPUT_BYTES,
|
|
841
|
+
signal,
|
|
842
|
+
),
|
|
843
|
+
verifyMediaArtifact(
|
|
844
|
+
application,
|
|
845
|
+
output.artifact,
|
|
846
|
+
MAXIMUM_MEDIA_OVERLAY_INPUT_BYTES,
|
|
847
|
+
signal,
|
|
848
|
+
),
|
|
849
|
+
]);
|
|
850
|
+
const project = await openProject(
|
|
851
|
+
application.paths.projectRoot,
|
|
852
|
+
input.project,
|
|
853
|
+
);
|
|
854
|
+
const expectedArtifactPath = relative(
|
|
855
|
+
application.paths.repositoryRoot,
|
|
856
|
+
join(project.directory.path, output.operation.source.asset.path),
|
|
857
|
+
);
|
|
858
|
+
if (
|
|
859
|
+
expectedArtifactPath !== output.artifact.path
|
|
860
|
+
|| output.operation.source.asset.bytes !== output.artifact.bytes
|
|
861
|
+
|| output.operation.source.asset.sha256 !== output.artifact.sha256
|
|
862
|
+
) {
|
|
863
|
+
throw new ApplicationError(
|
|
864
|
+
"incompatible",
|
|
865
|
+
"Recovered overlay operation disagrees with its project-relative asset.",
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
const receipt = await readCanonicalReceipt(
|
|
869
|
+
application,
|
|
870
|
+
output.receipt,
|
|
871
|
+
MediaOverlayReceiptSchema,
|
|
872
|
+
signal,
|
|
873
|
+
);
|
|
874
|
+
if (
|
|
875
|
+
receipt.projectGenerationSha256 !== expectedProjectGeneration
|
|
876
|
+
|| receipt.projectId !== input.project
|
|
877
|
+
|| receipt.exactInputSha256 !== canonicalJsonSha256(input)
|
|
878
|
+
|| receipt.operationSha256 !== canonicalJsonSha256(output.operation)
|
|
879
|
+
|| receipt.overlayId !== output.operation.overlayId
|
|
880
|
+
) {
|
|
881
|
+
throw new ApplicationError(
|
|
882
|
+
"incompatible",
|
|
883
|
+
"Recovered overlay receipt disagrees with its exact project-bound operation.",
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
assertEqual(
|
|
887
|
+
receipt.artifact,
|
|
888
|
+
output.artifact,
|
|
889
|
+
"Recovered overlay receipt disagrees with its published asset.",
|
|
890
|
+
);
|
|
891
|
+
return {
|
|
892
|
+
kind: "completed",
|
|
893
|
+
output,
|
|
894
|
+
receiptReference: output.receipt.path,
|
|
895
|
+
summary: {
|
|
896
|
+
bytes: output.artifact.bytes,
|
|
897
|
+
created: output.created,
|
|
898
|
+
kind: output.operation.source.kind,
|
|
899
|
+
overlayId: output.operation.overlayId,
|
|
900
|
+
receipt: output.receipt.path,
|
|
901
|
+
sha256: output.artifact.sha256,
|
|
902
|
+
},
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
async function recoverHtmlOverlay(
|
|
907
|
+
application: ApplicationContext,
|
|
908
|
+
exactInput: unknown,
|
|
909
|
+
outputValue: unknown,
|
|
910
|
+
expectedProjectGeneration: string | undefined,
|
|
911
|
+
signal: AbortSignal,
|
|
912
|
+
): Promise<VerifiedReceiptReconciliation> {
|
|
913
|
+
throwIfReconciliationAborted(signal);
|
|
914
|
+
const input = BoundHtmlOverlayInputSchema.parse(exactInput);
|
|
915
|
+
const output = HtmlOverlayOutputSchema.parse(outputValue);
|
|
916
|
+
if (expectedProjectGeneration === undefined) {
|
|
917
|
+
throw new ApplicationError(
|
|
918
|
+
"incompatible",
|
|
919
|
+
"Recovered HTML-overlay preparation is missing its exact project generation.",
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
if (input.capabilityBindings === undefined) {
|
|
923
|
+
throw new ApplicationError(
|
|
924
|
+
"incompatible",
|
|
925
|
+
"Recovered HTML-overlay preparation is missing its exact capability bindings.",
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
for (const resource of input.resources) {
|
|
929
|
+
assertEqual(
|
|
930
|
+
{
|
|
931
|
+
bytes: resource.bytes,
|
|
932
|
+
sha256: resource.sha256,
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
bytes: resource.artifact.bytes,
|
|
936
|
+
sha256: resource.artifact.sha256,
|
|
937
|
+
},
|
|
938
|
+
`Recovered HTML-overlay resource ${resource.name} disagrees with its bound artifact.`,
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
await Promise.all([
|
|
942
|
+
...("html" in input.document
|
|
943
|
+
? []
|
|
944
|
+
: [verifyMediaArtifact(
|
|
945
|
+
application,
|
|
946
|
+
input.document,
|
|
947
|
+
MAXIMUM_HTML_OVERLAY_DOCUMENT_BYTES,
|
|
948
|
+
signal,
|
|
949
|
+
)]),
|
|
950
|
+
...input.resources.map(async resource =>
|
|
951
|
+
await verifyMediaArtifact(
|
|
952
|
+
application,
|
|
953
|
+
resource.artifact,
|
|
954
|
+
MAXIMUM_HTML_OVERLAY_RESOURCE_BYTES,
|
|
955
|
+
signal,
|
|
956
|
+
)),
|
|
957
|
+
verifyMediaArtifact(
|
|
958
|
+
application,
|
|
959
|
+
output.artifact,
|
|
960
|
+
MAXIMUM_HTML_OVERLAY_OUTPUT_BYTES,
|
|
961
|
+
signal,
|
|
962
|
+
),
|
|
963
|
+
]);
|
|
964
|
+
const project = await openProject(
|
|
965
|
+
application.paths.projectRoot,
|
|
966
|
+
input.project,
|
|
967
|
+
);
|
|
968
|
+
const source = output.operation.source;
|
|
969
|
+
if (
|
|
970
|
+
source.kind !== "video"
|
|
971
|
+
|| source.audioPolicy.kind !== "mute"
|
|
972
|
+
) {
|
|
973
|
+
throw new ApplicationError(
|
|
974
|
+
"incompatible",
|
|
975
|
+
"Recovered HTML-overlay operation is not a muted video overlay.",
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
const expectedArtifactPath = relative(
|
|
979
|
+
application.paths.repositoryRoot,
|
|
980
|
+
join(project.directory.path, source.asset.path),
|
|
981
|
+
);
|
|
982
|
+
if (
|
|
983
|
+
expectedArtifactPath !== output.artifact.path
|
|
984
|
+
|| source.asset.bytes !== output.artifact.bytes
|
|
985
|
+
|| source.asset.sha256 !== output.artifact.sha256
|
|
986
|
+
) {
|
|
987
|
+
throw new ApplicationError(
|
|
988
|
+
"incompatible",
|
|
989
|
+
"Recovered HTML-overlay operation disagrees with its project-relative asset.",
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
const {
|
|
993
|
+
coordinateSpace,
|
|
994
|
+
intrinsicSize,
|
|
995
|
+
range,
|
|
996
|
+
} = output.operation;
|
|
997
|
+
const layout = {
|
|
998
|
+
anchor: output.operation.anchor,
|
|
999
|
+
blendMode: output.operation.blendMode,
|
|
1000
|
+
crop: output.operation.crop,
|
|
1001
|
+
entrance: output.operation.entrance,
|
|
1002
|
+
exit: output.operation.exit,
|
|
1003
|
+
fit: output.operation.fit,
|
|
1004
|
+
mask: output.operation.mask,
|
|
1005
|
+
motion: output.operation.motion,
|
|
1006
|
+
opacity: output.operation.opacity,
|
|
1007
|
+
position: output.operation.position,
|
|
1008
|
+
rotationDegrees: output.operation.rotationDegrees,
|
|
1009
|
+
scale: output.operation.scale,
|
|
1010
|
+
size: output.operation.size,
|
|
1011
|
+
zIndex: output.operation.zIndex,
|
|
1012
|
+
};
|
|
1013
|
+
if (coordinateSpace !== "output-pixels") {
|
|
1014
|
+
throw new ApplicationError(
|
|
1015
|
+
"incompatible",
|
|
1016
|
+
"Recovered HTML-overlay operation uses an incompatible coordinate space.",
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
assertEqual(
|
|
1020
|
+
intrinsicSize,
|
|
1021
|
+
{
|
|
1022
|
+
height: input.canvas.height,
|
|
1023
|
+
width: input.canvas.width,
|
|
1024
|
+
},
|
|
1025
|
+
"Recovered HTML-overlay operation disagrees with its exact canvas.",
|
|
1026
|
+
);
|
|
1027
|
+
assertEqual(
|
|
1028
|
+
range,
|
|
1029
|
+
input.range,
|
|
1030
|
+
"Recovered HTML-overlay operation disagrees with its exact project range.",
|
|
1031
|
+
);
|
|
1032
|
+
assertEqual(
|
|
1033
|
+
layout,
|
|
1034
|
+
input.layout,
|
|
1035
|
+
"Recovered HTML-overlay operation disagrees with its exact layout.",
|
|
1036
|
+
);
|
|
1037
|
+
const receipt = await readCanonicalReceipt(
|
|
1038
|
+
application,
|
|
1039
|
+
output.receipt,
|
|
1040
|
+
HtmlOverlayReceiptSchema,
|
|
1041
|
+
signal,
|
|
1042
|
+
);
|
|
1043
|
+
const capabilityVersions = new Map(
|
|
1044
|
+
input.capabilityBindings.map(binding => [
|
|
1045
|
+
binding.name,
|
|
1046
|
+
binding.version,
|
|
1047
|
+
] as const),
|
|
1048
|
+
);
|
|
1049
|
+
if (
|
|
1050
|
+
receipt.projectGenerationSha256 !== expectedProjectGeneration
|
|
1051
|
+
|| receipt.projectId !== input.project
|
|
1052
|
+
|| receipt.exactInputSha256 !== canonicalJsonSha256(input)
|
|
1053
|
+
|| receipt.operationSha256 !== canonicalJsonSha256(output.operation)
|
|
1054
|
+
|| receipt.overlayId !== output.operation.overlayId
|
|
1055
|
+
|| receipt.frameCount !== htmlOverlayFrameCount(input.timing)
|
|
1056
|
+
|| receipt.libraryLocksSha256
|
|
1057
|
+
!== canonicalJsonSha256(receipt.libraryLocks)
|
|
1058
|
+
|| receipt.browserVersion !== capabilityVersions.get("html-browser")
|
|
1059
|
+
|| receipt.ffmpegVersion !== capabilityVersions.get("ffmpeg")
|
|
1060
|
+
|| receipt.ffprobeVersion !== capabilityVersions.get("ffprobe")
|
|
1061
|
+
) {
|
|
1062
|
+
throw new ApplicationError(
|
|
1063
|
+
"incompatible",
|
|
1064
|
+
"Recovered HTML-overlay receipt disagrees with its exact project-bound operation.",
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
assertEqual(
|
|
1068
|
+
receipt.libraryLocks.map(lock => lock.specifier),
|
|
1069
|
+
input.libraries,
|
|
1070
|
+
"Recovered HTML-overlay receipt disagrees with its exact library selection.",
|
|
1071
|
+
);
|
|
1072
|
+
assertEqual(
|
|
1073
|
+
receipt.artifact,
|
|
1074
|
+
output.artifact,
|
|
1075
|
+
"Recovered HTML-overlay receipt disagrees with its published asset.",
|
|
1076
|
+
);
|
|
1077
|
+
return {
|
|
1078
|
+
kind: "completed",
|
|
1079
|
+
output,
|
|
1080
|
+
receiptReference: output.receipt.path,
|
|
1081
|
+
summary: {
|
|
1082
|
+
bytes: output.artifact.bytes,
|
|
1083
|
+
created: output.created,
|
|
1084
|
+
kind: "html",
|
|
1085
|
+
overlayId: output.operation.overlayId,
|
|
1086
|
+
receipt: output.receipt.path,
|
|
1087
|
+
sha256: output.artifact.sha256,
|
|
1088
|
+
},
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
async function recoverMediaAudioEffects(
|
|
1093
|
+
application: ApplicationContext,
|
|
1094
|
+
exactInput: unknown,
|
|
1095
|
+
outputValue: unknown,
|
|
1096
|
+
signal: AbortSignal,
|
|
1097
|
+
): Promise<VerifiedReceiptReconciliation> {
|
|
1098
|
+
throwIfReconciliationAborted(signal);
|
|
1099
|
+
const input = BoundMediaAudioEffectsInputSchema.parse(exactInput);
|
|
1100
|
+
const output = MediaAudioEffectsOutputSchema.parse(outputValue);
|
|
1101
|
+
await Promise.all([
|
|
1102
|
+
verifyMediaArtifact(
|
|
1103
|
+
application,
|
|
1104
|
+
input.input,
|
|
1105
|
+
MAXIMUM_MEDIA_EFFECT_INPUT_BYTES,
|
|
1106
|
+
signal,
|
|
1107
|
+
),
|
|
1108
|
+
verifyMediaArtifact(
|
|
1109
|
+
application,
|
|
1110
|
+
output.artifact,
|
|
1111
|
+
MAXIMUM_LOCAL_MEDIA_EFFECT_OUTPUT_BYTES,
|
|
1112
|
+
signal,
|
|
1113
|
+
),
|
|
1114
|
+
]);
|
|
1115
|
+
const receipt = await readCanonicalReceipt(
|
|
1116
|
+
application,
|
|
1117
|
+
output.receipt,
|
|
1118
|
+
MediaAudioEffectsReceiptSchema,
|
|
1119
|
+
signal,
|
|
1120
|
+
);
|
|
1121
|
+
assertEqual(
|
|
1122
|
+
receipt.input,
|
|
1123
|
+
input.input,
|
|
1124
|
+
"Recovered audio-effects receipt disagrees with its exact input.",
|
|
1125
|
+
);
|
|
1126
|
+
assertEqual(
|
|
1127
|
+
receipt.transform,
|
|
1128
|
+
input.transform,
|
|
1129
|
+
"Recovered audio-effects receipt disagrees with its exact transform.",
|
|
1130
|
+
);
|
|
1131
|
+
assertEqual(
|
|
1132
|
+
output.transform,
|
|
1133
|
+
input.transform,
|
|
1134
|
+
"Recovered audio-effects output disagrees with its exact transform.",
|
|
1135
|
+
);
|
|
1136
|
+
assertEqual(
|
|
1137
|
+
receipt.output,
|
|
1138
|
+
{ ...output.artifact, durationUs: output.durationUs },
|
|
1139
|
+
"Recovered audio-effects receipt disagrees with its published output.",
|
|
1140
|
+
);
|
|
1141
|
+
if (receipt.filterGraph !== output.filterGraph) {
|
|
1142
|
+
throw new ApplicationError(
|
|
1143
|
+
"incompatible",
|
|
1144
|
+
"Recovered audio-effects filter graph disagrees with its receipt.",
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
return {
|
|
1148
|
+
kind: "completed",
|
|
1149
|
+
output,
|
|
1150
|
+
receiptReference: output.receipt.path,
|
|
1151
|
+
summary: {
|
|
1152
|
+
bytes: output.artifact.bytes,
|
|
1153
|
+
created: output.created,
|
|
1154
|
+
durationUs: output.durationUs,
|
|
1155
|
+
effects: output.transform.effects.length,
|
|
1156
|
+
path: output.artifact.path,
|
|
1157
|
+
receipt: output.receipt.path,
|
|
1158
|
+
sha256: output.artifact.sha256,
|
|
1159
|
+
},
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
async function recoverMediaColorGrade(
|
|
1164
|
+
application: ApplicationContext,
|
|
1165
|
+
exactInput: unknown,
|
|
1166
|
+
outputValue: unknown,
|
|
1167
|
+
signal: AbortSignal,
|
|
1168
|
+
): Promise<VerifiedReceiptReconciliation> {
|
|
1169
|
+
throwIfReconciliationAborted(signal);
|
|
1170
|
+
const input = BoundMediaColorGradeInputSchema.parse(exactInput);
|
|
1171
|
+
const output = MediaColorGradeOutputSchema.parse(outputValue);
|
|
1172
|
+
await Promise.all([
|
|
1173
|
+
verifyMediaArtifact(
|
|
1174
|
+
application,
|
|
1175
|
+
input.input,
|
|
1176
|
+
MAXIMUM_MEDIA_EFFECT_INPUT_BYTES,
|
|
1177
|
+
signal,
|
|
1178
|
+
),
|
|
1179
|
+
verifyMediaArtifact(
|
|
1180
|
+
application,
|
|
1181
|
+
output.artifact,
|
|
1182
|
+
MAXIMUM_LOCAL_MEDIA_EFFECT_OUTPUT_BYTES,
|
|
1183
|
+
signal,
|
|
1184
|
+
),
|
|
1185
|
+
]);
|
|
1186
|
+
const receipt = await readCanonicalReceipt(
|
|
1187
|
+
application,
|
|
1188
|
+
output.receipt,
|
|
1189
|
+
MediaColorGradeReceiptSchema,
|
|
1190
|
+
signal,
|
|
1191
|
+
);
|
|
1192
|
+
assertEqual(
|
|
1193
|
+
receipt.input,
|
|
1194
|
+
input.input,
|
|
1195
|
+
"Recovered color-grade receipt disagrees with its exact input.",
|
|
1196
|
+
);
|
|
1197
|
+
assertEqual(
|
|
1198
|
+
receipt.transform,
|
|
1199
|
+
input.transform,
|
|
1200
|
+
"Recovered color-grade receipt disagrees with its exact transform.",
|
|
1201
|
+
);
|
|
1202
|
+
assertEqual(
|
|
1203
|
+
output.transform,
|
|
1204
|
+
input.transform,
|
|
1205
|
+
"Recovered color-grade output disagrees with its exact transform.",
|
|
1206
|
+
);
|
|
1207
|
+
assertEqual(
|
|
1208
|
+
receipt.output,
|
|
1209
|
+
{ ...output.artifact, durationUs: output.durationUs },
|
|
1210
|
+
"Recovered color-grade receipt disagrees with its published output.",
|
|
1211
|
+
);
|
|
1212
|
+
if (receipt.filterGraph !== output.filterGraph) {
|
|
1213
|
+
throw new ApplicationError(
|
|
1214
|
+
"incompatible",
|
|
1215
|
+
"Recovered color-grade filter graph disagrees with its receipt.",
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
1218
|
+
return {
|
|
1219
|
+
kind: "completed",
|
|
1220
|
+
output,
|
|
1221
|
+
receiptReference: output.receipt.path,
|
|
1222
|
+
summary: {
|
|
1223
|
+
bytes: output.artifact.bytes,
|
|
1224
|
+
created: output.created,
|
|
1225
|
+
durationUs: output.durationUs,
|
|
1226
|
+
path: output.artifact.path,
|
|
1227
|
+
profile: output.transform.outputProfile,
|
|
1228
|
+
receipt: output.receipt.path,
|
|
1229
|
+
sha256: output.artifact.sha256,
|
|
1230
|
+
},
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
async function recoverSlopcameraDiagramRender(
|
|
1235
|
+
application: ApplicationContext,
|
|
1236
|
+
exactInput: unknown,
|
|
1237
|
+
outputValue: unknown,
|
|
1238
|
+
signal: AbortSignal,
|
|
1239
|
+
): Promise<VerifiedReceiptReconciliation> {
|
|
1240
|
+
throwIfReconciliationAborted(signal);
|
|
1241
|
+
const input = BoundSlopcameraDiagramRenderInputSchema.parse(exactInput);
|
|
1242
|
+
const output = SlopcameraDiagramRenderOutputSchema.parse(outputValue);
|
|
1243
|
+
const source = await bindRepositoryMedia(
|
|
1244
|
+
application,
|
|
1245
|
+
input.path,
|
|
1246
|
+
signal,
|
|
1247
|
+
MAXIMUM_DIAGRAM_SOURCE_BYTES,
|
|
1248
|
+
);
|
|
1249
|
+
assertEqual(
|
|
1250
|
+
output.source,
|
|
1251
|
+
source.artifact,
|
|
1252
|
+
"Recovered diagram render disagrees with its exact source.",
|
|
1253
|
+
);
|
|
1254
|
+
await Promise.all(Object.values(output.artifacts).map(async artifact =>
|
|
1255
|
+
await verifyMediaArtifact(
|
|
1256
|
+
application,
|
|
1257
|
+
artifact,
|
|
1258
|
+
MAXIMUM_DIAGRAM_ARTIFACT_BYTES,
|
|
1259
|
+
signal,
|
|
1260
|
+
)));
|
|
1261
|
+
const receipt = await readCanonicalReceipt(
|
|
1262
|
+
application,
|
|
1263
|
+
output.receipt,
|
|
1264
|
+
SlopcameraDiagramRenderReceiptSchema,
|
|
1265
|
+
signal,
|
|
1266
|
+
);
|
|
1267
|
+
if (
|
|
1268
|
+
receipt.exactInputSha256 !== canonicalJsonSha256(input)
|
|
1269
|
+
|| receipt.scale !== (input.scale ?? 2)
|
|
1270
|
+
) {
|
|
1271
|
+
throw new ApplicationError(
|
|
1272
|
+
"incompatible",
|
|
1273
|
+
"Recovered diagram receipt disagrees with its exact operation input.",
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
assertEqual(
|
|
1277
|
+
receipt.source,
|
|
1278
|
+
output.source,
|
|
1279
|
+
"Recovered diagram receipt disagrees with its exact source.",
|
|
1280
|
+
);
|
|
1281
|
+
assertEqual(
|
|
1282
|
+
receipt.artifacts,
|
|
1283
|
+
output.artifacts,
|
|
1284
|
+
"Recovered diagram receipt disagrees with its published artifacts.",
|
|
1285
|
+
);
|
|
1286
|
+
assertEqual(
|
|
1287
|
+
receipt.findings,
|
|
1288
|
+
output.findings,
|
|
1289
|
+
"Recovered diagram receipt disagrees with its lint findings.",
|
|
1290
|
+
);
|
|
1291
|
+
return {
|
|
1292
|
+
kind: "completed",
|
|
1293
|
+
output,
|
|
1294
|
+
receiptReference: output.receipt.path,
|
|
1295
|
+
summary: {
|
|
1296
|
+
darkPng: output.artifacts.darkPng.path,
|
|
1297
|
+
findings: output.findings.length,
|
|
1298
|
+
lightPng: output.artifacts.lightPng.path,
|
|
1299
|
+
receipt: output.receipt.path,
|
|
1300
|
+
sourceSha256: output.source.sha256,
|
|
1301
|
+
},
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
async function recoverSlopcameraImageVectorize(
|
|
1306
|
+
application: ApplicationContext,
|
|
1307
|
+
exactInput: unknown,
|
|
1308
|
+
outputValue: unknown,
|
|
1309
|
+
signal: AbortSignal,
|
|
1310
|
+
): Promise<VerifiedReceiptReconciliation> {
|
|
1311
|
+
throwIfReconciliationAborted(signal);
|
|
1312
|
+
const input = BoundSlopcameraImageVectorizeInputSchema.parse(exactInput);
|
|
1313
|
+
const output = SlopcameraImageVectorizeOutputSchema.parse(outputValue);
|
|
1314
|
+
const source = await bindRepositoryMedia(
|
|
1315
|
+
application,
|
|
1316
|
+
input.inputPath,
|
|
1317
|
+
signal,
|
|
1318
|
+
MAXIMUM_MEDIA_EFFECT_INPUT_BYTES,
|
|
1319
|
+
);
|
|
1320
|
+
assertEqual(
|
|
1321
|
+
output.source,
|
|
1322
|
+
source.artifact,
|
|
1323
|
+
"Recovered vectorization disagrees with its exact raster source.",
|
|
1324
|
+
);
|
|
1325
|
+
await verifyMediaArtifact(
|
|
1326
|
+
application,
|
|
1327
|
+
output.artifact,
|
|
1328
|
+
MAXIMUM_VECTOR_ARTIFACT_BYTES,
|
|
1329
|
+
signal,
|
|
1330
|
+
);
|
|
1331
|
+
if (
|
|
1332
|
+
output.vectorizer.sourceSha256 !== output.source.sha256
|
|
1333
|
+
|| output.vectorizer.inputBytes !== output.source.bytes
|
|
1334
|
+
|| output.vectorizer.svgSha256 !== output.artifact.sha256
|
|
1335
|
+
|| output.vectorizer.bytes !== output.artifact.bytes
|
|
1336
|
+
|| output.vectorizer.outputMode !== (
|
|
1337
|
+
input.duotone === undefined ? "color" : "duotone"
|
|
1338
|
+
)
|
|
1339
|
+
) {
|
|
1340
|
+
throw new ApplicationError(
|
|
1341
|
+
"incompatible",
|
|
1342
|
+
"Recovered vectorizer evidence disagrees with its source or published SVG.",
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
const receipt = await readCanonicalReceipt(
|
|
1346
|
+
application,
|
|
1347
|
+
output.receipt,
|
|
1348
|
+
SlopcameraImageVectorizeReceiptSchema,
|
|
1349
|
+
signal,
|
|
1350
|
+
);
|
|
1351
|
+
if (receipt.exactInputSha256 !== canonicalJsonSha256(input)) {
|
|
1352
|
+
throw new ApplicationError(
|
|
1353
|
+
"incompatible",
|
|
1354
|
+
"Recovered vectorization receipt disagrees with its exact operation input.",
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
assertEqual(
|
|
1358
|
+
receipt.source,
|
|
1359
|
+
output.source,
|
|
1360
|
+
"Recovered vectorization receipt disagrees with its exact raster source.",
|
|
1361
|
+
);
|
|
1362
|
+
assertEqual(
|
|
1363
|
+
receipt.artifact,
|
|
1364
|
+
output.artifact,
|
|
1365
|
+
"Recovered vectorization receipt disagrees with its published SVG.",
|
|
1366
|
+
);
|
|
1367
|
+
assertEqual(
|
|
1368
|
+
receipt.vectorizer,
|
|
1369
|
+
output.vectorizer,
|
|
1370
|
+
"Recovered vectorization receipt disagrees with its vectorizer evidence.",
|
|
1371
|
+
);
|
|
1372
|
+
return {
|
|
1373
|
+
kind: "completed",
|
|
1374
|
+
output,
|
|
1375
|
+
receiptReference: output.receipt.path,
|
|
1376
|
+
summary: {
|
|
1377
|
+
bytes: output.artifact.bytes,
|
|
1378
|
+
created: output.created,
|
|
1379
|
+
paths: output.vectorizer.pathCount,
|
|
1380
|
+
receipt: output.receipt.path,
|
|
1381
|
+
sha256: output.artifact.sha256,
|
|
1382
|
+
sourceSha256: output.source.sha256,
|
|
1383
|
+
},
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
async function recoverAnalysis(
|
|
1388
|
+
application: ApplicationContext,
|
|
1389
|
+
kind: Extract<LocalVerifiedReceiptOperationKind, `analysis.${string}`>,
|
|
1390
|
+
input: unknown,
|
|
1391
|
+
control: Pick<
|
|
1392
|
+
VerifiedReceiptReconciliationRequest,
|
|
1393
|
+
"abortSignal" | "beforePublication"
|
|
1394
|
+
>,
|
|
1395
|
+
): Promise<VerifiedReceiptReconciliation> {
|
|
1396
|
+
switch (kind) {
|
|
1397
|
+
case "analysis.faces":
|
|
1398
|
+
return await recoverFaces(application, input, control);
|
|
1399
|
+
case "analysis.music":
|
|
1400
|
+
return await recoverMusic(application, input, control);
|
|
1401
|
+
case "analysis.project-inactivity":
|
|
1402
|
+
return await recoverProjectInactivity(application, input, control);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
async function recoverCheckpointMedia(
|
|
1407
|
+
application: ApplicationContext,
|
|
1408
|
+
request: VerifiedReceiptReconciliationRequest,
|
|
1409
|
+
kind: Extract<LocalVerifiedReceiptOperationKind, `media.${string}`>,
|
|
1410
|
+
output: unknown,
|
|
1411
|
+
): Promise<VerifiedReceiptReconciliation> {
|
|
1412
|
+
switch (kind) {
|
|
1413
|
+
case "media.ingest":
|
|
1414
|
+
return await recoverMediaIngest(
|
|
1415
|
+
application,
|
|
1416
|
+
request.exactInput,
|
|
1417
|
+
output,
|
|
1418
|
+
request.expectedProjectGeneration,
|
|
1419
|
+
request.abortSignal,
|
|
1420
|
+
);
|
|
1421
|
+
case "media.overlay":
|
|
1422
|
+
return await recoverMediaOverlay(
|
|
1423
|
+
application,
|
|
1424
|
+
request.exactInput,
|
|
1425
|
+
output,
|
|
1426
|
+
request.expectedProjectGeneration,
|
|
1427
|
+
request.abortSignal,
|
|
1428
|
+
);
|
|
1429
|
+
case "media.html-overlay":
|
|
1430
|
+
return await recoverHtmlOverlay(
|
|
1431
|
+
application,
|
|
1432
|
+
request.exactInput,
|
|
1433
|
+
output,
|
|
1434
|
+
request.expectedProjectGeneration,
|
|
1435
|
+
request.abortSignal,
|
|
1436
|
+
);
|
|
1437
|
+
case "media.audio-effects":
|
|
1438
|
+
return await recoverMediaAudioEffects(
|
|
1439
|
+
application,
|
|
1440
|
+
request.exactInput,
|
|
1441
|
+
output,
|
|
1442
|
+
request.abortSignal,
|
|
1443
|
+
);
|
|
1444
|
+
case "media.color-grade":
|
|
1445
|
+
return await recoverMediaColorGrade(
|
|
1446
|
+
application,
|
|
1447
|
+
request.exactInput,
|
|
1448
|
+
output,
|
|
1449
|
+
request.abortSignal,
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
async function recoverCheckpointSlopcameraVisual(
|
|
1455
|
+
application: ApplicationContext,
|
|
1456
|
+
request: VerifiedReceiptReconciliationRequest,
|
|
1457
|
+
kind: Extract<LocalVerifiedReceiptOperationKind, "slopcamera.diagram.render" | "slopcamera.image.vectorize">,
|
|
1458
|
+
output: unknown,
|
|
1459
|
+
): Promise<VerifiedReceiptReconciliation> {
|
|
1460
|
+
switch (kind) {
|
|
1461
|
+
case "slopcamera.diagram.render":
|
|
1462
|
+
return await recoverSlopcameraDiagramRender(
|
|
1463
|
+
application,
|
|
1464
|
+
request.exactInput,
|
|
1465
|
+
output,
|
|
1466
|
+
request.abortSignal,
|
|
1467
|
+
);
|
|
1468
|
+
case "slopcamera.image.vectorize":
|
|
1469
|
+
return await recoverSlopcameraImageVectorize(
|
|
1470
|
+
application,
|
|
1471
|
+
request.exactInput,
|
|
1472
|
+
output,
|
|
1473
|
+
request.abortSignal,
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
export function hasLocalVerifiedReceiptReconciler(
|
|
1479
|
+
kind: OperationKind,
|
|
1480
|
+
): kind is LocalVerifiedReceiptOperationKind {
|
|
1481
|
+
return (LOCAL_VERIFIED_RECEIPT_OPERATION_KINDS as readonly OperationKind[])
|
|
1482
|
+
.includes(kind);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
function isLocalAnalysisKind(
|
|
1486
|
+
kind: LocalVerifiedReceiptOperationKind,
|
|
1487
|
+
): kind is Extract<LocalVerifiedReceiptOperationKind, `analysis.${string}`> {
|
|
1488
|
+
return kind === "analysis.faces"
|
|
1489
|
+
|| kind === "analysis.music"
|
|
1490
|
+
|| kind === "analysis.project-inactivity";
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
function isLocalSlopcameraVisualKind(
|
|
1494
|
+
kind: LocalVerifiedReceiptOperationKind,
|
|
1495
|
+
): kind is Extract<LocalVerifiedReceiptOperationKind, "slopcamera.diagram.render" | "slopcamera.image.vectorize"> {
|
|
1496
|
+
return kind === "slopcamera.diagram.render"
|
|
1497
|
+
|| kind === "slopcamera.image.vectorize";
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
export async function reconcileLocalVerifiedReceiptOperation(
|
|
1501
|
+
application: ApplicationContext,
|
|
1502
|
+
request: VerifiedReceiptReconciliationRequest,
|
|
1503
|
+
): Promise<VerifiedReceiptReconciliation> {
|
|
1504
|
+
const kind = request.identity.kind;
|
|
1505
|
+
if (!hasLocalVerifiedReceiptReconciler(kind)) {
|
|
1506
|
+
return {
|
|
1507
|
+
kind: "incompatible",
|
|
1508
|
+
message: `Operation ${kind} has no local verified-receipt reconciler.`,
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
try {
|
|
1512
|
+
throwIfReconciliationAborted(request.abortSignal);
|
|
1513
|
+
const checkpoint = await readOperationCompletionCheckpoint({
|
|
1514
|
+
expected: request.identity,
|
|
1515
|
+
privateRoot: application.paths.privateRoot,
|
|
1516
|
+
workspaceDirectory: request.workspaceDirectory,
|
|
1517
|
+
});
|
|
1518
|
+
if (kind === "slopcamera.studio.run") {
|
|
1519
|
+
const recovered = await reconcileStudioRun({ application, abortSignal: request.abortSignal, workflow: {
|
|
1520
|
+
...request.identity, workspaceDirectory: request.workspaceDirectory, beforePublication: request.beforePublication,
|
|
1521
|
+
} }, request.exactInput, checkpoint?.output);
|
|
1522
|
+
if (recovered.kind !== "completed") return recovered;
|
|
1523
|
+
return { ...recovered, receiptReference: recovered.output.receipt.path, summary: { jobId: recovered.output.document.jobId, planSha256: recovered.output.document.planSha256, outputs: recovered.output.outputs.length } };
|
|
1524
|
+
}
|
|
1525
|
+
if (isLocalAnalysisKind(kind)) {
|
|
1526
|
+
const recovered = await recoverAnalysis(
|
|
1527
|
+
application,
|
|
1528
|
+
kind,
|
|
1529
|
+
request.exactInput,
|
|
1530
|
+
request,
|
|
1531
|
+
);
|
|
1532
|
+
if (checkpoint === null) return recovered;
|
|
1533
|
+
if (recovered.kind !== "completed") {
|
|
1534
|
+
throw new ApplicationError(
|
|
1535
|
+
"incompatible",
|
|
1536
|
+
"Analysis checkpoint exists without matching authoritative project evidence.",
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
assertEqual(
|
|
1540
|
+
checkpoint.output,
|
|
1541
|
+
recovered.output,
|
|
1542
|
+
"Analysis checkpoint output disagrees with authoritative project evidence.",
|
|
1543
|
+
);
|
|
1544
|
+
return recovered;
|
|
1545
|
+
}
|
|
1546
|
+
if (checkpoint === null) {
|
|
1547
|
+
if (kind === "scene.render") return await recoverSpatialRenderAttempt(application, request.exactInput, request.identity, request.workspaceDirectory, request.abortSignal);
|
|
1548
|
+
// These operations publish only immutable content-addressed bytes before
|
|
1549
|
+
// the private completion point. Orphans are safe to leave and retry.
|
|
1550
|
+
return { kind: "retry" };
|
|
1551
|
+
}
|
|
1552
|
+
if (kind === "scene.render") {
|
|
1553
|
+
const output = await recoverSpatialRenderOutput(application, request.exactInput, checkpoint.output, request.identity, request.abortSignal);
|
|
1554
|
+
return { kind: "completed", output, receiptReference: output.receipt.path,
|
|
1555
|
+
summary: { artifactSha256: output.artifact.sha256, frameCount: output.render.frameCount } };
|
|
1556
|
+
}
|
|
1557
|
+
if (isLocalSlopcameraVisualKind(kind)) {
|
|
1558
|
+
return await recoverCheckpointSlopcameraVisual(
|
|
1559
|
+
application,
|
|
1560
|
+
request,
|
|
1561
|
+
kind,
|
|
1562
|
+
checkpoint.output,
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
return await recoverCheckpointMedia(
|
|
1566
|
+
application,
|
|
1567
|
+
request,
|
|
1568
|
+
kind,
|
|
1569
|
+
checkpoint.output,
|
|
1570
|
+
);
|
|
1571
|
+
} catch (error) {
|
|
1572
|
+
return incompatible(error);
|
|
1573
|
+
}
|
|
1574
|
+
}
|