@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,70 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { lstat, mkdir, realpath } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { parseStudioJob, parseStudioSourceBundle, studioSourceBundleSha256 } from "../../../src/studio";
|
|
5
|
+
import blenderProduct from "../../../examples/studio/blender/product.py" with { type: "text" };
|
|
6
|
+
import blenderCharacter from "../../../examples/studio/blender/character.py" with { type: "text" };
|
|
7
|
+
import blenderShadedStreet from "../../../examples/studio/blender/shaded_street.py" with { type: "text" };
|
|
8
|
+
import blenderCloth from "../../../examples/studio/blender/cloth.py" with { type: "text" };
|
|
9
|
+
import blenderFluid from "../../../examples/studio/blender/fluid.py" with { type: "text" };
|
|
10
|
+
import blenderToolkit from "../../../examples/studio/blender/studio_scene.py" with { type: "text" };
|
|
11
|
+
import cadqueryBracket from "../../../examples/studio/cadquery/bracket.py" with { type: "text" };
|
|
12
|
+
import educationScene from "../../../examples/studio/education/scene.py" with { type: "text" };
|
|
13
|
+
import educationLesson from "../../../examples/studio/education/lesson.json";
|
|
14
|
+
import educationValidation from "../studio/education/lesson.py" with { type: "text" };
|
|
15
|
+
import educationToolkit from "../studio/education/toolkit.py" with { type: "text" };
|
|
16
|
+
import { createNodeBundleFileSystem } from "../core/storage";
|
|
17
|
+
import { studioBytesSha256, studioJson } from "./studio-files";
|
|
18
|
+
import type { StudioTemplate } from "./studio-template-names";
|
|
19
|
+
|
|
20
|
+
export function studioStarter(template: StudioTemplate) {
|
|
21
|
+
const engine = template.startsWith("blender-") ? "blender" : template === "cadquery-bracket" ? "cadquery" : "manim";
|
|
22
|
+
const files: Record<string, string> = engine === "blender" ? {
|
|
23
|
+
"scene.py": ({ "blender-product": blenderProduct, "blender-character": blenderCharacter, "blender-shaded-street": blenderShadedStreet, "blender-cloth": blenderCloth, "blender-fluid": blenderFluid } as Record<string, string>)[template]!,
|
|
24
|
+
"studio_scene.py": blenderToolkit,
|
|
25
|
+
...(template === "blender-shaded-street" ? { "character.py": blenderCharacter } : {}),
|
|
26
|
+
} : engine === "cadquery" ? { "scene.py": cadqueryBracket } : { "scene.py": educationScene, "lesson.json": studioJson(educationLesson), "lesson.py": educationValidation, "toolkit.py": educationToolkit };
|
|
27
|
+
const source = { engine, entrypoint: { kind: "python", path: "scene.py" }, files: Object.keys(files).sort() };
|
|
28
|
+
const bundle = parseStudioSourceBundle({ kind: "slopcamera.studio-source-bundle", schemaVersion: 1, engine, entrypoint: source.entrypoint,
|
|
29
|
+
files: Object.entries(files).map(([path, text]) => ({ path, bytes: Buffer.byteLength(text), sha256: studioBytesSha256(text) })) });
|
|
30
|
+
const raster = { kind: "raster", colorSpace: "srgb", alpha: "opaque", dataType: "uint8", channels: ["R", "G", "B"], semantic: "color", unit: "unitless" };
|
|
31
|
+
const simulation = template === "blender-cloth" || template === "blender-fluid";
|
|
32
|
+
const portraitCity = template === "blender-shaded-street";
|
|
33
|
+
const outputs = engine === "cadquery" ? [
|
|
34
|
+
{ id: "solid", kind: "file", role: "model", format: "step", path: "model.step", interpretation: { kind: "model", sourceSpace: { units: "millimeters", upAxis: "z", handedness: "right" } } },
|
|
35
|
+
{ id: "preview", kind: "file", role: "model", format: "glb", path: "model.glb", interpretation: { kind: "model", sourceSpace: { units: "meters", upAxis: "y", handedness: "right" } } },
|
|
36
|
+
] : simulation ? [
|
|
37
|
+
{ id: "native", kind: "file", role: "native-source", format: "blend", path: "native/scene.blend", interpretation: { kind: "native-source" } },
|
|
38
|
+
{ id: "cache", kind: "directory", role: "simulation-cache", format: "cache", path: "cache", interpretation: { kind: "cache", semantics: "opaque-native" } },
|
|
39
|
+
] : [
|
|
40
|
+
{ id: "beauty", kind: "sequence", role: "beauty", format: "png", pathPattern: "frames/%06d.png", interpretation: raster },
|
|
41
|
+
...(engine === "manim" ? [{ id: "movie", kind: "file", role: "beauty", format: "mp4", path: "lesson.mp4", interpretation: raster }]
|
|
42
|
+
: [{ id: "native", kind: "file", role: "native-source", format: "blend", path: "native/scene.blend", interpretation: { kind: "native-source" } }]),
|
|
43
|
+
];
|
|
44
|
+
const job = parseStudioJob({ kind: "slopcamera.studio-job", schemaVersion: 1, jobId: `studio_${template.replaceAll("-", "_")}_${randomUUID()}`, bundleSha256: studioSourceBundleSha256(bundle),
|
|
45
|
+
stage: engine === "cadquery" ? "build" : simulation ? "bake" : "render",
|
|
46
|
+
parameters: engine === "manim" ? { lessonFile: "lesson.json" } : portraitCity ? { shot: "establish", motion: "approach" } : {},
|
|
47
|
+
engine: engine === "blender" ? { engine, renderer: "cycles", device: "gpu", samples: portraitCity ? 16 : 32, transparent: false, viewTransform: portraitCity ? "Standard" : "AgX", denoise: true, seed: 0 }
|
|
48
|
+
: engine === "manim" ? { engine, scene: "PythagoreanLesson", renderer: "cairo", transparent: false }
|
|
49
|
+
: { engine, exportVariable: "model", tolerance: 0.05, angularTolerance: 0.1 },
|
|
50
|
+
...(engine === "cadquery" ? {} : { render: { width: engine === "manim" ? 480 : portraitCity ? 360 : 640, height: engine === "manim" ? 854 : portraitCity ? 640 : 360, frameRate: { numerator: 24, denominator: 1 },
|
|
51
|
+
startFrame: simulation || portraitCity ? 1 : 0, endFrameExclusive: engine === "manim" ? 240 : template === "blender-cloth" ? 41 : template === "blender-fluid" ? 33 : portraitCity ? 73 : 72 } }),
|
|
52
|
+
outputs, limits: { timeoutSeconds: 900, maximumOutputBytes: 2_147_483_648, maximumOutputFiles: 2048 },
|
|
53
|
+
execution: { trust: "trusted-current-user", isolation: "none", hermetic: false },
|
|
54
|
+
});
|
|
55
|
+
return { source, bundle, job, files };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function createStudioScaffold(directoryInput: string, template: StudioTemplate, fence: () => Promise<void>) {
|
|
59
|
+
const requested = resolve(directoryInput), parent = await realpath(dirname(requested)), directory = join(parent, requested.slice(dirname(requested).length + 1));
|
|
60
|
+
await fence();
|
|
61
|
+
await mkdir(directory, { mode: 0o700 }); // A scaffold never merges into an existing source directory.
|
|
62
|
+
if (!(await lstat(directory)).isDirectory()) throw new Error("Studio scaffold directory changed.");
|
|
63
|
+
const starter = studioStarter(template), fs = createNodeBundleFileSystem(directory);
|
|
64
|
+
for (const [path, text] of Object.entries(starter.files)) await fs.writeTextNoReplace!(path, text, fence);
|
|
65
|
+
await fs.writeTextNoReplace!("source.json", studioJson(starter.source), fence);
|
|
66
|
+
await fs.writeTextNoReplace!("job.json", studioJson(starter.job), fence);
|
|
67
|
+
return { directory, template, jobId: starter.job.jobId, bundleSha256: starter.job.bundleSha256, executed: false,
|
|
68
|
+
source: join(directory, "source.json"), job: join(directory, "job.json"),
|
|
69
|
+
next: ["Edit the retained source and job as needed.", `slopcamera studio bundle ${JSON.stringify(join(directory, "source.json"))} --json`, "After editing source, bind the returned bundleSha256 into a new job ID before running."] };
|
|
70
|
+
}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import {
|
|
5
|
+
parseStudioJob, parseStudioPlan, parseStudioSourceBundle, planStudioJob, studioRuntimeSha256, studioSourceBundleSha256,
|
|
6
|
+
validateStudioReceipt, type StudioJob, type StudioPlan, type StudioReceipt, type StudioSourceBundle,
|
|
7
|
+
} from "../../../src/studio";
|
|
8
|
+
import type { ApplicationContext } from "../application/context";
|
|
9
|
+
import {
|
|
10
|
+
MediaArtifactReferenceSchema, type MediaArtifactReference, type MediaArtifactRequest,
|
|
11
|
+
} from "../application/operations/media/shared";
|
|
12
|
+
import {
|
|
13
|
+
BoundStudioRunInputSchema, StudioRunOutputSchema, type ApplicationStudioPort, type StudioBindingControl, type StudioRunOutput,
|
|
14
|
+
} from "../application/studio-port";
|
|
15
|
+
import { createNodeBundleFileSystem, type BundleFileSystem } from "../core/storage";
|
|
16
|
+
import { CliError } from "./errors";
|
|
17
|
+
import { withMutationLock } from "./mutation-lock";
|
|
18
|
+
import { ensurePhysicalPrivateDirectoryWithin, ensurePrivateDirectory } from "./paths";
|
|
19
|
+
import { withStudioProcessCustody } from "./studio-custody";
|
|
20
|
+
import {
|
|
21
|
+
copyStudioSource, inspectStudioOutputs, inventoryStudioFiles, studioJson, verifyStudioSource,
|
|
22
|
+
} from "./studio-files";
|
|
23
|
+
import { validateStudioOutputMedia } from "./studio-output-validation";
|
|
24
|
+
import { NativeStudioProcess, type StudioProcessPort } from "./studio-process";
|
|
25
|
+
import { probeStudioRuntime, studioDriverArgv, type StudioRuntimeSelection } from "./studio-runtime";
|
|
26
|
+
|
|
27
|
+
export interface StudioServiceOptions {
|
|
28
|
+
readonly application: ApplicationContext; readonly selection: StudioRuntimeSelection;
|
|
29
|
+
readonly process?: StudioProcessPort;
|
|
30
|
+
}
|
|
31
|
+
export interface StudioExecutionControl {
|
|
32
|
+
readonly signal: AbortSignal; readonly allowTrustedCode: true;
|
|
33
|
+
readonly inheritedFileDescriptors?: readonly number[];
|
|
34
|
+
readonly workflow?: { readonly runId: string; readonly nodeKey: string; readonly nodePlanSha256: string };
|
|
35
|
+
/** Host monotonic deadline shared by native rendering and all output verification. */
|
|
36
|
+
readonly deadlineAt?: number;
|
|
37
|
+
beforePublication(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const completionSchema = z.strictObject({
|
|
41
|
+
kind: z.literal("slopcamera.studio-completion"), schemaVersion: z.literal(1), attemptId: z.string(), planSha256: z.string(),
|
|
42
|
+
startedAt: z.iso.datetime({ offset: true }), finishedAt: z.iso.datetime({ offset: true }),
|
|
43
|
+
exitCode: z.number().int().nullable(), custody: z.enum(["closed", "unknown"]),
|
|
44
|
+
failure: z.enum(["cancelled", "timeout", "output-limit", "spawn", "descendants"]).optional(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
async function optionalJson(fs: BundleFileSystem, path: string): Promise<unknown | undefined> {
|
|
48
|
+
try { return JSON.parse(await fs.readText(path, 32 * 1024 * 1024)) as unknown; }
|
|
49
|
+
catch (error) { if (error instanceof Error && "code" in error && error.code === "ENOENT") return undefined; throw error; }
|
|
50
|
+
}
|
|
51
|
+
async function immutableJson(fs: BundleFileSystem, path: string, value: unknown, fence: () => Promise<void>): Promise<void> {
|
|
52
|
+
const text = studioJson(value), disposition = await fs.writeTextNoReplace!(path, text, fence);
|
|
53
|
+
if (disposition !== "created" && await fs.readText(path) !== text) throw new CliError("conflict", `Retained studio document differs: ${path}`);
|
|
54
|
+
}
|
|
55
|
+
export async function studioStorageRoot(application: ApplicationContext): Promise<string> {
|
|
56
|
+
await ensurePrivateDirectory(application.paths.privateRoot);
|
|
57
|
+
return await ensurePhysicalPrivateDirectoryWithin(application.paths.privateRoot, "studio");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function readRetainedStudioBundle(application: ApplicationContext, job: StudioJob, requested?: MediaArtifactRequest): Promise<{ readonly bundle: StudioSourceBundle; readonly sourceRoot: string; readonly reference: MediaArtifactReference }> {
|
|
61
|
+
const root = await studioStorageRoot(application), directory = join(root, "bundles", job.bundleSha256);
|
|
62
|
+
const path = relative(application.paths.repositoryRoot, join(directory, "bundle.json"));
|
|
63
|
+
if (requested !== undefined && requested.path !== path) throw new CliError("unsafe-path", "Studio execution requires the exact retained bundle manifest returned by studio bundle.");
|
|
64
|
+
const fs = createNodeBundleFileSystem(application.paths.repositoryRoot), integrity = await fs.inspectFile!(path, 1024 * 1024);
|
|
65
|
+
if (requested !== undefined && "sha256" in requested && (requested.sha256 !== integrity.sha256 || requested.bytes !== integrity.bytes)) throw new CliError("conflict", "Retained studio manifest bytes changed.");
|
|
66
|
+
const bundle = parseStudioSourceBundle(JSON.parse(await fs.readText(path, 1024 * 1024)) as unknown);
|
|
67
|
+
if (studioSourceBundleSha256(bundle) !== job.bundleSha256) throw new CliError("conflict", "Studio bundle digest differs from the requested job.");
|
|
68
|
+
const sourceRoot = join(directory, "source");
|
|
69
|
+
await verifyStudioSource(sourceRoot, bundle);
|
|
70
|
+
return { bundle, sourceRoot, reference: MediaArtifactReferenceSchema.parse({ path, ...integrity }) };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function createStudioService(options: StudioServiceOptions) {
|
|
74
|
+
const application = options.application, native = options.process ?? new NativeStudioProcess();
|
|
75
|
+
const applicationFence = async () => { await application.hostResourceLease?.assertOwned(); };
|
|
76
|
+
const withCustody = async <T>(signal: AbortSignal, fence: () => Promise<void>, execute: (process: StudioProcessPort) => Promise<T>): Promise<T> => {
|
|
77
|
+
if (application.machineStateRoot === undefined) throw new CliError("unavailable", "Native studio execution requires machine-wide custody storage.");
|
|
78
|
+
await ensurePrivateDirectory(application.machineStateRoot);
|
|
79
|
+
return await withStudioProcessCustody({ machineStateRoot: application.machineStateRoot, process: native, label: "SLOPCAMERA native studio", signal, fence }, execute);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const plan = async (jobInput: unknown): Promise<StudioPlan> => {
|
|
83
|
+
const job = parseStudioJob(jobInput), retained = await readRetainedStudioBundle(application, job);
|
|
84
|
+
return planStudioJob({ bundle: retained.bundle, job });
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const bind = async (jobInput: unknown, signal: AbortSignal, requested?: MediaArtifactRequest, expected?: StudioPlan, control?: StudioBindingControl): Promise<StudioPlan> => {
|
|
88
|
+
const job = parseStudioJob(jobInput), retained = await readRetainedStudioBundle(application, job, requested);
|
|
89
|
+
const root = await studioStorageRoot(application), workingRoot = await ensurePhysicalPrivateDirectoryWithin(root, `probes/probe_${randomUUID()}`);
|
|
90
|
+
const fence = async () => { await applicationFence(); await control?.beforePublication(); };
|
|
91
|
+
const inheritedFileDescriptors = control?.inheritedFileDescriptors ?? application.hostResourceLease?.inheritedFileDescriptors;
|
|
92
|
+
return await withCustody(signal, fence, async process => {
|
|
93
|
+
const runtime = await probeStudioRuntime({ engine: retained.bundle.engine, selection: options.selection, workingRoot, process, signal, fence,
|
|
94
|
+
...(inheritedFileDescriptors === undefined ? {} : { inheritedFileDescriptors }) });
|
|
95
|
+
const bound = planStudioJob({ bundle: retained.bundle, job, runtime: runtime.identity });
|
|
96
|
+
if (expected !== undefined && bound.planSha256 !== parseStudioPlan(expected).planSha256) throw new CliError("incompatible", "Studio source, settings or observed runtime changed after planning.");
|
|
97
|
+
await immutableJson(createNodeBundleFileSystem(workingRoot), "probe.json", runtime.identity, fence);
|
|
98
|
+
return bound;
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const inspect = async (jobId: string): Promise<StudioRunOutput> => {
|
|
103
|
+
if (!/^studio_[a-zA-Z0-9][a-zA-Z0-9_-]{0,120}$/u.test(jobId)) throw new CliError("invalid-data", "Invalid studio job ID.");
|
|
104
|
+
const root = await studioStorageRoot(application), directory = join(root, "jobs", jobId), fs = createNodeBundleFileSystem(directory);
|
|
105
|
+
const planned = parseStudioPlan(await optionalJson(fs, "plan.json"));
|
|
106
|
+
const receipt = validateStudioReceipt({ plan: planned, receipt: await optionalJson(fs, "receipt.json") });
|
|
107
|
+
const artifacts = await inspectStudioOutputs(join(directory, "outputs"), planned.job);
|
|
108
|
+
if (studioJson(artifacts) !== studioJson(receipt.outputs)) throw new CliError("conflict", "Studio output files differ from the retained receipt.");
|
|
109
|
+
await verifyStudioSource(join(directory, "source"), planned.bundle);
|
|
110
|
+
const receiptPath = relative(application.paths.repositoryRoot, join(directory, "receipt.json"));
|
|
111
|
+
const integrity = await createNodeBundleFileSystem(application.paths.repositoryRoot).inspectFile!(receiptPath, 32 * 1024 * 1024);
|
|
112
|
+
return StudioRunOutputSchema.parse({ receipt: { path: receiptPath, ...integrity }, document: receipt,
|
|
113
|
+
outputs: receipt.outputs.map(item => ({ outputId: item.outputId, role: item.role, format: item.format,
|
|
114
|
+
artifact: { path: relative(application.paths.repositoryRoot, join(directory, "outputs", item.path)), sha256: item.sha256, bytes: item.bytes } })) });
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const runOnce = async (jobInput: unknown, control: StudioExecutionControl, expected?: StudioPlan): Promise<StudioRunOutput> => {
|
|
118
|
+
if (control.allowTrustedCode !== true) throw new CliError("authorization-required", "Native source requires explicit --allow-trusted-code authorization.");
|
|
119
|
+
if (control.signal.aborted) throw new CliError("cancelled", "Studio execution cancelled before dispatch.");
|
|
120
|
+
const job = parseStudioJob(jobInput), retained = await readRetainedStudioBundle(application, job);
|
|
121
|
+
const root = await studioStorageRoot(application), directory = await ensurePhysicalPrivateDirectoryWithin(root, `jobs/${job.jobId}`);
|
|
122
|
+
return await withMutationLock(directory, { command: "studio run", label: job.jobId }, async lease => {
|
|
123
|
+
const fence = async () => { await applicationFence(); await control.beforePublication(); await lease.assertOwned(); };
|
|
124
|
+
const terminalFence = async () => { await lease.assertOwned(); };
|
|
125
|
+
const fs = createNodeBundleFileSystem(directory), priorPlan = await optionalJson(fs, "plan.json"), priorReceipt = await optionalJson(fs, "receipt.json");
|
|
126
|
+
if (priorPlan !== undefined) {
|
|
127
|
+
const previous = parseStudioPlan(priorPlan);
|
|
128
|
+
const now = planStudioJob({ bundle: retained.bundle, job, runtime: previous.runtime });
|
|
129
|
+
if (previous.planSha256 !== now.planSha256 || expected !== undefined && previous.planSha256 !== expected.planSha256) throw new CliError("conflict", "Studio job ID already binds a different plan. Retain it and choose a new job ID.");
|
|
130
|
+
if (priorReceipt !== undefined) {
|
|
131
|
+
await fence();
|
|
132
|
+
const previousReceipt = validateStudioReceipt({ plan: previous, receipt: priorReceipt });
|
|
133
|
+
if (previousReceipt.state !== "succeeded") throw new CliError(previousReceipt.state === "unknown-custody" ? "ambiguous" : "subprocess", "Studio job did not complete successfully; its retained failure will not be rerun automatically.", { jobId: job.jobId, state: previousReceipt.state });
|
|
134
|
+
return await inspect(job.jobId);
|
|
135
|
+
}
|
|
136
|
+
throw new CliError("ambiguous", "Studio job has retained execution intent without a receipt. Inspect and reconcile it; it will not be rerun automatically.");
|
|
137
|
+
}
|
|
138
|
+
const sourceRoot = await ensurePhysicalPrivateDirectoryWithin(directory, "source"), outputRoot = await ensurePhysicalPrivateDirectoryWithin(directory, "outputs"), workingRoot = await ensurePhysicalPrivateDirectoryWithin(directory, "working");
|
|
139
|
+
await copyStudioSource(retained.sourceRoot, sourceRoot, retained.bundle, fence);
|
|
140
|
+
await verifyStudioSource(sourceRoot, retained.bundle);
|
|
141
|
+
if ((await inventoryStudioFiles(outputRoot, job.limits.maximumOutputFiles, job.limits.maximumOutputBytes)).length !== 0) throw new CliError("conflict", "Studio output directory already contains unreceipted files.");
|
|
142
|
+
return await withCustody(control.signal, fence, async supervised => {
|
|
143
|
+
let unknownCustody = false;
|
|
144
|
+
const process: StudioProcessPort = { run: async (argv, processOptions) => {
|
|
145
|
+
try {
|
|
146
|
+
const observed = await supervised.run(argv, processOptions);
|
|
147
|
+
if (observed.custody === "unknown") unknownCustody = true;
|
|
148
|
+
return observed;
|
|
149
|
+
} catch (error) { unknownCustody = true; throw error; }
|
|
150
|
+
} };
|
|
151
|
+
const runtime = await probeStudioRuntime({ engine: retained.bundle.engine, selection: options.selection, workingRoot, process, signal: control.signal, fence,
|
|
152
|
+
...(control.inheritedFileDescriptors === undefined ? {} : { inheritedFileDescriptors: control.inheritedFileDescriptors }) });
|
|
153
|
+
const planned = planStudioJob({ bundle: retained.bundle, job, runtime: runtime.identity });
|
|
154
|
+
if (expected !== undefined && planned.planSha256 !== parseStudioPlan(expected).planSha256) throw new CliError("incompatible", "Native runtime differs from the bound studio plan.");
|
|
155
|
+
if (planned.readiness !== "authorization-required") throw new CliError("unavailable", "The installed native runtime has not reported every capability required by this job.", { checks: planned.capabilityChecks });
|
|
156
|
+
const attemptId = `attempt_${randomUUID()}`, startedAt = application.clock.now().toISOString();
|
|
157
|
+
await immutableJson(fs, "plan.json", planned, fence);
|
|
158
|
+
await immutableJson(fs, "intent.json", { kind: "slopcamera.studio-intent", schemaVersion: 1, attemptId, startedAt, planSha256: planned.planSha256,
|
|
159
|
+
...(control.workflow === undefined ? {} : { workflow: control.workflow }) }, fence);
|
|
160
|
+
await immutableJson(fs, "working/request.json", { bundle: retained.bundle, job, sourceRoot, outputRoot, workingRoot }, fence);
|
|
161
|
+
let budgetFailure: { readonly stage: string; readonly code: string } | undefined;
|
|
162
|
+
const assertBudget = async () => {
|
|
163
|
+
let stage = "publication-fence";
|
|
164
|
+
try {
|
|
165
|
+
await fence();
|
|
166
|
+
stage = "output-inventory";
|
|
167
|
+
await inventoryStudioFiles(outputRoot, job.limits.maximumOutputFiles, job.limits.maximumOutputBytes, "live");
|
|
168
|
+
stage = "working-inventory";
|
|
169
|
+
await inventoryStudioFiles(workingRoot, job.limits.maximumOutputFiles + 2048, job.limits.maximumOutputBytes + 64 * 1024 * 1024, "live");
|
|
170
|
+
} catch (error) {
|
|
171
|
+
const code = error instanceof Error && "code" in error && typeof error.code === "string"
|
|
172
|
+
&& ["ENOENT", "EACCES", "EPERM", "ENOTDIR", "ELOOP"].includes(error.code) ? error.code : "inventory-or-fence-rejected";
|
|
173
|
+
budgetFailure ??= { stage, code };
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
await assertBudget();
|
|
178
|
+
const result = await process.run(studioDriverArgv(runtime, retained.bundle.engine, { request: join(workingRoot, "request.json") }), {
|
|
179
|
+
cwd: workingRoot, env: runtime.environment, timeoutMs: Math.max(1, Math.ceil(Math.min(job.limits.timeoutSeconds * 1000, (control.deadlineAt ?? Infinity) - performance.now()))), maximumLogBytes: 4_194_304,
|
|
180
|
+
signal: control.signal, assertBudget,
|
|
181
|
+
...(control.inheritedFileDescriptors === undefined ? {} : { inheritedFileDescriptors: control.inheritedFileDescriptors }),
|
|
182
|
+
onSpawn: async pid => await immutableJson(fs, "process.json", { attemptId, processGroup: pid, planSha256: planned.planSha256 }, fence),
|
|
183
|
+
});
|
|
184
|
+
const finishedAt = application.clock.now().toISOString();
|
|
185
|
+
await immutableJson(fs, "completion.json", { kind: "slopcamera.studio-completion", schemaVersion: 1, attemptId, planSha256: planned.planSha256, startedAt, finishedAt,
|
|
186
|
+
exitCode: result.exitCode, custody: result.custody, ...(result.failure === undefined ? {} : { failure: result.failure }) }, terminalFence);
|
|
187
|
+
await fs.writeTextNoReplace!("stdout.log", result.stdout, terminalFence);
|
|
188
|
+
await fs.writeTextNoReplace!("stderr.log", result.stderr, terminalFence);
|
|
189
|
+
if (budgetFailure !== undefined) await immutableJson(fs, "budget-failure.json", {
|
|
190
|
+
kind: "slopcamera.studio-budget-failure", schemaVersion: 1, planSha256: planned.planSha256, ...budgetFailure,
|
|
191
|
+
}, terminalFence);
|
|
192
|
+
let outputs: StudioReceipt["outputs"] = [], validationFailure = false;
|
|
193
|
+
let validationStage = "output-inventory";
|
|
194
|
+
try {
|
|
195
|
+
outputs = await inspectStudioOutputs(outputRoot, job);
|
|
196
|
+
validationStage = "source-integrity";
|
|
197
|
+
await verifyStudioSource(sourceRoot, retained.bundle);
|
|
198
|
+
await verifyStudioSource(retained.sourceRoot, retained.bundle);
|
|
199
|
+
if (result.custody === "closed" && result.exitCode === 0 && result.failure === undefined) {
|
|
200
|
+
validationStage = "media-validation";
|
|
201
|
+
const verified = await validateStudioOutputMedia({ application, process, root: outputRoot, job, outputs, signal: control.signal, cwd: workingRoot, env: runtime.environment, fence,
|
|
202
|
+
...(control.inheritedFileDescriptors === undefined ? {} : { inheritedFileDescriptors: control.inheritedFileDescriptors }) });
|
|
203
|
+
validationStage = "runtime-identity";
|
|
204
|
+
const after = await probeStudioRuntime({ engine: retained.bundle.engine, selection: options.selection, workingRoot, process, signal: control.signal, fence,
|
|
205
|
+
...(control.inheritedFileDescriptors === undefined ? {} : { inheritedFileDescriptors: control.inheritedFileDescriptors }) });
|
|
206
|
+
if (studioRuntimeSha256(after.identity) !== planned.runtimeSha256) throw new Error("Studio runtime changed during execution.");
|
|
207
|
+
validationStage = "working-inventory";
|
|
208
|
+
await inventoryStudioFiles(workingRoot, job.limits.maximumOutputFiles + 2048, job.limits.maximumOutputBytes + 64 * 1024 * 1024);
|
|
209
|
+
validationStage = "output-recheck";
|
|
210
|
+
const rechecked = await inspectStudioOutputs(outputRoot, job);
|
|
211
|
+
if (studioJson(outputs) !== studioJson(rechecked)) throw new Error("Studio outputs changed during validation.");
|
|
212
|
+
validationStage = "validation-publication";
|
|
213
|
+
await immutableJson(fs, "validation.json", { kind: "slopcamera.studio-output-validation", schemaVersion: 1, planSha256: planned.planSha256, runtimeSha256: planned.runtimeSha256, artifacts: outputs, outputs: verified }, fence);
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
validationFailure = true;
|
|
217
|
+
// A closed set of host stages records the failed check without persisting foreign exceptions or native output.
|
|
218
|
+
await immutableJson(fs, "validation-failure.json", { kind: "slopcamera.studio-validation-failure", schemaVersion: 1,
|
|
219
|
+
planSha256: planned.planSha256, stage: validationStage, custody: unknownCustody ? "unknown" : "closed",
|
|
220
|
+
cancelled: control.signal.aborted }, terminalFence);
|
|
221
|
+
}
|
|
222
|
+
const state = unknownCustody ? "unknown-custody" : result.exitCode === 0 && result.failure === undefined && !validationFailure && !control.signal.aborted ? "succeeded" : "failed";
|
|
223
|
+
const failureCode = unknownCustody ? "custody" : (control.deadlineAt ?? Infinity) <= performance.now() || result.failure === "timeout" ? "deadline" : control.signal.aborted || result.failure === "cancelled" ? "cancelled" : validationFailure ? "validation" : "subprocess";
|
|
224
|
+
const receiptInput = {
|
|
225
|
+
kind: "slopcamera.studio-receipt", schemaVersion: 1, jobId: job.jobId, attemptId, planSha256: planned.planSha256,
|
|
226
|
+
bundleSha256: planned.bundleSha256, jobSha256: planned.jobSha256, runtime: runtime.identity, runtimeSha256: planned.runtimeSha256,
|
|
227
|
+
startedAt, finishedAt: application.clock.now().toISOString(), outputs, state, custody: unknownCustody ? "unknown" : "closed",
|
|
228
|
+
exitCode: result.exitCode, ...(state === "succeeded" ? {} : { failure: { code: failureCode, message: "Native studio execution did not qualify for successful publication. Retain its private diagnostics and partial evidence." } }),
|
|
229
|
+
};
|
|
230
|
+
let document: StudioReceipt;
|
|
231
|
+
try { document = validateStudioReceipt({ plan: planned, receipt: receiptInput }); }
|
|
232
|
+
catch (error) {
|
|
233
|
+
if (state !== "succeeded") throw error;
|
|
234
|
+
document = validateStudioReceipt({ plan: planned, receipt: { ...receiptInput, state: "failed", custody: "closed", failure: { code: "validation", message: "Native outputs do not cover the complete declared job. Retain the partial evidence and use a new job for a revised attempt." } } });
|
|
235
|
+
}
|
|
236
|
+
await immutableJson(fs, "receipt.json", document, document.state === "succeeded" ? async () => {
|
|
237
|
+
if (control.signal.aborted) throw new CliError("cancelled", "Studio success publication was cancelled; retain its completed validation checkpoint.");
|
|
238
|
+
await fence();
|
|
239
|
+
} : terminalFence);
|
|
240
|
+
if (document.state !== "succeeded") throw new CliError(document.state === "unknown-custody" ? "ambiguous" : "subprocess", "Studio job did not complete successfully; its receipt and private diagnostics are retained.", { jobId: job.jobId, receipt: relative(application.paths.repositoryRoot, join(directory, "receipt.json")), state: document.state });
|
|
241
|
+
return await inspect(job.jobId);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const run = async (jobInput: unknown, control: StudioExecutionControl, expected?: StudioPlan): Promise<StudioRunOutput> => {
|
|
247
|
+
const job = parseStudioJob(jobInput), controller = new AbortController();
|
|
248
|
+
const deadlineAt = Math.min(control.deadlineAt ?? Infinity, performance.now() + job.limits.timeoutSeconds * 1000);
|
|
249
|
+
const abort = () => controller.abort();
|
|
250
|
+
control.signal.addEventListener("abort", abort, { once: true });
|
|
251
|
+
if (control.signal.aborted) abort();
|
|
252
|
+
const timer = setTimeout(abort, Math.max(1, Math.ceil(deadlineAt - performance.now())));
|
|
253
|
+
try { return await runOnce(job, { ...control, signal: controller.signal, deadlineAt }, expected); }
|
|
254
|
+
finally { clearTimeout(timer); control.signal.removeEventListener("abort", abort); }
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/** Recover only the post-validation publication window. Earlier ambiguity never authorizes rerendering. */
|
|
258
|
+
const reconcile = async (jobId: string, fence: () => Promise<void> = applicationFence, expectedPlanSha256?: string): Promise<StudioRunOutput> => {
|
|
259
|
+
if (!/^studio_[a-zA-Z0-9][a-zA-Z0-9_-]{0,120}$/u.test(jobId)) throw new CliError("invalid-data", "Invalid studio job ID.");
|
|
260
|
+
const root = await studioStorageRoot(application), directory = join(root, "jobs", jobId), fs = createNodeBundleFileSystem(directory);
|
|
261
|
+
return await withMutationLock(directory, { command: "studio reconcile", label: jobId }, async lease => {
|
|
262
|
+
const guard = async () => { await fence(); await lease.assertOwned(); };
|
|
263
|
+
const planned = parseStudioPlan(await optionalJson(fs, "plan.json"));
|
|
264
|
+
if (expectedPlanSha256 !== undefined && planned.planSha256 !== expectedPlanSha256) throw new CliError("conflict", "Retained studio plan differs from the requested recovery identity.");
|
|
265
|
+
if (await optionalJson(fs, "receipt.json") !== undefined) { await guard(); return await inspect(jobId); }
|
|
266
|
+
const completion = completionSchema.parse(await optionalJson(fs, "completion.json"));
|
|
267
|
+
const validation = z.object({ kind: z.literal("slopcamera.studio-output-validation"), schemaVersion: z.literal(1), planSha256: z.string(), runtimeSha256: z.string(), artifacts: z.array(z.unknown()).max(25_000) }).parse(await optionalJson(fs, "validation.json"));
|
|
268
|
+
if (completion.planSha256 !== planned.planSha256 || completion.custody !== "closed" || completion.exitCode !== 0 || completion.failure !== undefined
|
|
269
|
+
|| validation.planSha256 !== planned.planSha256 || validation.runtimeSha256 !== planned.runtimeSha256) throw new CliError("ambiguous", "Studio execution lacks a matching closed successful completion and validation checkpoint.");
|
|
270
|
+
await verifyStudioSource(join(directory, "source"), planned.bundle);
|
|
271
|
+
const outputs = await inspectStudioOutputs(join(directory, "outputs"), planned.job);
|
|
272
|
+
if (studioJson(outputs) !== studioJson(validation.artifacts)) throw new CliError("conflict", "Studio outputs changed after the retained validation checkpoint.");
|
|
273
|
+
const receipt = validateStudioReceipt({ plan: planned, receipt: {
|
|
274
|
+
kind: "slopcamera.studio-receipt", schemaVersion: 1, jobId, attemptId: completion.attemptId, planSha256: planned.planSha256,
|
|
275
|
+
bundleSha256: planned.bundleSha256, jobSha256: planned.jobSha256, runtime: planned.runtime, runtimeSha256: planned.runtimeSha256,
|
|
276
|
+
startedAt: completion.startedAt, finishedAt: application.clock.now().toISOString(), outputs, state: "succeeded", custody: "closed", exitCode: 0,
|
|
277
|
+
} });
|
|
278
|
+
await immutableJson(fs, "receipt.json", receipt, guard);
|
|
279
|
+
return await inspect(jobId);
|
|
280
|
+
});
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const port: ApplicationStudioPort = {
|
|
284
|
+
bind: async (input, signal, control) => {
|
|
285
|
+
const planned = await bind(input.job, signal, input.bundle, input.plan, control);
|
|
286
|
+
const retained = await readRetainedStudioBundle(application, planned.job, input.bundle);
|
|
287
|
+
return BoundStudioRunInputSchema.parse({ bundle: retained.reference, job: planned.job, plan: planned });
|
|
288
|
+
},
|
|
289
|
+
execute: async (input, control) => await run(input.job, { signal: control.signal, allowTrustedCode: control.allowTrustedCode,
|
|
290
|
+
inheritedFileDescriptors: control.inheritedFileDescriptors, workflow: control.identity, beforePublication: () => control.beforePublication() }, input.plan),
|
|
291
|
+
reconcile: async (input, control) => {
|
|
292
|
+
try {
|
|
293
|
+
await control.beforePublication();
|
|
294
|
+
const result = await reconcile(input.job.jobId, () => control.beforePublication(), input.plan.planSha256);
|
|
295
|
+
if (result.document.planSha256 !== input.plan.planSha256 || result.document.state !== "succeeded") return { kind: "incompatible", message: "Retained studio result differs from the bound successful plan." };
|
|
296
|
+
return { kind: "completed", output: result };
|
|
297
|
+
} catch { return { kind: "ambiguous", message: "No complete verified studio receipt exists. Native execution will not be resubmitted automatically." }; }
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
return { plan, bind, run, inspect, reconcile, port };
|
|
301
|
+
}
|