@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,1434 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export type DeepReadonly<T> =
|
|
4
|
+
T extends bigint | boolean | null | number | string | symbol | undefined
|
|
5
|
+
? T
|
|
6
|
+
: T extends readonly (infer Item)[]
|
|
7
|
+
? readonly DeepReadonly<Item>[]
|
|
8
|
+
: T extends object
|
|
9
|
+
? { readonly [Key in keyof T]: DeepReadonly<T[Key]> }
|
|
10
|
+
: T;
|
|
11
|
+
|
|
12
|
+
export type ReadonlyInferred<Schema extends z.ZodType> = DeepReadonly<z.infer<Schema>>;
|
|
13
|
+
|
|
14
|
+
const OPAQUE_ID_SUFFIX = /^[a-z0-9][a-z0-9_-]{7,63}$/u;
|
|
15
|
+
|
|
16
|
+
function opaqueId<Prefix extends string>(prefix: Prefix) {
|
|
17
|
+
return z.string().superRefine((value, context) => {
|
|
18
|
+
if (!value.startsWith(prefix) || !OPAQUE_ID_SUFFIX.test(value.slice(prefix.length))) {
|
|
19
|
+
context.addIssue({
|
|
20
|
+
code: "custom",
|
|
21
|
+
message: `Expected an opaque ${prefix} identifier.`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const RecordingIdSchema = opaqueId("rec_").brand<"RecordingId">();
|
|
28
|
+
export const TrackIdSchema = opaqueId("track_").brand<"TrackId">();
|
|
29
|
+
export const SegmentIdSchema = opaqueId("segment_").brand<"SegmentId">();
|
|
30
|
+
export const EventStreamIdSchema = opaqueId("events_").brand<"EventStreamId">();
|
|
31
|
+
|
|
32
|
+
export const MicrosecondsSchema = z.number().int().safe().nonnegative();
|
|
33
|
+
export const PositiveMicrosecondsSchema = z.number().int().safe().positive();
|
|
34
|
+
export const SignedMicrosecondsSchema = z.number().int().safe();
|
|
35
|
+
export const SequenceSchema = z.number().int().safe().nonnegative();
|
|
36
|
+
export const Sha256Schema = z.string().regex(/^[a-f0-9]{64}$/u);
|
|
37
|
+
export const IsoTimestampSchema = z.string().datetime({ offset: true });
|
|
38
|
+
|
|
39
|
+
export const CAPTURE_SYNC_ONSET_FLOOR_US = 50_000;
|
|
40
|
+
export const CAPTURE_SYNC_DRIFT_FLOOR_US = 20_000;
|
|
41
|
+
export const CAPTURE_SYNC_DRIFT_PPM = 200;
|
|
42
|
+
|
|
43
|
+
export interface CaptureSyncPlacementInput {
|
|
44
|
+
readonly clockNativeStartUs: number;
|
|
45
|
+
readonly clockSourceEndUs: number;
|
|
46
|
+
readonly clockSourceStartUs: number;
|
|
47
|
+
readonly fileContainerOriginPtsUs: number;
|
|
48
|
+
readonly fileEndPtsUs: number;
|
|
49
|
+
readonly fileFirstPtsUs: number;
|
|
50
|
+
readonly helperNativeStartUs: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface CaptureSyncPlacement {
|
|
54
|
+
readonly endUs: number;
|
|
55
|
+
readonly fileRangeEndUs: number;
|
|
56
|
+
readonly fileRangeStartUs: number;
|
|
57
|
+
readonly rawEndUs: number;
|
|
58
|
+
readonly rawStartUs: number;
|
|
59
|
+
readonly startUs: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function deriveCaptureSyncPlacement(
|
|
63
|
+
input: CaptureSyncPlacementInput,
|
|
64
|
+
): CaptureSyncPlacement | null {
|
|
65
|
+
if (
|
|
66
|
+
Object.values(input).some(value => !Number.isSafeInteger(value))
|
|
67
|
+
|| input.clockSourceStartUs < 0
|
|
68
|
+
|| input.clockSourceEndUs <= input.clockSourceStartUs
|
|
69
|
+
) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const fileDurationUs = input.fileEndPtsUs - input.fileFirstPtsUs;
|
|
73
|
+
const rawStartUs = input.clockSourceStartUs
|
|
74
|
+
+ input.helperNativeStartUs
|
|
75
|
+
- input.clockNativeStartUs;
|
|
76
|
+
const rawEndUs = rawStartUs + fileDurationUs;
|
|
77
|
+
const startUs = Math.max(input.clockSourceStartUs, rawStartUs);
|
|
78
|
+
const endUs = Math.min(input.clockSourceEndUs, rawEndUs);
|
|
79
|
+
const normalizedFileStartUs = input.fileFirstPtsUs
|
|
80
|
+
- input.fileContainerOriginPtsUs;
|
|
81
|
+
const fileRangeStartUs = normalizedFileStartUs + startUs - rawStartUs;
|
|
82
|
+
const fileRangeEndUs = fileRangeStartUs + endUs - startUs;
|
|
83
|
+
const values = [
|
|
84
|
+
fileDurationUs,
|
|
85
|
+
rawStartUs,
|
|
86
|
+
rawEndUs,
|
|
87
|
+
startUs,
|
|
88
|
+
endUs,
|
|
89
|
+
normalizedFileStartUs,
|
|
90
|
+
fileRangeStartUs,
|
|
91
|
+
fileRangeEndUs,
|
|
92
|
+
];
|
|
93
|
+
if (
|
|
94
|
+
values.some(value => !Number.isSafeInteger(value))
|
|
95
|
+
|| fileDurationUs <= 0
|
|
96
|
+
|| startUs < 0
|
|
97
|
+
|| endUs <= startUs
|
|
98
|
+
|| fileRangeStartUs < 0
|
|
99
|
+
|| fileRangeEndUs <= fileRangeStartUs
|
|
100
|
+
) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
endUs,
|
|
105
|
+
fileRangeEndUs,
|
|
106
|
+
fileRangeStartUs,
|
|
107
|
+
rawEndUs,
|
|
108
|
+
rawStartUs,
|
|
109
|
+
startUs,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function deriveCaptureSyncSpanToleranceUs(
|
|
114
|
+
tickUs: number,
|
|
115
|
+
maximumSampleDurationUs: number,
|
|
116
|
+
): number | null {
|
|
117
|
+
const toleranceUs = Math.max(1, tickUs, maximumSampleDurationUs);
|
|
118
|
+
return Number.isSafeInteger(toleranceUs) && toleranceUs > 0
|
|
119
|
+
? toleranceUs
|
|
120
|
+
: null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface CaptureSyncToleranceInput {
|
|
124
|
+
readonly referenceDurationUs: number;
|
|
125
|
+
readonly referenceEndUncertaintyUs: number;
|
|
126
|
+
readonly referenceFirstUncertaintyUs: number;
|
|
127
|
+
readonly referenceMaximumSampleDurationUs: number;
|
|
128
|
+
readonly subjectEndUncertaintyUs: number;
|
|
129
|
+
readonly subjectFirstUncertaintyUs: number;
|
|
130
|
+
readonly subjectMaximumSampleDurationUs: number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface CaptureSyncTolerances {
|
|
134
|
+
readonly durationDriftUs: number;
|
|
135
|
+
readonly onsetSkewUs: number;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function deriveCaptureSyncTolerances(
|
|
139
|
+
input: CaptureSyncToleranceInput,
|
|
140
|
+
): CaptureSyncTolerances | null {
|
|
141
|
+
if (
|
|
142
|
+
Object.values(input).some(value => !Number.isSafeInteger(value) || value < 0)
|
|
143
|
+
|| input.referenceDurationUs <= 0
|
|
144
|
+
|| input.referenceMaximumSampleDurationUs <= 0
|
|
145
|
+
|| input.subjectMaximumSampleDurationUs <= 0
|
|
146
|
+
) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const maximumSampleDurationUs = Math.max(
|
|
150
|
+
input.subjectMaximumSampleDurationUs,
|
|
151
|
+
input.referenceMaximumSampleDurationUs,
|
|
152
|
+
);
|
|
153
|
+
const onsetSkewUs = Math.max(
|
|
154
|
+
CAPTURE_SYNC_ONSET_FLOOR_US,
|
|
155
|
+
input.subjectFirstUncertaintyUs
|
|
156
|
+
+ input.referenceFirstUncertaintyUs
|
|
157
|
+
+ maximumSampleDurationUs,
|
|
158
|
+
);
|
|
159
|
+
const ppmToleranceUs = Number(
|
|
160
|
+
(
|
|
161
|
+
BigInt(input.referenceDurationUs) * BigInt(CAPTURE_SYNC_DRIFT_PPM)
|
|
162
|
+
+ 999_999n
|
|
163
|
+
) / 1_000_000n,
|
|
164
|
+
);
|
|
165
|
+
const durationDriftUs = Math.max(
|
|
166
|
+
CAPTURE_SYNC_DRIFT_FLOOR_US,
|
|
167
|
+
ppmToleranceUs,
|
|
168
|
+
input.subjectFirstUncertaintyUs
|
|
169
|
+
+ input.subjectEndUncertaintyUs
|
|
170
|
+
+ input.referenceFirstUncertaintyUs
|
|
171
|
+
+ input.referenceEndUncertaintyUs
|
|
172
|
+
+ 2 * maximumSampleDurationUs,
|
|
173
|
+
);
|
|
174
|
+
return Number.isSafeInteger(onsetSkewUs)
|
|
175
|
+
&& Number.isSafeInteger(durationDriftUs)
|
|
176
|
+
&& onsetSkewUs >= 0
|
|
177
|
+
&& durationDriftUs >= 0
|
|
178
|
+
? { durationDriftUs, onsetSkewUs }
|
|
179
|
+
: null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function deriveCaptureSyncDurationDriftPpm(
|
|
183
|
+
durationDriftUs: number,
|
|
184
|
+
referenceDurationUs: number,
|
|
185
|
+
): number | null {
|
|
186
|
+
if (
|
|
187
|
+
!Number.isSafeInteger(durationDriftUs)
|
|
188
|
+
|| !Number.isSafeInteger(referenceDurationUs)
|
|
189
|
+
|| referenceDurationUs <= 0
|
|
190
|
+
) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const scaledMagnitude = BigInt(Math.abs(durationDriftUs)) * 1_000_000n;
|
|
194
|
+
const denominator = BigInt(referenceDurationUs);
|
|
195
|
+
const quotient = scaledMagnitude / denominator;
|
|
196
|
+
const remainder = scaledMagnitude % denominator;
|
|
197
|
+
const roundsAwayFromZero = durationDriftUs >= 0
|
|
198
|
+
? remainder * 2n >= denominator
|
|
199
|
+
: remainder * 2n > denominator;
|
|
200
|
+
const magnitude = quotient + (roundsAwayFromZero ? 1n : 0n);
|
|
201
|
+
const result = Number(durationDriftUs < 0 ? -magnitude : magnitude);
|
|
202
|
+
return Number.isSafeInteger(result) ? result : null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function isSafeRepositoryRelativePath(value: string): boolean {
|
|
206
|
+
if (
|
|
207
|
+
value.length === 0
|
|
208
|
+
|| value.length > 1024
|
|
209
|
+
|| value.startsWith("/")
|
|
210
|
+
|| value.startsWith("\\")
|
|
211
|
+
|| /^[a-zA-Z]:/u.test(value)
|
|
212
|
+
|| value.includes("\\")
|
|
213
|
+
|| value.includes("\0")
|
|
214
|
+
|| [...value].some((character) => {
|
|
215
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
216
|
+
return codePoint <= 31 || codePoint === 127;
|
|
217
|
+
})
|
|
218
|
+
) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
const parts = value.split("/");
|
|
222
|
+
return parts.every((part) => part.length > 0 && part !== "." && part !== "..");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export const RepositoryRelativePathSchema = z.string().refine(
|
|
226
|
+
isSafeRepositoryRelativePath,
|
|
227
|
+
"Use a normalized repository-relative path without empty, dot, or parent segments.",
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
export const PointSchema = z.strictObject({
|
|
231
|
+
x: z.number().finite(),
|
|
232
|
+
y: z.number().finite(),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
export const RectSchema = PointSchema.extend({
|
|
236
|
+
height: z.number().finite().positive(),
|
|
237
|
+
width: z.number().finite().positive(),
|
|
238
|
+
}).strict();
|
|
239
|
+
|
|
240
|
+
export const PixelSizeSchema = z.strictObject({
|
|
241
|
+
height: z.number().int().safe().positive(),
|
|
242
|
+
width: z.number().int().safe().positive(),
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
export const PermissionStateSchema = z.enum([
|
|
246
|
+
"not-determined",
|
|
247
|
+
"authorized",
|
|
248
|
+
"denied",
|
|
249
|
+
"restricted",
|
|
250
|
+
"unavailable",
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
export const CapturePermissionsSchema = z.strictObject({
|
|
254
|
+
accessibility: PermissionStateSchema,
|
|
255
|
+
camera: PermissionStateSchema,
|
|
256
|
+
inputMonitoring: PermissionStateSchema,
|
|
257
|
+
microphone: PermissionStateSchema,
|
|
258
|
+
screenCapture: PermissionStateSchema,
|
|
259
|
+
systemAudio: PermissionStateSchema,
|
|
260
|
+
windowMetadata: PermissionStateSchema,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
export const DisplaySourceSchema = z.strictObject({
|
|
264
|
+
bounds: RectSchema,
|
|
265
|
+
displayId: z.string().min(1).max(256),
|
|
266
|
+
isPrimary: z.boolean(),
|
|
267
|
+
label: z.string().min(1).max(512),
|
|
268
|
+
pixelSize: PixelSizeSchema,
|
|
269
|
+
refreshRateHz: z.number().finite().positive(),
|
|
270
|
+
scaleFactor: z.number().finite().positive(),
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
export const CameraSourceSchema = z.strictObject({
|
|
274
|
+
cameraId: z.string().min(1).max(256),
|
|
275
|
+
frameRate: z.number().finite().positive(),
|
|
276
|
+
label: z.string().min(1).max(512),
|
|
277
|
+
pixelSize: PixelSizeSchema,
|
|
278
|
+
position: z.enum(["front", "back", "external", "unspecified"]),
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
export const AudioSourceSchema = z.discriminatedUnion("kind", [
|
|
282
|
+
z.strictObject({
|
|
283
|
+
audioSourceId: z.string().min(1).max(256),
|
|
284
|
+
channels: z.number().int().safe().positive().max(64),
|
|
285
|
+
kind: z.literal("system"),
|
|
286
|
+
label: z.string().min(1).max(512),
|
|
287
|
+
sampleRateHz: z.number().int().safe().positive(),
|
|
288
|
+
}),
|
|
289
|
+
z.strictObject({
|
|
290
|
+
audioSourceId: z.string().min(1).max(256),
|
|
291
|
+
channels: z.number().int().safe().positive().max(64),
|
|
292
|
+
kind: z.literal("microphone"),
|
|
293
|
+
label: z.string().min(1).max(512),
|
|
294
|
+
sampleRateHz: z.number().int().safe().positive(),
|
|
295
|
+
}),
|
|
296
|
+
]);
|
|
297
|
+
|
|
298
|
+
export const SourceInventorySchema = z.strictObject({
|
|
299
|
+
audio: z.array(AudioSourceSchema),
|
|
300
|
+
cameras: z.array(CameraSourceSchema),
|
|
301
|
+
displays: z.array(DisplaySourceSchema),
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
export const MAX_CAPTURE_INTERRUPTION_SEGMENTS = 128;
|
|
305
|
+
export const MAX_CAPTURE_INTERRUPTION_SOURCE_ID_BYTES = 256;
|
|
306
|
+
|
|
307
|
+
function utf8ByteLength(value: string): number {
|
|
308
|
+
return new TextEncoder().encode(value).byteLength;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const CaptureInterruptionSourceIdSchema = z.string().min(1).refine(
|
|
312
|
+
value =>
|
|
313
|
+
utf8ByteLength(value) <= MAX_CAPTURE_INTERRUPTION_SOURCE_ID_BYTES
|
|
314
|
+
&& !value.includes("\0"),
|
|
315
|
+
`Expected a non-NUL source ID of at most ${MAX_CAPTURE_INTERRUPTION_SOURCE_ID_BYTES} UTF-8 bytes.`,
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const CaptureInterruptionBaseShape = {
|
|
319
|
+
nativeTimeUs: MicrosecondsSchema,
|
|
320
|
+
recoverable: z.boolean(),
|
|
321
|
+
segmentIndex: z.number().int().safe().nonnegative()
|
|
322
|
+
.max(MAX_CAPTURE_INTERRUPTION_SEGMENTS - 1),
|
|
323
|
+
sourceId: CaptureInterruptionSourceIdSchema.nullable(),
|
|
324
|
+
sourceTimeUs: MicrosecondsSchema,
|
|
325
|
+
} as const;
|
|
326
|
+
|
|
327
|
+
export const CaptureInterruptionSchema = z.discriminatedUnion("source", [
|
|
328
|
+
z.strictObject({
|
|
329
|
+
...CaptureInterruptionBaseShape,
|
|
330
|
+
code: z.enum([
|
|
331
|
+
"selected-display-disconnected",
|
|
332
|
+
"screen-stream-stopped",
|
|
333
|
+
"screen-recording-failed",
|
|
334
|
+
]),
|
|
335
|
+
source: z.literal("screen"),
|
|
336
|
+
}),
|
|
337
|
+
z.strictObject({
|
|
338
|
+
...CaptureInterruptionBaseShape,
|
|
339
|
+
code: z.literal("system-audio-track-missing"),
|
|
340
|
+
source: z.literal("system-audio"),
|
|
341
|
+
}),
|
|
342
|
+
z.strictObject({
|
|
343
|
+
...CaptureInterruptionBaseShape,
|
|
344
|
+
code: z.enum([
|
|
345
|
+
"camera-device-disconnected",
|
|
346
|
+
"camera-session-interrupted",
|
|
347
|
+
"camera-runtime-error",
|
|
348
|
+
"camera-session-stopped",
|
|
349
|
+
"camera-recording-failed",
|
|
350
|
+
]),
|
|
351
|
+
source: z.literal("camera"),
|
|
352
|
+
}),
|
|
353
|
+
z.strictObject({
|
|
354
|
+
...CaptureInterruptionBaseShape,
|
|
355
|
+
code: z.enum([
|
|
356
|
+
"microphone-device-disconnected",
|
|
357
|
+
"microphone-session-interrupted",
|
|
358
|
+
"microphone-runtime-error",
|
|
359
|
+
"microphone-session-stopped",
|
|
360
|
+
"microphone-recording-failed",
|
|
361
|
+
]),
|
|
362
|
+
source: z.literal("microphone"),
|
|
363
|
+
}),
|
|
364
|
+
]);
|
|
365
|
+
|
|
366
|
+
export const PendingIntegritySchema = z.strictObject({
|
|
367
|
+
state: z.literal("pending"),
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
export const VerifiedIntegritySchema = z.strictObject({
|
|
371
|
+
bytes: z.number().int().safe().nonnegative(),
|
|
372
|
+
sha256: Sha256Schema,
|
|
373
|
+
state: z.literal("verified"),
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
export const FileIntegritySchema = z.discriminatedUnion("state", [
|
|
377
|
+
PendingIntegritySchema,
|
|
378
|
+
VerifiedIntegritySchema,
|
|
379
|
+
]);
|
|
380
|
+
|
|
381
|
+
const LegacyMediaSegmentV1Schema = z.strictObject({
|
|
382
|
+
codec: z.string().min(1).max(128),
|
|
383
|
+
containerTrackIdentity: z.discriminatedUnion("kind", [
|
|
384
|
+
z.strictObject({ containerTrackId: z.string().min(1).max(256), kind: z.literal("verified") }),
|
|
385
|
+
z.strictObject({
|
|
386
|
+
diagnosticCode: z.string().min(1).max(128),
|
|
387
|
+
expectedRole: z.enum(["display-video", "camera-video", "system-audio", "microphone-audio"]),
|
|
388
|
+
kind: z.literal("provisional"),
|
|
389
|
+
}),
|
|
390
|
+
]),
|
|
391
|
+
container: z.string().min(1).max(64),
|
|
392
|
+
endUs: PositiveMicrosecondsSchema,
|
|
393
|
+
integrity: FileIntegritySchema,
|
|
394
|
+
nativeEndUs: PositiveMicrosecondsSchema,
|
|
395
|
+
nativeStartUs: MicrosecondsSchema,
|
|
396
|
+
path: RepositoryRelativePathSchema,
|
|
397
|
+
segmentId: SegmentIdSchema,
|
|
398
|
+
startUs: MicrosecondsSchema,
|
|
399
|
+
streamIndex: z.number().int().safe().nonnegative(),
|
|
400
|
+
}).superRefine((segment, context) => {
|
|
401
|
+
if (segment.endUs <= segment.startUs) {
|
|
402
|
+
context.addIssue({ code: "custom", message: "Segment endUs must be greater than startUs." });
|
|
403
|
+
}
|
|
404
|
+
if (segment.nativeEndUs <= segment.nativeStartUs) {
|
|
405
|
+
context.addIssue({ code: "custom", message: "Segment nativeEndUs must be greater than nativeStartUs." });
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const MediaFileRangeSchema = z.strictObject({
|
|
410
|
+
endUs: PositiveMicrosecondsSchema,
|
|
411
|
+
startUs: MicrosecondsSchema,
|
|
412
|
+
}).superRefine((range, context) => {
|
|
413
|
+
if (range.endUs <= range.startUs) {
|
|
414
|
+
context.addIssue({ code: "custom", message: "Media file range must have positive duration." });
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const LegacyCaptureTimingSchema = z.strictObject({
|
|
419
|
+
kind: z.literal("legacy-estimate"),
|
|
420
|
+
nativeRange: z.strictObject({
|
|
421
|
+
endUs: PositiveMicrosecondsSchema,
|
|
422
|
+
startUs: MicrosecondsSchema,
|
|
423
|
+
}),
|
|
424
|
+
reason: z.literal("recording-manifest-v1-container-duration"),
|
|
425
|
+
}).superRefine((timing, context) => {
|
|
426
|
+
if (timing.nativeRange.endUs <= timing.nativeRange.startUs) {
|
|
427
|
+
context.addIssue({ code: "custom", message: "Legacy native timing range must have positive duration." });
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
export const CaptureSyncMeasurementSchema = z.strictObject({
|
|
432
|
+
captureSegmentIndex: z.number().int().safe().nonnegative().max(127),
|
|
433
|
+
durationDriftPpm: z.number().int().safe(),
|
|
434
|
+
durationDriftUs: SignedMicrosecondsSchema,
|
|
435
|
+
evidence: z.strictObject({
|
|
436
|
+
file: z.strictObject({
|
|
437
|
+
containerOriginPtsUs: SignedMicrosecondsSchema,
|
|
438
|
+
endPtsUs: SignedMicrosecondsSchema,
|
|
439
|
+
firstPtsUs: SignedMicrosecondsSchema,
|
|
440
|
+
spanToleranceUs: PositiveMicrosecondsSchema,
|
|
441
|
+
tickUs: PositiveMicrosecondsSchema,
|
|
442
|
+
}),
|
|
443
|
+
helper: z.strictObject({
|
|
444
|
+
bufferCount: z.number().int().safe().positive(),
|
|
445
|
+
clockAnchors: z.strictObject({
|
|
446
|
+
end: z.strictObject({
|
|
447
|
+
nativeTimeUs: MicrosecondsSchema,
|
|
448
|
+
ptsUs: SignedMicrosecondsSchema,
|
|
449
|
+
uncertaintyUs: MicrosecondsSchema,
|
|
450
|
+
}),
|
|
451
|
+
first: z.strictObject({
|
|
452
|
+
nativeTimeUs: MicrosecondsSchema,
|
|
453
|
+
ptsUs: SignedMicrosecondsSchema,
|
|
454
|
+
uncertaintyUs: MicrosecondsSchema,
|
|
455
|
+
}),
|
|
456
|
+
}),
|
|
457
|
+
containerDurationUs: PositiveMicrosecondsSchema,
|
|
458
|
+
presentation: z.strictObject({
|
|
459
|
+
endPtsUs: SignedMicrosecondsSchema,
|
|
460
|
+
firstPtsUs: SignedMicrosecondsSchema,
|
|
461
|
+
lastPtsUs: SignedMicrosecondsSchema,
|
|
462
|
+
maximumSampleDurationUs: PositiveMicrosecondsSchema,
|
|
463
|
+
}),
|
|
464
|
+
sampleCount: z.number().int().safe().positive(),
|
|
465
|
+
}),
|
|
466
|
+
}),
|
|
467
|
+
kind: z.literal("measured"),
|
|
468
|
+
nativeRange: z.strictObject({
|
|
469
|
+
endUs: PositiveMicrosecondsSchema,
|
|
470
|
+
startUs: MicrosecondsSchema,
|
|
471
|
+
}),
|
|
472
|
+
onsetSkewUs: SignedMicrosecondsSchema,
|
|
473
|
+
policy: z.literal("capture-sync-v1"),
|
|
474
|
+
presentation: z.strictObject({
|
|
475
|
+
endPtsUs: SignedMicrosecondsSchema,
|
|
476
|
+
firstPtsUs: SignedMicrosecondsSchema,
|
|
477
|
+
lastPtsUs: SignedMicrosecondsSchema,
|
|
478
|
+
}),
|
|
479
|
+
referenceTrackId: TrackIdSchema,
|
|
480
|
+
status: z.enum(["within-tolerance", "out-of-tolerance"]),
|
|
481
|
+
tolerance: z.strictObject({
|
|
482
|
+
durationDriftUs: MicrosecondsSchema,
|
|
483
|
+
onsetSkewUs: MicrosecondsSchema,
|
|
484
|
+
}),
|
|
485
|
+
}).superRefine((timing, context) => {
|
|
486
|
+
if (timing.nativeRange.endUs <= timing.nativeRange.startUs) {
|
|
487
|
+
context.addIssue({ code: "custom", message: "Measured native timing range must have positive duration." });
|
|
488
|
+
}
|
|
489
|
+
if (timing.presentation.lastPtsUs < timing.presentation.firstPtsUs) {
|
|
490
|
+
context.addIssue({ code: "custom", message: "Measured last PTS cannot precede first PTS." });
|
|
491
|
+
}
|
|
492
|
+
if (timing.presentation.endPtsUs <= timing.presentation.lastPtsUs) {
|
|
493
|
+
context.addIssue({ code: "custom", message: "Measured exclusive-end PTS must follow the last PTS." });
|
|
494
|
+
}
|
|
495
|
+
if (
|
|
496
|
+
timing.evidence.helper.presentation.firstPtsUs !== timing.presentation.firstPtsUs
|
|
497
|
+
|| timing.evidence.helper.presentation.lastPtsUs !== timing.presentation.lastPtsUs
|
|
498
|
+
|| timing.evidence.helper.presentation.endPtsUs !== timing.presentation.endPtsUs
|
|
499
|
+
) {
|
|
500
|
+
context.addIssue({ code: "custom", message: "Capture sync summary PTS must match its raw timing evidence." });
|
|
501
|
+
}
|
|
502
|
+
if (
|
|
503
|
+
timing.evidence.helper.clockAnchors.first.nativeTimeUs !== timing.nativeRange.startUs
|
|
504
|
+
|| timing.evidence.helper.clockAnchors.end.nativeTimeUs !== timing.nativeRange.endUs
|
|
505
|
+
|| timing.evidence.helper.clockAnchors.first.ptsUs !== timing.evidence.helper.presentation.firstPtsUs
|
|
506
|
+
|| timing.evidence.helper.clockAnchors.end.ptsUs !== timing.evidence.helper.presentation.endPtsUs
|
|
507
|
+
) {
|
|
508
|
+
context.addIssue({ code: "custom", message: "Capture sync ranges must match their raw clock anchors." });
|
|
509
|
+
}
|
|
510
|
+
const presentationSpanUs = timing.evidence.helper.presentation.endPtsUs
|
|
511
|
+
- timing.evidence.helper.presentation.firstPtsUs;
|
|
512
|
+
const nativeSpanUs = timing.evidence.helper.clockAnchors.end.nativeTimeUs
|
|
513
|
+
- timing.evidence.helper.clockAnchors.first.nativeTimeUs;
|
|
514
|
+
const clockMappingToleranceUs = timing.evidence.helper.clockAnchors.first.uncertaintyUs
|
|
515
|
+
+ timing.evidence.helper.clockAnchors.end.uncertaintyUs
|
|
516
|
+
+ 2;
|
|
517
|
+
if (Math.abs(nativeSpanUs - presentationSpanUs) > clockMappingToleranceUs) {
|
|
518
|
+
context.addIssue({ code: "custom", message: "Capture native clock anchors exceed their recorded mapping uncertainty." });
|
|
519
|
+
}
|
|
520
|
+
if (
|
|
521
|
+
timing.evidence.helper.presentation.endPtsUs
|
|
522
|
+
- timing.evidence.helper.presentation.lastPtsUs
|
|
523
|
+
> timing.evidence.helper.presentation.maximumSampleDurationUs
|
|
524
|
+
) {
|
|
525
|
+
context.addIssue({ code: "custom", message: "Capture sync final sample exceeds its recorded maximum duration." });
|
|
526
|
+
}
|
|
527
|
+
const helperSpanUs = timing.evidence.helper.presentation.endPtsUs
|
|
528
|
+
- timing.evidence.helper.presentation.firstPtsUs;
|
|
529
|
+
const fileSpanUs = timing.evidence.file.endPtsUs - timing.evidence.file.firstPtsUs;
|
|
530
|
+
const expectedSpanToleranceUs = deriveCaptureSyncSpanToleranceUs(
|
|
531
|
+
timing.evidence.file.tickUs,
|
|
532
|
+
timing.evidence.helper.presentation.maximumSampleDurationUs,
|
|
533
|
+
);
|
|
534
|
+
if (fileSpanUs <= 0) {
|
|
535
|
+
context.addIssue({ code: "custom", message: "Finalized file timing evidence must have positive duration." });
|
|
536
|
+
}
|
|
537
|
+
if (
|
|
538
|
+
expectedSpanToleranceUs === null
|
|
539
|
+
|| timing.evidence.file.spanToleranceUs !== expectedSpanToleranceUs
|
|
540
|
+
) {
|
|
541
|
+
context.addIssue({ code: "custom", message: "Finalized file span tolerance must match capture-sync-v1 policy." });
|
|
542
|
+
} else if (Math.abs(fileSpanUs - helperSpanUs) > expectedSpanToleranceUs) {
|
|
543
|
+
context.addIssue({ code: "custom", message: "Finalized file span exceeds its recorded helper tolerance." });
|
|
544
|
+
}
|
|
545
|
+
if (timing.evidence.file.firstPtsUs < timing.evidence.file.containerOriginPtsUs) {
|
|
546
|
+
context.addIssue({ code: "custom", message: "Finalized file PTS cannot precede its container origin." });
|
|
547
|
+
}
|
|
548
|
+
if (timing.evidence.helper.sampleCount < timing.evidence.helper.bufferCount) {
|
|
549
|
+
context.addIssue({ code: "custom", message: "Capture sample count cannot be smaller than buffer count." });
|
|
550
|
+
}
|
|
551
|
+
const outOfTolerance = Math.abs(timing.onsetSkewUs) > timing.tolerance.onsetSkewUs
|
|
552
|
+
|| Math.abs(timing.durationDriftUs) > timing.tolerance.durationDriftUs;
|
|
553
|
+
if (outOfTolerance !== (timing.status === "out-of-tolerance")) {
|
|
554
|
+
context.addIssue({ code: "custom", message: "Capture sync status must match its explicit tolerances." });
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
export const MediaSegmentSchema = z.strictObject({
|
|
559
|
+
codec: z.string().min(1).max(128),
|
|
560
|
+
containerTrackIdentity: z.discriminatedUnion("kind", [
|
|
561
|
+
z.strictObject({ containerTrackId: z.string().min(1).max(256), kind: z.literal("verified") }),
|
|
562
|
+
z.strictObject({
|
|
563
|
+
diagnosticCode: z.string().min(1).max(128),
|
|
564
|
+
expectedRole: z.enum(["display-video", "camera-video", "system-audio", "microphone-audio"]),
|
|
565
|
+
kind: z.literal("provisional"),
|
|
566
|
+
}),
|
|
567
|
+
]),
|
|
568
|
+
container: z.string().min(1).max(64),
|
|
569
|
+
endUs: PositiveMicrosecondsSchema,
|
|
570
|
+
fileRange: MediaFileRangeSchema,
|
|
571
|
+
integrity: FileIntegritySchema,
|
|
572
|
+
path: RepositoryRelativePathSchema,
|
|
573
|
+
segmentId: SegmentIdSchema,
|
|
574
|
+
startUs: MicrosecondsSchema,
|
|
575
|
+
streamIndex: z.number().int().safe().nonnegative(),
|
|
576
|
+
timing: z.discriminatedUnion("kind", [
|
|
577
|
+
LegacyCaptureTimingSchema,
|
|
578
|
+
CaptureSyncMeasurementSchema,
|
|
579
|
+
]),
|
|
580
|
+
}).superRefine((segment, context) => {
|
|
581
|
+
if (segment.endUs <= segment.startUs) {
|
|
582
|
+
context.addIssue({ code: "custom", message: "Segment endUs must be greater than startUs." });
|
|
583
|
+
}
|
|
584
|
+
if (
|
|
585
|
+
segment.endUs - segment.startUs
|
|
586
|
+
!== segment.fileRange.endUs - segment.fileRange.startUs
|
|
587
|
+
) {
|
|
588
|
+
context.addIssue({ code: "custom", message: "Media source and file ranges must have equal duration." });
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
const TrackBaseSchema = z.strictObject({
|
|
593
|
+
enabled: z.boolean(),
|
|
594
|
+
label: z.string().min(1).max(512),
|
|
595
|
+
segments: z.array(MediaSegmentSchema),
|
|
596
|
+
trackId: TrackIdSchema,
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
export const DisplayVideoTrackSchema = TrackBaseSchema.extend({
|
|
600
|
+
kind: z.literal("display-video"),
|
|
601
|
+
source: z.strictObject({ displayId: z.string().min(1).max(256) }),
|
|
602
|
+
}).strict();
|
|
603
|
+
|
|
604
|
+
export const CameraVideoTrackSchema = TrackBaseSchema.extend({
|
|
605
|
+
kind: z.literal("camera-video"),
|
|
606
|
+
source: z.strictObject({ cameraId: z.string().min(1).max(256) }),
|
|
607
|
+
}).strict();
|
|
608
|
+
|
|
609
|
+
export const SystemAudioTrackSchema = TrackBaseSchema.extend({
|
|
610
|
+
kind: z.literal("system-audio"),
|
|
611
|
+
source: z.strictObject({ audioSourceId: z.string().min(1).max(256) }),
|
|
612
|
+
}).strict();
|
|
613
|
+
|
|
614
|
+
export const MicrophoneAudioTrackSchema = TrackBaseSchema.extend({
|
|
615
|
+
kind: z.literal("microphone-audio"),
|
|
616
|
+
source: z.strictObject({ audioSourceId: z.string().min(1).max(256) }),
|
|
617
|
+
}).strict();
|
|
618
|
+
|
|
619
|
+
export const LogicalTrackSchema = z.discriminatedUnion("kind", [
|
|
620
|
+
DisplayVideoTrackSchema,
|
|
621
|
+
CameraVideoTrackSchema,
|
|
622
|
+
SystemAudioTrackSchema,
|
|
623
|
+
MicrophoneAudioTrackSchema,
|
|
624
|
+
]);
|
|
625
|
+
|
|
626
|
+
export const EventKindSchema = z.enum([
|
|
627
|
+
"cursor.sample",
|
|
628
|
+
"mouse.click",
|
|
629
|
+
"key.activity",
|
|
630
|
+
"typing.input",
|
|
631
|
+
"focus.changed",
|
|
632
|
+
"window.snapshot",
|
|
633
|
+
"window.changed",
|
|
634
|
+
"display.topology",
|
|
635
|
+
"lifecycle.marker",
|
|
636
|
+
"diagnostic.dropped-events",
|
|
637
|
+
]);
|
|
638
|
+
|
|
639
|
+
export const EventStreamReferenceSchema = z.strictObject({
|
|
640
|
+
endUs: MicrosecondsSchema,
|
|
641
|
+
eventKinds: z.array(EventKindSchema).min(1),
|
|
642
|
+
eventStreamId: EventStreamIdSchema,
|
|
643
|
+
integrity: FileIntegritySchema,
|
|
644
|
+
path: RepositoryRelativePathSchema,
|
|
645
|
+
recordCount: z.number().int().safe().nonnegative(),
|
|
646
|
+
startUs: MicrosecondsSchema,
|
|
647
|
+
}).superRefine((stream, context) => {
|
|
648
|
+
if (stream.endUs < stream.startUs) {
|
|
649
|
+
context.addIssue({ code: "custom", message: "Event stream endUs cannot precede startUs." });
|
|
650
|
+
}
|
|
651
|
+
if (new Set(stream.eventKinds).size !== stream.eventKinds.length) {
|
|
652
|
+
context.addIssue({ code: "custom", message: "Event stream kinds must be unique." });
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
export const RecordingDiagnosticSchema = z.strictObject({
|
|
657
|
+
code: z.string().min(1).max(128),
|
|
658
|
+
count: z.number().int().safe().positive(),
|
|
659
|
+
firstSourceTimeUs: MicrosecondsSchema.nullable(),
|
|
660
|
+
lastSourceTimeUs: MicrosecondsSchema.nullable(),
|
|
661
|
+
level: z.enum(["info", "warning", "error"]),
|
|
662
|
+
message: z.string().min(1).max(4096),
|
|
663
|
+
}).superRefine((diagnostic, context) => {
|
|
664
|
+
if ((diagnostic.firstSourceTimeUs === null) !== (diagnostic.lastSourceTimeUs === null)) {
|
|
665
|
+
context.addIssue({ code: "custom", message: "Diagnostic source-time bounds must both be null or both be present." });
|
|
666
|
+
} else if (
|
|
667
|
+
diagnostic.firstSourceTimeUs !== null
|
|
668
|
+
&& diagnostic.lastSourceTimeUs !== null
|
|
669
|
+
&& diagnostic.lastSourceTimeUs < diagnostic.firstSourceTimeUs
|
|
670
|
+
) {
|
|
671
|
+
context.addIssue({ code: "custom", message: "Diagnostic lastSourceTimeUs cannot precede firstSourceTimeUs." });
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
export const RecordingLifecycleStateSchema = z.enum([
|
|
676
|
+
"preparing",
|
|
677
|
+
"recording",
|
|
678
|
+
"paused",
|
|
679
|
+
"stopped",
|
|
680
|
+
"failed",
|
|
681
|
+
]);
|
|
682
|
+
|
|
683
|
+
function addDuplicateIssue(
|
|
684
|
+
values: readonly string[],
|
|
685
|
+
label: string,
|
|
686
|
+
context: z.core.$RefinementCtx<unknown>,
|
|
687
|
+
): void {
|
|
688
|
+
if (new Set(values).size !== values.length) {
|
|
689
|
+
context.addIssue({ code: "custom", message: `${label} must be unique.` });
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function validateOrderedSegments(
|
|
694
|
+
track: z.infer<typeof LogicalTrackSchema>,
|
|
695
|
+
context: z.core.$RefinementCtx<unknown>,
|
|
696
|
+
): void {
|
|
697
|
+
let priorEnd = -1;
|
|
698
|
+
for (const segment of track.segments) {
|
|
699
|
+
if (segment.startUs < priorEnd) {
|
|
700
|
+
context.addIssue({
|
|
701
|
+
code: "custom",
|
|
702
|
+
message: `Track ${track.trackId} segments must be ordered and non-overlapping.`,
|
|
703
|
+
});
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
priorEnd = segment.endUs;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const RecordingManifestBaseShape = {
|
|
711
|
+
capture: z.strictObject({
|
|
712
|
+
cursor: z.enum(["disabled", "metadata"]),
|
|
713
|
+
typedText: z.enum(["disabled", "enabled"]).default("disabled"),
|
|
714
|
+
windowMetadata: z.enum(["disabled", "bounds-only", "titles-and-bounds"]),
|
|
715
|
+
}),
|
|
716
|
+
coordinateSpace: z.strictObject({
|
|
717
|
+
kind: z.literal("global-display-points"),
|
|
718
|
+
origin: z.literal("top-left"),
|
|
719
|
+
xAxis: z.literal("right"),
|
|
720
|
+
yAxis: z.literal("down"),
|
|
721
|
+
}),
|
|
722
|
+
createdAt: IsoTimestampSchema,
|
|
723
|
+
diagnostics: z.array(RecordingDiagnosticSchema),
|
|
724
|
+
eventStreams: z.array(EventStreamReferenceSchema),
|
|
725
|
+
kind: z.union([
|
|
726
|
+
z.literal("slopcamera.recording-bundle"),
|
|
727
|
+
z.literal("studio.recording-bundle"),
|
|
728
|
+
]),
|
|
729
|
+
permissions: CapturePermissionsSchema,
|
|
730
|
+
platform: z.strictObject({
|
|
731
|
+
architecture: z.string().min(1).max(128),
|
|
732
|
+
os: z.enum(["macos", "linux", "windows", "unknown"]),
|
|
733
|
+
osVersion: z.string().min(1).max(128),
|
|
734
|
+
}),
|
|
735
|
+
recordingId: RecordingIdSchema,
|
|
736
|
+
sources: SourceInventorySchema,
|
|
737
|
+
state: RecordingLifecycleStateSchema,
|
|
738
|
+
tool: z.strictObject({
|
|
739
|
+
captureVersion: z.string().min(1).max(128),
|
|
740
|
+
name: z.union([
|
|
741
|
+
z.literal("slopcamera"),
|
|
742
|
+
z.literal("studio"),
|
|
743
|
+
]),
|
|
744
|
+
version: z.string().min(1).max(128),
|
|
745
|
+
}),
|
|
746
|
+
tracks: z.array(LogicalTrackSchema),
|
|
747
|
+
updatedAt: IsoTimestampSchema,
|
|
748
|
+
} as const;
|
|
749
|
+
|
|
750
|
+
const CaptureNativeClockSegmentSchema = z.strictObject({
|
|
751
|
+
index: z.number().int().safe().nonnegative().max(127),
|
|
752
|
+
nativeRange: z.strictObject({
|
|
753
|
+
endUs: PositiveMicrosecondsSchema,
|
|
754
|
+
startUs: MicrosecondsSchema,
|
|
755
|
+
}),
|
|
756
|
+
sourceRange: z.strictObject({
|
|
757
|
+
endUs: PositiveMicrosecondsSchema,
|
|
758
|
+
startUs: MicrosecondsSchema,
|
|
759
|
+
}),
|
|
760
|
+
}).superRefine((segment, context) => {
|
|
761
|
+
const nativeDurationUs = segment.nativeRange.endUs - segment.nativeRange.startUs;
|
|
762
|
+
const sourceDurationUs = segment.sourceRange.endUs - segment.sourceRange.startUs;
|
|
763
|
+
if (nativeDurationUs <= 0 || sourceDurationUs <= 0) {
|
|
764
|
+
context.addIssue({ code: "custom", message: "Capture native-clock ranges must have positive duration." });
|
|
765
|
+
} else if (nativeDurationUs !== sourceDurationUs) {
|
|
766
|
+
context.addIssue({ code: "custom", message: "Capture native and source ranges must have equal duration." });
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
const RecordingTimelineV1Schema = z.strictObject({
|
|
771
|
+
durationUs: MicrosecondsSchema,
|
|
772
|
+
timebase: z.literal("microseconds"),
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
const RecordingTimelineV2Schema = z.strictObject({
|
|
776
|
+
durationUs: MicrosecondsSchema,
|
|
777
|
+
nativeClock: z.strictObject({
|
|
778
|
+
kind: z.literal("mach-continuous-microseconds"),
|
|
779
|
+
segments: z.array(CaptureNativeClockSegmentSchema).max(128),
|
|
780
|
+
}),
|
|
781
|
+
timebase: z.literal("microseconds"),
|
|
782
|
+
}).superRefine((timeline, context) => {
|
|
783
|
+
let priorIndex = -1;
|
|
784
|
+
let priorNativeEndUs = -1;
|
|
785
|
+
let priorSourceEndUs = 0;
|
|
786
|
+
for (const segment of timeline.nativeClock.segments) {
|
|
787
|
+
if (segment.index <= priorIndex) {
|
|
788
|
+
context.addIssue({ code: "custom", message: "Capture native-clock segment indices must increase." });
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if (segment.sourceRange.startUs !== priorSourceEndUs) {
|
|
792
|
+
context.addIssue({ code: "custom", message: "Capture native-clock source ranges must be contiguous across pauses." });
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
if (segment.nativeRange.startUs < priorNativeEndUs) {
|
|
796
|
+
context.addIssue({ code: "custom", message: "Capture native-clock ranges must be ordered and non-overlapping." });
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
priorIndex = segment.index;
|
|
800
|
+
priorNativeEndUs = segment.nativeRange.endUs;
|
|
801
|
+
priorSourceEndUs = segment.sourceRange.endUs;
|
|
802
|
+
}
|
|
803
|
+
if (priorSourceEndUs > timeline.durationUs) {
|
|
804
|
+
context.addIssue({ code: "custom", message: "Capture native-clock ranges exceed the recording timeline." });
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
interface ValidatableRecordingManifest {
|
|
809
|
+
readonly createdAt: string;
|
|
810
|
+
readonly diagnostics: readonly z.infer<typeof RecordingDiagnosticSchema>[];
|
|
811
|
+
readonly eventStreams: readonly z.infer<typeof EventStreamReferenceSchema>[];
|
|
812
|
+
readonly kind: "slopcamera.recording-bundle" | "studio.recording-bundle";
|
|
813
|
+
readonly sources: z.infer<typeof SourceInventorySchema>;
|
|
814
|
+
readonly state: z.infer<typeof RecordingLifecycleStateSchema>;
|
|
815
|
+
readonly timeline: { readonly durationUs: number };
|
|
816
|
+
readonly tool: { readonly name: "slopcamera" | "studio" };
|
|
817
|
+
readonly tracks: readonly z.infer<typeof LogicalTrackSchema>[];
|
|
818
|
+
readonly updatedAt: string;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function validateRecordingManifest(
|
|
822
|
+
manifest: ValidatableRecordingManifest,
|
|
823
|
+
context: z.core.$RefinementCtx<unknown>,
|
|
824
|
+
): void {
|
|
825
|
+
const canonicalIdentity = manifest.kind === "slopcamera.recording-bundle"
|
|
826
|
+
&& manifest.tool.name === "slopcamera";
|
|
827
|
+
const legacyStudioIdentity = manifest.kind === "studio.recording-bundle"
|
|
828
|
+
&& manifest.tool.name === "studio";
|
|
829
|
+
if (!canonicalIdentity && !legacyStudioIdentity) {
|
|
830
|
+
context.addIssue({
|
|
831
|
+
code: "custom",
|
|
832
|
+
message: "Recording bundle kind and tool name must use the same product identity.",
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
if (Date.parse(manifest.updatedAt) < Date.parse(manifest.createdAt)) {
|
|
836
|
+
context.addIssue({ code: "custom", message: "updatedAt cannot precede createdAt." });
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
addDuplicateIssue(manifest.tracks.map(({ trackId }) => trackId), "Track IDs", context);
|
|
840
|
+
addDuplicateIssue(manifest.sources.displays.map(({ displayId }) => displayId), "Display source IDs", context);
|
|
841
|
+
addDuplicateIssue(manifest.sources.cameras.map(({ cameraId }) => cameraId), "Camera source IDs", context);
|
|
842
|
+
addDuplicateIssue(manifest.sources.audio.map(({ audioSourceId }) => audioSourceId), "Audio source IDs", context);
|
|
843
|
+
addDuplicateIssue(
|
|
844
|
+
manifest.tracks.flatMap(({ segments }) => segments.map(({ segmentId }) => segmentId)),
|
|
845
|
+
"Segment IDs",
|
|
846
|
+
context,
|
|
847
|
+
);
|
|
848
|
+
addDuplicateIssue(
|
|
849
|
+
manifest.tracks.flatMap(({ segments }) => segments.map(({ path, streamIndex }) => `${path}\0${streamIndex}`)),
|
|
850
|
+
"Segment path and stream-index pairs",
|
|
851
|
+
context,
|
|
852
|
+
);
|
|
853
|
+
addDuplicateIssue(
|
|
854
|
+
manifest.eventStreams.map(({ eventStreamId }) => eventStreamId),
|
|
855
|
+
"Event stream IDs",
|
|
856
|
+
context,
|
|
857
|
+
);
|
|
858
|
+
addDuplicateIssue(
|
|
859
|
+
manifest.eventStreams.map(({ path }) => path),
|
|
860
|
+
"Event stream paths",
|
|
861
|
+
context,
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
const displayIds = new Set(manifest.sources.displays.map(({ displayId }) => displayId));
|
|
865
|
+
const cameraIds = new Set(manifest.sources.cameras.map(({ cameraId }) => cameraId));
|
|
866
|
+
const systemAudioIds = new Set(
|
|
867
|
+
manifest.sources.audio.filter(({ kind }) => kind === "system").map(({ audioSourceId }) => audioSourceId),
|
|
868
|
+
);
|
|
869
|
+
const microphoneIds = new Set(
|
|
870
|
+
manifest.sources.audio.filter(({ kind }) => kind === "microphone").map(({ audioSourceId }) => audioSourceId),
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
if (
|
|
874
|
+
manifest.sources.displays.length > 0
|
|
875
|
+
&& manifest.sources.displays.filter(({ isPrimary }) => isPrimary).length !== 1
|
|
876
|
+
) {
|
|
877
|
+
context.addIssue({ code: "custom", message: "Display inventory must identify exactly one primary display." });
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
const mediaPaths = new Set(manifest.tracks.flatMap(({ segments }) => segments.map(({ path }) => path)));
|
|
881
|
+
if (manifest.eventStreams.some(({ path }) => mediaPaths.has(path))) {
|
|
882
|
+
context.addIssue({ code: "custom", message: "Media segments and event streams must not share a file path." });
|
|
883
|
+
}
|
|
884
|
+
if (manifest.eventStreams.some(({ endUs }) => endUs > manifest.timeline.durationUs)) {
|
|
885
|
+
context.addIssue({ code: "custom", message: "Event streams cannot exceed the recording timeline." });
|
|
886
|
+
}
|
|
887
|
+
if (manifest.diagnostics.some(({ lastSourceTimeUs }) => (
|
|
888
|
+
lastSourceTimeUs !== null && lastSourceTimeUs > manifest.timeline.durationUs
|
|
889
|
+
))) {
|
|
890
|
+
context.addIssue({ code: "custom", message: "Recording diagnostics cannot exceed the recording timeline." });
|
|
891
|
+
}
|
|
892
|
+
if (
|
|
893
|
+
manifest.state === "stopped"
|
|
894
|
+
&& (
|
|
895
|
+
manifest.tracks.some(({ segments }) => segments.some(({ integrity }) => integrity.state !== "verified"))
|
|
896
|
+
|| manifest.eventStreams.some(({ integrity }) => integrity.state !== "verified")
|
|
897
|
+
)
|
|
898
|
+
) {
|
|
899
|
+
context.addIssue({ code: "custom", message: "Stopped recordings must have verified integrity for every referenced file." });
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
for (const track of manifest.tracks) {
|
|
903
|
+
validateOrderedSegments(track, context);
|
|
904
|
+
if (track.segments.some(({ endUs }) => endUs > manifest.timeline.durationUs)) {
|
|
905
|
+
context.addIssue({ code: "custom", message: `Track ${track.trackId} exceeds the recording timeline.` });
|
|
906
|
+
}
|
|
907
|
+
const sourceExists = track.kind === "display-video"
|
|
908
|
+
? displayIds.has(track.source.displayId)
|
|
909
|
+
: track.kind === "camera-video"
|
|
910
|
+
? cameraIds.has(track.source.cameraId)
|
|
911
|
+
: track.kind === "system-audio"
|
|
912
|
+
? systemAudioIds.has(track.source.audioSourceId)
|
|
913
|
+
: microphoneIds.has(track.source.audioSourceId);
|
|
914
|
+
if (!sourceExists) {
|
|
915
|
+
context.addIssue({ code: "custom", message: `Track ${track.trackId} references an unknown source.` });
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const NormalizedRecordingManifestV1Schema = z.strictObject({
|
|
921
|
+
...RecordingManifestBaseShape,
|
|
922
|
+
interruptions: z.array(CaptureInterruptionSchema).max(0).default([]),
|
|
923
|
+
schemaVersion: z.literal(1),
|
|
924
|
+
timeline: RecordingTimelineV1Schema,
|
|
925
|
+
}).superRefine((manifest, context) => {
|
|
926
|
+
validateRecordingManifest(manifest, context);
|
|
927
|
+
if (manifest.tracks.some(track => track.segments.some(segment => segment.timing.kind !== "legacy-estimate"))) {
|
|
928
|
+
context.addIssue({ code: "custom", message: "Recording manifest v1 may contain only legacy-estimate timing." });
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
const MeasuredRecordingManifestShape = {
|
|
933
|
+
...RecordingManifestBaseShape,
|
|
934
|
+
timeline: RecordingTimelineV2Schema,
|
|
935
|
+
} as const;
|
|
936
|
+
|
|
937
|
+
type ValidatableMeasuredRecordingManifest = z.infer<
|
|
938
|
+
z.ZodObject<typeof MeasuredRecordingManifestShape>
|
|
939
|
+
>;
|
|
940
|
+
|
|
941
|
+
function validateMeasuredRecordingManifest(
|
|
942
|
+
manifest: ValidatableMeasuredRecordingManifest,
|
|
943
|
+
context: z.core.$RefinementCtx<unknown>,
|
|
944
|
+
): void {
|
|
945
|
+
validateRecordingManifest(manifest, context);
|
|
946
|
+
if (manifest.tracks.some(track => track.segments.some(segment => segment.timing.kind !== "measured"))) {
|
|
947
|
+
context.addIssue({ code: "custom", message: "Recording manifest v2 requires measured timing for every media segment." });
|
|
948
|
+
}
|
|
949
|
+
const clockSegments = new Map(
|
|
950
|
+
manifest.timeline.nativeClock.segments.map(segment => [segment.index, segment] as const),
|
|
951
|
+
);
|
|
952
|
+
for (const track of manifest.tracks) {
|
|
953
|
+
const captureSegmentIndices = track.segments.flatMap(segment => (
|
|
954
|
+
segment.timing.kind === "measured" ? [segment.timing.captureSegmentIndex] : []
|
|
955
|
+
));
|
|
956
|
+
if (new Set(captureSegmentIndices).size !== captureSegmentIndices.length) {
|
|
957
|
+
context.addIssue({
|
|
958
|
+
code: "custom",
|
|
959
|
+
message: `Track ${track.trackId} must have at most one media segment per capture segment.`,
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
for (const segment of track.segments) {
|
|
963
|
+
if (segment.timing.kind !== "measured") continue;
|
|
964
|
+
const timing = segment.timing;
|
|
965
|
+
const clockSegment = clockSegments.get(timing.captureSegmentIndex);
|
|
966
|
+
if (clockSegment === undefined) {
|
|
967
|
+
context.addIssue({ code: "custom", message: "Measured timing references an unknown capture clock segment." });
|
|
968
|
+
} else {
|
|
969
|
+
const placement = deriveCaptureSyncPlacement({
|
|
970
|
+
clockNativeStartUs: clockSegment.nativeRange.startUs,
|
|
971
|
+
clockSourceEndUs: clockSegment.sourceRange.endUs,
|
|
972
|
+
clockSourceStartUs: clockSegment.sourceRange.startUs,
|
|
973
|
+
fileContainerOriginPtsUs: timing.evidence.file.containerOriginPtsUs,
|
|
974
|
+
fileEndPtsUs: timing.evidence.file.endPtsUs,
|
|
975
|
+
fileFirstPtsUs: timing.evidence.file.firstPtsUs,
|
|
976
|
+
helperNativeStartUs: timing.evidence.helper.clockAnchors.first.nativeTimeUs,
|
|
977
|
+
});
|
|
978
|
+
if (
|
|
979
|
+
placement === null
|
|
980
|
+
|| segment.startUs !== placement.startUs
|
|
981
|
+
|| segment.endUs !== placement.endUs
|
|
982
|
+
|| segment.fileRange.startUs !== placement.fileRangeStartUs
|
|
983
|
+
|| segment.fileRange.endUs !== placement.fileRangeEndUs
|
|
984
|
+
) {
|
|
985
|
+
context.addIssue({
|
|
986
|
+
code: "custom",
|
|
987
|
+
message: "capture-timing-evidence-mismatch: media placement does not match raw clock and file evidence.",
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
const referenceTrack = manifest.tracks.find(
|
|
993
|
+
({ trackId }) => trackId === timing.referenceTrackId,
|
|
994
|
+
);
|
|
995
|
+
if (referenceTrack === undefined) {
|
|
996
|
+
context.addIssue({ code: "custom", message: "Capture sync measurement references an unknown track." });
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
if (referenceTrack.kind !== "display-video") {
|
|
1000
|
+
context.addIssue({
|
|
1001
|
+
code: "custom",
|
|
1002
|
+
message: "Capture sync measurement must reference a display-video track.",
|
|
1003
|
+
});
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
const referenceSegments = referenceTrack.segments.filter(candidate => (
|
|
1007
|
+
candidate.timing.kind === "measured"
|
|
1008
|
+
&& candidate.timing.captureSegmentIndex === timing.captureSegmentIndex
|
|
1009
|
+
));
|
|
1010
|
+
if (referenceSegments.length !== 1) {
|
|
1011
|
+
context.addIssue({
|
|
1012
|
+
code: "custom",
|
|
1013
|
+
message: "Capture sync reference track must have exactly one segment for the capture segment.",
|
|
1014
|
+
});
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
const referenceTiming = referenceSegments[0]!.timing;
|
|
1018
|
+
if (referenceTiming.kind !== "measured") continue;
|
|
1019
|
+
const onsetSkewUs = timing.evidence.helper.clockAnchors.first.nativeTimeUs
|
|
1020
|
+
- referenceTiming.evidence.helper.clockAnchors.first.nativeTimeUs;
|
|
1021
|
+
const endSkewUs = timing.evidence.helper.clockAnchors.end.nativeTimeUs
|
|
1022
|
+
- referenceTiming.evidence.helper.clockAnchors.end.nativeTimeUs;
|
|
1023
|
+
const durationDriftUs = endSkewUs - onsetSkewUs;
|
|
1024
|
+
const referenceDurationUs = referenceTiming.evidence.helper.clockAnchors.end.nativeTimeUs
|
|
1025
|
+
- referenceTiming.evidence.helper.clockAnchors.first.nativeTimeUs;
|
|
1026
|
+
const durationDriftPpm = deriveCaptureSyncDurationDriftPpm(
|
|
1027
|
+
durationDriftUs,
|
|
1028
|
+
referenceDurationUs,
|
|
1029
|
+
);
|
|
1030
|
+
const tolerances = deriveCaptureSyncTolerances({
|
|
1031
|
+
referenceDurationUs,
|
|
1032
|
+
referenceEndUncertaintyUs: referenceTiming.evidence.helper.clockAnchors.end.uncertaintyUs,
|
|
1033
|
+
referenceFirstUncertaintyUs: referenceTiming.evidence.helper.clockAnchors.first.uncertaintyUs,
|
|
1034
|
+
referenceMaximumSampleDurationUs: referenceTiming.evidence.helper.presentation.maximumSampleDurationUs,
|
|
1035
|
+
subjectEndUncertaintyUs: timing.evidence.helper.clockAnchors.end.uncertaintyUs,
|
|
1036
|
+
subjectFirstUncertaintyUs: timing.evidence.helper.clockAnchors.first.uncertaintyUs,
|
|
1037
|
+
subjectMaximumSampleDurationUs: timing.evidence.helper.presentation.maximumSampleDurationUs,
|
|
1038
|
+
});
|
|
1039
|
+
if (
|
|
1040
|
+
durationDriftPpm === null
|
|
1041
|
+
|| timing.onsetSkewUs !== onsetSkewUs
|
|
1042
|
+
|| timing.durationDriftUs !== durationDriftUs
|
|
1043
|
+
|| timing.durationDriftPpm !== durationDriftPpm
|
|
1044
|
+
) {
|
|
1045
|
+
context.addIssue({
|
|
1046
|
+
code: "custom",
|
|
1047
|
+
message: "capture-timing-evidence-mismatch: derived synchronization does not match raw clock anchors.",
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
if (
|
|
1051
|
+
tolerances === null
|
|
1052
|
+
|| timing.tolerance.durationDriftUs !== tolerances.durationDriftUs
|
|
1053
|
+
|| timing.tolerance.onsetSkewUs !== tolerances.onsetSkewUs
|
|
1054
|
+
) {
|
|
1055
|
+
context.addIssue({
|
|
1056
|
+
code: "custom",
|
|
1057
|
+
message: "capture-timing-evidence-mismatch: synchronization tolerances do not match capture-sync-v1 policy.",
|
|
1058
|
+
});
|
|
1059
|
+
} else {
|
|
1060
|
+
const expectedStatus = Math.abs(onsetSkewUs) > tolerances.onsetSkewUs
|
|
1061
|
+
|| Math.abs(durationDriftUs) > tolerances.durationDriftUs
|
|
1062
|
+
? "out-of-tolerance"
|
|
1063
|
+
: "within-tolerance";
|
|
1064
|
+
if (timing.status !== expectedStatus) {
|
|
1065
|
+
context.addIssue({
|
|
1066
|
+
code: "custom",
|
|
1067
|
+
message: "capture-timing-evidence-mismatch: synchronization status does not match capture-sync-v1 policy.",
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
export const RecordingManifestV2Schema = z.strictObject({
|
|
1076
|
+
...MeasuredRecordingManifestShape,
|
|
1077
|
+
interruptions: z.array(CaptureInterruptionSchema).max(0).default([]),
|
|
1078
|
+
schemaVersion: z.literal(2),
|
|
1079
|
+
}).superRefine(validateMeasuredRecordingManifest);
|
|
1080
|
+
|
|
1081
|
+
export const RecordingManifestV3Schema = z.strictObject({
|
|
1082
|
+
...MeasuredRecordingManifestShape,
|
|
1083
|
+
interruptions: z.array(CaptureInterruptionSchema)
|
|
1084
|
+
.max(MAX_CAPTURE_INTERRUPTION_SEGMENTS),
|
|
1085
|
+
schemaVersion: z.literal(3),
|
|
1086
|
+
}).superRefine((manifest, context) => {
|
|
1087
|
+
validateMeasuredRecordingManifest(manifest, context);
|
|
1088
|
+
|
|
1089
|
+
const clockSegments = new Map(
|
|
1090
|
+
manifest.timeline.nativeClock.segments.map(segment => [segment.index, segment] as const),
|
|
1091
|
+
);
|
|
1092
|
+
const lastClockSegment = manifest.timeline.nativeClock.segments.at(-1);
|
|
1093
|
+
let priorInterruptionIndex = -1;
|
|
1094
|
+
let unmatchedFailureSeen = false;
|
|
1095
|
+
for (const interruption of manifest.interruptions) {
|
|
1096
|
+
if (interruption.segmentIndex <= priorInterruptionIndex) {
|
|
1097
|
+
context.addIssue({
|
|
1098
|
+
code: "custom",
|
|
1099
|
+
message: "Capture interruptions must be ordered with at most one interruption per segment.",
|
|
1100
|
+
});
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
priorInterruptionIndex = interruption.segmentIndex;
|
|
1104
|
+
const clockSegment = clockSegments.get(interruption.segmentIndex);
|
|
1105
|
+
if (clockSegment !== undefined) {
|
|
1106
|
+
if (
|
|
1107
|
+
!interruption.recoverable
|
|
1108
|
+
|| interruption.nativeTimeUs !== clockSegment.nativeRange.endUs
|
|
1109
|
+
|| interruption.sourceTimeUs !== clockSegment.sourceRange.endUs
|
|
1110
|
+
) {
|
|
1111
|
+
context.addIssue({
|
|
1112
|
+
code: "custom",
|
|
1113
|
+
message: "A completed-segment interruption must be recoverable and match its capture clock end anchors.",
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
const expectedPreparedIndex = (lastClockSegment?.index ?? -1) + 1;
|
|
1120
|
+
const persistedSourceFrontierUs =
|
|
1121
|
+
lastClockSegment?.sourceRange.endUs ?? 0;
|
|
1122
|
+
const persistedNativeFrontierUs =
|
|
1123
|
+
lastClockSegment?.nativeRange.endUs ?? 0;
|
|
1124
|
+
if (
|
|
1125
|
+
unmatchedFailureSeen
|
|
1126
|
+
|| manifest.state !== "failed"
|
|
1127
|
+
|| interruption.segmentIndex !== expectedPreparedIndex
|
|
1128
|
+
|| interruption.sourceTimeUs !== persistedSourceFrontierUs
|
|
1129
|
+
|| interruption.nativeTimeUs < persistedNativeFrontierUs
|
|
1130
|
+
|| interruption.recoverable
|
|
1131
|
+
|| manifest.timeline.durationUs !== persistedSourceFrontierUs
|
|
1132
|
+
) {
|
|
1133
|
+
context.addIssue({
|
|
1134
|
+
code: "custom",
|
|
1135
|
+
message: "Only one nonrecoverable failed prepared-start interruption may be anchored at the persisted capture frontier.",
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
unmatchedFailureSeen = true;
|
|
1139
|
+
}
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
function unknownArray(value: unknown): readonly unknown[] | null {
|
|
1143
|
+
return Array.isArray(value) ? value as readonly unknown[] : null;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function normalizeLegacyRecordingManifest(input: unknown): unknown {
|
|
1147
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) return input;
|
|
1148
|
+
const manifest = input as Readonly<Record<string, unknown>>;
|
|
1149
|
+
const rawTracks = unknownArray(manifest.tracks);
|
|
1150
|
+
if (manifest.schemaVersion !== 1 || rawTracks === null) return input;
|
|
1151
|
+
let normalizedLegacySegment = false;
|
|
1152
|
+
const tracks = rawTracks.map(rawTrack => {
|
|
1153
|
+
if (typeof rawTrack !== "object" || rawTrack === null || Array.isArray(rawTrack)) return rawTrack;
|
|
1154
|
+
const track = rawTrack as Readonly<Record<string, unknown>>;
|
|
1155
|
+
const rawSegments = unknownArray(track.segments);
|
|
1156
|
+
if (rawSegments === null) return rawTrack;
|
|
1157
|
+
const segments = rawSegments.map(rawSegment => {
|
|
1158
|
+
if (typeof rawSegment !== "object" || rawSegment === null || Array.isArray(rawSegment)) return rawSegment;
|
|
1159
|
+
const segment = rawSegment as Readonly<Record<string, unknown>>;
|
|
1160
|
+
if (segment.fileRange !== undefined || segment.timing !== undefined) {
|
|
1161
|
+
if (
|
|
1162
|
+
typeof segment.timing === "object"
|
|
1163
|
+
&& segment.timing !== null
|
|
1164
|
+
&& !Array.isArray(segment.timing)
|
|
1165
|
+
&& (segment.timing as Readonly<Record<string, unknown>>).kind === "legacy-estimate"
|
|
1166
|
+
) {
|
|
1167
|
+
normalizedLegacySegment = true;
|
|
1168
|
+
}
|
|
1169
|
+
return rawSegment;
|
|
1170
|
+
}
|
|
1171
|
+
const parsed = LegacyMediaSegmentV1Schema.safeParse(rawSegment);
|
|
1172
|
+
if (!parsed.success) return rawSegment;
|
|
1173
|
+
normalizedLegacySegment = true;
|
|
1174
|
+
const legacy = parsed.data;
|
|
1175
|
+
return {
|
|
1176
|
+
codec: legacy.codec,
|
|
1177
|
+
container: legacy.container,
|
|
1178
|
+
containerTrackIdentity: legacy.containerTrackIdentity,
|
|
1179
|
+
endUs: legacy.endUs,
|
|
1180
|
+
fileRange: { endUs: legacy.endUs - legacy.startUs, startUs: 0 },
|
|
1181
|
+
integrity: legacy.integrity,
|
|
1182
|
+
path: legacy.path,
|
|
1183
|
+
segmentId: legacy.segmentId,
|
|
1184
|
+
startUs: legacy.startUs,
|
|
1185
|
+
streamIndex: legacy.streamIndex,
|
|
1186
|
+
timing: {
|
|
1187
|
+
kind: "legacy-estimate",
|
|
1188
|
+
nativeRange: { endUs: legacy.nativeEndUs, startUs: legacy.nativeStartUs },
|
|
1189
|
+
reason: "recording-manifest-v1-container-duration",
|
|
1190
|
+
},
|
|
1191
|
+
};
|
|
1192
|
+
});
|
|
1193
|
+
return { ...track, segments };
|
|
1194
|
+
});
|
|
1195
|
+
const diagnostics = unknownArray(manifest.diagnostics);
|
|
1196
|
+
if (diagnostics === null) return { ...manifest, tracks };
|
|
1197
|
+
const hasLegacyDiagnostic = diagnostics.some(diagnostic => (
|
|
1198
|
+
typeof diagnostic === "object"
|
|
1199
|
+
&& diagnostic !== null
|
|
1200
|
+
&& !Array.isArray(diagnostic)
|
|
1201
|
+
&& (diagnostic as Readonly<Record<string, unknown>>).code === "legacy-capture-timing"
|
|
1202
|
+
));
|
|
1203
|
+
return {
|
|
1204
|
+
...manifest,
|
|
1205
|
+
diagnostics: normalizedLegacySegment && !hasLegacyDiagnostic
|
|
1206
|
+
? [...diagnostics, {
|
|
1207
|
+
code: "legacy-capture-timing",
|
|
1208
|
+
count: 1,
|
|
1209
|
+
firstSourceTimeUs: null,
|
|
1210
|
+
lastSourceTimeUs: null,
|
|
1211
|
+
level: "warning",
|
|
1212
|
+
message: "Recording manifest v1 has estimated container timing; per-track capture synchronization was not measured.",
|
|
1213
|
+
}]
|
|
1214
|
+
: diagnostics,
|
|
1215
|
+
tracks,
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
export const RecordingManifestSchema = z.preprocess(
|
|
1220
|
+
normalizeLegacyRecordingManifest,
|
|
1221
|
+
z.discriminatedUnion("schemaVersion", [
|
|
1222
|
+
NormalizedRecordingManifestV1Schema,
|
|
1223
|
+
RecordingManifestV2Schema,
|
|
1224
|
+
RecordingManifestV3Schema,
|
|
1225
|
+
]),
|
|
1226
|
+
);
|
|
1227
|
+
|
|
1228
|
+
/** @deprecated Use RecordingManifestSchema. Retained as a source-compatible reader alias. */
|
|
1229
|
+
export const RecordingManifestV1Schema = RecordingManifestSchema;
|
|
1230
|
+
|
|
1231
|
+
const EventBaseShape = {
|
|
1232
|
+
nativeTimeUs: MicrosecondsSchema,
|
|
1233
|
+
sequence: SequenceSchema,
|
|
1234
|
+
sourceTimeUs: MicrosecondsSchema,
|
|
1235
|
+
} as const;
|
|
1236
|
+
|
|
1237
|
+
export const CursorSampleEventSchema = z.strictObject({
|
|
1238
|
+
...EventBaseShape,
|
|
1239
|
+
displayId: z.string().min(1).max(256),
|
|
1240
|
+
position: PointSchema,
|
|
1241
|
+
type: z.literal("cursor.sample"),
|
|
1242
|
+
visible: z.boolean(),
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
export const MouseClickEventSchema = z.strictObject({
|
|
1246
|
+
...EventBaseShape,
|
|
1247
|
+
button: z.enum(["left", "right", "middle", "other"]),
|
|
1248
|
+
clickCount: z.number().int().safe().positive().max(16),
|
|
1249
|
+
displayId: z.string().min(1).max(256),
|
|
1250
|
+
phase: z.enum(["down", "up"]),
|
|
1251
|
+
position: PointSchema,
|
|
1252
|
+
type: z.literal("mouse.click"),
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
const ModifierSchema = z.enum(["command", "control", "option", "shift", "caps-lock", "function"]);
|
|
1256
|
+
|
|
1257
|
+
export const KeyActivityEventSchema = z.strictObject({
|
|
1258
|
+
...EventBaseShape,
|
|
1259
|
+
activity: z.discriminatedUnion("kind", [
|
|
1260
|
+
z.strictObject({
|
|
1261
|
+
kind: z.literal("shortcut"),
|
|
1262
|
+
keyCode: z.string().min(1).max(128),
|
|
1263
|
+
modifiers: z.array(ModifierSchema).min(1),
|
|
1264
|
+
phase: z.enum(["down", "up"]),
|
|
1265
|
+
repeat: z.boolean(),
|
|
1266
|
+
}),
|
|
1267
|
+
z.strictObject({
|
|
1268
|
+
control: z.enum(["arrow-up", "arrow-down", "arrow-left", "arrow-right", "escape", "tab", "enter", "delete"]),
|
|
1269
|
+
kind: z.literal("control"),
|
|
1270
|
+
modifiers: z.array(ModifierSchema),
|
|
1271
|
+
phase: z.enum(["down", "up"]),
|
|
1272
|
+
repeat: z.boolean(),
|
|
1273
|
+
}),
|
|
1274
|
+
z.strictObject({
|
|
1275
|
+
kind: z.literal("printable"),
|
|
1276
|
+
modifiers: z.array(ModifierSchema),
|
|
1277
|
+
phase: z.enum(["down", "up"]),
|
|
1278
|
+
repeat: z.boolean(),
|
|
1279
|
+
token: z.literal("[PRINTABLE]"),
|
|
1280
|
+
}),
|
|
1281
|
+
]),
|
|
1282
|
+
type: z.literal("key.activity"),
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
const PublicTypingPayloadSchema = z.strictObject({
|
|
1286
|
+
action: z.enum(["insert", "delete-backward", "delete-forward", "replace"]),
|
|
1287
|
+
bounds: RectSchema,
|
|
1288
|
+
fieldId: z.string().min(1).max(512),
|
|
1289
|
+
secure: z.literal(false),
|
|
1290
|
+
text: z.string().max(4096),
|
|
1291
|
+
windowId: z.string().min(1).max(256),
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
export const TypingInputEventSchema = z.strictObject({
|
|
1295
|
+
...EventBaseShape,
|
|
1296
|
+
input: PublicTypingPayloadSchema,
|
|
1297
|
+
type: z.literal("typing.input"),
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
export const FocusTargetSchema = z.discriminatedUnion("kind", [
|
|
1301
|
+
z.strictObject({ kind: z.literal("none") }),
|
|
1302
|
+
z.strictObject({
|
|
1303
|
+
bounds: RectSchema,
|
|
1304
|
+
fieldId: z.string().min(1).max(512),
|
|
1305
|
+
kind: z.literal("public-input"),
|
|
1306
|
+
processId: z.number().int().positive().max(2_147_483_647).optional(),
|
|
1307
|
+
role: z.string().min(1).max(128),
|
|
1308
|
+
windowId: z.string().min(1).max(256),
|
|
1309
|
+
}),
|
|
1310
|
+
z.strictObject({
|
|
1311
|
+
bounds: RectSchema,
|
|
1312
|
+
fieldId: z.literal("[REDACTED]"),
|
|
1313
|
+
kind: z.literal("secure-input"),
|
|
1314
|
+
processId: z.number().int().positive().max(2_147_483_647).optional(),
|
|
1315
|
+
redacted: z.literal(true),
|
|
1316
|
+
role: z.literal("secure-text-field"),
|
|
1317
|
+
windowId: z.string().min(1).max(256),
|
|
1318
|
+
}),
|
|
1319
|
+
]);
|
|
1320
|
+
|
|
1321
|
+
export const FocusChangedEventSchema = z.strictObject({
|
|
1322
|
+
...EventBaseShape,
|
|
1323
|
+
target: FocusTargetSchema,
|
|
1324
|
+
type: z.literal("focus.changed"),
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
export const WindowTitleSchema = z.discriminatedUnion("state", [
|
|
1328
|
+
z.strictObject({ state: z.literal("available"), value: z.string().max(2048) }),
|
|
1329
|
+
z.strictObject({ reason: z.enum(["permission", "secure", "unavailable"]), state: z.literal("redacted") }),
|
|
1330
|
+
]);
|
|
1331
|
+
|
|
1332
|
+
export const WindowRecordSchema = z.strictObject({
|
|
1333
|
+
applicationBundleId: z.string().min(1).max(512),
|
|
1334
|
+
applicationName: z.string().min(1).max(512),
|
|
1335
|
+
bounds: RectSchema,
|
|
1336
|
+
displayId: z.string().min(1).max(256),
|
|
1337
|
+
isFocused: z.boolean(),
|
|
1338
|
+
layer: z.number().int().safe(),
|
|
1339
|
+
title: WindowTitleSchema,
|
|
1340
|
+
windowId: z.string().min(1).max(256),
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
export const WindowSnapshotEventSchema = z.strictObject({
|
|
1344
|
+
...EventBaseShape,
|
|
1345
|
+
type: z.literal("window.snapshot"),
|
|
1346
|
+
windows: z.array(WindowRecordSchema),
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
export const WindowChangedEventSchema = z.strictObject({
|
|
1350
|
+
...EventBaseShape,
|
|
1351
|
+
change: z.discriminatedUnion("kind", [
|
|
1352
|
+
z.strictObject({ kind: z.enum(["created", "updated", "focused"]), window: WindowRecordSchema }),
|
|
1353
|
+
z.strictObject({ kind: z.literal("destroyed"), windowId: z.string().min(1).max(256) }),
|
|
1354
|
+
]),
|
|
1355
|
+
type: z.literal("window.changed"),
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
export const DisplayTopologyEventSchema = z.strictObject({
|
|
1359
|
+
...EventBaseShape,
|
|
1360
|
+
displays: z.array(DisplaySourceSchema).min(1),
|
|
1361
|
+
type: z.literal("display.topology"),
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
export const LifecycleMarkerEventSchema = z.strictObject({
|
|
1365
|
+
...EventBaseShape,
|
|
1366
|
+
marker: z.enum([
|
|
1367
|
+
"capture-requested",
|
|
1368
|
+
"recording-started",
|
|
1369
|
+
"pause-requested",
|
|
1370
|
+
"paused",
|
|
1371
|
+
"resume-requested",
|
|
1372
|
+
"resumed",
|
|
1373
|
+
"stop-requested",
|
|
1374
|
+
"stopped",
|
|
1375
|
+
"segment-opened",
|
|
1376
|
+
"segment-closed",
|
|
1377
|
+
"failed",
|
|
1378
|
+
]),
|
|
1379
|
+
segmentId: SegmentIdSchema.nullable(),
|
|
1380
|
+
type: z.literal("lifecycle.marker"),
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
export const DroppedEventsDiagnosticSchema = z.strictObject({
|
|
1384
|
+
...EventBaseShape,
|
|
1385
|
+
category: z.enum(["cursor", "mouse", "keyboard", "typing", "window", "display", "lifecycle"]),
|
|
1386
|
+
droppedCount: z.number().int().safe().positive(),
|
|
1387
|
+
firstDroppedNativeTimeUs: MicrosecondsSchema,
|
|
1388
|
+
lastDroppedNativeTimeUs: MicrosecondsSchema,
|
|
1389
|
+
reason: z.string().min(1).max(1024),
|
|
1390
|
+
type: z.literal("diagnostic.dropped-events"),
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
export const RecordingEventV1Schema = z.discriminatedUnion("type", [
|
|
1394
|
+
CursorSampleEventSchema,
|
|
1395
|
+
MouseClickEventSchema,
|
|
1396
|
+
KeyActivityEventSchema,
|
|
1397
|
+
TypingInputEventSchema,
|
|
1398
|
+
FocusChangedEventSchema,
|
|
1399
|
+
WindowSnapshotEventSchema,
|
|
1400
|
+
WindowChangedEventSchema,
|
|
1401
|
+
DisplayTopologyEventSchema,
|
|
1402
|
+
LifecycleMarkerEventSchema,
|
|
1403
|
+
DroppedEventsDiagnosticSchema,
|
|
1404
|
+
]);
|
|
1405
|
+
|
|
1406
|
+
export type RecordingId = z.infer<typeof RecordingIdSchema>;
|
|
1407
|
+
export type TrackId = z.infer<typeof TrackIdSchema>;
|
|
1408
|
+
export type SegmentId = z.infer<typeof SegmentIdSchema>;
|
|
1409
|
+
export type Rect = ReadonlyInferred<typeof RectSchema>;
|
|
1410
|
+
export type Point = ReadonlyInferred<typeof PointSchema>;
|
|
1411
|
+
export type DisplaySource = ReadonlyInferred<typeof DisplaySourceSchema>;
|
|
1412
|
+
export type SourceInventory = ReadonlyInferred<typeof SourceInventorySchema>;
|
|
1413
|
+
export type CapturePermissions = ReadonlyInferred<typeof CapturePermissionsSchema>;
|
|
1414
|
+
export type CaptureInterruption = ReadonlyInferred<typeof CaptureInterruptionSchema>;
|
|
1415
|
+
export type RecordingManifestV1 = ReadonlyInferred<typeof RecordingManifestV1Schema>;
|
|
1416
|
+
export type RecordingManifestV2 = ReadonlyInferred<typeof RecordingManifestV2Schema>;
|
|
1417
|
+
export type RecordingManifestV3 = ReadonlyInferred<typeof RecordingManifestV3Schema>;
|
|
1418
|
+
export type RecordingManifest = ReadonlyInferred<typeof RecordingManifestSchema>;
|
|
1419
|
+
export type RecordingEventV1 = ReadonlyInferred<typeof RecordingEventV1Schema>;
|
|
1420
|
+
export type CursorSampleEvent = ReadonlyInferred<typeof CursorSampleEventSchema>;
|
|
1421
|
+
export type TypingInputEvent = ReadonlyInferred<typeof TypingInputEventSchema>;
|
|
1422
|
+
export type WindowRecord = ReadonlyInferred<typeof WindowRecordSchema>;
|
|
1423
|
+
|
|
1424
|
+
export function parseRecordingManifestV1(input: unknown): RecordingManifestV1 {
|
|
1425
|
+
return RecordingManifestV1Schema.parse(input);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
export function parseRecordingManifest(input: unknown): RecordingManifest {
|
|
1429
|
+
return RecordingManifestSchema.parse(input);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
export function parseRecordingEventV1(input: unknown): RecordingEventV1 {
|
|
1433
|
+
return RecordingEventV1Schema.parse(input);
|
|
1434
|
+
}
|