rig-create 0.0.0-stage → 1.0.0
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/README.md +163 -2
- package/docs/CHECKS.md +1665 -0
- package/docs/COLLECTIONS.md +337 -0
- package/docs/CRAFT.md +277 -0
- package/docs/INTERVIEW.md +886 -0
- package/docs/LESSONS.md +172 -0
- package/docs/PROJECT.md +2657 -0
- package/docs/SOURCE.md +225 -0
- package/package.json +44 -4
- package/src/agent-bounce.ts +27 -0
- package/src/agent-guide.ts +237 -0
- package/src/amend.ts +193 -0
- package/src/applied-review.ts +104 -0
- package/src/ask-coverage.ts +69 -0
- package/src/assemble-answers.ts +53 -0
- package/src/cg-gates.ts +211 -0
- package/src/check-reads.ts +106 -0
- package/src/check.ts +1059 -0
- package/src/checkpoint-evidence-writer.ts +125 -0
- package/src/checkpoint-evidence.ts +152 -0
- package/src/choice-writers.ts +271 -0
- package/src/choices.ts +378 -0
- package/src/cli.ts +371 -0
- package/src/collection-answer.ts +136 -0
- package/src/collection-binding-reader.ts +496 -0
- package/src/collection-binding.ts +478 -0
- package/src/collection.ts +348 -0
- package/src/config-inputs.ts +155 -0
- package/src/config-lost.ts +135 -0
- package/src/correction-writers.ts +207 -0
- package/src/corrections.ts +685 -0
- package/src/diff-classes.ts +219 -0
- package/src/diff-map.ts +248 -0
- package/src/direction-citation.ts +13 -0
- package/src/direction-writers.ts +119 -0
- package/src/direction.ts +266 -0
- package/src/drafts.ts +363 -0
- package/src/feedback.ts +744 -0
- package/src/figure-mask.ts +143 -0
- package/src/fix-ledger.ts +355 -0
- package/src/init.ts +265 -0
- package/src/inputs.ts +160 -0
- package/src/intake.ts +158 -0
- package/src/interview.ts +53 -0
- package/src/invariants.ts +132 -0
- package/src/job-acts.ts +80 -0
- package/src/job-advance.ts +65 -0
- package/src/job-close.ts +226 -0
- package/src/job-host.ts +529 -0
- package/src/job-outbox.ts +383 -0
- package/src/job-publication.ts +701 -0
- package/src/job-runtime.ts +99 -0
- package/src/job-writer.ts +123 -0
- package/src/meshes-bust.ts +63 -0
- package/src/next.ts +1506 -0
- package/src/overrides.ts +42 -0
- package/src/package-upgrades.ts +32 -0
- package/src/packages-upgrade-check.ts +6 -0
- package/src/packages-upgrade.ts +168 -0
- package/src/parts-act.ts +96 -0
- package/src/parts-check.ts +242 -0
- package/src/parts-review.ts +196 -0
- package/src/parts-sections.ts +98 -0
- package/src/parts-verdict-record.ts +265 -0
- package/src/parts-verdict.ts +33 -0
- package/src/pause.ts +118 -0
- package/src/pins-check.ts +619 -0
- package/src/pins.ts +17 -0
- package/src/plan-bar-amendments.ts +54 -0
- package/src/plan-history.ts +147 -0
- package/src/plan-writers.ts +202 -0
- package/src/plate-receipt.ts +110 -0
- package/src/pre-interview.ts +302 -0
- package/src/prepared-inputs.ts +113 -0
- package/src/progress-writers.ts +181 -0
- package/src/provenance.ts +301 -0
- package/src/record-direction-target.ts +26 -0
- package/src/record-input.ts +18 -0
- package/src/refusal-baseline.ts +55 -0
- package/src/refusal.ts +30 -0
- package/src/reopen-lapses.ts +80 -0
- package/src/reopen.ts +144 -0
- package/src/republish.ts +93 -0
- package/src/restart.ts +141 -0
- package/src/review-act-runtime.ts +16 -0
- package/src/review-act.ts +592 -0
- package/src/review-package.ts +524 -0
- package/src/review-read.ts +238 -0
- package/src/review.ts +432 -0
- package/src/rig-version-date.ts +22 -0
- package/src/rig-version.ts +1047 -0
- package/src/run-record.ts +232 -0
- package/src/scene-authority.ts +410 -0
- package/src/scene-writers.ts +144 -0
- package/src/scene.ts +284 -0
- package/src/setup-painting.ts +46 -0
- package/src/signable.ts +65 -0
- package/src/signoff-answers.ts +76 -0
- package/src/signoff-checkpoint-act.ts +95 -0
- package/src/signoff-target.ts +127 -0
- package/src/signoff.ts +331 -0
- package/src/split-captions.ts +154 -0
- package/src/stage-progress.ts +379 -0
- package/src/stages.ts +148 -0
- package/src/step-hash.ts +19 -0
- package/src/structure-snapshot.ts +109 -0
- package/src/structure.ts +166 -0
- package/src/templates.ts +16 -0
- package/src/waypoints.ts +163 -0
- package/templates/agent-guide.md +108 -0
- package/templates/collection-agent-guide.md +72 -0
- package/templates/collection.json +252 -0
- package/templates/example-chain/AGENTS.md +134 -0
- package/templates/example-chain/CLAUDE.md +3 -0
- package/templates/example-chain/README.md +13 -0
- package/templates/example-chain/chain.json +2869 -0
- package/templates/example-chain/checkpoints/0a-interview-options/AGENTS.md +134 -0
- package/templates/example-chain/checkpoints/0a-interview-options/CLAUDE.md +3 -0
- package/templates/example-chain/checkpoints/0a-interview-options/evidence/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/0a-interview-options/evidence/approval.txt +1 -0
- package/templates/example-chain/checkpoints/0a-interview-options/evidence/interview/rig_type.json +59 -0
- package/templates/example-chain/checkpoints/0a-interview-options/evidence/split-config.json +1 -0
- package/templates/example-chain/checkpoints/0a-interview-options/inputs/painting.png +0 -0
- package/templates/example-chain/checkpoints/0a-interview-options/inputs/references/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/0a-interview-options/package.json +9 -0
- package/templates/example-chain/checkpoints/0a-interview-options/project.json +295 -0
- package/templates/example-chain/checkpoints/0a-interview-options.json +127 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/AGENTS.md +134 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/CLAUDE.md +3 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/evidence/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/evidence/approval.txt +1 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/assemble.json +309 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/reference.json +59 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/rig_type.json +59 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/seethrough.json +186 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/evidence/split-config.json +1 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/inputs/painting.png +0 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/inputs/references/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/package.json +9 -0
- package/templates/example-chain/checkpoints/0b-plan-pending/project.json +685 -0
- package/templates/example-chain/checkpoints/0b-plan-pending.json +81 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/AGENTS.md +134 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/CLAUDE.md +3 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/approval.txt +1 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/assemble.json +309 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/reference.json +59 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/rig_type.json +59 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/seethrough.json +186 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/parts-check.json +127 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/evidence/split-config.json +1 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/inputs/painting.png +0 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/inputs/references/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/package.json +9 -0
- package/templates/example-chain/checkpoints/1-parts-verdict/project.json +955 -0
- package/templates/example-chain/checkpoints/1-parts-verdict.json +204 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/AGENTS.md +134 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/CLAUDE.md +3 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/approval.txt +1 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/diff-components.json +34 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/assemble.json +309 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/bones.json +190 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/reference.json +59 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/rig_type.json +59 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/seethrough.json +186 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/parts-check.json +127 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/parts-review.json +88 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/split-config.json +1 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/evidence/structure.json +30 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/inputs/painting.png +0 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/inputs/references/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/package.json +9 -0
- package/templates/example-chain/checkpoints/2-rigging-answers/project.json +1198 -0
- package/templates/example-chain/checkpoints/2-rigging-answers.json +254 -0
- package/templates/example-chain/checkpoints/3-rig-review/AGENTS.md +134 -0
- package/templates/example-chain/checkpoints/3-rig-review/CLAUDE.md +3 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/approval.txt +1 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/diff-components.json +34 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/assemble.json +309 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/bones.json +190 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/constraints.json +272 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/meshes.json +190 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/motion.json +272 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/reference.json +59 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/rig_type.json +59 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/seethrough.json +186 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/parts-check.json +127 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/parts-review.json +88 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/setup.png +0 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/split-config.json +1 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/structure.json +30 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/v1/config.json +88 -0
- package/templates/example-chain/checkpoints/3-rig-review/evidence/v1/interview-requirements.json +8 -0
- package/templates/example-chain/checkpoints/3-rig-review/inputs/painting.png +0 -0
- package/templates/example-chain/checkpoints/3-rig-review/inputs/references/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/3-rig-review/package.json +9 -0
- package/templates/example-chain/checkpoints/3-rig-review/project.json +1909 -0
- package/templates/example-chain/checkpoints/3-rig-review.json +424 -0
- package/templates/example-chain/checkpoints/4-direction-post/AGENTS.md +134 -0
- package/templates/example-chain/checkpoints/4-direction-post/CLAUDE.md +3 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/approval.txt +1 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/diff-components.json +34 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/assemble.json +309 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/bones.json +190 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/camera_depth.json +102 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/constraints.json +272 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/meshes.json +190 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/motion.json +272 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/post_composition.json +122 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/post_timing.json +102 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/reference.json +59 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/rig_type.json +59 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/seethrough.json +186 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/parts-check.json +127 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/parts-review.json +88 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/setup.png +0 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/split-config.json +1 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/structure.json +30 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/v1/config.json +88 -0
- package/templates/example-chain/checkpoints/4-direction-post/evidence/v1/interview-requirements.json +8 -0
- package/templates/example-chain/checkpoints/4-direction-post/inputs/painting.png +0 -0
- package/templates/example-chain/checkpoints/4-direction-post/inputs/references/.gitkeep +0 -0
- package/templates/example-chain/checkpoints/4-direction-post/package.json +9 -0
- package/templates/example-chain/checkpoints/4-direction-post/project.json +2302 -0
- package/templates/example-chain/checkpoints/4-direction-post.json +456 -0
- package/templates/example-chain/evidence/.gitkeep +0 -0
- package/templates/example-chain/evidence/approval.txt +1 -0
- package/templates/example-chain/evidence/captions.json +101 -0
- package/templates/example-chain/evidence/diff-components.json +34 -0
- package/templates/example-chain/evidence/direction/v2.json +74 -0
- package/templates/example-chain/evidence/direction-input.json +74 -0
- package/templates/example-chain/evidence/interview/assemble.json +309 -0
- package/templates/example-chain/evidence/interview/bones.json +190 -0
- package/templates/example-chain/evidence/interview/camera_depth.json +102 -0
- package/templates/example-chain/evidence/interview/constraints.json +272 -0
- package/templates/example-chain/evidence/interview/meshes.json +190 -0
- package/templates/example-chain/evidence/interview/motion.json +272 -0
- package/templates/example-chain/evidence/interview/post_composition.json +122 -0
- package/templates/example-chain/evidence/interview/post_timing.json +102 -0
- package/templates/example-chain/evidence/interview/reference.json +59 -0
- package/templates/example-chain/evidence/interview/rig_type.json +59 -0
- package/templates/example-chain/evidence/interview/seethrough.json +186 -0
- package/templates/example-chain/evidence/parts-check.json +127 -0
- package/templates/example-chain/evidence/parts-review.json +88 -0
- package/templates/example-chain/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
- package/templates/example-chain/evidence/scene/s1.json +65 -0
- package/templates/example-chain/evidence/scene-citations.json +9 -0
- package/templates/example-chain/evidence/setup.png +0 -0
- package/templates/example-chain/evidence/split-config.json +1 -0
- package/templates/example-chain/evidence/structure.json +30 -0
- package/templates/example-chain/evidence/v1/config.json +88 -0
- package/templates/example-chain/evidence/v1/interview-requirements.json +8 -0
- package/templates/example-chain/inputs/background.png +0 -0
- package/templates/example-chain/inputs/painting.png +0 -0
- package/templates/example-chain/inputs/references/.gitkeep +0 -0
- package/templates/example-chain/package.json +9 -0
- package/templates/example-chain/project.json +2593 -0
- package/templates/example-diff/diff-components.json +71 -0
- package/templates/example-diff/recomposite_rig.png +0 -0
- package/templates/example-direction/approval.txt +1 -0
- package/templates/example-direction/derived/review/v1/out/check/build/rectangle.png +0 -0
- package/templates/example-direction/derived/review/v1/out/check/build/run.json +40 -0
- package/templates/example-direction/derived/review/v1/out/check/build/skeleton.atlas +8 -0
- package/templates/example-direction/derived/review/v1/out/check/build/skeleton.json +73 -0
- package/templates/example-direction/derived/review/v1/out/check/build/skeleton.model.json +195 -0
- package/templates/example-direction/evidence/direction/v2.json +69 -0
- package/templates/example-direction/evidence/direction/v3.json +59 -0
- package/templates/example-direction/evidence/images/rectangle.png +0 -0
- package/templates/example-direction/evidence/input.json +69 -0
- package/templates/example-direction/evidence/motion.json +33 -0
- package/templates/example-direction/evidence/patch.json +23 -0
- package/templates/example-direction/evidence/rig-config.json +55 -0
- package/templates/example-direction/evidence/still.png +0 -0
- package/templates/example-direction/project.json +372 -0
- package/templates/example-direction/reasoning.txt +1 -0
- package/templates/example-direction/words.txt +1 -0
- package/templates/example-draft/README.md +28 -0
- package/templates/example-draft/config.json +71 -0
- package/templates/example-draft/derived/drafts/lantern/config.json +71 -0
- package/templates/example-draft/derived/drafts/lantern/input/painting_in_rig.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/input/parts/background.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/input/parts/eye_l.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/input/parts/eye_r.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/input/parts/face.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/input/parts/lantern.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/input/parts/torso.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/input/parts.json +188 -0
- package/templates/example-draft/derived/drafts/lantern/input/recomposite_rig.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/interview-requirements.json +8 -0
- package/templates/example-draft/derived/drafts/lantern/out/check/build/run.json +40 -0
- package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.atlas +28 -0
- package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.json +193 -0
- package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.model.json +483 -0
- package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/out/images/background.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/out/images/eye_l.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/out/images/eye_r.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/out/images/face.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/out/images/lantern.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/out/images/torso.png +0 -0
- package/templates/example-draft/derived/drafts/lantern/out/mesh_report.json +1 -0
- package/templates/example-draft/derived/drafts/lantern/out/motion.json +137 -0
- package/templates/example-draft/derived/drafts/lantern/out/rig.json +102 -0
- package/templates/example-draft/derived/drafts/lantern/out/run.json +52 -0
- package/templates/example-draft/derived/parts/rig/painting_in_rig.png +0 -0
- package/templates/example-draft/derived/parts/rig/parts/background.png +0 -0
- package/templates/example-draft/derived/parts/rig/parts/eye_l.png +0 -0
- package/templates/example-draft/derived/parts/rig/parts/eye_r.png +0 -0
- package/templates/example-draft/derived/parts/rig/parts/face.png +0 -0
- package/templates/example-draft/derived/parts/rig/parts/lantern.png +0 -0
- package/templates/example-draft/derived/parts/rig/parts/torso.png +0 -0
- package/templates/example-draft/derived/parts/rig/parts.json +188 -0
- package/templates/example-draft/derived/parts/rig/recomposite_rig.png +0 -0
- package/templates/example-draft/derived/review/v1/config.json +71 -0
- package/templates/example-draft/derived/review/v1/interview-requirements.json +8 -0
- package/templates/example-draft/derived/review/v1/out/check/build/run.json +40 -0
- package/templates/example-draft/derived/review/v1/out/check/build/skeleton.atlas +28 -0
- package/templates/example-draft/derived/review/v1/out/check/build/skeleton.json +193 -0
- package/templates/example-draft/derived/review/v1/out/check/build/skeleton.model.json +483 -0
- package/templates/example-draft/derived/review/v1/out/check/build/skeleton.png +0 -0
- package/templates/example-draft/derived/review/v1/out/parts/background.png +0 -0
- package/templates/example-draft/derived/review/v1/out/parts/eye_l.png +0 -0
- package/templates/example-draft/derived/review/v1/out/parts/eye_r.png +0 -0
- package/templates/example-draft/derived/review/v1/out/parts/face.png +0 -0
- package/templates/example-draft/derived/review/v1/out/parts/lantern.png +0 -0
- package/templates/example-draft/derived/review/v1/out/parts/torso.png +0 -0
- package/templates/example-draft/derived/review/v1/out/parts.json +188 -0
- package/templates/example-draft/derived/review/v1/out/rig/images/background.png +0 -0
- package/templates/example-draft/derived/review/v1/out/rig/images/eye_l.png +0 -0
- package/templates/example-draft/derived/review/v1/out/rig/images/eye_r.png +0 -0
- package/templates/example-draft/derived/review/v1/out/rig/images/face.png +0 -0
- package/templates/example-draft/derived/review/v1/out/rig/images/lantern.png +0 -0
- package/templates/example-draft/derived/review/v1/out/rig/images/torso.png +0 -0
- package/templates/example-draft/derived/review/v1/out/rig/mesh_report.json +1 -0
- package/templates/example-draft/derived/review/v1/out/rig/motion.json +137 -0
- package/templates/example-draft/derived/review/v1/out/rig/rig.json +102 -0
- package/templates/example-draft/derived/review/v1/out/rig/run.json +52 -0
- package/templates/example-draft/derived/review/v2/config.json +71 -0
- package/templates/example-draft/derived/review/v2/interview-requirements.json +8 -0
- package/templates/example-draft/derived/review/v2/out/check/build/run.json +40 -0
- package/templates/example-draft/derived/review/v2/out/check/build/skeleton.atlas +28 -0
- package/templates/example-draft/derived/review/v2/out/check/build/skeleton.json +193 -0
- package/templates/example-draft/derived/review/v2/out/check/build/skeleton.model.json +483 -0
- package/templates/example-draft/derived/review/v2/out/check/build/skeleton.png +0 -0
- package/templates/example-draft/derived/review/v2/out/parts/background.png +0 -0
- package/templates/example-draft/derived/review/v2/out/parts/eye_l.png +0 -0
- package/templates/example-draft/derived/review/v2/out/parts/eye_r.png +0 -0
- package/templates/example-draft/derived/review/v2/out/parts/face.png +0 -0
- package/templates/example-draft/derived/review/v2/out/parts/lantern.png +0 -0
- package/templates/example-draft/derived/review/v2/out/parts/torso.png +0 -0
- package/templates/example-draft/derived/review/v2/out/parts.json +188 -0
- package/templates/example-draft/derived/review/v2/out/rig/images/background.png +0 -0
- package/templates/example-draft/derived/review/v2/out/rig/images/eye_l.png +0 -0
- package/templates/example-draft/derived/review/v2/out/rig/images/eye_r.png +0 -0
- package/templates/example-draft/derived/review/v2/out/rig/images/face.png +0 -0
- package/templates/example-draft/derived/review/v2/out/rig/images/lantern.png +0 -0
- package/templates/example-draft/derived/review/v2/out/rig/images/torso.png +0 -0
- package/templates/example-draft/derived/review/v2/out/rig/mesh_report.json +1 -0
- package/templates/example-draft/derived/review/v2/out/rig/motion.json +137 -0
- package/templates/example-draft/derived/review/v2/out/rig/rig.json +102 -0
- package/templates/example-draft/derived/review/v2/out/rig/run.json +52 -0
- package/templates/example-draft/evidence/diff-components.json +71 -0
- package/templates/example-draft/evidence/direction/v4.json +64 -0
- package/templates/example-draft/evidence/direction-input-v4.json +64 -0
- package/templates/example-draft/evidence/fix-ledger/lantern.json +17 -0
- package/templates/example-draft/evidence/parts-check.json +127 -0
- package/templates/example-draft/evidence/parts-review.json +88 -0
- package/templates/example-draft/evidence/review-reads/4ed0507381809746443021554e012032be5b5f8417da0f2e525e9b6df16e9f05.json +59 -0
- package/templates/example-draft/evidence/scene/s1.json +65 -0
- package/templates/example-draft/evidence/scene-background.png +0 -0
- package/templates/example-draft/evidence/scene-citations.json +9 -0
- package/templates/example-draft/evidence/structure-decisions.json +9 -0
- package/templates/example-draft/evidence/v1/interview-config.json +71 -0
- package/templates/example-draft/evidence/v1/interview-requirements.json +8 -0
- package/templates/example-draft/evidence/v1/review-config.json +71 -0
- package/templates/example-draft/evidence/v2/interview-config.json +71 -0
- package/templates/example-draft/evidence/v2/interview-requirements.json +8 -0
- package/templates/example-draft/evidence/v2/review-config.json +71 -0
- package/templates/example-draft/input/painting_in_rig.png +0 -0
- package/templates/example-draft/input/parts/background.png +0 -0
- package/templates/example-draft/input/parts/eye_l.png +0 -0
- package/templates/example-draft/input/parts/eye_r.png +0 -0
- package/templates/example-draft/input/parts/face.png +0 -0
- package/templates/example-draft/input/parts/lantern.png +0 -0
- package/templates/example-draft/input/parts/torso.png +0 -0
- package/templates/example-draft/input/parts.json +188 -0
- package/templates/example-draft/input/recomposite_rig.png +0 -0
- package/templates/example-draft/inputs/painting.png +0 -0
- package/templates/example-draft/interview-requirements.json +8 -0
- package/templates/example-draft/out/check/build/run.json +40 -0
- package/templates/example-draft/out/check/build/skeleton.atlas +28 -0
- package/templates/example-draft/out/check/build/skeleton.json +193 -0
- package/templates/example-draft/out/check/build/skeleton.model.json +483 -0
- package/templates/example-draft/out/check/build/skeleton.png +0 -0
- package/templates/example-draft/out/images/background.png +0 -0
- package/templates/example-draft/out/images/eye_l.png +0 -0
- package/templates/example-draft/out/images/eye_r.png +0 -0
- package/templates/example-draft/out/images/face.png +0 -0
- package/templates/example-draft/out/images/lantern.png +0 -0
- package/templates/example-draft/out/images/torso.png +0 -0
- package/templates/example-draft/out/mesh_report.json +1 -0
- package/templates/example-draft/out/motion.json +137 -0
- package/templates/example-draft/out/rig.json +102 -0
- package/templates/example-draft/out/run.json +52 -0
- package/templates/example-draft/outputs/v1/out/check/build/run.json +40 -0
- package/templates/example-draft/outputs/v1/out/check/build/skeleton.atlas +28 -0
- package/templates/example-draft/outputs/v1/out/check/build/skeleton.json +193 -0
- package/templates/example-draft/outputs/v1/out/check/build/skeleton.model.json +483 -0
- package/templates/example-draft/outputs/v1/out/check/build/skeleton.png +0 -0
- package/templates/example-draft/outputs/v1/out/painting_in_rig.png +0 -0
- package/templates/example-draft/outputs/v1/out/parts/background.png +0 -0
- package/templates/example-draft/outputs/v1/out/parts/eye_l.png +0 -0
- package/templates/example-draft/outputs/v1/out/parts/eye_r.png +0 -0
- package/templates/example-draft/outputs/v1/out/parts/face.png +0 -0
- package/templates/example-draft/outputs/v1/out/parts/lantern.png +0 -0
- package/templates/example-draft/outputs/v1/out/parts/torso.png +0 -0
- package/templates/example-draft/outputs/v1/out/parts.json +188 -0
- package/templates/example-draft/outputs/v1/out/rig/images/background.png +0 -0
- package/templates/example-draft/outputs/v1/out/rig/images/eye_l.png +0 -0
- package/templates/example-draft/outputs/v1/out/rig/images/eye_r.png +0 -0
- package/templates/example-draft/outputs/v1/out/rig/images/face.png +0 -0
- package/templates/example-draft/outputs/v1/out/rig/images/lantern.png +0 -0
- package/templates/example-draft/outputs/v1/out/rig/images/torso.png +0 -0
- package/templates/example-draft/outputs/v1/out/rig/mesh_report.json +1 -0
- package/templates/example-draft/outputs/v1/out/rig/motion.json +137 -0
- package/templates/example-draft/outputs/v1/out/rig/rig.json +102 -0
- package/templates/example-draft/outputs/v1/out/rig/run.json +52 -0
- package/templates/example-draft/outputs/v2/out/check/build/run.json +40 -0
- package/templates/example-draft/outputs/v2/out/check/build/skeleton.atlas +28 -0
- package/templates/example-draft/outputs/v2/out/check/build/skeleton.json +193 -0
- package/templates/example-draft/outputs/v2/out/check/build/skeleton.model.json +483 -0
- package/templates/example-draft/outputs/v2/out/check/build/skeleton.png +0 -0
- package/templates/example-draft/outputs/v2/out/painting_in_rig.png +0 -0
- package/templates/example-draft/outputs/v2/out/parts/background.png +0 -0
- package/templates/example-draft/outputs/v2/out/parts/eye_l.png +0 -0
- package/templates/example-draft/outputs/v2/out/parts/eye_r.png +0 -0
- package/templates/example-draft/outputs/v2/out/parts/face.png +0 -0
- package/templates/example-draft/outputs/v2/out/parts/lantern.png +0 -0
- package/templates/example-draft/outputs/v2/out/parts/torso.png +0 -0
- package/templates/example-draft/outputs/v2/out/parts.json +188 -0
- package/templates/example-draft/outputs/v2/out/rig/images/background.png +0 -0
- package/templates/example-draft/outputs/v2/out/rig/images/eye_l.png +0 -0
- package/templates/example-draft/outputs/v2/out/rig/images/eye_r.png +0 -0
- package/templates/example-draft/outputs/v2/out/rig/images/face.png +0 -0
- package/templates/example-draft/outputs/v2/out/rig/images/lantern.png +0 -0
- package/templates/example-draft/outputs/v2/out/rig/images/torso.png +0 -0
- package/templates/example-draft/outputs/v2/out/rig/mesh_report.json +1 -0
- package/templates/example-draft/outputs/v2/out/rig/motion.json +137 -0
- package/templates/example-draft/outputs/v2/out/rig/rig.json +102 -0
- package/templates/example-draft/outputs/v2/out/rig/run.json +52 -0
- package/templates/example-interview-choice.json +1068 -0
- package/templates/example-interview-options/constraint.json +376 -0
- package/templates/example-interview-options/example-assemble.json +376 -0
- package/templates/example-interview-options/example-bones.json +190 -0
- package/templates/example-interview-options/example-camera-depth.json +99 -0
- package/templates/example-interview-options/example-constraints.json +272 -0
- package/templates/example-interview-options/example-meshes.json +190 -0
- package/templates/example-interview-options/example-motion.json +272 -0
- package/templates/example-interview-options/example-picks.json +15 -0
- package/templates/example-interview-options/example-post-composition.json +110 -0
- package/templates/example-interview-options/example-post-timing.json +105 -0
- package/templates/example-interview-options/example-reference.json +63 -0
- package/templates/example-interview-options/example-rig-type.json +63 -0
- package/templates/example-interview-options/example-seethrough.json +190 -0
- package/templates/example-interview-options/fullscreen.json +63 -0
- package/templates/example-interview-options/other-explicit.json +63 -0
- package/templates/example-interview-options/other-pending.json +63 -0
- package/templates/example-interview-options/other-picks.json +16 -0
- package/templates/example-interview.md +213 -0
- package/templates/example-invariants/invariants.json +1339 -0
- package/templates/example-prepared/prepared.svg +1 -0
- package/templates/example-prepared/source.svg +1 -0
- package/templates/example-project.json +4259 -0
- package/templates/example-remesh/AGENTS.md +134 -0
- package/templates/example-remesh/CLAUDE.md +3 -0
- package/templates/example-remesh/evidence/.gitkeep +0 -0
- package/templates/example-remesh/evidence/approval.txt +1 -0
- package/templates/example-remesh/evidence/captions.json +101 -0
- package/templates/example-remesh/evidence/diff-components.json +34 -0
- package/templates/example-remesh/evidence/direction/v4.json +74 -0
- package/templates/example-remesh/evidence/direction/v5.json +74 -0
- package/templates/example-remesh/evidence/direction-input-v4.json +74 -0
- package/templates/example-remesh/evidence/direction-input-v5.json +74 -0
- package/templates/example-remesh/evidence/interview/assemble.json +309 -0
- package/templates/example-remesh/evidence/interview/bones.json +190 -0
- package/templates/example-remesh/evidence/interview/camera_depth.json +103 -0
- package/templates/example-remesh/evidence/interview/constraints.json +272 -0
- package/templates/example-remesh/evidence/interview/meshes.json +190 -0
- package/templates/example-remesh/evidence/interview/motion.json +272 -0
- package/templates/example-remesh/evidence/interview/post_composition.json +123 -0
- package/templates/example-remesh/evidence/interview/post_timing.json +103 -0
- package/templates/example-remesh/evidence/interview/reference.json +59 -0
- package/templates/example-remesh/evidence/interview/rig_type.json +59 -0
- package/templates/example-remesh/evidence/interview/seethrough.json +186 -0
- package/templates/example-remesh/evidence/parts-check.json +127 -0
- package/templates/example-remesh/evidence/parts-review.json +88 -0
- package/templates/example-remesh/evidence/reopen.txt +1 -0
- package/templates/example-remesh/evidence/review-reads/6a17db694db96f1b6049a61a017e0c8e66757b982b8851e0187e561cfcf9af68.json +59 -0
- package/templates/example-remesh/evidence/scene/s1.json +69 -0
- package/templates/example-remesh/evidence/scene-citations.json +9 -0
- package/templates/example-remesh/evidence/setup.png +0 -0
- package/templates/example-remesh/evidence/split-config.json +1 -0
- package/templates/example-remesh/evidence/structure.json +30 -0
- package/templates/example-remesh/evidence/v1/registration/at-source.json +44 -0
- package/templates/example-remesh/evidence/v1/registration/citations.json +68 -0
- package/templates/example-remesh/evidence/v1/registration/compile-report.json +368 -0
- package/templates/example-remesh/evidence/v1/registration/config-source.json +88 -0
- package/templates/example-remesh/evidence/v1/registration/config.json +88 -0
- package/templates/example-remesh/evidence/v1/registration/interview-requirements.json +8 -0
- package/templates/example-remesh/evidence/v1/registration/package-lock.json +11 -0
- package/templates/example-remesh/evidence/v1/registration/path-adapter.json +1 -0
- package/templates/example-remesh/evidence/v1/registration/rigc-run-1.json +44 -0
- package/templates/example-remesh/evidence/v2/registration/at-source.json +44 -0
- package/templates/example-remesh/evidence/v2/registration/citations.json +68 -0
- package/templates/example-remesh/evidence/v2/registration/compile-report.json +368 -0
- package/templates/example-remesh/evidence/v2/registration/config-source.json +88 -0
- package/templates/example-remesh/evidence/v2/registration/config.json +88 -0
- package/templates/example-remesh/evidence/v2/registration/interview-requirements.json +8 -0
- package/templates/example-remesh/evidence/v2/registration/package-lock.json +11 -0
- package/templates/example-remesh/evidence/v2/registration/path-adapter.json +1 -0
- package/templates/example-remesh/evidence/v2/registration/rigc-run-1.json +44 -0
- package/templates/example-remesh/evidence/v3/registration/at-source.json +44 -0
- package/templates/example-remesh/evidence/v3/registration/citations.json +68 -0
- package/templates/example-remesh/evidence/v3/registration/compile-report.json +368 -0
- package/templates/example-remesh/evidence/v3/registration/config-source.json +88 -0
- package/templates/example-remesh/evidence/v3/registration/config.json +88 -0
- package/templates/example-remesh/evidence/v3/registration/interview-requirements.json +8 -0
- package/templates/example-remesh/evidence/v3/registration/package-lock.json +11 -0
- package/templates/example-remesh/evidence/v3/registration/path-adapter.json +1 -0
- package/templates/example-remesh/evidence/v3/registration/rigc-run-1.json +44 -0
- package/templates/example-remesh/inputs/background.png +0 -0
- package/templates/example-remesh/inputs/painting.png +0 -0
- package/templates/example-remesh/inputs/references/.gitkeep +0 -0
- package/templates/example-remesh/package.json +9 -0
- package/templates/example-remesh/project.json +3916 -0
- package/templates/example-reopened/project.json +4568 -0
- package/templates/example-review/config.json +1 -0
- package/templates/example-review/out/check/build/skeleton.model.json +1 -0
- package/templates/example-review/out/parts/piece.svg +1 -0
- package/templates/example-review/out/parts.json +1 -0
- package/templates/example-review/out/rig/mesh_report.json +1 -0
- package/templates/example-review/out/rig/motion.json +1 -0
- package/templates/example-review/out/rig/rig.json +1 -0
- package/templates/example-split/captions.json +357 -0
- package/templates/example-split/config.json +215 -0
- package/templates/legacy-example-project.json +1071 -0
- package/templates/project.json +199 -0
- package/templates/shared/conventions.json +23 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import { recordedPartsVerdict, recordedPartsFinding } from './parts-verdict-record.ts';
|
|
2
|
+
import { configInputReferences, configInputsLegacy, DIRECTORY_INPUT_RECEIPT, plateReceiptSpec, receiptSpec, type ConfigInputsWarning } from './config-inputs.ts';
|
|
3
|
+
import { DirectionError, objects as directionObjects, readDirection } from './direction.ts';
|
|
4
|
+
/** Frozen producer inputs in review's fixed discovery layout (create#38). */
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { closeSync, openSync, mkdirSync, mkdtempSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from './check-reads.ts';
|
|
8
|
+
import { dirname, isAbsolute, join, posix, relative, resolve, sep } from 'node:path';
|
|
9
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
10
|
+
|
|
11
|
+
import { asArray, isObject, readJsonObject, type Ctx } from './check.ts';
|
|
12
|
+
import { draftFileUnavailable, draftVersionFindings } from './drafts.ts';
|
|
13
|
+
import { Refusal, refuse, USAGE_EXIT } from './refusal.ts';
|
|
14
|
+
import { assertRigVersionDate, rigVersionDateFinding } from './rig-version-date.ts';
|
|
15
|
+
import { latestPartsApproval, retainedJson } from './structure.ts';
|
|
16
|
+
import type { Json, JsonObject } from './templates.ts';
|
|
17
|
+
|
|
18
|
+
const hash = (bytes: Buffer): string => createHash('sha256').update(bytes).digest('hex');
|
|
19
|
+
export const BUILD_IDENTITY_SINCE = '2026-11-06T00:00:00Z';
|
|
20
|
+
export const PART_FILE_IDENTITY_SINCE = '2026-11-06T00:00:00Z';
|
|
21
|
+
|
|
22
|
+
/** Compare declared identities even when regenerable package bytes are absent. */
|
|
23
|
+
function checkBuildIdentity(ctx: Pick<Ctx, 'dir' | 'project' | 'refusals' | 'warnings'>, version: JsonObject, record: JsonObject): void {
|
|
24
|
+
const at = Date.parse(String(version.at));
|
|
25
|
+
const findings = Number.isFinite(at) && at >= Date.parse(BUILD_IDENTITY_SINCE) ? ctx.refusals : ctx.warnings;
|
|
26
|
+
const path = `versions[v${String(version.n)}].review_package`;
|
|
27
|
+
const mismatch = (code: string, field: string, expected: string, actual: string): void => {
|
|
28
|
+
findings.push({ code, path: `${path}.${field}`, message: `${path}.${field}: package sha256 ${actual} differs from recorded build sha256 ${expected}; preserve the historical package and record a new version from the same config and accepted parts` });
|
|
29
|
+
};
|
|
30
|
+
const provenance = isObject(version.provenance) ? version.provenance : {};
|
|
31
|
+
const config = isObject(provenance.config) ? provenance.config : {};
|
|
32
|
+
if (typeof config.sha256 === 'string' && isObject(record.config) && typeof record.config.sha256 === 'string' && config.sha256 !== record.config.sha256) {
|
|
33
|
+
mismatch('REVIEW_PACKAGE_CONFIG_MISMATCH', 'config', config.sha256, record.config.sha256);
|
|
34
|
+
}
|
|
35
|
+
const approval = latestPartsApproval(ctx.project, Number.isFinite(at) ? at : Infinity);
|
|
36
|
+
if (!approval) return;
|
|
37
|
+
const approvalPath = `signoffs[${asArray(ctx.project.signoffs).indexOf(approval)}]`;
|
|
38
|
+
const finding = recordedPartsFinding(ctx.dir, ctx.project, approval, approvalPath);
|
|
39
|
+
if (finding) {
|
|
40
|
+
if (finding.code === 'PARTS_VERDICT_FILE_LEGACY') {
|
|
41
|
+
if (!ctx.warnings.some(existing => existing.code === finding.code && existing.path === finding.path)) ctx.warnings.push(finding);
|
|
42
|
+
} else findings.push({ code: 'REVIEW_PACKAGE_PARTS_UNREAD', path: `${path}.files[out/parts.json]`,
|
|
43
|
+
message: `${path}.files[out/parts.json]: accepted parts receipt is unavailable (${finding.code}); restore its recorded API response and selected draft before packaging this version` });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
recordedPartsVerdict(ctx.dir, ctx.project, approval.draft_id, approval.evidence, { requireAvailable: false });
|
|
47
|
+
const draft = asArray(ctx.project.drafts).find(draft => isObject(draft) && draft.id === approval.draft_id) as JsonObject;
|
|
48
|
+
const parts = draft.parts as JsonObject;
|
|
49
|
+
const files = asArray(parts.files).filter(isObject);
|
|
50
|
+
const manifest = files.find(file => file.path === `${parts.path}/parts.json`);
|
|
51
|
+
const packaged = asArray(record.files).find(f => isObject(f) && f.path === 'out/parts.json');
|
|
52
|
+
if (isObject(manifest) && typeof manifest.sha256 === 'string' && isObject(packaged) && typeof packaged.sha256 === 'string' && manifest.sha256 !== packaged.sha256) {
|
|
53
|
+
mismatch('REVIEW_PACKAGE_PARTS_MISMATCH', 'files[out/parts.json]', manifest.sha256, packaged.sha256);
|
|
54
|
+
}
|
|
55
|
+
if (!isObject(manifest) || typeof manifest.path !== 'string') return;
|
|
56
|
+
const legacy = !Number.isFinite(at) || at < Date.parse(PART_FILE_IDENTITY_SINCE);
|
|
57
|
+
if (draftFileUnavailable(ctx.dir, manifest.path, `derived/drafts/${String(draft.id)}`)) return;
|
|
58
|
+
const declaration = retainedJson(ctx.dir, manifest);
|
|
59
|
+
const painting = isObject(declaration.painting_in_rig) && typeof declaration.painting_in_rig.path === 'string'
|
|
60
|
+
? posix.join(String(parts.path), declaration.painting_in_rig.path) : undefined;
|
|
61
|
+
for (const image of files) {
|
|
62
|
+
if (!isObject(image) || typeof image.path !== 'string' || typeof image.sha256 !== 'string' || image === manifest || image.path === painting) continue;
|
|
63
|
+
const name = posix.join('out', posix.relative(posix.dirname(manifest.path), image.path));
|
|
64
|
+
const file = asArray(record.files).find(f => isObject(f) && f.path === name);
|
|
65
|
+
const actual = isObject(file) && typeof file.sha256 === 'string' ? file.sha256 : 'missing';
|
|
66
|
+
if (actual === image.sha256) continue;
|
|
67
|
+
const field = `${path}.files[${name}]`;
|
|
68
|
+
(legacy ? ctx.warnings : ctx.refusals).push({
|
|
69
|
+
code: legacy ? 'REVIEW_PACKAGE_PART_FILE_LEGACY' : 'REVIEW_PACKAGE_PART_FILE_MISMATCH', path: field,
|
|
70
|
+
message: `${field}: package sha256 ${actual} differs from accepted part ${image.path} sha256 ${image.sha256}; preserve the package and record a new version from the reviewed part files`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const word = (s: string): string => `'${s.replaceAll("'", "'\\''")}'`;
|
|
75
|
+
const packagePath = (n: number): string => `derived/review/v${n}`;
|
|
76
|
+
const openCommand = (n: number): string => `rig-review open ${word(`${packagePath(n)}/out`)}`;
|
|
77
|
+
const required = ['parts.json', 'parts', 'rig', 'check/build'] as const;
|
|
78
|
+
const inside = (root: string, file: string): boolean => {
|
|
79
|
+
const rel = relative(root, file);
|
|
80
|
+
return rel !== '' && rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
interface FileHash { path: string; sha256: string }
|
|
84
|
+
|
|
85
|
+
/** Named files are separate from producer outputs and required by their reader's date. */
|
|
86
|
+
export function reviewConfigInputs(record: JsonObject, dir: string, n: number, missing?: (path: string) => void, unavailable?: ConfigInputsWarning, pkgDir = join(dir, packagePath(n))): JsonObject[] {
|
|
87
|
+
const path = join(pkgDir, 'config.json');
|
|
88
|
+
if (!existsSync(path)) return [];
|
|
89
|
+
const config = JSON.parse(readFileSync(path, 'utf8'));
|
|
90
|
+
const refs = configInputReferences(dir, config, unavailable);
|
|
91
|
+
const declared = new Set(refs.flatMap(ref => {
|
|
92
|
+
const name = relative(dirname(path), resolve(dirname(path), ref.value)).split(sep).join('/');
|
|
93
|
+
return [name, `${name}/${DIRECTORY_INPUT_RECEIPT}`];
|
|
94
|
+
}));
|
|
95
|
+
if (isObject(config) && typeof config.requirements === 'string') {
|
|
96
|
+
const reqFile = resolve(dirname(path), config.requirements);
|
|
97
|
+
if (existsSync(reqFile) && lstatSync(reqFile).isFile()) {
|
|
98
|
+
try {
|
|
99
|
+
const doc = JSON.parse(readFileSync(reqFile, 'utf8'));
|
|
100
|
+
const setup = isObject(doc) && isObject(doc.setup_painting) ? doc.setup_painting : {};
|
|
101
|
+
for (const key of ['figure_mask', 'backdrop'] as const) {
|
|
102
|
+
const entry = isObject(setup[key]) ? setup[key] : undefined;
|
|
103
|
+
if (typeof entry?.path === 'string' && entry.path.trim()) {
|
|
104
|
+
declared.add(relative(dirname(path), resolve(dirname(reqFile), entry.path)).split(sep).join('/'));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} catch { /* Handled by figure-mask checks. */ }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return refs.flatMap(ref => {
|
|
111
|
+
const name = relative(dirname(path), resolve(dirname(path), ref.value)).split(sep).join('/');
|
|
112
|
+
const file = asArray(record.files).find(f => isObject(f) && f.path === name);
|
|
113
|
+
if (isObject(file)) return [file];
|
|
114
|
+
const bound = `${name}/${DIRECTORY_INPUT_RECEIPT}`;
|
|
115
|
+
const receipt = asArray(record.files).find(f => isObject(f) && f.path === bound);
|
|
116
|
+
if (isObject(receipt)) {
|
|
117
|
+
if (asArray(record.files).some(f => isObject(f) && typeof f.path === 'string' && f.path.startsWith(`${name}/`) && f.path !== bound && !declared.has(f.path))) {
|
|
118
|
+
throw new Error(`config.${ref.at}: directory input ${name} must bind only ${bound}`);
|
|
119
|
+
}
|
|
120
|
+
const spec = plateReceiptSpec(dir);
|
|
121
|
+
const receiptFile = join(dirname(path), bound);
|
|
122
|
+
if (existsSync(receiptFile)) {
|
|
123
|
+
const declaredSpec = receiptSpec(readFileSync(receiptFile));
|
|
124
|
+
if (declaredSpec !== spec) throw new Error(`config.${ref.at}: ${bound} declares spec ${JSON.stringify(declaredSpec)}; a directory input binds a ${spec} receipt, such as a comfy plate --mode occluder output`);
|
|
125
|
+
}
|
|
126
|
+
return [receipt];
|
|
127
|
+
}
|
|
128
|
+
if (!missing) throw new Error(`config.${ref.at}: named input ${name} is missing from source package`);
|
|
129
|
+
missing(name);
|
|
130
|
+
return [{ path: name, sha256: null }];
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
export function reviewHandoffInputs(record: JsonObject, dir: string, n: number, named = reviewConfigInputs(record, dir, n)): JsonObject[] {
|
|
134
|
+
const base = asArray(record.files).filter((file): file is JsonObject => isObject(file) && typeof file.path === 'string'
|
|
135
|
+
&& (file.path === 'config.json' || file.path.startsWith('out/') && !file.path.startsWith('out/check/')));
|
|
136
|
+
return [...base, ...named.filter(file => !base.some(f => f.path === file.path))];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function packageExists(path: string): boolean {
|
|
140
|
+
try { lstatSync(path); return true; } catch (error) {
|
|
141
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false;
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Check each existing ancestor before creating the next directory. */
|
|
147
|
+
function makePackageParent(dir: string): void {
|
|
148
|
+
let parent = dir;
|
|
149
|
+
for (const name of ['derived', 'review']) {
|
|
150
|
+
parent = join(parent, name);
|
|
151
|
+
if (!packageExists(parent)) mkdirSync(parent);
|
|
152
|
+
if (!lstatSync(parent).isDirectory()) refuse('REVIEW_PACKAGE_PATH_INVALID', 'derived/review requires real directories without symlinks');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function validFiles(value: JsonObject['files']): boolean {
|
|
157
|
+
if (!Array.isArray(value) || value.length === 0) return false;
|
|
158
|
+
let previous = '';
|
|
159
|
+
for (const entry of value) {
|
|
160
|
+
if (!isObject(entry) || typeof entry.path !== 'string' || typeof entry.sha256 !== 'string'
|
|
161
|
+
|| !/^[a-f0-9]{64}$/.test(entry.sha256) || entry.path <= previous
|
|
162
|
+
|| isAbsolute(entry.path) || entry.path.split('/').some((part) => part === '..' || part === '.' || part === '')) return false;
|
|
163
|
+
previous = entry.path;
|
|
164
|
+
}
|
|
165
|
+
return value.some((entry) => isObject(entry) && entry.path === 'config.json');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function sourceFiles(out: string, imagePaths: readonly string[]): FileHash[] {
|
|
169
|
+
const files = required.flatMap((entry) => entry === 'parts.json'
|
|
170
|
+
? [{ path: entry, sha256: hash(readFileSync(join(out, entry))) }]
|
|
171
|
+
: filesAt(join(out, entry)).map((file) => ({ ...file, path: `${entry}/${file.path}` })));
|
|
172
|
+
const seen = new Set(files.map(file => file.path));
|
|
173
|
+
for (const path of imagePaths) {
|
|
174
|
+
const copiedTree = required.some(entry => entry !== 'parts.json' && path.startsWith(`${entry}/`));
|
|
175
|
+
if (seen.has(path) || copiedTree) continue;
|
|
176
|
+
const absolute = resolve(out, path);
|
|
177
|
+
if (!inside(out, absolute) || path.split('/').some(segment => !segment || segment === '.' || segment === '..')
|
|
178
|
+
|| realpathSync(absolute) !== absolute || !lstatSync(absolute).isFile()) refuse('REVIEW_PACKAGE_PATH_INVALID', `${path}: restore the retained regular image before packaging`);
|
|
179
|
+
files.push({ path, sha256: hash(readFileSync(absolute)) });
|
|
180
|
+
seen.add(path);
|
|
181
|
+
}
|
|
182
|
+
return files;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Reject symlinks throughout the copied tree, including otherwise-contained ones. */
|
|
186
|
+
function filesAt(root: string, prefix = ''): FileHash[] {
|
|
187
|
+
const files: FileHash[] = [];
|
|
188
|
+
for (const name of readdirSync(join(root, prefix)).sort()) {
|
|
189
|
+
const path = prefix ? `${prefix}/${name}` : name;
|
|
190
|
+
const info = lstatSync(join(root, path));
|
|
191
|
+
if (info.isDirectory()) files.push(...filesAt(root, path));
|
|
192
|
+
else if (info.isFile()) files.push({ path, sha256: hash(readFileSync(join(root, path))) });
|
|
193
|
+
else refuse('REVIEW_PACKAGE_INPUT_INVALID', `${path}: expected a regular file or directory, without symlinks`);
|
|
194
|
+
}
|
|
195
|
+
return files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function requireLayout(out: string): void {
|
|
199
|
+
for (const entry of required) {
|
|
200
|
+
const file = join(out, entry);
|
|
201
|
+
if (!existsSync(file)) refuse('REVIEW_PACKAGE_INPUT_MISSING', `${entry}: required in the declared build out directory`);
|
|
202
|
+
const stat = lstatSync(file);
|
|
203
|
+
if (entry === 'parts.json' ? !stat.isFile() : !stat.isDirectory()) refuse('REVIEW_PACKAGE_INPUT_INVALID', `${entry}: wrong file kind`);
|
|
204
|
+
}
|
|
205
|
+
if (!lstatSync(join(out, 'check')).isDirectory()) refuse('REVIEW_PACKAGE_INPUT_INVALID', 'check: expected a real directory');
|
|
206
|
+
for (const file of ['rig/rig.json', 'rig/motion.json', 'rig/mesh_report.json']) {
|
|
207
|
+
if (!existsSync(join(out, file)) || !lstatSync(join(out, file)).isFile()) refuse('REVIEW_PACKAGE_INPUT_MISSING', `${file}: required producer rig file`);
|
|
208
|
+
}
|
|
209
|
+
const model = join(out, 'check/build/skeleton.model.json');
|
|
210
|
+
if (!existsSync(model) || !lstatSync(model).isFile()) refuse('REVIEW_PACKAGE_INPUT_MISSING', 'check/build/skeleton.model.json: required compiled model');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function regeneration(n: number, record: JsonObject | null): string {
|
|
214
|
+
return `rig-create review-package ${n} --config ${word(isObject(record?.config) && typeof record.config.path === 'string' ? record.config.path : '<config>')} --from ${word(typeof record?.from === 'string' ? record.from : '<build out dir>')}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Hash the complete package, so added files cannot silently change discovery. */
|
|
218
|
+
export function checkReviewPackages(ctx: Pick<Ctx, 'dir' | 'project' | 'refusals' | 'warnings'>): void {
|
|
219
|
+
for (const version of asArray(ctx.project.versions)) {
|
|
220
|
+
if (!isObject(version) || version.kind !== 'rig') continue;
|
|
221
|
+
const dateFinding = rigVersionDateFinding(version);
|
|
222
|
+
if (dateFinding) ctx.refusals.push(dateFinding);
|
|
223
|
+
if (version.pre_interview === true) continue;
|
|
224
|
+
const path = `versions[v${String(version.n)}].review_package`;
|
|
225
|
+
const record = isObject(version.review_package) ? version.review_package : null;
|
|
226
|
+
if (!Number.isSafeInteger(version.n) || (version.n as number) < 1) continue;
|
|
227
|
+
const n = version.n as number;
|
|
228
|
+
const pkg = join(ctx.dir, packagePath(n));
|
|
229
|
+
if (version.review_package === undefined || version.review_package === null) {
|
|
230
|
+
ctx.warnings.push({ code: 'REVIEW_PACKAGE_MISSING', path, message: `${path}: no package recorded, so the figure mask has no verdict; write it with ${regeneration(n, null)}` });
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (record === null || !isObject(record.config) || typeof record.config.path !== 'string' || typeof record.config.sha256 !== 'string'
|
|
234
|
+
|| !/^[a-f0-9]{64}$/.test(record.config.sha256) || record.config.path.trim() === '' || isAbsolute(record.config.path) || !inside(ctx.dir, resolve(ctx.dir, record.config.path))
|
|
235
|
+
|| typeof record.from !== 'string' || record.from.trim() === '' || isAbsolute(record.from) || !inside(ctx.dir, resolve(ctx.dir, record.from)) || !validFiles(record.files)
|
|
236
|
+
|| record.open !== openCommand(n)) {
|
|
237
|
+
ctx.refusals.push({ code: 'REVIEW_PACKAGE_MALFORMED', path, message: `${path}: expected config {path, sha256}, from, sorted files {path, sha256} and the package open command` });
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
checkBuildIdentity(ctx, version, record);
|
|
241
|
+
if (!packageExists(pkg)) {
|
|
242
|
+
ctx.warnings.push({ code: 'REVIEW_PACKAGE_MISSING', path, message: `${path}: package absent, so the figure mask has no verdict; regenerate with ${regeneration(n, record)}` });
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
if (!inside(realpathSync(ctx.dir), realpathSync(pkg)) || lstatSync(pkg).isSymbolicLink()) throw new Error('package leaves its project or is a symlink');
|
|
247
|
+
requireLayout(join(pkg, 'out'));
|
|
248
|
+
const actual = filesAt(pkg);
|
|
249
|
+
if (!isDeepStrictEqual(actual, record.files) || hash(readFileSync(join(pkg, 'config.json'))) !== record.config.sha256) throw new Error('package files differ from recorded hashes');
|
|
250
|
+
} catch (error) {
|
|
251
|
+
ctx.refusals.push({ code: 'REVIEW_PACKAGE_CHANGED', path, message: `${path}: ${error instanceof Error ? error.message : String(error)}` });
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
// The project-level loader diagnostic already owns this unavailable dependency.
|
|
255
|
+
if (ctx.refusals.some(f => f.code === 'PARTS_SECTIONS_UNREADABLE' || f.code === 'PARTS_NOT_INSTALLED')) continue;
|
|
256
|
+
try {
|
|
257
|
+
const missing = (name: string): void => {
|
|
258
|
+
const finding = { code: 'REVIEW_PACKAGE_INPUT_MISSING', path, message: `${path}: config-named input ${name} is missing from the frozen package inventory; preserve the package and record the complete inputs on the next build` };
|
|
259
|
+
(configInputsLegacy(version.at) ? ctx.warnings : ctx.refusals).push(finding);
|
|
260
|
+
};
|
|
261
|
+
const unavailable = (error: Error): void => {
|
|
262
|
+
const finding = { code: 'REVIEW_PACKAGE_INPUT_INVALID', path, message: `${path}: ${error.message}` };
|
|
263
|
+
(configInputsLegacy(version.at) ? ctx.warnings : ctx.refusals).push(finding);
|
|
264
|
+
};
|
|
265
|
+
reviewConfigInputs(record, ctx.dir, n, missing, unavailable);
|
|
266
|
+
} catch (error) { ctx.refusals.push({ code: 'REVIEW_PACKAGE_INPUT_INVALID', path, message: `${path}: ${error instanceof Error ? error.message : String(error)}; restore the frozen package config` }); }
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export const REVIEW_PACKAGE_HELP = `review-package <n> [<project dir>] --config <path> --from <build out dir> [--json]
|
|
271
|
+
Copy frozen producer inputs into derived/review/v<n>; record hashes and the open command.
|
|
272
|
+
Source paths are relative to the project and must stay inside it. Run the open command there.`;
|
|
273
|
+
|
|
274
|
+
export function reviewPackageCommand(args: string[]): { stdout: string; exit: number } {
|
|
275
|
+
return packageCommand(args);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Prepare the normal package against a candidate, without publishing project.json. */
|
|
279
|
+
export function prepareRigReviewPackage(args: string[], project: JsonObject, imagePaths?: readonly string[]): void {
|
|
280
|
+
packageCommand(args, project, imagePaths);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function packageCommand(args: string[], candidate?: JsonObject, imagePaths?: readonly string[]): { stdout: string; exit: number } {
|
|
284
|
+
const positional: string[] = [];
|
|
285
|
+
const options: Record<string, string> = {};
|
|
286
|
+
let json = false;
|
|
287
|
+
for (let i = 0; i < args.length; i++) {
|
|
288
|
+
const arg = args[i] as string;
|
|
289
|
+
if (arg === '--json') json = true;
|
|
290
|
+
else if (arg === '--config' || arg === '--from') {
|
|
291
|
+
const value = args[++i];
|
|
292
|
+
if (!value || value.startsWith('--') || options[arg] !== undefined) refuse('REVIEW_PACKAGE_USAGE', `${arg} needs one value`, USAGE_EXIT);
|
|
293
|
+
options[arg] = value;
|
|
294
|
+
} else if (arg.startsWith('-')) refuse('UNKNOWN_OPTION', `review-package does not take ${arg}`, USAGE_EXIT);
|
|
295
|
+
else positional.push(arg);
|
|
296
|
+
}
|
|
297
|
+
const n = Number(positional[0]?.replace(/^v/, ''));
|
|
298
|
+
if (!Number.isSafeInteger(n) || n < 1 || positional.length > 2 || !options['--config'] || !options['--from']) refuse('REVIEW_PACKAGE_USAGE', REVIEW_PACKAGE_HELP, USAGE_EXIT);
|
|
299
|
+
const dir = realpathSync(resolve(positional[1] ?? process.cwd()));
|
|
300
|
+
const projectFile = join(dir, 'project.json');
|
|
301
|
+
if (!existsSync(projectFile)) refuse('PROJECT_JSON_MISSING', 'project.json is absent');
|
|
302
|
+
const lock = `${projectFile}.review-package.lock`;
|
|
303
|
+
let descriptor: number;
|
|
304
|
+
try { descriptor = openSync(lock, 'wx'); } catch { refuse('REVIEW_PACKAGE_BUSY', 'another package writer holds the project lock'); }
|
|
305
|
+
try {
|
|
306
|
+
const original = readFileSync(projectFile, 'utf8');
|
|
307
|
+
const read = readJsonObject(projectFile);
|
|
308
|
+
if (!read.ok) refuse('PROJECT_JSON_INVALID', read.error);
|
|
309
|
+
const project = candidate ?? read.value;
|
|
310
|
+
if (project.schema !== 'rig-create/project@0') refuse('SCHEMA_UNKNOWN', 'review-package reads rig-create/project@0');
|
|
311
|
+
const version = asArray(project.versions).find((v) => isObject(v) && v.n === n);
|
|
312
|
+
if (!isObject(version)) refuse('REVIEW_PACKAGE_VERSION_UNKNOWN', `no version ${n}`);
|
|
313
|
+
assertRigVersionDate(version);
|
|
314
|
+
const draftProblem = draftVersionFindings(dir, { ...project, versions: [version] })[0];
|
|
315
|
+
if (draftProblem !== undefined) refuse(draftProblem.code, draftProblem.message);
|
|
316
|
+
if (version.kind !== 'rig' || version.pre_interview === true) refuse('REVIEW_PACKAGE_VERSION_INVALID', 'review packages are for create-built rig versions');
|
|
317
|
+
const source = (value: string): string => {
|
|
318
|
+
const file = resolve(dir, value);
|
|
319
|
+
if (isAbsolute(value) || !inside(dir, file)) refuse('REVIEW_PACKAGE_PATH_INVALID', `${value}: expected a project-relative source`);
|
|
320
|
+
if (!existsSync(file)) refuse('REVIEW_PACKAGE_INPUT_MISSING', `${value}: source absent`);
|
|
321
|
+
const canonical = realpathSync(file);
|
|
322
|
+
if (!inside(dir, canonical)) refuse('REVIEW_PACKAGE_PATH_INVALID', `${value}: source resolves outside the project`);
|
|
323
|
+
return canonical;
|
|
324
|
+
};
|
|
325
|
+
const declaredDraft = draftVersionFindings(dir, { ...project, versions: [{ kind: 'rig', source_dir: options['--from'], config: { path: options['--config'] } }] })[0];
|
|
326
|
+
if (declaredDraft !== undefined) refuse(declaredDraft.code, declaredDraft.message);
|
|
327
|
+
const config = source(options['--config']);
|
|
328
|
+
const from = source(options['--from']);
|
|
329
|
+
if (!lstatSync(config).isFile() || !lstatSync(from).isDirectory()) refuse('REVIEW_PACKAGE_INPUT_INVALID', 'config must be a file and --from a directory');
|
|
330
|
+
requireLayout(from);
|
|
331
|
+
const configBytes = readFileSync(config);
|
|
332
|
+
try { if (!isObject(JSON.parse(configBytes.toString()))) throw new Error(); } catch { refuse('REVIEW_PACKAGE_INPUT_INVALID', 'config must be a JSON object'); }
|
|
333
|
+
let named: { path: string; source: string; bytes: Buffer }[];
|
|
334
|
+
const directories = new Set<string>();
|
|
335
|
+
try {
|
|
336
|
+
named = configInputReferences(dir, JSON.parse(configBytes.toString())).map(ref => {
|
|
337
|
+
const path = relative(dirname(config), resolve(dirname(config), ref.value)).split(sep).join('/');
|
|
338
|
+
if (isAbsolute(ref.value) || !path || path.split('/').some(part => part === '..' || part === '.' || part === '')) refuse('REVIEW_PACKAGE_PATH_INVALID', `config.${ref.at}: ${ref.value} cannot be retained inside the package; use a config-relative input inside its directory`);
|
|
339
|
+
const file = source(relative(dir, resolve(dirname(config), ref.value)));
|
|
340
|
+
if (lstatSync(file).isDirectory()) {
|
|
341
|
+
const receipt = join(file, DIRECTORY_INPUT_RECEIPT);
|
|
342
|
+
const bound = `${path}/${DIRECTORY_INPUT_RECEIPT}`;
|
|
343
|
+
if (!existsSync(receipt) || !lstatSync(receipt).isFile()) {
|
|
344
|
+
refuse('REVIEW_PACKAGE_INPUT_INVALID', `config.${ref.at}: named input directory ${path} holds no regular-file ${DIRECTORY_INPUT_RECEIPT}; a directory input binds its producer's receipt, such as a comfy plate --mode occluder output`);
|
|
345
|
+
}
|
|
346
|
+
const bytes = readFileSync(receipt);
|
|
347
|
+
const spec = plateReceiptSpec(dir);
|
|
348
|
+
const declared = receiptSpec(bytes);
|
|
349
|
+
if (declared !== spec) {
|
|
350
|
+
refuse('REVIEW_PACKAGE_INPUT_INVALID', `config.${ref.at}: ${bound} declares spec ${JSON.stringify(declared)}; a directory input binds a ${spec} receipt, such as a comfy plate --mode occluder output`);
|
|
351
|
+
}
|
|
352
|
+
directories.add(path);
|
|
353
|
+
return { path: bound, source: receipt, bytes };
|
|
354
|
+
}
|
|
355
|
+
if (!lstatSync(file).isFile()) refuse('REVIEW_PACKAGE_INPUT_INVALID', `config.${ref.at}: ${path} must be a regular file`);
|
|
356
|
+
return { path, source: file, bytes: readFileSync(file) };
|
|
357
|
+
});
|
|
358
|
+
} catch (error) { if (error instanceof Refusal) throw error; refuse('REVIEW_PACKAGE_INPUT_INVALID', `versions[v${n}].review_package.config: ${error instanceof Error ? error.message : String(error)}`); }
|
|
359
|
+
// Registered builds retain adapter-relocated runtime inputs as well as the
|
|
360
|
+
// loader's three config-file declarations. Preserve them on regeneration.
|
|
361
|
+
if (isObject(version.registration) && version.registration.kind === 'frozen-rig/1') {
|
|
362
|
+
const configDir = relative(dir, dirname(config)).split(sep).join('/');
|
|
363
|
+
const prefix = `${configDir}/`;
|
|
364
|
+
const runFiles = new Set(asArray(version.registration.runs).flatMap(r => isObject(r) && isObject(r.source) && typeof r.source.path === 'string' ? [r.source.path] : []));
|
|
365
|
+
const metadataNames = new Set(['at-source.json', 'compile-report.json', 'config-source.json', 'requirements-source.json', 'config.json', 'package-lock.json', 'path-adapter.json', 'citations.json']);
|
|
366
|
+
for (const binding of asArray(version.registration.files)) {
|
|
367
|
+
if (!isObject(binding) || typeof binding.path !== 'string' || !binding.path.startsWith(prefix)) continue;
|
|
368
|
+
const rel = binding.path.slice(prefix.length);
|
|
369
|
+
if (metadataNames.has(rel) || runFiles.has(binding.path)) continue;
|
|
370
|
+
if ([...directories].some(d => rel.startsWith(`${d}/`)) && !named.some(input => input.path === rel)) continue;
|
|
371
|
+
const file = source(binding.path);
|
|
372
|
+
const bytes = readFileSync(file);
|
|
373
|
+
if (hash(bytes) !== binding.sha256) refuse('REVIEW_PACKAGE_SOURCE_CHANGED', `${binding.path}: registered runtime input changed`);
|
|
374
|
+
const path = relative(dirname(config), file).split(sep).join('/');
|
|
375
|
+
const existing = named.find(input => input.path === path);
|
|
376
|
+
if (existing && !existing.bytes.equals(bytes)) refuse('REVIEW_PACKAGE_INPUT_INVALID', `${path}: runtime input collides with a named input`);
|
|
377
|
+
if (!existing) named.push({ path, source: file, bytes });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const configObject = JSON.parse(configBytes.toString()) as JsonObject;
|
|
381
|
+
if (typeof configObject.requirements === 'string') {
|
|
382
|
+
const requirementsPath = relative(dirname(config), resolve(dirname(config), configObject.requirements)).split(sep).join('/');
|
|
383
|
+
const requirements = named.find(input => input.path === requirementsPath);
|
|
384
|
+
if (requirements) {
|
|
385
|
+
let document: Json;
|
|
386
|
+
try { document = JSON.parse(requirements.bytes.toString()); }
|
|
387
|
+
catch { refuse('REVIEW_PACKAGE_INPUT_INVALID', `${requirements.path}: restore readable requirements JSON`); }
|
|
388
|
+
const setup = isObject(document) && isObject(document.setup_painting) ? document.setup_painting : {};
|
|
389
|
+
if (setup.figure_mask !== undefined) {
|
|
390
|
+
const mask = setup.figure_mask;
|
|
391
|
+
if (!isObject(mask) || typeof mask.path !== 'string' || !mask.path.trim()
|
|
392
|
+
|| typeof mask.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(mask.sha256)) {
|
|
393
|
+
refuse('FIGURE_MASK_MISSING', `${requirements.path}: declare setup_painting.figure_mask {path, sha256} before packaging`);
|
|
394
|
+
}
|
|
395
|
+
const maskFile = resolve(dirname(config), dirname(requirements.path), mask.path);
|
|
396
|
+
const maskPath = relative(dirname(config), maskFile).split(sep).join('/');
|
|
397
|
+
if (isAbsolute(mask.path) || !maskPath || maskPath.split('/').some(part => part === '..' || part === '.' || part === '')) {
|
|
398
|
+
refuse('REVIEW_PACKAGE_PATH_INVALID', `${requirements.path}: figure mask ${mask.path} must stay inside the config directory`);
|
|
399
|
+
}
|
|
400
|
+
let current = dirname(config);
|
|
401
|
+
for (const part of maskPath.split('/')) {
|
|
402
|
+
current = join(current, part);
|
|
403
|
+
if (existsSync(current) && lstatSync(current).isSymbolicLink()) refuse('REVIEW_PACKAGE_PATH_INVALID', `${maskPath}: retain a figure mask without symlinks`);
|
|
404
|
+
}
|
|
405
|
+
let regularFile = false;
|
|
406
|
+
try { regularFile = lstatSync(maskFile).isFile(); } catch { /* Missing paths are refused below. */ }
|
|
407
|
+
if (!regularFile) refuse('FIGURE_MASK_CHANGED', `${maskPath}: restore the declared mask as a readable PNG file before packaging`);
|
|
408
|
+
let bytes: Buffer;
|
|
409
|
+
try { bytes = readFileSync(maskFile); }
|
|
410
|
+
catch { refuse('FIGURE_MASK_CHANGED', `${maskPath}: cannot read the declared mask; restore its PNG before packaging`); }
|
|
411
|
+
if (hash(bytes) !== mask.sha256) refuse('FIGURE_MASK_CHANGED', `${maskPath}: bytes differ from the declared sha256; restore the mask or record its corrected declaration before packaging`);
|
|
412
|
+
if (maskPath === 'config.json' && !bytes.equals(configBytes)) refuse('REVIEW_PACKAGE_INPUT_INVALID', `${maskPath}: figure mask collides with the packaged config`);
|
|
413
|
+
const existing = named.find(input => input.path === maskPath);
|
|
414
|
+
if (existing && !existing.bytes.equals(bytes)) refuse('REVIEW_PACKAGE_INPUT_INVALID', `${maskPath}: figure mask collides with a named input`);
|
|
415
|
+
if (!existing) named.push({ path: maskPath, source: maskFile, bytes });
|
|
416
|
+
}
|
|
417
|
+
const legacyUnadaptedReg = isObject(version.registration) && version.registration.kind === 'frozen-rig/1' && version.registration.requirements_source === undefined;
|
|
418
|
+
if (setup.backdrop !== undefined && !legacyUnadaptedReg) {
|
|
419
|
+
const backdrop = setup.backdrop;
|
|
420
|
+
if (!isObject(backdrop) || typeof backdrop.path !== 'string' || !backdrop.path.trim()
|
|
421
|
+
|| typeof backdrop.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(backdrop.sha256)) {
|
|
422
|
+
refuse('REVIEW_PACKAGE_INPUT_INVALID', `${requirements.path}: declare setup_painting.backdrop {path, sha256} before packaging`);
|
|
423
|
+
}
|
|
424
|
+
const backdropFile = resolve(dirname(config), dirname(requirements.path), backdrop.path);
|
|
425
|
+
const backdropPath = relative(dirname(config), backdropFile).split(sep).join('/');
|
|
426
|
+
if (isAbsolute(backdrop.path) || !backdropPath || backdropPath.split('/').some(part => part === '..' || part === '.' || part === '')) {
|
|
427
|
+
refuse('REVIEW_PACKAGE_PATH_INVALID', `${requirements.path}: backdrop ${backdrop.path} must stay inside the config directory`);
|
|
428
|
+
}
|
|
429
|
+
let current = dirname(config);
|
|
430
|
+
for (const part of backdropPath.split('/')) {
|
|
431
|
+
current = join(current, part);
|
|
432
|
+
if (existsSync(current) && lstatSync(current).isSymbolicLink()) refuse('REVIEW_PACKAGE_PATH_INVALID', `${backdropPath}: retain a backdrop without symlinks`);
|
|
433
|
+
}
|
|
434
|
+
let regularFile = false;
|
|
435
|
+
try { regularFile = lstatSync(backdropFile).isFile(); } catch { /* Missing paths are refused below. */ }
|
|
436
|
+
if (!regularFile) refuse('REVIEW_PACKAGE_INPUT_INVALID', `${backdropPath}: restore the declared backdrop as a readable PNG file before packaging`);
|
|
437
|
+
let bytes: Buffer;
|
|
438
|
+
try { bytes = readFileSync(backdropFile); }
|
|
439
|
+
catch { refuse('REVIEW_PACKAGE_INPUT_INVALID', `${backdropPath}: cannot read the declared backdrop; restore its PNG before packaging`); }
|
|
440
|
+
if (hash(bytes) !== backdrop.sha256) refuse('REVIEW_PACKAGE_INPUT_INVALID', `${backdropPath}: bytes differ from the declared sha256; restore the backdrop or record its corrected declaration before packaging`);
|
|
441
|
+
if (backdropPath === 'config.json' && !bytes.equals(configBytes)) refuse('REVIEW_PACKAGE_INPUT_INVALID', `${backdropPath}: backdrop collides with the packaged config`);
|
|
442
|
+
const existing = named.find(input => input.path === backdropPath);
|
|
443
|
+
if (existing && !existing.bytes.equals(bytes)) refuse('REVIEW_PACKAGE_INPUT_INVALID', `${backdropPath}: backdrop collides with a named input`);
|
|
444
|
+
if (!existing) named.push({ path: backdropPath, source: backdropFile, bytes });
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// A compiled model already cited by an authored take cannot be rebound by packaging.
|
|
449
|
+
for (const post of directionObjects(project.versions)) {
|
|
450
|
+
if (post.kind !== 'post' || post.rig_version !== n || !isObject(post.provenance) || post.provenance.direction === undefined) continue;
|
|
451
|
+
try {
|
|
452
|
+
const document = readDirection(dir, project, post.n, false);
|
|
453
|
+
const expected = ((document.rig as JsonObject).model as JsonObject).sha256;
|
|
454
|
+
if (hash(readFileSync(join(from, 'check/build/skeleton.model.json'))) !== expected) refuse('DIRECTION_RIG_STALE', `versions[v${n}].review_package: this model is bound by a direction document; preserve it and author a new rig version`);
|
|
455
|
+
} catch (error) { if (error instanceof DirectionError) refuse(error.code, error.message); throw error; }
|
|
456
|
+
}
|
|
457
|
+
// Regeneration uses the existing declaration, preserving historical packages
|
|
458
|
+
// that omitted sibling images. New registration supplies official rows.
|
|
459
|
+
const recordedFiles = isObject(version.review_package) ? asArray(version.review_package.files) : [];
|
|
460
|
+
const namedPaths = new Set(named.map(input => input.path));
|
|
461
|
+
const retainedImages = imagePaths ?? recordedFiles.filter(isObject).flatMap(file =>
|
|
462
|
+
typeof file.path === 'string' && file.path.startsWith('out/') && !namedPaths.has(file.path) ? [file.path.slice(4)] : []);
|
|
463
|
+
const inputs = sourceFiles(from, retainedImages);
|
|
464
|
+
const pkg = join(dir, packagePath(n));
|
|
465
|
+
makePackageParent(dir);
|
|
466
|
+
const scratch = mkdtempSync(join(dirname(pkg), `.v${n}-`));
|
|
467
|
+
let published = false;
|
|
468
|
+
try {
|
|
469
|
+
writeFileSync(join(scratch, 'config.json'), configBytes);
|
|
470
|
+
for (const input of named) {
|
|
471
|
+
const dest = join(scratch, input.path);
|
|
472
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
473
|
+
writeFileSync(dest, input.bytes);
|
|
474
|
+
}
|
|
475
|
+
for (const entry of required) {
|
|
476
|
+
if (entry !== 'parts.json') mkdirSync(join(scratch, 'out', entry), { recursive: true });
|
|
477
|
+
}
|
|
478
|
+
for (const file of inputs) {
|
|
479
|
+
const bytes = readFileSync(join(from, file.path));
|
|
480
|
+
if (hash(bytes) !== file.sha256) refuse('REVIEW_PACKAGE_SOURCE_CHANGED', `${file.path}: source changed during copy`);
|
|
481
|
+
const dest = join(scratch, 'out', file.path);
|
|
482
|
+
if (existsSync(dest) && !readFileSync(dest).equals(bytes)) refuse('REVIEW_PACKAGE_INPUT_INVALID', `${file.path}: config input collides with a producer output`);
|
|
483
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
484
|
+
writeFileSync(dest, bytes);
|
|
485
|
+
}
|
|
486
|
+
if (!isDeepStrictEqual(sourceFiles(from, retainedImages), inputs) || hash(readFileSync(config)) !== hash(configBytes)) {
|
|
487
|
+
refuse('REVIEW_PACKAGE_SOURCE_CHANGED', 'producer inputs changed during copy');
|
|
488
|
+
}
|
|
489
|
+
for (const input of named) if (!readFileSync(input.source).equals(input.bytes)) refuse('REVIEW_PACKAGE_SOURCE_CHANGED', `${input.path}: config input changed during copy`);
|
|
490
|
+
const files = filesAt(scratch);
|
|
491
|
+
const record: JsonObject = { config: { path: relative(dir, config), sha256: hash(configBytes) }, from: relative(dir, from), files: files.map((f) => ({ ...f })), open: openCommand(n) };
|
|
492
|
+
const identity = { dir, project, refusals: [] as Ctx['refusals'], warnings: [] as Ctx['warnings'] };
|
|
493
|
+
checkBuildIdentity(identity, version, record);
|
|
494
|
+
if (identity.refusals[0]) refuse(identity.refusals[0].code, identity.refusals[0].message);
|
|
495
|
+
try { reviewConfigInputs(record, dir, n, undefined, undefined, scratch); }
|
|
496
|
+
catch (error) { refuse('REVIEW_PACKAGE_INPUT_INVALID', `versions[v${n}].review_package: ${error instanceof Error ? error.message : String(error)}`); }
|
|
497
|
+
if (packageExists(pkg)) {
|
|
498
|
+
if (lstatSync(pkg).isSymbolicLink() || realpathSync(pkg) !== pkg) refuse('REVIEW_PACKAGE_PATH_INVALID', 'existing package must have no symlink parents');
|
|
499
|
+
if (!isDeepStrictEqual(filesAt(pkg), files)) refuse('REVIEW_PACKAGE_EXISTS', 'existing package differs; preserve it and inspect the recorded version before regenerating');
|
|
500
|
+
} else {
|
|
501
|
+
renameSync(scratch, pkg);
|
|
502
|
+
published = true;
|
|
503
|
+
}
|
|
504
|
+
if (readFileSync(projectFile, 'utf8') !== original) refuse('REVIEW_PACKAGE_RECORD_CHANGED', 'project.json changed during copying; retry from the current record');
|
|
505
|
+
version.review_package = record;
|
|
506
|
+
if (candidate !== undefined) {
|
|
507
|
+
published = false;
|
|
508
|
+
return { stdout: JSON.stringify(record), exit: 0 };
|
|
509
|
+
}
|
|
510
|
+
const pending = join(scratch, 'project.json');
|
|
511
|
+
mkdirSync(scratch, { recursive: true });
|
|
512
|
+
writeFileSync(pending, `${JSON.stringify(project, null, 2)}\n`);
|
|
513
|
+
renameSync(pending, projectFile);
|
|
514
|
+
published = false;
|
|
515
|
+
return { stdout: json ? JSON.stringify(record, null, 2) : `review-package: v${n} copied; ${openCommand(n)}`, exit: 0 };
|
|
516
|
+
} finally {
|
|
517
|
+
if (published) rmSync(pkg, { recursive: true });
|
|
518
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
519
|
+
}
|
|
520
|
+
} finally {
|
|
521
|
+
closeSync(descriptor);
|
|
522
|
+
rmSync(lock);
|
|
523
|
+
}
|
|
524
|
+
}
|