rig-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.
Files changed (555) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +169 -2
  3. package/docs/CHECKS.md +1682 -0
  4. package/docs/COLLECTIONS.md +337 -0
  5. package/docs/CRAFT.md +277 -0
  6. package/docs/INTERVIEW.md +886 -0
  7. package/docs/LESSONS.md +172 -0
  8. package/docs/PROJECT.md +2657 -0
  9. package/docs/SOURCE.md +225 -0
  10. package/package.json +44 -4
  11. package/src/agent-bounce.ts +27 -0
  12. package/src/agent-guide.ts +237 -0
  13. package/src/amend.ts +193 -0
  14. package/src/applied-review.ts +104 -0
  15. package/src/ask-coverage.ts +69 -0
  16. package/src/assemble-answers.ts +53 -0
  17. package/src/cg-gates.ts +211 -0
  18. package/src/check-reads.ts +106 -0
  19. package/src/check.ts +1059 -0
  20. package/src/checkpoint-evidence-writer.ts +125 -0
  21. package/src/checkpoint-evidence.ts +152 -0
  22. package/src/choice-writers.ts +271 -0
  23. package/src/choices.ts +378 -0
  24. package/src/cli.ts +371 -0
  25. package/src/collection-answer.ts +136 -0
  26. package/src/collection-binding-reader.ts +496 -0
  27. package/src/collection-binding.ts +478 -0
  28. package/src/collection.ts +348 -0
  29. package/src/config-inputs.ts +155 -0
  30. package/src/config-lost.ts +135 -0
  31. package/src/correction-writers.ts +207 -0
  32. package/src/corrections.ts +685 -0
  33. package/src/diff-classes.ts +219 -0
  34. package/src/diff-map.ts +248 -0
  35. package/src/direction-citation.ts +13 -0
  36. package/src/direction-writers.ts +119 -0
  37. package/src/direction.ts +266 -0
  38. package/src/drafts.ts +363 -0
  39. package/src/feedback.ts +744 -0
  40. package/src/figure-mask.ts +143 -0
  41. package/src/fix-ledger.ts +355 -0
  42. package/src/init.ts +265 -0
  43. package/src/inputs.ts +160 -0
  44. package/src/intake.ts +158 -0
  45. package/src/interview.ts +53 -0
  46. package/src/invariants.ts +132 -0
  47. package/src/job-acts.ts +80 -0
  48. package/src/job-advance.ts +65 -0
  49. package/src/job-close.ts +226 -0
  50. package/src/job-host.ts +529 -0
  51. package/src/job-outbox.ts +383 -0
  52. package/src/job-publication.ts +701 -0
  53. package/src/job-runtime.ts +99 -0
  54. package/src/job-writer.ts +123 -0
  55. package/src/meshes-bust.ts +63 -0
  56. package/src/next.ts +1506 -0
  57. package/src/overrides.ts +42 -0
  58. package/src/package-upgrades.ts +32 -0
  59. package/src/packages-upgrade-check.ts +6 -0
  60. package/src/packages-upgrade.ts +168 -0
  61. package/src/parts-act.ts +96 -0
  62. package/src/parts-check.ts +242 -0
  63. package/src/parts-review.ts +196 -0
  64. package/src/parts-sections.ts +98 -0
  65. package/src/parts-verdict-record.ts +265 -0
  66. package/src/parts-verdict.ts +33 -0
  67. package/src/pause.ts +118 -0
  68. package/src/pins-check.ts +619 -0
  69. package/src/pins.ts +17 -0
  70. package/src/plan-bar-amendments.ts +54 -0
  71. package/src/plan-history.ts +147 -0
  72. package/src/plan-writers.ts +202 -0
  73. package/src/plate-receipt.ts +110 -0
  74. package/src/pre-interview.ts +302 -0
  75. package/src/prepared-inputs.ts +113 -0
  76. package/src/progress-writers.ts +181 -0
  77. package/src/provenance.ts +301 -0
  78. package/src/record-direction-target.ts +26 -0
  79. package/src/record-input.ts +18 -0
  80. package/src/refusal-baseline.ts +55 -0
  81. package/src/refusal.ts +30 -0
  82. package/src/reopen-lapses.ts +80 -0
  83. package/src/reopen.ts +144 -0
  84. package/src/republish.ts +93 -0
  85. package/src/restart.ts +141 -0
  86. package/src/review-act-runtime.ts +16 -0
  87. package/src/review-act.ts +592 -0
  88. package/src/review-package.ts +524 -0
  89. package/src/review-read.ts +238 -0
  90. package/src/review.ts +432 -0
  91. package/src/rig-version-date.ts +22 -0
  92. package/src/rig-version.ts +1047 -0
  93. package/src/run-record.ts +232 -0
  94. package/src/scene-authority.ts +410 -0
  95. package/src/scene-writers.ts +144 -0
  96. package/src/scene.ts +284 -0
  97. package/src/setup-painting.ts +46 -0
  98. package/src/signable.ts +65 -0
  99. package/src/signoff-answers.ts +76 -0
  100. package/src/signoff-checkpoint-act.ts +95 -0
  101. package/src/signoff-target.ts +127 -0
  102. package/src/signoff.ts +331 -0
  103. package/src/split-captions.ts +154 -0
  104. package/src/stage-progress.ts +379 -0
  105. package/src/stages.ts +148 -0
  106. package/src/step-hash.ts +19 -0
  107. package/src/structure-snapshot.ts +109 -0
  108. package/src/structure.ts +166 -0
  109. package/src/templates.ts +16 -0
  110. package/src/waypoints.ts +163 -0
  111. package/templates/agent-guide.md +108 -0
  112. package/templates/collection-agent-guide.md +72 -0
  113. package/templates/collection.json +252 -0
  114. package/templates/example-chain/AGENTS.md +134 -0
  115. package/templates/example-chain/CLAUDE.md +3 -0
  116. package/templates/example-chain/README.md +13 -0
  117. package/templates/example-chain/chain.json +2869 -0
  118. package/templates/example-chain/checkpoints/0a-interview-options/AGENTS.md +134 -0
  119. package/templates/example-chain/checkpoints/0a-interview-options/CLAUDE.md +3 -0
  120. package/templates/example-chain/checkpoints/0a-interview-options/evidence/.gitkeep +0 -0
  121. package/templates/example-chain/checkpoints/0a-interview-options/evidence/approval.txt +1 -0
  122. package/templates/example-chain/checkpoints/0a-interview-options/evidence/interview/rig_type.json +59 -0
  123. package/templates/example-chain/checkpoints/0a-interview-options/evidence/split-config.json +1 -0
  124. package/templates/example-chain/checkpoints/0a-interview-options/inputs/painting.png +0 -0
  125. package/templates/example-chain/checkpoints/0a-interview-options/inputs/references/.gitkeep +0 -0
  126. package/templates/example-chain/checkpoints/0a-interview-options/package.json +9 -0
  127. package/templates/example-chain/checkpoints/0a-interview-options/project.json +295 -0
  128. package/templates/example-chain/checkpoints/0a-interview-options.json +127 -0
  129. package/templates/example-chain/checkpoints/0b-plan-pending/AGENTS.md +134 -0
  130. package/templates/example-chain/checkpoints/0b-plan-pending/CLAUDE.md +3 -0
  131. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/.gitkeep +0 -0
  132. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/approval.txt +1 -0
  133. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/assemble.json +309 -0
  134. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/reference.json +59 -0
  135. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/rig_type.json +59 -0
  136. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/interview/seethrough.json +186 -0
  137. package/templates/example-chain/checkpoints/0b-plan-pending/evidence/split-config.json +1 -0
  138. package/templates/example-chain/checkpoints/0b-plan-pending/inputs/painting.png +0 -0
  139. package/templates/example-chain/checkpoints/0b-plan-pending/inputs/references/.gitkeep +0 -0
  140. package/templates/example-chain/checkpoints/0b-plan-pending/package.json +9 -0
  141. package/templates/example-chain/checkpoints/0b-plan-pending/project.json +685 -0
  142. package/templates/example-chain/checkpoints/0b-plan-pending.json +81 -0
  143. package/templates/example-chain/checkpoints/1-parts-verdict/AGENTS.md +134 -0
  144. package/templates/example-chain/checkpoints/1-parts-verdict/CLAUDE.md +3 -0
  145. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/.gitkeep +0 -0
  146. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/approval.txt +1 -0
  147. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/assemble.json +309 -0
  148. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/reference.json +59 -0
  149. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/rig_type.json +59 -0
  150. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/interview/seethrough.json +186 -0
  151. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/parts-check.json +127 -0
  152. package/templates/example-chain/checkpoints/1-parts-verdict/evidence/split-config.json +1 -0
  153. package/templates/example-chain/checkpoints/1-parts-verdict/inputs/painting.png +0 -0
  154. package/templates/example-chain/checkpoints/1-parts-verdict/inputs/references/.gitkeep +0 -0
  155. package/templates/example-chain/checkpoints/1-parts-verdict/package.json +9 -0
  156. package/templates/example-chain/checkpoints/1-parts-verdict/project.json +955 -0
  157. package/templates/example-chain/checkpoints/1-parts-verdict.json +204 -0
  158. package/templates/example-chain/checkpoints/2-rigging-answers/AGENTS.md +134 -0
  159. package/templates/example-chain/checkpoints/2-rigging-answers/CLAUDE.md +3 -0
  160. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/.gitkeep +0 -0
  161. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/approval.txt +1 -0
  162. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/diff-components.json +34 -0
  163. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/assemble.json +309 -0
  164. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/bones.json +190 -0
  165. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/reference.json +59 -0
  166. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/rig_type.json +59 -0
  167. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/interview/seethrough.json +186 -0
  168. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/parts-check.json +127 -0
  169. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/parts-review.json +88 -0
  170. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
  171. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/split-config.json +1 -0
  172. package/templates/example-chain/checkpoints/2-rigging-answers/evidence/structure.json +30 -0
  173. package/templates/example-chain/checkpoints/2-rigging-answers/inputs/painting.png +0 -0
  174. package/templates/example-chain/checkpoints/2-rigging-answers/inputs/references/.gitkeep +0 -0
  175. package/templates/example-chain/checkpoints/2-rigging-answers/package.json +9 -0
  176. package/templates/example-chain/checkpoints/2-rigging-answers/project.json +1198 -0
  177. package/templates/example-chain/checkpoints/2-rigging-answers.json +254 -0
  178. package/templates/example-chain/checkpoints/3-rig-review/AGENTS.md +134 -0
  179. package/templates/example-chain/checkpoints/3-rig-review/CLAUDE.md +3 -0
  180. package/templates/example-chain/checkpoints/3-rig-review/evidence/.gitkeep +0 -0
  181. package/templates/example-chain/checkpoints/3-rig-review/evidence/approval.txt +1 -0
  182. package/templates/example-chain/checkpoints/3-rig-review/evidence/diff-components.json +34 -0
  183. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/assemble.json +309 -0
  184. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/bones.json +190 -0
  185. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/constraints.json +272 -0
  186. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/meshes.json +190 -0
  187. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/motion.json +272 -0
  188. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/reference.json +59 -0
  189. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/rig_type.json +59 -0
  190. package/templates/example-chain/checkpoints/3-rig-review/evidence/interview/seethrough.json +186 -0
  191. package/templates/example-chain/checkpoints/3-rig-review/evidence/parts-check.json +127 -0
  192. package/templates/example-chain/checkpoints/3-rig-review/evidence/parts-review.json +88 -0
  193. package/templates/example-chain/checkpoints/3-rig-review/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
  194. package/templates/example-chain/checkpoints/3-rig-review/evidence/setup.png +0 -0
  195. package/templates/example-chain/checkpoints/3-rig-review/evidence/split-config.json +1 -0
  196. package/templates/example-chain/checkpoints/3-rig-review/evidence/structure.json +30 -0
  197. package/templates/example-chain/checkpoints/3-rig-review/evidence/v1/config.json +88 -0
  198. package/templates/example-chain/checkpoints/3-rig-review/evidence/v1/interview-requirements.json +8 -0
  199. package/templates/example-chain/checkpoints/3-rig-review/inputs/painting.png +0 -0
  200. package/templates/example-chain/checkpoints/3-rig-review/inputs/references/.gitkeep +0 -0
  201. package/templates/example-chain/checkpoints/3-rig-review/package.json +9 -0
  202. package/templates/example-chain/checkpoints/3-rig-review/project.json +1909 -0
  203. package/templates/example-chain/checkpoints/3-rig-review.json +424 -0
  204. package/templates/example-chain/checkpoints/4-direction-post/AGENTS.md +134 -0
  205. package/templates/example-chain/checkpoints/4-direction-post/CLAUDE.md +3 -0
  206. package/templates/example-chain/checkpoints/4-direction-post/evidence/.gitkeep +0 -0
  207. package/templates/example-chain/checkpoints/4-direction-post/evidence/approval.txt +1 -0
  208. package/templates/example-chain/checkpoints/4-direction-post/evidence/diff-components.json +34 -0
  209. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/assemble.json +309 -0
  210. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/bones.json +190 -0
  211. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/camera_depth.json +102 -0
  212. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/constraints.json +272 -0
  213. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/meshes.json +190 -0
  214. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/motion.json +272 -0
  215. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/post_composition.json +122 -0
  216. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/post_timing.json +102 -0
  217. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/reference.json +59 -0
  218. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/rig_type.json +59 -0
  219. package/templates/example-chain/checkpoints/4-direction-post/evidence/interview/seethrough.json +186 -0
  220. package/templates/example-chain/checkpoints/4-direction-post/evidence/parts-check.json +127 -0
  221. package/templates/example-chain/checkpoints/4-direction-post/evidence/parts-review.json +88 -0
  222. package/templates/example-chain/checkpoints/4-direction-post/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
  223. package/templates/example-chain/checkpoints/4-direction-post/evidence/setup.png +0 -0
  224. package/templates/example-chain/checkpoints/4-direction-post/evidence/split-config.json +1 -0
  225. package/templates/example-chain/checkpoints/4-direction-post/evidence/structure.json +30 -0
  226. package/templates/example-chain/checkpoints/4-direction-post/evidence/v1/config.json +88 -0
  227. package/templates/example-chain/checkpoints/4-direction-post/evidence/v1/interview-requirements.json +8 -0
  228. package/templates/example-chain/checkpoints/4-direction-post/inputs/painting.png +0 -0
  229. package/templates/example-chain/checkpoints/4-direction-post/inputs/references/.gitkeep +0 -0
  230. package/templates/example-chain/checkpoints/4-direction-post/package.json +9 -0
  231. package/templates/example-chain/checkpoints/4-direction-post/project.json +2302 -0
  232. package/templates/example-chain/checkpoints/4-direction-post.json +456 -0
  233. package/templates/example-chain/evidence/.gitkeep +0 -0
  234. package/templates/example-chain/evidence/approval.txt +1 -0
  235. package/templates/example-chain/evidence/captions.json +101 -0
  236. package/templates/example-chain/evidence/diff-components.json +34 -0
  237. package/templates/example-chain/evidence/direction/v2.json +74 -0
  238. package/templates/example-chain/evidence/direction-input.json +74 -0
  239. package/templates/example-chain/evidence/interview/assemble.json +309 -0
  240. package/templates/example-chain/evidence/interview/bones.json +190 -0
  241. package/templates/example-chain/evidence/interview/camera_depth.json +102 -0
  242. package/templates/example-chain/evidence/interview/constraints.json +272 -0
  243. package/templates/example-chain/evidence/interview/meshes.json +190 -0
  244. package/templates/example-chain/evidence/interview/motion.json +272 -0
  245. package/templates/example-chain/evidence/interview/post_composition.json +122 -0
  246. package/templates/example-chain/evidence/interview/post_timing.json +102 -0
  247. package/templates/example-chain/evidence/interview/reference.json +59 -0
  248. package/templates/example-chain/evidence/interview/rig_type.json +59 -0
  249. package/templates/example-chain/evidence/interview/seethrough.json +186 -0
  250. package/templates/example-chain/evidence/parts-check.json +127 -0
  251. package/templates/example-chain/evidence/parts-review.json +88 -0
  252. package/templates/example-chain/evidence/review-reads/a12dea02d32508acc99386f894af76a14f1c85f45f804db2aa6b4dea5297973f.json +59 -0
  253. package/templates/example-chain/evidence/scene/s1.json +65 -0
  254. package/templates/example-chain/evidence/scene-citations.json +9 -0
  255. package/templates/example-chain/evidence/setup.png +0 -0
  256. package/templates/example-chain/evidence/split-config.json +1 -0
  257. package/templates/example-chain/evidence/structure.json +30 -0
  258. package/templates/example-chain/evidence/v1/config.json +88 -0
  259. package/templates/example-chain/evidence/v1/interview-requirements.json +8 -0
  260. package/templates/example-chain/inputs/background.png +0 -0
  261. package/templates/example-chain/inputs/painting.png +0 -0
  262. package/templates/example-chain/inputs/references/.gitkeep +0 -0
  263. package/templates/example-chain/package.json +9 -0
  264. package/templates/example-chain/project.json +2593 -0
  265. package/templates/example-diff/diff-components.json +71 -0
  266. package/templates/example-diff/recomposite_rig.png +0 -0
  267. package/templates/example-direction/approval.txt +1 -0
  268. package/templates/example-direction/derived/review/v1/out/check/build/rectangle.png +0 -0
  269. package/templates/example-direction/derived/review/v1/out/check/build/run.json +40 -0
  270. package/templates/example-direction/derived/review/v1/out/check/build/skeleton.atlas +8 -0
  271. package/templates/example-direction/derived/review/v1/out/check/build/skeleton.json +73 -0
  272. package/templates/example-direction/derived/review/v1/out/check/build/skeleton.model.json +195 -0
  273. package/templates/example-direction/evidence/direction/v2.json +69 -0
  274. package/templates/example-direction/evidence/direction/v3.json +59 -0
  275. package/templates/example-direction/evidence/images/rectangle.png +0 -0
  276. package/templates/example-direction/evidence/input.json +69 -0
  277. package/templates/example-direction/evidence/motion.json +33 -0
  278. package/templates/example-direction/evidence/patch.json +23 -0
  279. package/templates/example-direction/evidence/rig-config.json +55 -0
  280. package/templates/example-direction/evidence/still.png +0 -0
  281. package/templates/example-direction/project.json +372 -0
  282. package/templates/example-direction/reasoning.txt +1 -0
  283. package/templates/example-direction/words.txt +1 -0
  284. package/templates/example-draft/README.md +28 -0
  285. package/templates/example-draft/config.json +71 -0
  286. package/templates/example-draft/derived/drafts/lantern/config.json +71 -0
  287. package/templates/example-draft/derived/drafts/lantern/input/painting_in_rig.png +0 -0
  288. package/templates/example-draft/derived/drafts/lantern/input/parts/background.png +0 -0
  289. package/templates/example-draft/derived/drafts/lantern/input/parts/eye_l.png +0 -0
  290. package/templates/example-draft/derived/drafts/lantern/input/parts/eye_r.png +0 -0
  291. package/templates/example-draft/derived/drafts/lantern/input/parts/face.png +0 -0
  292. package/templates/example-draft/derived/drafts/lantern/input/parts/lantern.png +0 -0
  293. package/templates/example-draft/derived/drafts/lantern/input/parts/torso.png +0 -0
  294. package/templates/example-draft/derived/drafts/lantern/input/parts.json +188 -0
  295. package/templates/example-draft/derived/drafts/lantern/input/recomposite_rig.png +0 -0
  296. package/templates/example-draft/derived/drafts/lantern/interview-requirements.json +8 -0
  297. package/templates/example-draft/derived/drafts/lantern/out/check/build/run.json +40 -0
  298. package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.atlas +28 -0
  299. package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.json +193 -0
  300. package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.model.json +483 -0
  301. package/templates/example-draft/derived/drafts/lantern/out/check/build/skeleton.png +0 -0
  302. package/templates/example-draft/derived/drafts/lantern/out/images/background.png +0 -0
  303. package/templates/example-draft/derived/drafts/lantern/out/images/eye_l.png +0 -0
  304. package/templates/example-draft/derived/drafts/lantern/out/images/eye_r.png +0 -0
  305. package/templates/example-draft/derived/drafts/lantern/out/images/face.png +0 -0
  306. package/templates/example-draft/derived/drafts/lantern/out/images/lantern.png +0 -0
  307. package/templates/example-draft/derived/drafts/lantern/out/images/torso.png +0 -0
  308. package/templates/example-draft/derived/drafts/lantern/out/mesh_report.json +1 -0
  309. package/templates/example-draft/derived/drafts/lantern/out/motion.json +137 -0
  310. package/templates/example-draft/derived/drafts/lantern/out/rig.json +102 -0
  311. package/templates/example-draft/derived/drafts/lantern/out/run.json +52 -0
  312. package/templates/example-draft/derived/parts/rig/painting_in_rig.png +0 -0
  313. package/templates/example-draft/derived/parts/rig/parts/background.png +0 -0
  314. package/templates/example-draft/derived/parts/rig/parts/eye_l.png +0 -0
  315. package/templates/example-draft/derived/parts/rig/parts/eye_r.png +0 -0
  316. package/templates/example-draft/derived/parts/rig/parts/face.png +0 -0
  317. package/templates/example-draft/derived/parts/rig/parts/lantern.png +0 -0
  318. package/templates/example-draft/derived/parts/rig/parts/torso.png +0 -0
  319. package/templates/example-draft/derived/parts/rig/parts.json +188 -0
  320. package/templates/example-draft/derived/parts/rig/recomposite_rig.png +0 -0
  321. package/templates/example-draft/derived/review/v1/config.json +71 -0
  322. package/templates/example-draft/derived/review/v1/interview-requirements.json +8 -0
  323. package/templates/example-draft/derived/review/v1/out/check/build/run.json +40 -0
  324. package/templates/example-draft/derived/review/v1/out/check/build/skeleton.atlas +28 -0
  325. package/templates/example-draft/derived/review/v1/out/check/build/skeleton.json +193 -0
  326. package/templates/example-draft/derived/review/v1/out/check/build/skeleton.model.json +483 -0
  327. package/templates/example-draft/derived/review/v1/out/check/build/skeleton.png +0 -0
  328. package/templates/example-draft/derived/review/v1/out/parts/background.png +0 -0
  329. package/templates/example-draft/derived/review/v1/out/parts/eye_l.png +0 -0
  330. package/templates/example-draft/derived/review/v1/out/parts/eye_r.png +0 -0
  331. package/templates/example-draft/derived/review/v1/out/parts/face.png +0 -0
  332. package/templates/example-draft/derived/review/v1/out/parts/lantern.png +0 -0
  333. package/templates/example-draft/derived/review/v1/out/parts/torso.png +0 -0
  334. package/templates/example-draft/derived/review/v1/out/parts.json +188 -0
  335. package/templates/example-draft/derived/review/v1/out/rig/images/background.png +0 -0
  336. package/templates/example-draft/derived/review/v1/out/rig/images/eye_l.png +0 -0
  337. package/templates/example-draft/derived/review/v1/out/rig/images/eye_r.png +0 -0
  338. package/templates/example-draft/derived/review/v1/out/rig/images/face.png +0 -0
  339. package/templates/example-draft/derived/review/v1/out/rig/images/lantern.png +0 -0
  340. package/templates/example-draft/derived/review/v1/out/rig/images/torso.png +0 -0
  341. package/templates/example-draft/derived/review/v1/out/rig/mesh_report.json +1 -0
  342. package/templates/example-draft/derived/review/v1/out/rig/motion.json +137 -0
  343. package/templates/example-draft/derived/review/v1/out/rig/rig.json +102 -0
  344. package/templates/example-draft/derived/review/v1/out/rig/run.json +52 -0
  345. package/templates/example-draft/derived/review/v2/config.json +71 -0
  346. package/templates/example-draft/derived/review/v2/interview-requirements.json +8 -0
  347. package/templates/example-draft/derived/review/v2/out/check/build/run.json +40 -0
  348. package/templates/example-draft/derived/review/v2/out/check/build/skeleton.atlas +28 -0
  349. package/templates/example-draft/derived/review/v2/out/check/build/skeleton.json +193 -0
  350. package/templates/example-draft/derived/review/v2/out/check/build/skeleton.model.json +483 -0
  351. package/templates/example-draft/derived/review/v2/out/check/build/skeleton.png +0 -0
  352. package/templates/example-draft/derived/review/v2/out/parts/background.png +0 -0
  353. package/templates/example-draft/derived/review/v2/out/parts/eye_l.png +0 -0
  354. package/templates/example-draft/derived/review/v2/out/parts/eye_r.png +0 -0
  355. package/templates/example-draft/derived/review/v2/out/parts/face.png +0 -0
  356. package/templates/example-draft/derived/review/v2/out/parts/lantern.png +0 -0
  357. package/templates/example-draft/derived/review/v2/out/parts/torso.png +0 -0
  358. package/templates/example-draft/derived/review/v2/out/parts.json +188 -0
  359. package/templates/example-draft/derived/review/v2/out/rig/images/background.png +0 -0
  360. package/templates/example-draft/derived/review/v2/out/rig/images/eye_l.png +0 -0
  361. package/templates/example-draft/derived/review/v2/out/rig/images/eye_r.png +0 -0
  362. package/templates/example-draft/derived/review/v2/out/rig/images/face.png +0 -0
  363. package/templates/example-draft/derived/review/v2/out/rig/images/lantern.png +0 -0
  364. package/templates/example-draft/derived/review/v2/out/rig/images/torso.png +0 -0
  365. package/templates/example-draft/derived/review/v2/out/rig/mesh_report.json +1 -0
  366. package/templates/example-draft/derived/review/v2/out/rig/motion.json +137 -0
  367. package/templates/example-draft/derived/review/v2/out/rig/rig.json +102 -0
  368. package/templates/example-draft/derived/review/v2/out/rig/run.json +52 -0
  369. package/templates/example-draft/evidence/diff-components.json +71 -0
  370. package/templates/example-draft/evidence/direction/v4.json +64 -0
  371. package/templates/example-draft/evidence/direction-input-v4.json +64 -0
  372. package/templates/example-draft/evidence/fix-ledger/lantern.json +17 -0
  373. package/templates/example-draft/evidence/parts-check.json +127 -0
  374. package/templates/example-draft/evidence/parts-review.json +88 -0
  375. package/templates/example-draft/evidence/review-reads/4ed0507381809746443021554e012032be5b5f8417da0f2e525e9b6df16e9f05.json +59 -0
  376. package/templates/example-draft/evidence/scene/s1.json +65 -0
  377. package/templates/example-draft/evidence/scene-background.png +0 -0
  378. package/templates/example-draft/evidence/scene-citations.json +9 -0
  379. package/templates/example-draft/evidence/structure-decisions.json +9 -0
  380. package/templates/example-draft/evidence/v1/interview-config.json +71 -0
  381. package/templates/example-draft/evidence/v1/interview-requirements.json +8 -0
  382. package/templates/example-draft/evidence/v1/review-config.json +71 -0
  383. package/templates/example-draft/evidence/v2/interview-config.json +71 -0
  384. package/templates/example-draft/evidence/v2/interview-requirements.json +8 -0
  385. package/templates/example-draft/evidence/v2/review-config.json +71 -0
  386. package/templates/example-draft/input/painting_in_rig.png +0 -0
  387. package/templates/example-draft/input/parts/background.png +0 -0
  388. package/templates/example-draft/input/parts/eye_l.png +0 -0
  389. package/templates/example-draft/input/parts/eye_r.png +0 -0
  390. package/templates/example-draft/input/parts/face.png +0 -0
  391. package/templates/example-draft/input/parts/lantern.png +0 -0
  392. package/templates/example-draft/input/parts/torso.png +0 -0
  393. package/templates/example-draft/input/parts.json +188 -0
  394. package/templates/example-draft/input/recomposite_rig.png +0 -0
  395. package/templates/example-draft/inputs/painting.png +0 -0
  396. package/templates/example-draft/interview-requirements.json +8 -0
  397. package/templates/example-draft/out/check/build/run.json +40 -0
  398. package/templates/example-draft/out/check/build/skeleton.atlas +28 -0
  399. package/templates/example-draft/out/check/build/skeleton.json +193 -0
  400. package/templates/example-draft/out/check/build/skeleton.model.json +483 -0
  401. package/templates/example-draft/out/check/build/skeleton.png +0 -0
  402. package/templates/example-draft/out/images/background.png +0 -0
  403. package/templates/example-draft/out/images/eye_l.png +0 -0
  404. package/templates/example-draft/out/images/eye_r.png +0 -0
  405. package/templates/example-draft/out/images/face.png +0 -0
  406. package/templates/example-draft/out/images/lantern.png +0 -0
  407. package/templates/example-draft/out/images/torso.png +0 -0
  408. package/templates/example-draft/out/mesh_report.json +1 -0
  409. package/templates/example-draft/out/motion.json +137 -0
  410. package/templates/example-draft/out/rig.json +102 -0
  411. package/templates/example-draft/out/run.json +52 -0
  412. package/templates/example-draft/outputs/v1/out/check/build/run.json +40 -0
  413. package/templates/example-draft/outputs/v1/out/check/build/skeleton.atlas +28 -0
  414. package/templates/example-draft/outputs/v1/out/check/build/skeleton.json +193 -0
  415. package/templates/example-draft/outputs/v1/out/check/build/skeleton.model.json +483 -0
  416. package/templates/example-draft/outputs/v1/out/check/build/skeleton.png +0 -0
  417. package/templates/example-draft/outputs/v1/out/painting_in_rig.png +0 -0
  418. package/templates/example-draft/outputs/v1/out/parts/background.png +0 -0
  419. package/templates/example-draft/outputs/v1/out/parts/eye_l.png +0 -0
  420. package/templates/example-draft/outputs/v1/out/parts/eye_r.png +0 -0
  421. package/templates/example-draft/outputs/v1/out/parts/face.png +0 -0
  422. package/templates/example-draft/outputs/v1/out/parts/lantern.png +0 -0
  423. package/templates/example-draft/outputs/v1/out/parts/torso.png +0 -0
  424. package/templates/example-draft/outputs/v1/out/parts.json +188 -0
  425. package/templates/example-draft/outputs/v1/out/rig/images/background.png +0 -0
  426. package/templates/example-draft/outputs/v1/out/rig/images/eye_l.png +0 -0
  427. package/templates/example-draft/outputs/v1/out/rig/images/eye_r.png +0 -0
  428. package/templates/example-draft/outputs/v1/out/rig/images/face.png +0 -0
  429. package/templates/example-draft/outputs/v1/out/rig/images/lantern.png +0 -0
  430. package/templates/example-draft/outputs/v1/out/rig/images/torso.png +0 -0
  431. package/templates/example-draft/outputs/v1/out/rig/mesh_report.json +1 -0
  432. package/templates/example-draft/outputs/v1/out/rig/motion.json +137 -0
  433. package/templates/example-draft/outputs/v1/out/rig/rig.json +102 -0
  434. package/templates/example-draft/outputs/v1/out/rig/run.json +52 -0
  435. package/templates/example-draft/outputs/v2/out/check/build/run.json +40 -0
  436. package/templates/example-draft/outputs/v2/out/check/build/skeleton.atlas +28 -0
  437. package/templates/example-draft/outputs/v2/out/check/build/skeleton.json +193 -0
  438. package/templates/example-draft/outputs/v2/out/check/build/skeleton.model.json +483 -0
  439. package/templates/example-draft/outputs/v2/out/check/build/skeleton.png +0 -0
  440. package/templates/example-draft/outputs/v2/out/painting_in_rig.png +0 -0
  441. package/templates/example-draft/outputs/v2/out/parts/background.png +0 -0
  442. package/templates/example-draft/outputs/v2/out/parts/eye_l.png +0 -0
  443. package/templates/example-draft/outputs/v2/out/parts/eye_r.png +0 -0
  444. package/templates/example-draft/outputs/v2/out/parts/face.png +0 -0
  445. package/templates/example-draft/outputs/v2/out/parts/lantern.png +0 -0
  446. package/templates/example-draft/outputs/v2/out/parts/torso.png +0 -0
  447. package/templates/example-draft/outputs/v2/out/parts.json +188 -0
  448. package/templates/example-draft/outputs/v2/out/rig/images/background.png +0 -0
  449. package/templates/example-draft/outputs/v2/out/rig/images/eye_l.png +0 -0
  450. package/templates/example-draft/outputs/v2/out/rig/images/eye_r.png +0 -0
  451. package/templates/example-draft/outputs/v2/out/rig/images/face.png +0 -0
  452. package/templates/example-draft/outputs/v2/out/rig/images/lantern.png +0 -0
  453. package/templates/example-draft/outputs/v2/out/rig/images/torso.png +0 -0
  454. package/templates/example-draft/outputs/v2/out/rig/mesh_report.json +1 -0
  455. package/templates/example-draft/outputs/v2/out/rig/motion.json +137 -0
  456. package/templates/example-draft/outputs/v2/out/rig/rig.json +102 -0
  457. package/templates/example-draft/outputs/v2/out/rig/run.json +52 -0
  458. package/templates/example-interview-choice.json +1068 -0
  459. package/templates/example-interview-options/constraint.json +376 -0
  460. package/templates/example-interview-options/example-assemble.json +376 -0
  461. package/templates/example-interview-options/example-bones.json +190 -0
  462. package/templates/example-interview-options/example-camera-depth.json +99 -0
  463. package/templates/example-interview-options/example-constraints.json +272 -0
  464. package/templates/example-interview-options/example-meshes.json +190 -0
  465. package/templates/example-interview-options/example-motion.json +272 -0
  466. package/templates/example-interview-options/example-picks.json +15 -0
  467. package/templates/example-interview-options/example-post-composition.json +110 -0
  468. package/templates/example-interview-options/example-post-timing.json +105 -0
  469. package/templates/example-interview-options/example-reference.json +63 -0
  470. package/templates/example-interview-options/example-rig-type.json +63 -0
  471. package/templates/example-interview-options/example-seethrough.json +190 -0
  472. package/templates/example-interview-options/fullscreen.json +63 -0
  473. package/templates/example-interview-options/other-explicit.json +63 -0
  474. package/templates/example-interview-options/other-pending.json +63 -0
  475. package/templates/example-interview-options/other-picks.json +16 -0
  476. package/templates/example-interview.md +213 -0
  477. package/templates/example-invariants/invariants.json +1339 -0
  478. package/templates/example-prepared/prepared.svg +1 -0
  479. package/templates/example-prepared/source.svg +1 -0
  480. package/templates/example-project.json +4259 -0
  481. package/templates/example-remesh/AGENTS.md +134 -0
  482. package/templates/example-remesh/CLAUDE.md +3 -0
  483. package/templates/example-remesh/evidence/.gitkeep +0 -0
  484. package/templates/example-remesh/evidence/approval.txt +1 -0
  485. package/templates/example-remesh/evidence/captions.json +101 -0
  486. package/templates/example-remesh/evidence/diff-components.json +34 -0
  487. package/templates/example-remesh/evidence/direction/v4.json +74 -0
  488. package/templates/example-remesh/evidence/direction/v5.json +74 -0
  489. package/templates/example-remesh/evidence/direction-input-v4.json +74 -0
  490. package/templates/example-remesh/evidence/direction-input-v5.json +74 -0
  491. package/templates/example-remesh/evidence/interview/assemble.json +309 -0
  492. package/templates/example-remesh/evidence/interview/bones.json +190 -0
  493. package/templates/example-remesh/evidence/interview/camera_depth.json +103 -0
  494. package/templates/example-remesh/evidence/interview/constraints.json +272 -0
  495. package/templates/example-remesh/evidence/interview/meshes.json +190 -0
  496. package/templates/example-remesh/evidence/interview/motion.json +272 -0
  497. package/templates/example-remesh/evidence/interview/post_composition.json +123 -0
  498. package/templates/example-remesh/evidence/interview/post_timing.json +103 -0
  499. package/templates/example-remesh/evidence/interview/reference.json +59 -0
  500. package/templates/example-remesh/evidence/interview/rig_type.json +59 -0
  501. package/templates/example-remesh/evidence/interview/seethrough.json +186 -0
  502. package/templates/example-remesh/evidence/parts-check.json +127 -0
  503. package/templates/example-remesh/evidence/parts-review.json +88 -0
  504. package/templates/example-remesh/evidence/reopen.txt +1 -0
  505. package/templates/example-remesh/evidence/review-reads/6a17db694db96f1b6049a61a017e0c8e66757b982b8851e0187e561cfcf9af68.json +59 -0
  506. package/templates/example-remesh/evidence/scene/s1.json +69 -0
  507. package/templates/example-remesh/evidence/scene-citations.json +9 -0
  508. package/templates/example-remesh/evidence/setup.png +0 -0
  509. package/templates/example-remesh/evidence/split-config.json +1 -0
  510. package/templates/example-remesh/evidence/structure.json +30 -0
  511. package/templates/example-remesh/evidence/v1/registration/at-source.json +44 -0
  512. package/templates/example-remesh/evidence/v1/registration/citations.json +68 -0
  513. package/templates/example-remesh/evidence/v1/registration/compile-report.json +368 -0
  514. package/templates/example-remesh/evidence/v1/registration/config-source.json +88 -0
  515. package/templates/example-remesh/evidence/v1/registration/config.json +88 -0
  516. package/templates/example-remesh/evidence/v1/registration/interview-requirements.json +8 -0
  517. package/templates/example-remesh/evidence/v1/registration/package-lock.json +11 -0
  518. package/templates/example-remesh/evidence/v1/registration/path-adapter.json +1 -0
  519. package/templates/example-remesh/evidence/v1/registration/rigc-run-1.json +44 -0
  520. package/templates/example-remesh/evidence/v2/registration/at-source.json +44 -0
  521. package/templates/example-remesh/evidence/v2/registration/citations.json +68 -0
  522. package/templates/example-remesh/evidence/v2/registration/compile-report.json +368 -0
  523. package/templates/example-remesh/evidence/v2/registration/config-source.json +88 -0
  524. package/templates/example-remesh/evidence/v2/registration/config.json +88 -0
  525. package/templates/example-remesh/evidence/v2/registration/interview-requirements.json +8 -0
  526. package/templates/example-remesh/evidence/v2/registration/package-lock.json +11 -0
  527. package/templates/example-remesh/evidence/v2/registration/path-adapter.json +1 -0
  528. package/templates/example-remesh/evidence/v2/registration/rigc-run-1.json +44 -0
  529. package/templates/example-remesh/evidence/v3/registration/at-source.json +44 -0
  530. package/templates/example-remesh/evidence/v3/registration/citations.json +68 -0
  531. package/templates/example-remesh/evidence/v3/registration/compile-report.json +368 -0
  532. package/templates/example-remesh/evidence/v3/registration/config-source.json +88 -0
  533. package/templates/example-remesh/evidence/v3/registration/config.json +88 -0
  534. package/templates/example-remesh/evidence/v3/registration/interview-requirements.json +8 -0
  535. package/templates/example-remesh/evidence/v3/registration/package-lock.json +11 -0
  536. package/templates/example-remesh/evidence/v3/registration/path-adapter.json +1 -0
  537. package/templates/example-remesh/evidence/v3/registration/rigc-run-1.json +44 -0
  538. package/templates/example-remesh/inputs/background.png +0 -0
  539. package/templates/example-remesh/inputs/painting.png +0 -0
  540. package/templates/example-remesh/inputs/references/.gitkeep +0 -0
  541. package/templates/example-remesh/package.json +9 -0
  542. package/templates/example-remesh/project.json +3916 -0
  543. package/templates/example-reopened/project.json +4568 -0
  544. package/templates/example-review/config.json +1 -0
  545. package/templates/example-review/out/check/build/skeleton.model.json +1 -0
  546. package/templates/example-review/out/parts/piece.svg +1 -0
  547. package/templates/example-review/out/parts.json +1 -0
  548. package/templates/example-review/out/rig/mesh_report.json +1 -0
  549. package/templates/example-review/out/rig/motion.json +1 -0
  550. package/templates/example-review/out/rig/rig.json +1 -0
  551. package/templates/example-split/captions.json +357 -0
  552. package/templates/example-split/config.json +215 -0
  553. package/templates/legacy-example-project.json +1071 -0
  554. package/templates/project.json +199 -0
  555. 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
+ }