@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,291 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { presignUrl } from "@vercel/blob";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
import type { ApplicationContext } from "../application/context";
|
|
8
|
+
import { GatewayMediaSourceReferenceSchema, type GatewayMediaSourceReference } from "../application/gateway-port";
|
|
9
|
+
import { canonicalJson } from "../core/canonical-json";
|
|
10
|
+
import { createNodeBundleFileSystem } from "../core/storage";
|
|
11
|
+
import { CliError } from "./errors";
|
|
12
|
+
import { gatewayMediaBytesMatchType } from "./gateway-media-signature";
|
|
13
|
+
import { withMutationLock } from "./mutation-lock";
|
|
14
|
+
import { ensurePhysicalPrivateDirectoryWithin } from "./paths";
|
|
15
|
+
import { resolveVerifiedProjectMedia } from "./project-media-integrity";
|
|
16
|
+
|
|
17
|
+
// Narrow API-v12 transport reviewed against @vercel/blob 2.8.0's api.ts,
|
|
18
|
+
// put.ts, del.ts, and signed-token.ts in https://github.com/vercel/storage.
|
|
19
|
+
// The SDK's network retry/environment overrides are not part of this adapter.
|
|
20
|
+
const API = "https://vercel.com/api/blob";
|
|
21
|
+
const LIMITS = { imageBytes: 30 * 1024 * 1024, entries: 2, receiptBytes: 64 * 1024, responseBytes: 32 * 1024, requestMs: 60_000, cleanupMs: 120_000, accessMs: 15 * 60_000 } as const;
|
|
22
|
+
const RECEIPT = "receipt.json";
|
|
23
|
+
const NOTICE = "Private reference hosting uses Vercel Blob operations, storage, and transfer billed separately from the Gateway generation budget. Expiring model access does not delete the stored object.";
|
|
24
|
+
const IdSchema = z.string().regex(/^direct_[a-z][a-z0-9_-]{0,63}$/u);
|
|
25
|
+
const TakeSchema = z.string().regex(/^take_[a-z][a-z0-9_-]{0,63}$/u);
|
|
26
|
+
const StoreSchema = z.string().regex(/^[A-Za-z0-9]{1,64}$/u);
|
|
27
|
+
const TimeSchema = z.number().int().safe().positive();
|
|
28
|
+
const EtagSchema = z.string().min(1).max(256).regex(/^[\x21-\x7e]+$/u);
|
|
29
|
+
const SourceSchema = GatewayMediaSourceReferenceSchema.refine(source => ["image/png", "image/jpeg", "image/webp"].includes(source.mediaType) && source.bytes <= LIMITS.imageBytes, "Hosted directing references must be PNG, JPEG, or WebP images at most 30 MiB.");
|
|
30
|
+
const EntrySchema = z.strictObject({
|
|
31
|
+
source: SourceSchema,
|
|
32
|
+
pathname: z.string().regex(/^slopcamera\/directing\/[a-f0-9]{32}\/[01]-[a-f0-9]{64}\.(?:png|jpg|webp)$/u),
|
|
33
|
+
putStartedAt: TimeSchema,
|
|
34
|
+
putCompletedAt: TimeSchema.optional(),
|
|
35
|
+
etag: EtagSchema.optional(),
|
|
36
|
+
accessExpiresAt: TimeSchema.optional(),
|
|
37
|
+
verifiedAt: TimeSchema.optional(),
|
|
38
|
+
cleanup: z.enum(["pending", "deleted", "uncertain"]),
|
|
39
|
+
deleteStartedAt: TimeSchema.optional(),
|
|
40
|
+
deletedAt: TimeSchema.optional(),
|
|
41
|
+
failures: z.array(z.strictObject({ phase: z.enum(["put", "sign", "verify", "delete"]), at: TimeSchema })).max(32),
|
|
42
|
+
}).superRefine((entry, context) => {
|
|
43
|
+
if ((entry.putCompletedAt === undefined) !== (entry.etag === undefined)) context.addIssue({ code: "custom", message: "Confirmed hosting uploads require their exact ETag." });
|
|
44
|
+
if ((entry.cleanup === "deleted") !== (entry.deletedAt !== undefined)) context.addIssue({ code: "custom", message: "Deleted references require retained cleanup completion." });
|
|
45
|
+
});
|
|
46
|
+
export const DirectingBlobReceiptSchema = z.strictObject({
|
|
47
|
+
kind: z.literal("slopcamera.directing-blob"), schemaVersion: z.literal(1),
|
|
48
|
+
directingId: IdSchema, attemptId: TakeSchema,
|
|
49
|
+
namespace: z.string().regex(/^[a-f0-9]{32}$/u),
|
|
50
|
+
storeId: StoreSchema, createdAt: TimeSchema, closedAt: TimeSchema.optional(),
|
|
51
|
+
access: z.literal("private"), notice: z.literal(NOTICE),
|
|
52
|
+
entries: z.array(EntrySchema).max(LIMITS.entries),
|
|
53
|
+
}).superRefine((receipt, context) => {
|
|
54
|
+
for (const [index, entry] of receipt.entries.entries()) {
|
|
55
|
+
const extension = entry.source.mediaType === "image/jpeg" ? "jpg" : entry.source.mediaType.slice(6);
|
|
56
|
+
if (entry.pathname !== `slopcamera/directing/${receipt.namespace}/${index}-${entry.source.sha256}.${extension}`) context.addIssue({ code: "custom", message: "A hosted object must bind this session's exact private namespace and source bytes." });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
export type DirectingBlobReceipt = z.infer<typeof DirectingBlobReceiptSchema>;
|
|
60
|
+
type Entry = z.infer<typeof EntrySchema>;
|
|
61
|
+
|
|
62
|
+
export interface DirectingBlobSession {
|
|
63
|
+
readonly receiptPath: string;
|
|
64
|
+
resolveSourceUrl(source: GatewayMediaSourceReference, data: Uint8Array, signal: AbortSignal): Promise<string>;
|
|
65
|
+
/** Uses its own bounded cancellation scope so generation cancellation still permits cleanup. */
|
|
66
|
+
cleanup(): Promise<DirectingBlobReceipt>;
|
|
67
|
+
inspect(): Promise<DirectingBlobReceipt>;
|
|
68
|
+
}
|
|
69
|
+
export interface DirectingBlobOptions {
|
|
70
|
+
readonly application: ApplicationContext;
|
|
71
|
+
readonly environment: Readonly<Record<string, string | undefined>>;
|
|
72
|
+
readonly signal: AbortSignal;
|
|
73
|
+
readonly directingId: string;
|
|
74
|
+
readonly attemptId: string;
|
|
75
|
+
/** Injected transport for deterministic tests; production uses native fetch. */
|
|
76
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function failure(message: string, code: "invalid-data" | "authorization-required" | "conflict" | "ambiguous" | "cancelled" | "unavailable" = "unavailable"): never { throw new CliError(code, message); }
|
|
80
|
+
function token(value: string | undefined): string | undefined {
|
|
81
|
+
if (value === undefined || value === "") return undefined;
|
|
82
|
+
if (value.length > 16_384 || !/^[A-Za-z0-9_.-]+$/u.test(value)) failure("Blob credential has an unsupported format.", "authorization-required");
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
function credentials(environment: DirectingBlobOptions["environment"]): { storeId: string; token: string } {
|
|
86
|
+
const oidc = token(environment.VERCEL_OIDC_TOKEN), configuredStore = environment.BLOB_STORE_ID?.replace(/^store_/u, "");
|
|
87
|
+
if (oidc !== undefined && configuredStore !== undefined) {
|
|
88
|
+
const parsed = StoreSchema.safeParse(configuredStore);
|
|
89
|
+
if (!parsed.success) failure("BLOB_STORE_ID is invalid.", "authorization-required");
|
|
90
|
+
return { storeId: parsed.data, token: oidc };
|
|
91
|
+
}
|
|
92
|
+
const readWrite = token(environment.BLOB_READ_WRITE_TOKEN);
|
|
93
|
+
const match = /^vercel_blob_rw_([A-Za-z0-9]{1,64})_[A-Za-z0-9]+$/u.exec(readWrite ?? "");
|
|
94
|
+
if (readWrite === undefined || match === null) failure("Reference hosting requires BLOB_STORE_ID with VERCEL_OIDC_TOKEN, or BLOB_READ_WRITE_TOKEN for an existing private store.", "authorization-required");
|
|
95
|
+
if (configuredStore !== undefined && configuredStore !== match[1]) failure("Blob token and configured store identity differ.", "authorization-required");
|
|
96
|
+
return { storeId: match[1]!, token: readWrite };
|
|
97
|
+
}
|
|
98
|
+
function sourceIdentity(source: GatewayMediaSourceReference): string {
|
|
99
|
+
return canonicalJson({ path: source.path, bytes: source.bytes, sha256: source.sha256, mediaType: source.mediaType });
|
|
100
|
+
}
|
|
101
|
+
function privateUrl(storeId: string, pathname: string): string { return `https://${storeId.toLowerCase()}.private.blob.vercel-storage.com/${pathname}`; }
|
|
102
|
+
function exactPrivateUrl(value: string, storeId: string, pathname: string, signed: boolean): URL {
|
|
103
|
+
const parsed = new URL(value), expected = new URL(privateUrl(storeId, pathname));
|
|
104
|
+
if (parsed.origin !== expected.origin || parsed.pathname !== expected.pathname || parsed.username !== "" || parsed.password !== "" || parsed.hash !== "" || (!signed && parsed.search !== "")) failure("Blob returned a reference outside the exact private object scope.", "conflict");
|
|
105
|
+
if (signed) {
|
|
106
|
+
const keys = [...parsed.searchParams.keys()];
|
|
107
|
+
if (!parsed.searchParams.has("vercel-blob-delegation") || !parsed.searchParams.has("vercel-blob-signature") || new Set(keys).size !== keys.length || keys.some(key => !["cache", "vercel-blob-delegation", "vercel-blob-signature", "vercel-blob-valid-until"].includes(key))) failure("Blob returned an unsupported signed reference scope.", "conflict");
|
|
108
|
+
}
|
|
109
|
+
return parsed;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function readBounded(response: Response, maximum: number, signal: AbortSignal): Promise<Uint8Array> {
|
|
113
|
+
const declared = response.headers.get("content-length");
|
|
114
|
+
if (declared !== null && (!/^\d+$/u.test(declared) || Number(declared) > maximum)) { await response.body?.cancel(); failure("Blob response exceeds its byte limit.", "invalid-data"); }
|
|
115
|
+
const reader = response.body?.getReader();
|
|
116
|
+
if (reader === undefined) return new Uint8Array();
|
|
117
|
+
const chunks: Uint8Array[] = [];
|
|
118
|
+
let total = 0;
|
|
119
|
+
try {
|
|
120
|
+
while (true) {
|
|
121
|
+
signal.throwIfAborted();
|
|
122
|
+
const part = await reader.read();
|
|
123
|
+
if (part.done) break;
|
|
124
|
+
total += part.value.length;
|
|
125
|
+
if (total > maximum) failure("Blob response exceeds its byte limit.", "invalid-data");
|
|
126
|
+
chunks.push(part.value);
|
|
127
|
+
}
|
|
128
|
+
const output = new Uint8Array(total);
|
|
129
|
+
let offset = 0;
|
|
130
|
+
for (const chunk of chunks) { output.set(chunk, offset); offset += chunk.length; }
|
|
131
|
+
return output;
|
|
132
|
+
} finally { await reader.cancel().catch(() => undefined); reader.releaseLock(); }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Private immutable copies with short-lived, memory-only model access. No provisioning. */
|
|
136
|
+
export async function createDirectingBlobSession(options: DirectingBlobOptions): Promise<DirectingBlobSession> {
|
|
137
|
+
const directingId = IdSchema.parse(options.directingId), attemptId = TakeSchema.parse(options.attemptId);
|
|
138
|
+
const { application } = options, request = options.fetch ?? globalThis.fetch;
|
|
139
|
+
const parent = await ensurePhysicalPrivateDirectoryWithin(application.paths.repositoryRoot, relative(application.paths.repositoryRoot, application.paths.privateRoot));
|
|
140
|
+
const directory = await ensurePhysicalPrivateDirectoryWithin(parent, `directing-blob/${directingId}/${attemptId}`);
|
|
141
|
+
const fs = createNodeBundleFileSystem(directory), receiptPath = relative(application.paths.repositoryRoot, join(directory, RECEIPT));
|
|
142
|
+
const now = () => application.clock.now().getTime();
|
|
143
|
+
const read = async (): Promise<DirectingBlobReceipt> => {
|
|
144
|
+
const parsed = DirectingBlobReceiptSchema.parse(JSON.parse(await fs.readText(RECEIPT, LIMITS.receiptBytes)) as unknown);
|
|
145
|
+
if (parsed.directingId !== directingId || parsed.attemptId !== attemptId) failure("Blob receipt belongs to a different directing attempt.", "conflict");
|
|
146
|
+
return parsed;
|
|
147
|
+
};
|
|
148
|
+
await withMutationLock(directory, { command: "direct reference hosting", label: "Directing reference hosting" }, async lease => {
|
|
149
|
+
try { await read(); }
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
152
|
+
const auth = credentials(options.environment);
|
|
153
|
+
const receipt = DirectingBlobReceiptSchema.parse({ kind: "slopcamera.directing-blob", schemaVersion: 1, directingId, attemptId, namespace: randomUUID().replaceAll("-", ""), storeId: auth.storeId, createdAt: now(), access: "private", notice: NOTICE, entries: [] });
|
|
154
|
+
await fs.writeTextNoReplace!(RECEIPT, `${canonicalJson(receipt)}\n`, lease.assertOwned);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const mutate = async <T>(execute: (receipt: DirectingBlobReceipt, save: () => Promise<void>, assertOwned: () => Promise<void>) => Promise<T>): Promise<T> =>
|
|
159
|
+
await withMutationLock(directory, { command: "direct reference hosting", label: "Directing reference hosting" }, async lease => {
|
|
160
|
+
const receipt = await read();
|
|
161
|
+
const save = async () => { await fs.writeTextAtomicGuarded!(RECEIPT, `${canonicalJson(DirectingBlobReceiptSchema.parse(receipt))}\n`, lease.assertOwned); };
|
|
162
|
+
return await execute(receipt, save, lease.assertOwned);
|
|
163
|
+
});
|
|
164
|
+
const api = async (receipt: DirectingBlobReceipt, pathname: string, method: "GET" | "PUT" | "POST", body: string | Uint8Array | undefined, headers: Record<string, string>, signal: AbortSignal, assertOwned: () => Promise<void>): Promise<{ status: number; data: Uint8Array }> => {
|
|
165
|
+
const auth = credentials(options.environment);
|
|
166
|
+
if (auth.storeId !== receipt.storeId) failure("Blob credentials don't match the retained store.", "authorization-required");
|
|
167
|
+
await assertOwned();
|
|
168
|
+
signal.throwIfAborted();
|
|
169
|
+
const bounded = AbortSignal.any([signal, AbortSignal.timeout(LIMITS.requestMs)]);
|
|
170
|
+
const response = await request(`${API}${pathname}`, { method, redirect: "error", signal: bounded,
|
|
171
|
+
headers: { authorization: `Bearer ${auth.token}`, "x-vercel-blob-store-id": auth.storeId, "x-api-version": "12", "x-api-blob-request-id": `${auth.storeId}:${randomUUID()}`, "x-api-blob-request-attempt": "0", ...headers },
|
|
172
|
+
...(body === undefined ? {} : { body: typeof body === "string" ? body : Buffer.from(body) }),
|
|
173
|
+
});
|
|
174
|
+
return { status: response.status, data: await readBounded(response, LIMITS.responseBytes, bounded) };
|
|
175
|
+
};
|
|
176
|
+
const signedGet = async (receipt: DirectingBlobReceipt, entry: Entry, signal: AbortSignal, assertOwned: () => Promise<void>): Promise<string> => {
|
|
177
|
+
const validUntil = now() + LIMITS.accessMs;
|
|
178
|
+
const issued = await api(receipt, "/signed-token", "POST", JSON.stringify({ pathname: entry.pathname, operations: ["get"], validUntil }), { "content-type": "application/json" }, signal, assertOwned);
|
|
179
|
+
if (issued.status !== 200) failure("Blob could not grant temporary model access.");
|
|
180
|
+
const material = z.object({ delegationToken: z.string().min(1).max(16_384), clientSigningToken: z.string().min(1).max(4096), validUntil: TimeSchema }).parse(JSON.parse(new TextDecoder().decode(issued.data)) as unknown);
|
|
181
|
+
// Decode only scope assertions; server authorization remains authoritative.
|
|
182
|
+
const encoded = material.delegationToken.split(".")[0]!;
|
|
183
|
+
const scope = z.object({ storeId: z.string(), pathname: z.string(), operations: z.array(z.string()), validUntil: TimeSchema }).parse(JSON.parse(Buffer.from(encoded, "base64url").toString("utf8")) as unknown);
|
|
184
|
+
if (scope.storeId.replace(/^store_/u, "") !== receipt.storeId || scope.pathname !== entry.pathname || scope.operations.length !== 1 || scope.operations[0] !== "get" || scope.validUntil !== validUntil || material.validUntil !== validUntil) failure("Blob signing material exceeds the exact temporary read scope.", "conflict");
|
|
185
|
+
const signed = await presignUrl(material, { operation: "get", pathname: entry.pathname, access: "private", validUntil, useCache: false });
|
|
186
|
+
const url = exactPrivateUrl(signed.presignedUrl, receipt.storeId, entry.pathname, true).href;
|
|
187
|
+
entry.accessExpiresAt = validUntil;
|
|
188
|
+
return url;
|
|
189
|
+
};
|
|
190
|
+
const verifyGet = async (receipt: DirectingBlobReceipt, entry: Entry, url: string, signal: AbortSignal): Promise<{ found: false } | { found: true; etag: string }> => {
|
|
191
|
+
exactPrivateUrl(url, receipt.storeId, entry.pathname, true);
|
|
192
|
+
const bounded = AbortSignal.any([signal, AbortSignal.timeout(LIMITS.requestMs)]);
|
|
193
|
+
const response = await request(url, { method: "GET", redirect: "error", signal: bounded, headers: { "cache-control": "no-cache" } });
|
|
194
|
+
if (response.status === 404) { await response.body?.cancel(); return { found: false }; }
|
|
195
|
+
if (response.status !== 200) { await response.body?.cancel(); failure("Private hosted reference could not be verified."); }
|
|
196
|
+
const bytes = await readBounded(response, entry.source.bytes, bounded);
|
|
197
|
+
const etag = EtagSchema.parse(response.headers.get("etag"));
|
|
198
|
+
if (bytes.length !== entry.source.bytes || createHash("sha256").update(bytes).digest("hex") !== entry.source.sha256 || response.headers.get("content-type")?.split(";")[0]?.trim() !== entry.source.mediaType || (entry.etag !== undefined && entry.etag !== etag)) failure("Private hosted reference differs from the retained source bytes or object identity.", "conflict");
|
|
199
|
+
return { found: true, etag };
|
|
200
|
+
};
|
|
201
|
+
const verifyPrivateAccess = async (receipt: DirectingBlobReceipt, entry: Entry, signal: AbortSignal): Promise<void> => {
|
|
202
|
+
const response = await request(privateUrl(receipt.storeId, entry.pathname), { method: "GET", redirect: "error", signal: AbortSignal.any([signal, AbortSignal.timeout(LIMITS.requestMs)]) });
|
|
203
|
+
await response.body?.cancel();
|
|
204
|
+
if (![401, 403, 404].includes(response.status)) failure("Hosted reference is not proven private without temporary authorization.", "conflict");
|
|
205
|
+
};
|
|
206
|
+
const markFailure = (entry: Entry, phase: "put" | "sign" | "verify" | "delete") => {
|
|
207
|
+
if (entry.failures.length < 32) entry.failures.push({ phase, at: now() });
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
receiptPath,
|
|
212
|
+
inspect: read,
|
|
213
|
+
resolveSourceUrl: async (input, data, signal) => {
|
|
214
|
+
const source = SourceSchema.parse(input);
|
|
215
|
+
if (!(data instanceof Uint8Array) || data.length !== source.bytes) failure("Reference hosting requires the exact retained image bytes.", "invalid-data");
|
|
216
|
+
const uploadBytes = new Uint8Array(data);
|
|
217
|
+
if (createHash("sha256").update(uploadBytes).digest("hex") !== source.sha256 || !gatewayMediaBytesMatchType(uploadBytes, source.mediaType)) failure("Reference hosting requires the exact retained image bytes.", "invalid-data");
|
|
218
|
+
await resolveVerifiedProjectMedia({ repositoryRoot: application.paths.repositoryRoot, path: source.path, expected: source, label: "Directing hosted reference" });
|
|
219
|
+
const activeSignal = AbortSignal.any([options.signal, signal]);
|
|
220
|
+
return await mutate(async (receipt, save, assertOwned) => {
|
|
221
|
+
activeSignal.throwIfAborted();
|
|
222
|
+
if (receipt.closedAt !== undefined) failure("This reference-hosting session is closed; use a new take for another generation.", "conflict");
|
|
223
|
+
let entry = receipt.entries.find(value => sourceIdentity(value.source) === sourceIdentity(source));
|
|
224
|
+
if (entry === undefined) {
|
|
225
|
+
if (receipt.entries.length >= LIMITS.entries) failure("One directing take may host at most two exact image references.", "invalid-data");
|
|
226
|
+
const extension = source.mediaType === "image/jpeg" ? "jpg" : source.mediaType.slice(6);
|
|
227
|
+
entry = { source, pathname: `slopcamera/directing/${receipt.namespace}/${receipt.entries.length}-${source.sha256}.${extension}`, putStartedAt: now(), cleanup: "pending", failures: [] };
|
|
228
|
+
receipt.entries.push(entry);
|
|
229
|
+
await save(); // Durable exact upload intent precedes the only PUT.
|
|
230
|
+
try {
|
|
231
|
+
const uploaded = await api(receipt, `/?${new URLSearchParams({ pathname: entry.pathname })}`, "PUT", uploadBytes,
|
|
232
|
+
{ "content-type": source.mediaType, "x-content-type": source.mediaType, "x-vercel-blob-access": "private", "x-add-random-suffix": "0", "x-allow-overwrite": "0", "x-cache-control-max-age": "60" }, activeSignal, assertOwned);
|
|
233
|
+
if (uploaded.status < 200 || uploaded.status > 299) failure("Private reference upload did not complete.");
|
|
234
|
+
const result = z.object({ url: z.string().max(2048), pathname: z.string().max(512), contentType: z.string().max(128), etag: EtagSchema }).parse(JSON.parse(new TextDecoder().decode(uploaded.data)) as unknown);
|
|
235
|
+
exactPrivateUrl(result.url, receipt.storeId, entry.pathname, false);
|
|
236
|
+
if (result.pathname !== entry.pathname || result.contentType !== source.mediaType) failure("Blob upload response differs from the exact reference scope.", "conflict");
|
|
237
|
+
entry.putCompletedAt = now(); entry.etag = result.etag;
|
|
238
|
+
await save();
|
|
239
|
+
} catch {
|
|
240
|
+
markFailure(entry, "put"); await save();
|
|
241
|
+
failure(`Reference upload is uncertain. Retained cleanup evidence: ${receiptPath}. No upload retry was made.`, "ambiguous");
|
|
242
|
+
}
|
|
243
|
+
} else if (entry.putCompletedAt === undefined) {
|
|
244
|
+
failure(`A previous upload may exist. Run reference cleanup for this take; it will not be uploaded again. Evidence: ${receiptPath}.`, "ambiguous");
|
|
245
|
+
}
|
|
246
|
+
let phase: "sign" | "verify" = "sign";
|
|
247
|
+
try {
|
|
248
|
+
const url = await signedGet(receipt, entry, activeSignal, assertOwned);
|
|
249
|
+
await save();
|
|
250
|
+
phase = "verify";
|
|
251
|
+
if (!(await verifyGet(receipt, entry, url, activeSignal)).found) failure("Uploaded private reference is absent.", "conflict");
|
|
252
|
+
await verifyPrivateAccess(receipt, entry, activeSignal);
|
|
253
|
+
entry.verifiedAt = now(); await save();
|
|
254
|
+
return url;
|
|
255
|
+
} catch {
|
|
256
|
+
markFailure(entry, phase); await save();
|
|
257
|
+
failure(`Private reference access could not be verified. Retained cleanup evidence: ${receiptPath}.`, "unavailable");
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
},
|
|
261
|
+
cleanup: async () => {
|
|
262
|
+
const cleanupSignal = AbortSignal.timeout(LIMITS.cleanupMs);
|
|
263
|
+
return await mutate(async (receipt, save, assertOwned) => {
|
|
264
|
+
receipt.closedAt ??= now(); await save();
|
|
265
|
+
for (const entry of receipt.entries) {
|
|
266
|
+
if (entry.cleanup === "deleted") continue;
|
|
267
|
+
try {
|
|
268
|
+
const url = await signedGet(receipt, entry, cleanupSignal, assertOwned);
|
|
269
|
+
await save();
|
|
270
|
+
const observed = await verifyGet(receipt, entry, url, cleanupSignal);
|
|
271
|
+
if (!observed.found) {
|
|
272
|
+
if (entry.putCompletedAt === undefined) failure("An ambiguous upload's absence cannot prove that its remote write settled.", "ambiguous");
|
|
273
|
+
entry.cleanup = "deleted"; entry.deletedAt = now(); await save(); continue;
|
|
274
|
+
}
|
|
275
|
+
// Verification can recover an exact object after a lost PUT response.
|
|
276
|
+
entry.etag = observed.etag; entry.putCompletedAt ??= now(); entry.verifiedAt = now();
|
|
277
|
+
entry.deleteStartedAt = now(); await save();
|
|
278
|
+
const deleted = await api(receipt, "/delete", "POST", JSON.stringify({ urls: [entry.pathname] }), { "content-type": "application/json", "x-if-match": observed.etag }, cleanupSignal, assertOwned);
|
|
279
|
+
if (deleted.status < 200 || deleted.status > 299) failure("Private reference deletion did not complete.");
|
|
280
|
+
const absent = await verifyGet(receipt, entry, url, cleanupSignal);
|
|
281
|
+
if (absent.found) failure("The private reference is still readable after deletion.", "ambiguous");
|
|
282
|
+
entry.cleanup = "deleted"; entry.deletedAt = now(); await save();
|
|
283
|
+
} catch {
|
|
284
|
+
entry.cleanup = "uncertain"; markFailure(entry, "delete"); await save();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return DirectingBlobReceiptSchema.parse(receipt);
|
|
288
|
+
});
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|