@rig-c/create 0.0.0-stage → 1.0.1
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 +169 -2
- package/docs/CHECKS.md +1682 -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,496 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
4
|
+
import { existsSync, lstatSync, readFileSync, realpathSync } from './check-reads.ts';
|
|
5
|
+
import type { Json, JsonObject } from './templates.ts';
|
|
6
|
+
|
|
7
|
+
export const COLLECTION_SCHEMA = 'rig-create/collection@0';
|
|
8
|
+
export const COLLECTION_BINDING_SPEC = 'rig-create/collection-binding@1';
|
|
9
|
+
export const DEFAULT_COLLECTION_BINDING_PATH = 'evidence/collection-binding.json';
|
|
10
|
+
|
|
11
|
+
export const IGNORED_BINDING_ROOTS = new Set([
|
|
12
|
+
'.git',
|
|
13
|
+
'node_modules',
|
|
14
|
+
'.rig-create-review',
|
|
15
|
+
'.rig-review',
|
|
16
|
+
'derived',
|
|
17
|
+
'outputs',
|
|
18
|
+
'scratch',
|
|
19
|
+
'jobs',
|
|
20
|
+
'build',
|
|
21
|
+
'dist',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
export interface CollectionBindingFinding {
|
|
25
|
+
readonly code: 'JOB_RECORD_COLLECTION_EXTERNAL';
|
|
26
|
+
readonly path: string;
|
|
27
|
+
readonly message: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CollectionBindingVerification {
|
|
31
|
+
readonly valid: boolean;
|
|
32
|
+
readonly boundCollection: JsonObject | null;
|
|
33
|
+
readonly bindingFile: { readonly path: string; readonly sha256: string } | null;
|
|
34
|
+
readonly finding: CollectionBindingFinding | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isObject(v: Json | undefined): v is JsonObject {
|
|
38
|
+
return v !== null && v !== undefined && typeof v === 'object' && !Array.isArray(v);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function asArray(v: Json | undefined): Json[] {
|
|
42
|
+
return Array.isArray(v) ? v : [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function sha256Hex(bytes: string | Uint8Array): string {
|
|
46
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isLiveMemberRoot(dir: string): boolean {
|
|
50
|
+
return basename(dirname(resolve(dir))) === 'members';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function externalCollectionFile(dir: string): string {
|
|
54
|
+
return join(dirname(dirname(resolve(dir))), 'collection.json');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function isValidCollectionBindingPath(path: string): boolean {
|
|
58
|
+
if (typeof path !== 'string' || !path || !path.endsWith('.json')) return false;
|
|
59
|
+
if (/[\\:%?#\x00-\x1f]/.test(path)) return false;
|
|
60
|
+
const pieces = path.split('/');
|
|
61
|
+
if (pieces.some((piece) => !piece || piece === '.' || piece === '..' || piece.startsWith('.'))) return false;
|
|
62
|
+
if (IGNORED_BINDING_ROOTS.has(pieces[0]!)) return false;
|
|
63
|
+
if (path === 'project.json' || path === 'package.json') return false;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const candidateStack: { readonly dir: string; readonly project: JsonObject }[] = [];
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Scope `loadCollection` calls inside `checkProjectOnce` or writer validation
|
|
71
|
+
* so nested checks that call `loadCollection(dir)` without a `project` argument
|
|
72
|
+
* read the in-memory candidate rather than a stale on-disk `project.json`.
|
|
73
|
+
*/
|
|
74
|
+
export function withCandidateProject<T>(dir: string, project: JsonObject, fn: () => T): T {
|
|
75
|
+
const entry = { dir: resolve(dir), project };
|
|
76
|
+
candidateStack.push(entry);
|
|
77
|
+
try {
|
|
78
|
+
return fn();
|
|
79
|
+
} finally {
|
|
80
|
+
candidateStack.pop();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseJsonObjectBytes(bytes: Buffer): { ok: true; value: JsonObject } | { ok: false; error: string } {
|
|
85
|
+
let parsed: unknown;
|
|
86
|
+
try {
|
|
87
|
+
parsed = JSON.parse(bytes.toString('utf8'));
|
|
88
|
+
} catch (e) {
|
|
89
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
90
|
+
}
|
|
91
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
92
|
+
return { ok: false, error: 'not a JSON object' };
|
|
93
|
+
}
|
|
94
|
+
return { ok: true, value: parsed as JsonObject };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resolveProjectRecord(dir: string, project?: JsonObject): JsonObject | null {
|
|
98
|
+
if (project !== undefined) return project;
|
|
99
|
+
const resolved = resolve(dir);
|
|
100
|
+
for (let i = candidateStack.length - 1; i >= 0; i--) {
|
|
101
|
+
if (candidateStack[i]!.dir === resolved) return candidateStack[i]!.project;
|
|
102
|
+
}
|
|
103
|
+
const file = join(resolved, 'project.json');
|
|
104
|
+
if (!existsSync(file)) return null;
|
|
105
|
+
try {
|
|
106
|
+
const parsed = parseJsonObjectBytes(readFileSync(file));
|
|
107
|
+
return parsed.ok ? parsed.value : null;
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function readRegularBindingFile(dir: string, relPath: string): { ok: true; bytes: Buffer } | { ok: false; reason: string } {
|
|
114
|
+
if (!isValidCollectionBindingPath(relPath)) {
|
|
115
|
+
return { ok: false, reason: `${relPath} is not a safe project-relative JSON path outside ignored roots` };
|
|
116
|
+
}
|
|
117
|
+
const resolvedRoot = resolve(dir);
|
|
118
|
+
let realRoot = resolvedRoot;
|
|
119
|
+
try {
|
|
120
|
+
realRoot = realpathSync(resolvedRoot);
|
|
121
|
+
} catch {
|
|
122
|
+
return { ok: false, reason: `${resolvedRoot} is not a readable directory` };
|
|
123
|
+
}
|
|
124
|
+
const absolute = join(realRoot, relPath);
|
|
125
|
+
try {
|
|
126
|
+
const stat = lstatSync(absolute);
|
|
127
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
128
|
+
return { ok: false, reason: `${relPath} must be a regular file without symlinks` };
|
|
129
|
+
}
|
|
130
|
+
if (realpathSync(absolute) !== absolute) {
|
|
131
|
+
return { ok: false, reason: `${relPath} must resolve inside the project root without symlinks` };
|
|
132
|
+
}
|
|
133
|
+
return { ok: true, bytes: readFileSync(absolute) };
|
|
134
|
+
} catch {
|
|
135
|
+
return { ok: false, reason: `${relPath} does not exist or is unreadable` };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function readExternalCollection(dir: string): {
|
|
140
|
+
readonly liveMember: boolean;
|
|
141
|
+
readonly file: string;
|
|
142
|
+
readonly exists: boolean;
|
|
143
|
+
readonly rawBytes: Buffer | null;
|
|
144
|
+
readonly sha256: string | null;
|
|
145
|
+
readonly collection: JsonObject | null;
|
|
146
|
+
readonly error: string | null;
|
|
147
|
+
} {
|
|
148
|
+
const liveMember = isLiveMemberRoot(dir);
|
|
149
|
+
const file = externalCollectionFile(dir);
|
|
150
|
+
if (!liveMember) {
|
|
151
|
+
return { liveMember: false, file, exists: false, rawBytes: null, sha256: null, collection: null, error: null };
|
|
152
|
+
}
|
|
153
|
+
if (!existsSync(file)) {
|
|
154
|
+
return { liveMember: true, file, exists: false, rawBytes: null, sha256: null, collection: null, error: `${file} does not exist` };
|
|
155
|
+
}
|
|
156
|
+
let rawBytes: Buffer;
|
|
157
|
+
try {
|
|
158
|
+
rawBytes = readFileSync(file);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
return { liveMember: true, file, exists: true, rawBytes: null, sha256: null, collection: null, error: e instanceof Error ? e.message : String(e) };
|
|
161
|
+
}
|
|
162
|
+
const sha256 = sha256Hex(rawBytes);
|
|
163
|
+
const parsed = parseJsonObjectBytes(rawBytes);
|
|
164
|
+
if (!parsed.ok) {
|
|
165
|
+
return { liveMember: true, file, exists: true, rawBytes, sha256, collection: null, error: parsed.error };
|
|
166
|
+
}
|
|
167
|
+
return { liveMember: true, file, exists: true, rawBytes, sha256, collection: parsed.value, error: null };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function normalizeQuestionEntries(raw: Json | undefined, valueKey: 'answer' | 'guide'): JsonObject[] | null {
|
|
171
|
+
if (!Array.isArray(raw)) return null;
|
|
172
|
+
const out: JsonObject[] = [];
|
|
173
|
+
for (const entry of raw) {
|
|
174
|
+
if (!isObject(entry) || typeof entry.id !== 'string' || entry.id.trim() === '') return null;
|
|
175
|
+
if (entry.history !== undefined && !Array.isArray(entry.history)) return null;
|
|
176
|
+
const history = Array.isArray(entry.history) ? entry.history : [];
|
|
177
|
+
if (history.some((h) => !isObject(h))) return null;
|
|
178
|
+
out.push({
|
|
179
|
+
id: entry.id,
|
|
180
|
+
[valueKey]: entry[valueKey] ?? null,
|
|
181
|
+
history,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Extracts the exact interview/history snapshot from a `collection.json` record
|
|
189
|
+
* without computing or re-deriving any producer measurements.
|
|
190
|
+
*/
|
|
191
|
+
export function extractCollectionInterviewSnapshot(collection: JsonObject): JsonObject | null {
|
|
192
|
+
if (!isObject(collection.interview)) return null;
|
|
193
|
+
const interview = collection.interview;
|
|
194
|
+
const post = isObject(interview.post) ? interview.post : null;
|
|
195
|
+
if (post === null) return null;
|
|
196
|
+
const questions = normalizeQuestionEntries(interview.questions, 'answer');
|
|
197
|
+
const framing = normalizeQuestionEntries(interview.framing, 'answer');
|
|
198
|
+
const waypoints = normalizeQuestionEntries(interview.waypoints, 'guide');
|
|
199
|
+
const postItems = normalizeQuestionEntries(post.items, 'guide');
|
|
200
|
+
if (questions === null || framing === null || waypoints === null || postItems === null) return null;
|
|
201
|
+
return {
|
|
202
|
+
questions,
|
|
203
|
+
framing,
|
|
204
|
+
waypoints,
|
|
205
|
+
post: { items: postItems },
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function lookupCollectionAnswer(collection: JsonObject, question: string): Json | undefined {
|
|
210
|
+
const interview = isObject(collection.interview) ? collection.interview : {};
|
|
211
|
+
for (const q of asArray(interview.questions)) if (isObject(q) && q.id === question) return q.answer;
|
|
212
|
+
for (const q of asArray(interview.framing)) if (isObject(q) && q.id === question) return q.answer;
|
|
213
|
+
for (const w of asArray(interview.waypoints)) if (isObject(w) && w.id === question) return w.guide;
|
|
214
|
+
const post = isObject(interview.post) ? interview.post : {};
|
|
215
|
+
for (const p of asArray(post.items)) if (isObject(p) && p.id === question) return p.guide;
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function projectUsesCollection(project: JsonObject): boolean {
|
|
220
|
+
if (typeof project.collection === 'string' && project.collection.trim() !== '') return true;
|
|
221
|
+
const interview = isObject(project.interview) ? project.interview : {};
|
|
222
|
+
const post = isObject(interview.post) ? interview.post : {};
|
|
223
|
+
const lists: [Json | undefined, 'answer' | 'guide'][] = [
|
|
224
|
+
[interview.framing, 'answer'],
|
|
225
|
+
[interview.waypoints, 'guide'],
|
|
226
|
+
[post.items, 'guide'],
|
|
227
|
+
];
|
|
228
|
+
for (const [list, key] of lists) {
|
|
229
|
+
for (const item of asArray(list)) {
|
|
230
|
+
if (!isObject(item)) continue;
|
|
231
|
+
for (const candidate of [item[key], ...asArray(item.history)]) {
|
|
232
|
+
if (isObject(candidate) && (candidate.basis === 'inherited' || candidate.basis === 'overridden')) return true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function extractInheritedAnswers(
|
|
240
|
+
project: JsonObject,
|
|
241
|
+
collection: JsonObject,
|
|
242
|
+
):
|
|
243
|
+
| { readonly ok: true; readonly answers: JsonObject; readonly overridden: JsonObject }
|
|
244
|
+
| { readonly ok: false; readonly reason: string } {
|
|
245
|
+
const collectionName = typeof collection.name === 'string' ? collection.name : '';
|
|
246
|
+
if (!collectionName) return { ok: false, reason: 'collection has no name' };
|
|
247
|
+
if (project.collection !== undefined && project.collection !== null && project.collection !== collectionName) {
|
|
248
|
+
return { ok: false, reason: `project.collection is ${JSON.stringify(project.collection)}, not ${JSON.stringify(collectionName)}` };
|
|
249
|
+
}
|
|
250
|
+
const interview = isObject(project.interview) ? project.interview : {};
|
|
251
|
+
const post = isObject(interview.post) ? interview.post : {};
|
|
252
|
+
const lists: [Json | undefined, 'answer' | 'guide', string][] = [
|
|
253
|
+
[interview.framing, 'answer', 'interview.framing'],
|
|
254
|
+
[interview.waypoints, 'guide', 'interview.waypoints'],
|
|
255
|
+
[post.items, 'guide', 'interview.post.items'],
|
|
256
|
+
];
|
|
257
|
+
const answers: JsonObject = {};
|
|
258
|
+
const overridden: JsonObject = {};
|
|
259
|
+
for (const [list, key, prefix] of lists) {
|
|
260
|
+
for (const item of asArray(list)) {
|
|
261
|
+
if (!isObject(item) || typeof item.id !== 'string') continue;
|
|
262
|
+
const raw = item[key];
|
|
263
|
+
if (isObject(raw) && raw.basis === 'inherited') {
|
|
264
|
+
const from = isObject(raw.from) ? raw.from : null;
|
|
265
|
+
const fromCollection = from !== null && typeof from.collection === 'string' ? from.collection : null;
|
|
266
|
+
const fromQuestion = from !== null && typeof from.question === 'string' ? from.question : null;
|
|
267
|
+
if (fromCollection === null || fromQuestion === null) {
|
|
268
|
+
return { ok: false, reason: `${prefix}[${item.id}].${key}: inherited answer needs from.collection and from.question` };
|
|
269
|
+
}
|
|
270
|
+
if (fromCollection !== collectionName) {
|
|
271
|
+
return { ok: false, reason: `${prefix}[${item.id}].${key}: inherits from collection '${fromCollection}', not '${collectionName}'` };
|
|
272
|
+
}
|
|
273
|
+
const resolved = lookupCollectionAnswer(collection, fromQuestion);
|
|
274
|
+
if (resolved === undefined) {
|
|
275
|
+
return { ok: false, reason: `${prefix}[${item.id}].${key}: collection '${collectionName}' does not ask '${fromQuestion}'` };
|
|
276
|
+
}
|
|
277
|
+
if (resolved === null || (isObject(resolved) && ('deferred' in resolved || resolved.value === 'per-member'))) {
|
|
278
|
+
return { ok: false, reason: `${prefix}[${item.id}].${key}: collection '${collectionName}' answer '${fromQuestion}' is not an inheritable collection answer` };
|
|
279
|
+
}
|
|
280
|
+
answers[fromQuestion] = resolved;
|
|
281
|
+
}
|
|
282
|
+
for (const entry of [raw, ...asArray(item.history)]) {
|
|
283
|
+
if (!isObject(entry) || entry.basis !== 'overridden') continue;
|
|
284
|
+
const ov = isObject(entry.overrides) ? entry.overrides : null;
|
|
285
|
+
const ovCollection = ov !== null && typeof ov.collection === 'string' ? ov.collection : null;
|
|
286
|
+
if (ovCollection !== collectionName) {
|
|
287
|
+
return { ok: false, reason: `${prefix}[${item.id}].${key}: overrides.collection is ${JSON.stringify(ovCollection)}, not '${collectionName}'` };
|
|
288
|
+
}
|
|
289
|
+
const ovQuestion = ov !== null && typeof ov.question === 'string' ? ov.question : null;
|
|
290
|
+
if (ovQuestion !== null && !(ovQuestion in answers)) {
|
|
291
|
+
const resolved = lookupCollectionAnswer(collection, ovQuestion);
|
|
292
|
+
if (resolved !== undefined && resolved !== null && !(isObject(resolved) && ('deferred' in resolved || resolved.value === 'per-member'))) {
|
|
293
|
+
overridden[ovQuestion] = resolved;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return { ok: true, answers, overridden };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function matchesBoundInheritedAnswers(
|
|
303
|
+
boundAnswers: JsonObject,
|
|
304
|
+
expected: { readonly answers: JsonObject; readonly overridden: JsonObject },
|
|
305
|
+
): boolean {
|
|
306
|
+
for (const [q, val] of Object.entries(expected.answers)) {
|
|
307
|
+
if (!(q in boundAnswers) || !isDeepStrictEqual(boundAnswers[q], val)) return false;
|
|
308
|
+
}
|
|
309
|
+
for (const [q, val] of Object.entries(boundAnswers)) {
|
|
310
|
+
if (q in expected.answers) continue;
|
|
311
|
+
if (q in expected.overridden && isDeepStrictEqual(val, expected.overridden[q])) continue;
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function failBinding(path: string, detail: string): CollectionBindingVerification {
|
|
318
|
+
return {
|
|
319
|
+
valid: false,
|
|
320
|
+
boundCollection: null,
|
|
321
|
+
bindingFile: null,
|
|
322
|
+
finding: {
|
|
323
|
+
code: 'JOB_RECORD_COLLECTION_EXTERNAL',
|
|
324
|
+
path,
|
|
325
|
+
message: `${path}: ${detail}; bind the current ../../collection.json with rig-create collection-binding <member dir>`,
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function verifyCollectionBinding(
|
|
331
|
+
dir: string,
|
|
332
|
+
project: JsonObject,
|
|
333
|
+
options: { readonly requireBindingWhenExternal?: boolean } = {},
|
|
334
|
+
): CollectionBindingVerification {
|
|
335
|
+
const external = readExternalCollection(dir);
|
|
336
|
+
const hasPointer = project.collection_binding !== undefined && project.collection_binding !== null;
|
|
337
|
+
|
|
338
|
+
if (!hasPointer) {
|
|
339
|
+
if (external.liveMember && options.requireBindingWhenExternal) {
|
|
340
|
+
return failBinding(
|
|
341
|
+
'project.json',
|
|
342
|
+
"a member record's inherited answers depend on ../../collection.json, which the project-relative closure cannot seal without collection_binding",
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
if (!external.liveMember && options.requireBindingWhenExternal && projectUsesCollection(project)) {
|
|
346
|
+
return failBinding(
|
|
347
|
+
'collection_binding',
|
|
348
|
+
'member record references a collection without a retained collection_binding',
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
return { valid: true, boundCollection: null, bindingFile: null, finding: null };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (!isObject(project.collection_binding)) {
|
|
355
|
+
return failBinding('collection_binding', 'collection_binding must be an object with path, sha256, spec, and collection_sha256');
|
|
356
|
+
}
|
|
357
|
+
const pointer = project.collection_binding;
|
|
358
|
+
if (pointer.spec !== COLLECTION_BINDING_SPEC) {
|
|
359
|
+
return failBinding('collection_binding.spec', `collection_binding.spec is ${JSON.stringify(pointer.spec ?? null)}, expected ${COLLECTION_BINDING_SPEC}`);
|
|
360
|
+
}
|
|
361
|
+
if (typeof pointer.path !== 'string' || !isValidCollectionBindingPath(pointer.path)) {
|
|
362
|
+
return failBinding('collection_binding.path', 'collection_binding.path must be a safe project-relative .json path outside ignored roots');
|
|
363
|
+
}
|
|
364
|
+
if (typeof pointer.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(pointer.sha256)) {
|
|
365
|
+
return failBinding('collection_binding.sha256', 'collection_binding.sha256 must be a 64-character lowercase hex sha256');
|
|
366
|
+
}
|
|
367
|
+
if (typeof pointer.collection_sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(pointer.collection_sha256)) {
|
|
368
|
+
return failBinding('collection_binding.collection_sha256', 'collection_binding.collection_sha256 must be a 64-character lowercase hex sha256');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const fileRead = readRegularBindingFile(dir, pointer.path);
|
|
372
|
+
if (!fileRead.ok) {
|
|
373
|
+
return failBinding('collection_binding.path', fileRead.reason);
|
|
374
|
+
}
|
|
375
|
+
const actualSha256 = sha256Hex(fileRead.bytes);
|
|
376
|
+
if (actualSha256 !== pointer.sha256) {
|
|
377
|
+
return failBinding('collection_binding.sha256', `${pointer.path} sha256 is ${actualSha256}, not bound ${pointer.sha256}`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const parsed = parseJsonObjectBytes(fileRead.bytes);
|
|
381
|
+
if (!parsed.ok) {
|
|
382
|
+
return failBinding(pointer.path, `invalid JSON in ${pointer.path}: ${parsed.error}`);
|
|
383
|
+
}
|
|
384
|
+
const snapshot = parsed.value;
|
|
385
|
+
if (snapshot.schema !== COLLECTION_BINDING_SPEC) {
|
|
386
|
+
return failBinding(pointer.path, `${pointer.path} schema is ${JSON.stringify(snapshot.schema ?? null)}, expected ${COLLECTION_BINDING_SPEC}`);
|
|
387
|
+
}
|
|
388
|
+
if (!isObject(snapshot.collection) || typeof snapshot.collection.name !== 'string' || snapshot.collection.name.trim() === '') {
|
|
389
|
+
return failBinding(pointer.path, `${pointer.path} collection.name must be a non-empty string`);
|
|
390
|
+
}
|
|
391
|
+
if (snapshot.collection.sha256 !== pointer.collection_sha256) {
|
|
392
|
+
return failBinding(pointer.path, `${pointer.path} collection.sha256 does not match collection_binding.collection_sha256`);
|
|
393
|
+
}
|
|
394
|
+
if (typeof project.name !== 'string' || project.name.trim() === '' || snapshot.member !== project.name) {
|
|
395
|
+
return failBinding(pointer.path, `${pointer.path} member ${JSON.stringify(snapshot.member ?? null)} does not match project.name ${JSON.stringify(project.name ?? null)}`);
|
|
396
|
+
}
|
|
397
|
+
if (project.collection !== undefined && project.collection !== null && project.collection !== snapshot.collection.name) {
|
|
398
|
+
return failBinding('collection', `project.collection ${JSON.stringify(project.collection)} does not match bound collection '${snapshot.collection.name}'`);
|
|
399
|
+
}
|
|
400
|
+
if (external.liveMember && basename(resolve(dir)) !== project.name) {
|
|
401
|
+
return failBinding('name', `member folder '${basename(resolve(dir))}' does not match project.name '${project.name}'`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const normalizedSnapshotInterview = extractCollectionInterviewSnapshot({ interview: snapshot.interview });
|
|
405
|
+
if (normalizedSnapshotInterview === null || !isDeepStrictEqual(snapshot.interview, normalizedSnapshotInterview)) {
|
|
406
|
+
return failBinding(pointer.path, `${pointer.path} interview snapshot is malformed`);
|
|
407
|
+
}
|
|
408
|
+
if (!isObject(snapshot.inherited_answers)) {
|
|
409
|
+
return failBinding(pointer.path, `${pointer.path} inherited_answers must be an object`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const boundCollection: JsonObject = {
|
|
413
|
+
schema: COLLECTION_SCHEMA,
|
|
414
|
+
name: snapshot.collection.name,
|
|
415
|
+
interview: snapshot.interview,
|
|
416
|
+
};
|
|
417
|
+
const fromSnapshot = extractInheritedAnswers(project, boundCollection);
|
|
418
|
+
if (!fromSnapshot.ok) {
|
|
419
|
+
return failBinding(pointer.path, fromSnapshot.reason);
|
|
420
|
+
}
|
|
421
|
+
if (!matchesBoundInheritedAnswers(snapshot.inherited_answers, fromSnapshot)) {
|
|
422
|
+
return failBinding(pointer.path, `${pointer.path} inherited_answers does not match the member's inherited items and bound interview`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// In a live member root (<collection>/members/<name>), external ../../collection.json
|
|
426
|
+
// must exist, parse cleanly, and match the bound snapshot byte-for-byte and answer-for-answer.
|
|
427
|
+
if (external.liveMember) {
|
|
428
|
+
if (!external.exists || external.collection === null || external.sha256 === null) {
|
|
429
|
+
return failBinding('collection_binding', `external ../../collection.json is missing or unreadable (${external.error ?? 'missing'})`);
|
|
430
|
+
}
|
|
431
|
+
if (external.collection.schema !== COLLECTION_SCHEMA) {
|
|
432
|
+
return failBinding('collection_binding', `external ../../collection.json schema is ${JSON.stringify(external.collection.schema ?? null)}, not ${COLLECTION_SCHEMA}`);
|
|
433
|
+
}
|
|
434
|
+
if (external.collection.name !== snapshot.collection.name) {
|
|
435
|
+
return failBinding('collection_binding', `external ../../collection.json name is ${JSON.stringify(external.collection.name ?? null)}, not bound '${snapshot.collection.name}'`);
|
|
436
|
+
}
|
|
437
|
+
if (
|
|
438
|
+
Array.isArray(external.collection.members) &&
|
|
439
|
+
external.collection.members.length > 0 &&
|
|
440
|
+
!external.collection.members.some((m) => isObject(m) && m.name === project.name)
|
|
441
|
+
) {
|
|
442
|
+
return failBinding('collection_binding', `member '${project.name}' is not listed in external ../../collection.json members[]`);
|
|
443
|
+
}
|
|
444
|
+
if (external.sha256 !== pointer.collection_sha256) {
|
|
445
|
+
return failBinding('collection_binding.collection_sha256', `external ../../collection.json sha256 changed from bound ${pointer.collection_sha256} to ${external.sha256}`);
|
|
446
|
+
}
|
|
447
|
+
const externalInterview = extractCollectionInterviewSnapshot(external.collection);
|
|
448
|
+
if (externalInterview === null || !isDeepStrictEqual(snapshot.interview, externalInterview)) {
|
|
449
|
+
return failBinding(pointer.path, `${pointer.path} interview/history snapshot differs from external ../../collection.json`);
|
|
450
|
+
}
|
|
451
|
+
const fromExternal = extractInheritedAnswers(project, external.collection);
|
|
452
|
+
if (!fromExternal.ok || !matchesBoundInheritedAnswers(snapshot.inherited_answers, fromExternal)) {
|
|
453
|
+
return failBinding(pointer.path, `${pointer.path} inherited_answers differs from external ../../collection.json`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return {
|
|
458
|
+
valid: true,
|
|
459
|
+
boundCollection,
|
|
460
|
+
bindingFile: { path: pointer.path, sha256: pointer.sha256 },
|
|
461
|
+
finding: null,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Loads the collection record for `dir`:
|
|
467
|
+
* - In a live member root (`basename(dirname(dir)) === 'members'`), reads ONLY
|
|
468
|
+
* the external `../../collection.json` and never falls back to a bound snapshot
|
|
469
|
+
* if the external file is missing or malformed.
|
|
470
|
+
* - Outside a live member root (such as an isolated `retained` root), returns the
|
|
471
|
+
* verified bound collection snapshot when `project.collection_binding` is valid.
|
|
472
|
+
*/
|
|
473
|
+
export function loadCollectionForDir(dir: string, project?: JsonObject): JsonObject | null {
|
|
474
|
+
if (isLiveMemberRoot(dir)) {
|
|
475
|
+
const external = readExternalCollection(dir);
|
|
476
|
+
return external.collection;
|
|
477
|
+
}
|
|
478
|
+
const record = resolveProjectRecord(dir, project);
|
|
479
|
+
if (record === null || record.collection_binding === undefined || record.collection_binding === null) {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
const verified = verifyCollectionBinding(dir, record);
|
|
483
|
+
return verified.valid ? verified.boundCollection : null;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export function checkCollectionBinding(
|
|
487
|
+
dir: string,
|
|
488
|
+
project: JsonObject,
|
|
489
|
+
refusals: { code: string; path: string; message: string }[],
|
|
490
|
+
): void {
|
|
491
|
+
if (project.collection_binding === undefined) return;
|
|
492
|
+
const verified = verifyCollectionBinding(dir, project);
|
|
493
|
+
if (!verified.valid && verified.finding !== null) {
|
|
494
|
+
refusals.push(verified.finding);
|
|
495
|
+
}
|
|
496
|
+
}
|