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,1047 @@
1
+ /** Register frozen bytes. Toolchain association and the not-after date are declarations. */
2
+ import { createHash } from 'node:crypto';
3
+ import { closeSync, mkdirSync, openSync, renameSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from './check-reads.ts';
5
+ import { acquireBoundJobWrite, prepareVersionIntent, publishCommittedVersion } from './job-writer.ts';
6
+ import { cancelUncommittedIntent, type PendingPublication } from './job-outbox.ts';
7
+ import { partsFileReferences } from '@rig-c/parts/src/parts.ts';
8
+ import { tmpdir } from 'node:os';
9
+ import { dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from 'node:path';
10
+ import { isDeepStrictEqual } from 'node:util';
11
+ import { asArray, checkProject, isObject, type Finding } from './check.ts';
12
+ import { literalFile } from './choices.ts';
13
+ import { configInputReferences } from './config-inputs.ts';
14
+ import { parseFeedbackAnswers, readProject } from './feedback.ts';
15
+ import { Refusal, refuse, USAGE_EXIT } from './refusal.ts';
16
+ import { baselineOf, newFindings } from './refusal-baseline.ts';
17
+ import { collectReopens, reopenTime } from './reopen.ts';
18
+ import { prepareRigReviewPackage } from './review-package.ts';
19
+ import { rigVersionDateFinding } from './rig-version-date.ts';
20
+ import { checkRegisteredRuns, RUN_RECORD_SINCE, RunRecordError, validateRunRecord, type RunSpec } from './run-record.ts';
21
+ import { approvedStructureDelta, structurePathIsIgnored } from './structure.ts';
22
+ import type { Json, JsonObject } from './templates.ts';
23
+
24
+ const sha = (bytes: Buffer): string => createHash('sha256').update(bytes).digest('hex');
25
+ const absolute = (path: string): boolean => isAbsolute(path) || win32.isAbsolute(path);
26
+ const literal = (path: string): boolean => !!path && !absolute(path) && !path.includes('\\') && !path.includes(':') && path.split('/').every(p => p !== '' && p !== '.' && p !== '..');
27
+ const escapePointerToken = (token: string): string => token.replaceAll('~', '~0').replaceAll('/', '~1');
28
+ const insideOrEqual = (root: string, target: string): boolean => {
29
+ const rel = relative(root, target);
30
+ return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
31
+ };
32
+ const RESERVED_REGISTRATION_NAMES = new Set([
33
+ 'at-source.json',
34
+ 'compile-report.json',
35
+ 'config-source.json',
36
+ 'requirements-source.json',
37
+ 'config.json',
38
+ 'package-lock.json',
39
+ 'path-adapter.json',
40
+ 'citations.json',
41
+ ]);
42
+ function isValidAdaptedTarget(target: string): boolean {
43
+ return literal(target)
44
+ && !RESERVED_REGISTRATION_NAMES.has(target)
45
+ && !/^rig(?:c)?-run-\d+\.json$/.test(target)
46
+ && target !== 'out'
47
+ && !target.startsWith('out/');
48
+ }
49
+ function fail(code: string, path: string, why: string): never {
50
+ refuse(code, `${path}: ${why}; retain the cited frozen inputs and register a new rig version`);
51
+ }
52
+
53
+ function bytesAt(dir: string, binding: Json | undefined, output = false): Buffer {
54
+ if (!isObject(binding) || typeof binding.path !== 'string' || !literal(binding.path)
55
+ || typeof binding.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(binding.sha256)) throw Error('expected literal retained path and sha256');
56
+ const file = literalFile(dir, binding.path);
57
+ if (!output && structurePathIsIgnored(dir, binding.path)) throw Error('evidence is under an ignored root');
58
+ const bytes = readFileSync(file);
59
+ if (sha(bytes) !== binding.sha256) throw Error(`${binding.path} bytes changed`);
60
+ return bytes;
61
+ }
62
+
63
+ function lockVersions(raw: Json): JsonObject {
64
+ if (!isObject(raw) || raw.lockfileVersion !== 3 || !isObject(raw.packages)) throw Error('supply an npm lockfileVersion 3 package-lock.json');
65
+ const versions: JsonObject = {};
66
+ for (const name of ['@rig-c/parts', '@rig-c/core']) {
67
+ const pkg = raw.packages[`node_modules/${name}`];
68
+ if (!isObject(pkg) || typeof pkg.version !== 'string' || !pkg.version.trim()) throw Error(`missing packages[node_modules/${name}].version`);
69
+ versions[name] = pkg.version;
70
+ }
71
+ return versions;
72
+ }
73
+
74
+ function pointerTokens(pointer: Json | undefined): string[] {
75
+ if (typeof pointer !== 'string' || !pointer.startsWith('/') || pointer.length < 2 || /~(?![01])/.test(pointer)) {
76
+ throw Error('supply a nonempty RFC 6901 JSON pointer');
77
+ }
78
+ const raw = pointer.slice(1).split('/');
79
+ if (raw.some(t => t === '')) throw Error('supply a nonempty RFC 6901 JSON pointer without empty segments');
80
+ return raw.map(t => t.replaceAll('~1', '/').replaceAll('~0', '~'));
81
+ }
82
+
83
+ function pointerValue(raw: Json, pointer: Json | undefined): Json | undefined {
84
+ let value: Json | undefined = raw;
85
+ for (const key of pointerTokens(pointer)) {
86
+ if (!isObject(value) && !Array.isArray(value)) return undefined;
87
+ if (!Object.hasOwn(value, key)) return undefined;
88
+ value = (value as JsonObject)[key];
89
+ }
90
+ return value;
91
+ }
92
+
93
+ function setPointerValue(raw: Json, pointer: string, next: string): void {
94
+ const tokens = pointerTokens(pointer);
95
+ let cur: Json = raw;
96
+ for (let i = 0; i < tokens.length - 1; i++) {
97
+ const key = tokens[i]!;
98
+ if (Array.isArray(cur)) {
99
+ if (!/^(?:0|[1-9]\d*)$/.test(key) || Number(key) >= cur.length) throw Error(`pointer ${pointer} not found`);
100
+ cur = cur[Number(key)]!;
101
+ } else if (isObject(cur) && Object.hasOwn(cur, key)) {
102
+ cur = cur[key]!;
103
+ } else throw Error(`pointer ${pointer} not found`);
104
+ }
105
+ const last = tokens.at(-1)!;
106
+ if (Array.isArray(cur)) {
107
+ if (!/^(?:0|[1-9]\d*)$/.test(last) || Number(last) >= cur.length || typeof cur[Number(last)] !== 'string') {
108
+ throw Error(`pointer ${pointer} does not select a string`);
109
+ }
110
+ cur[Number(last)] = next;
111
+ } else if (isObject(cur) && Object.hasOwn(cur, last) && typeof cur[last] === 'string') {
112
+ cur[last] = next;
113
+ } else throw Error(`pointer ${pointer} does not select a string`);
114
+ }
115
+
116
+ function utcDate(raw: Json | undefined): raw is string {
117
+ return typeof raw === 'string' && /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{1,9})?(?:Z|\+00:00)$/.test(raw)
118
+ && rigVersionDateFinding({ kind: 'rig', at: raw }) === null
119
+ && Number(raw.slice(11, 13)) < 24 && Number(raw.slice(14, 16)) < 60 && Number(raw.slice(17, 19)) < 60;
120
+ }
121
+
122
+ const versionDestinations = (n: number): [string, string, string] => [
123
+ `outputs/v${n}`,
124
+ `evidence/v${n}/registration`,
125
+ `derived/review/v${n}`,
126
+ ];
127
+
128
+ function destinationExists(dir: string, target: string, onInvalidPath: (target: string) => never): boolean {
129
+ let current = dir;
130
+ for (const part of target.split('/')) {
131
+ current = join(current, part);
132
+ if (existsSync(current) && (lstatSync(current).isSymbolicLink() || !lstatSync(current).isDirectory())) onInvalidPath(target);
133
+ }
134
+ return existsSync(join(dir, target));
135
+ }
136
+
137
+ function treeBindings(dir: string, root: string): JsonObject[] {
138
+ return readdirSync(join(dir, root)).sort().flatMap(name => {
139
+ const path = `${root}/${name}`;
140
+ const stat = lstatSync(join(dir, path));
141
+ if (stat.isSymbolicLink()) throw Error(`${path}: symlinks are unsupported`);
142
+ if (stat.isDirectory()) return treeBindings(dir, path);
143
+ const bytes = readFileSync(literalFile(dir, path));
144
+ return [{ path, sha256: sha(bytes), bytes: bytes.length }];
145
+ });
146
+ }
147
+
148
+ interface NormalizedAdapterRow {
149
+ kind: 'legacy-abs' | 'pointer';
150
+ doc: 'config' | 'requirements';
151
+ pointer: string;
152
+ originalString: string;
153
+ sourcePath?: string;
154
+ adaptedString: string;
155
+ raw: JsonObject;
156
+ }
157
+
158
+ function parsePathAdapter(raw: Json): NormalizedAdapterRow[] {
159
+ if (!Array.isArray(raw)) throw Error('supply an array of path adapter mappings');
160
+ const rows: NormalizedAdapterRow[] = [];
161
+ const seenLegacyAt = new Set<string>();
162
+ const seenPointers = new Set<string>();
163
+ for (const entry of raw) {
164
+ if (!isObject(entry)) throw Error('supply object path adapter mappings');
165
+ let row: NormalizedAdapterRow;
166
+ if (entry.doc === undefined && entry.pointer === undefined) {
167
+ if (typeof entry.at !== 'string' || typeof entry.before !== 'string' || typeof entry.after !== 'string'
168
+ || seenLegacyAt.has(entry.at)) {
169
+ throw Error('supply distinct at/before/after mappings');
170
+ }
171
+ seenLegacyAt.add(entry.at);
172
+ const validAt = entry.at.startsWith('.') && entry.at.length >= 2 && !entry.at.slice(1).split('.').some(p => !p);
173
+ const parts = validAt ? entry.at.slice(1).split('.') : [];
174
+ const pointer = validAt ? `/${parts.map(escapePointerToken).join('/')}` : '';
175
+ const adaptedString = validAt ? `inputs/${parts.join('/')}` : '';
176
+ row = { kind: 'legacy-abs', doc: 'config', pointer, originalString: entry.after, sourcePath: entry.before, adaptedString, raw: entry };
177
+ } else if (Object.keys(entry).sort().join(',') === 'after,before,doc,pointer') {
178
+ if ((entry.doc !== 'config' && entry.doc !== 'requirements')
179
+ || typeof entry.pointer !== 'string'
180
+ || typeof entry.before !== 'string' || !entry.before || absolute(entry.before)
181
+ || typeof entry.after !== 'string' || !isValidAdaptedTarget(entry.after)) {
182
+ throw Error('supply doc ("config"|"requirements"), RFC 6901 pointer, relative before and literal in-package after');
183
+ }
184
+ pointerTokens(entry.pointer);
185
+ const pointerKey = `${entry.doc}:${entry.pointer}`;
186
+ if (seenPointers.has(pointerKey)) throw Error(`duplicate path adapter target ${pointerKey}`);
187
+ seenPointers.add(pointerKey);
188
+ row = { kind: 'pointer', doc: entry.doc, pointer: entry.pointer, originalString: entry.before, adaptedString: entry.after, raw: entry };
189
+ } else {
190
+ throw Error('supply distinct at/before/after or doc/pointer/before/after mappings');
191
+ }
192
+ rows.push(row);
193
+ }
194
+ return rows;
195
+ }
196
+
197
+ interface AssetRefSpec {
198
+ doc: 'config' | 'requirements';
199
+ pointer: string;
200
+ dotPath: string;
201
+ value: string;
202
+ kind: 'file' | 'dir' | 'file-or-dir';
203
+ autoRelocate: boolean;
204
+ pre394: boolean;
205
+ declaredSha256?: string;
206
+ }
207
+
208
+ function collectConfigAssetRefs(dir: string, config: JsonObject, strict = true): AssetRefSpec[] {
209
+ const byPointer = new Map<string, AssetRefSpec>();
210
+ const addRef = (spec: AssetRefSpec): void => {
211
+ if (!byPointer.has(spec.pointer)) byPointer.set(spec.pointer, spec);
212
+ };
213
+ const assemble = isObject(config.assemble) ? config.assemble : undefined;
214
+ const occluders = isObject(assemble) && Array.isArray(assemble.occluders) ? assemble.occluders : [];
215
+ for (const ref of configInputReferences(dir, config)) {
216
+ const dotPath = ref.at.replace(/\[(\d+)\]/g, '.$1');
217
+ const pointer = `/${dotPath.split('.').map(escapePointerToken).join('/')}`;
218
+ let declaredSha256: string | undefined;
219
+ const occMatch = /^assemble\.occluders\.(\d+)\.image$/.exec(dotPath);
220
+ if (occMatch) {
221
+ const entry = occluders[Number(occMatch[1])];
222
+ if (isObject(entry) && entry.sha256 !== undefined) {
223
+ if (typeof entry.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(entry.sha256)) {
224
+ if (strict) throw Error(`config.${dotPath}: expected 64-character lowercase hex sha256`);
225
+ } else {
226
+ declaredSha256 = entry.sha256;
227
+ }
228
+ }
229
+ }
230
+ addRef({
231
+ doc: 'config',
232
+ pointer,
233
+ dotPath,
234
+ value: ref.value,
235
+ kind: dotPath.endsWith('.plate') ? 'dir' : 'file-or-dir',
236
+ autoRelocate: absolute(ref.value) || !isValidAdaptedTarget(ref.value),
237
+ pre394: true,
238
+ ...(strict && declaredSha256 ? { declaredSha256 } : {}),
239
+ });
240
+ }
241
+ if (isObject(assemble)) {
242
+ if (Array.isArray(assemble.take)) {
243
+ for (const [i, entry] of assemble.take.entries()) {
244
+ if (!isObject(entry)) continue;
245
+ if (typeof entry.run_dir === 'string' && entry.run_dir !== '') {
246
+ addRef({ doc: 'config', pointer: `/assemble/take/${i}/run_dir`, dotPath: `assemble.take.${i}.run_dir`, value: entry.run_dir, kind: 'dir', autoRelocate: true, pre394: true });
247
+ }
248
+ if (typeof entry.st_input === 'string' && entry.st_input !== '') {
249
+ addRef({ doc: 'config', pointer: `/assemble/take/${i}/st_input`, dotPath: `assemble.take.${i}.st_input`, value: entry.st_input, kind: 'file', autoRelocate: true, pre394: true });
250
+ }
251
+ if (isObject(entry.reference) && typeof entry.reference.painting === 'string' && entry.reference.painting !== '') {
252
+ let declaredSha256: string | undefined;
253
+ if (entry.reference.sha256 !== undefined) {
254
+ if (typeof entry.reference.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(entry.reference.sha256)) {
255
+ if (strict) throw Error(`config.assemble.take.${i}.reference.sha256: expected 64-character lowercase hex sha256`);
256
+ } else {
257
+ declaredSha256 = entry.reference.sha256;
258
+ }
259
+ }
260
+ addRef({
261
+ doc: 'config',
262
+ pointer: `/assemble/take/${i}/reference/painting`,
263
+ dotPath: `assemble.take.${i}.reference.painting`,
264
+ value: entry.reference.painting,
265
+ kind: 'file',
266
+ autoRelocate: true,
267
+ pre394: false,
268
+ ...(strict && declaredSha256 ? { declaredSha256 } : {}),
269
+ });
270
+ }
271
+ }
272
+ }
273
+ if (isObject(assemble.editor_receipts) && Array.isArray(assemble.editor_receipts.receipts)) {
274
+ for (const [i, entry] of assemble.editor_receipts.receipts.entries()) {
275
+ if (!isObject(entry) || typeof entry.file !== 'string' || entry.file === '') continue;
276
+ let declaredSha256: string | undefined;
277
+ if (entry.sha256 !== undefined) {
278
+ if (typeof entry.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(entry.sha256)) {
279
+ if (strict) throw Error(`config.assemble.editor_receipts.receipts.${i}.sha256: expected 64-character lowercase hex sha256`);
280
+ } else {
281
+ declaredSha256 = entry.sha256;
282
+ }
283
+ }
284
+ addRef({
285
+ doc: 'config',
286
+ pointer: `/assemble/editor_receipts/receipts/${i}/file`,
287
+ dotPath: `assemble.editor_receipts.receipts.${i}.file`,
288
+ value: entry.file,
289
+ kind: 'file',
290
+ autoRelocate: true,
291
+ pre394: true,
292
+ ...(strict && declaredSha256 ? { declaredSha256 } : {}),
293
+ });
294
+ }
295
+ }
296
+ if (Array.isArray(assemble.cuts)) {
297
+ for (const [i, entry] of assemble.cuts.entries()) {
298
+ if (!isObject(entry)) continue;
299
+ if (typeof entry.under === 'string' && entry.under !== '') {
300
+ addRef({ doc: 'config', pointer: `/assemble/cuts/${i}/under`, dotPath: `assemble.cuts.${i}.under`, value: entry.under, kind: 'file', autoRelocate: true, pre394: false });
301
+ } else if (isObject(entry.under) && typeof entry.under.plate === 'string' && entry.under.plate !== '') {
302
+ addRef({ doc: 'config', pointer: `/assemble/cuts/${i}/under/plate`, dotPath: `assemble.cuts.${i}.under.plate`, value: entry.under.plate, kind: 'dir', autoRelocate: true, pre394: false });
303
+ }
304
+ }
305
+ }
306
+ }
307
+ if (isObject(config.limbs)) {
308
+ for (const [limbName, limb] of Object.entries(config.limbs)) {
309
+ if (!isObject(limb) || !Array.isArray(limb.segments)) continue;
310
+ for (const [i, seg] of limb.segments.entries()) {
311
+ if (isObject(seg) && typeof seg.cap === 'string' && seg.cap !== '') {
312
+ addRef({
313
+ doc: 'config',
314
+ pointer: `/limbs/${escapePointerToken(limbName)}/segments/${i}/cap`,
315
+ dotPath: `limbs.${limbName}.segments.${i}.cap`,
316
+ value: seg.cap,
317
+ kind: 'file',
318
+ autoRelocate: true,
319
+ pre394: false,
320
+ });
321
+ }
322
+ }
323
+ }
324
+ }
325
+ if (isObject(config.depth)) {
326
+ for (const run of ['full', 'head'] as const) {
327
+ const block = config.depth[run];
328
+ if (!isObject(block)) continue;
329
+ if (isObject(block.atlas) && typeof block.atlas.image === 'string' && block.atlas.image !== '') {
330
+ addRef({ doc: 'config', pointer: `/depth/${run}/atlas/image`, dotPath: `depth.${run}.atlas.image`, value: block.atlas.image, kind: 'file', autoRelocate: true, pre394: false });
331
+ }
332
+ if (isObject(block.layers)) {
333
+ for (const [layer, spec] of Object.entries(block.layers)) {
334
+ if (isObject(spec) && typeof spec.image === 'string' && spec.image !== '') {
335
+ addRef({
336
+ doc: 'config',
337
+ pointer: `/depth/${run}/layers/${escapePointerToken(layer)}/image`,
338
+ dotPath: `depth.${run}.layers.${layer}.image`,
339
+ value: spec.image,
340
+ kind: 'file',
341
+ autoRelocate: true,
342
+ pre394: false,
343
+ });
344
+ }
345
+ }
346
+ }
347
+ }
348
+ }
349
+ const scanAbsolute = (value: Json, pointer: string, dotPath: string): void => {
350
+ if (typeof value === 'string' && absolute(value)) {
351
+ addRef({ doc: 'config', pointer, dotPath, value, kind: 'file-or-dir', autoRelocate: true, pre394: true });
352
+ return;
353
+ }
354
+ if (Array.isArray(value)) value.forEach((v, i) => scanAbsolute(v, `${pointer}/${i}`, dotPath ? `${dotPath}.${i}` : String(i)));
355
+ else if (isObject(value)) {
356
+ for (const [k, v] of Object.entries(value)) {
357
+ scanAbsolute(v, `${pointer}/${escapePointerToken(k)}`, dotPath ? `${dotPath}.${k}` : k);
358
+ }
359
+ }
360
+ };
361
+ scanAbsolute(config, '', '');
362
+ return [...byPointer.values()];
363
+ }
364
+
365
+ function collectRequirementsAssetRefs(requirements: JsonObject, portableRequirementsPath: string, strict = true): AssetRefSpec[] {
366
+ const refs: AssetRefSpec[] = [];
367
+ const setup = isObject(requirements.setup_painting) ? requirements.setup_painting : {};
368
+ const reqParent = dirname(portableRequirementsPath);
369
+ for (const key of ['figure_mask', 'backdrop'] as const) {
370
+ if (setup[key] === undefined) continue;
371
+ const entry = setup[key];
372
+ if (!isObject(entry) || typeof entry.path !== 'string' || !entry.path.trim()
373
+ || typeof entry.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(entry.sha256)) {
374
+ if (!strict && key === 'backdrop') continue;
375
+ throw Error(`requirements.setup_painting.${key} must declare a nonempty path and 64-character lowercase hex sha256`);
376
+ }
377
+ const candidateRel = reqParent === '.' ? entry.path : `${reqParent}/${entry.path}`;
378
+ refs.push({
379
+ doc: 'requirements',
380
+ pointer: `/setup_painting/${key}/path`,
381
+ dotPath: `setup_painting.${key}.path`,
382
+ value: entry.path,
383
+ kind: 'file',
384
+ autoRelocate: key === 'backdrop' || absolute(entry.path) || !isValidAdaptedTarget(entry.path) || !isValidAdaptedTarget(candidateRel),
385
+ pre394: key === 'figure_mask',
386
+ declaredSha256: entry.sha256,
387
+ });
388
+ }
389
+ return refs;
390
+ }
391
+
392
+ /** Only explicitly registered versions opt in. Older version contracts are unchanged. */
393
+ export function rigRegistrationFindings(dir: string, project: JsonObject, warnings: Finding[] = []): Finding[] {
394
+ const findings: Finding[] = [];
395
+ const allVersions = asArray(project.versions).filter(isObject);
396
+ const registeredNumbers = new Set(allVersions.map(v => v.n).filter((n): n is number => typeof n === 'number' && Number.isSafeInteger(n)));
397
+ for (const [index, version] of allVersions.entries()) {
398
+ if (version.registration === undefined) continue;
399
+ const path = `versions[v${String(version.n)}]`;
400
+ const add = (code: string, field: string, why: string): void => { findings.push({ code, path: `${path}.${field}`, message: `${path}.${field}: ${why}; restore the retained registration inputs or register a new rig version` }); };
401
+ const record = version.registration;
402
+ if (version.kind !== 'rig' || !isObject(record) || record.kind !== 'frozen-rig/1' || !utcDate(record.at)
403
+ || !isObject(record.toolchain) || record.toolchain.kind !== 'npm-lock-v3' || version.at_basis !== 'not-after') {
404
+ add('RIG_REGISTRATION_INVALID', 'registration', 'expected frozen-rig/1, a UTC registration date and npm-lock-v3 declaration'); continue;
405
+ }
406
+ const prevMax = Math.max(0, ...allVersions.slice(0, index).map(v => typeof v.n === 'number' && Number.isSafeInteger(v.n) ? v.n : 0));
407
+ const versionN = typeof version.n === 'number' && Number.isSafeInteger(version.n) ? version.n : 0;
408
+ if (versionN <= prevMax || allVersions.filter(v => v.n === versionN).length !== 1) {
409
+ add('RIG_REGISTRATION_INVALID', 'registration', 'version number must strictly follow prior registered versions');
410
+ } else {
411
+ const expectedSkipped: number[] = [];
412
+ for (let k = prevMax + 1; k < versionN; k++) expectedSkipped.push(k);
413
+ if (expectedSkipped.length === 0) {
414
+ if (record.skipped !== undefined) add('RIG_REGISTRATION_INVALID', 'registration.skipped', 'no version numbers were skipped before this version');
415
+ } else if (!Array.isArray(record.skipped) || record.skipped.length !== expectedSkipped.length) {
416
+ add('RIG_REGISTRATION_INVALID', 'registration.skipped', `expected skipped records for v${expectedSkipped.join(', v')}`);
417
+ } else {
418
+ for (const [i, raw] of record.skipped.entries()) {
419
+ const k = expectedSkipped[i]!;
420
+ const allowed = versionDestinations(k);
421
+ if (!isObject(raw) || Object.keys(raw).length !== 2 || raw.n !== k || registeredNumbers.has(k)
422
+ || !Array.isArray(raw.destinations) || !raw.destinations.length
423
+ || raw.destinations.some(d => typeof d !== 'string' || !allowed.includes(d))
424
+ || !isDeepStrictEqual(raw.destinations, allowed.filter(d => (raw.destinations as Json[]).includes(d)))) {
425
+ add('RIG_REGISTRATION_INVALID', `registration.skipped[${i}]`, `expected unregistered v${k} occupying destinations`);
426
+ }
427
+ }
428
+ }
429
+ }
430
+ const runPaths = checkRegisteredRuns(dir, path, record, findings, warnings);
431
+ try {
432
+ const versions = lockVersions(JSON.parse(bytesAt(dir, record.toolchain.source).toString()));
433
+ if (!isDeepStrictEqual(versions, version.built_with)) add('RIG_TOOLCHAIN_MISMATCH', 'built_with', 'declared versions disagree with the retained npm lock');
434
+ } catch (error) { add('RIG_TOOLCHAIN_LOCK_INVALID', 'registration.toolchain.source', String(error)); }
435
+ try {
436
+ const source = version.at_source;
437
+ const raw = JSON.parse(bytesAt(dir, source).toString()) as Json;
438
+ const at = pointerValue(raw, isObject(source) ? source.pointer : undefined);
439
+ if (!utcDate(at) || at !== version.at) throw Error('the cited pointer must equal at and contain a valid UTC instant');
440
+ if (Date.parse(at) > Date.parse(String(record.at))) throw Error('the cited not-after instant is later than registration');
441
+ // Later reopens preserve the historical registration, judged at its own event time.
442
+ const reopens = collectReopens(project, []).filter(r => r.at <= Date.parse(String(record.at)));
443
+ if (Date.parse(at) <= reopenTime(reopens, 'rig-review')) add('RIG_BUILD_BEFORE_REOPEN', 'at_source', 'the cited build bound does not follow the reopen covering rig-review at registration');
444
+ } catch (error) { add('RIG_AT_SOURCE_INVALID', 'at_source', String(error)); }
445
+ let adapterRows: NormalizedAdapterRow[] | null = null;
446
+ try { adapterRows = parsePathAdapter(JSON.parse(bytesAt(dir, record.path_adapter).toString())); }
447
+ catch (error) { add('RIG_PATH_ADAPTER_INVALID', 'registration.path_adapter', String(error)); }
448
+ try {
449
+ const files = asArray(record.files);
450
+ if (!files.length || files.some(f => !isObject(f))) throw Error('missing retained input inventory');
451
+ for (const file of files) if (!isObject(file) || typeof file.path !== 'string' || !runPaths.has(file.path)) bytesAt(dir, file);
452
+ const config = isObject(version.provenance) ? version.provenance.config : undefined;
453
+ for (const binding of [record.toolchain.source, version.at_source, record.compile, record.config_source, record.path_adapter, config]) {
454
+ if (!isObject(binding) || !files.some(f => isObject(f) && f.path === binding.path && f.sha256 === binding.sha256)) throw Error('a declared input is absent from the retained inventory');
455
+ bytesAt(dir, binding);
456
+ }
457
+ const configSourceRaw = JSON.parse(bytesAt(dir, record.config_source).toString()) as Json;
458
+ const portable = JSON.parse(bytesAt(dir, config).toString()) as Json;
459
+ if (!isObject(configSourceRaw) || !isObject(portable)) throw Error('config-source and portable config must be JSON objects');
460
+ const checkPortable = (value: Json, document: string, requirementsDocument = false, location: string[] = []): void => {
461
+ if (typeof value === 'string' && absolute(value)) {
462
+ const field = `${document}#/${location.map(escapePointerToken).join('/')}`;
463
+ const provenancePointer = requirementsDocument
464
+ && location.at(-2) === 'x-source' && location.at(-1) === 'json_pointer';
465
+ if (!provenancePointer) throw Error(`${field}: contains an unresolved absolute path`);
466
+ // Parts leaves x-* metadata opaque, without declaring a target document to resolve.
467
+ if (!value.startsWith('/') || /~(?![01])/.test(value)) throw Error(`${field}: expected an RFC 6901 JSON pointer with valid escapes`);
468
+ }
469
+ if (Array.isArray(value)) {
470
+ value.forEach((entry, index) => checkPortable(entry, document, requirementsDocument, [...location, String(index)]));
471
+ } else if (isObject(value)) {
472
+ for (const [key, entry] of Object.entries(value)) {
473
+ checkPortable(entry, document, requirementsDocument, [...location, key]);
474
+ }
475
+ }
476
+ };
477
+ checkPortable(portable, String((config as JsonObject).path));
478
+ const evidenceRoot = dirname(String((config as JsonObject).path));
479
+ const reqAdapters = (adapterRows ?? []).filter(r => r.doc === 'requirements');
480
+ const hasPointerAdapters = (adapterRows ?? []).some(r => r.kind === 'pointer');
481
+ const hasReqBindings = record.requirements_source !== undefined || record.requirements !== undefined || reqAdapters.length > 0;
482
+ const adapterFormat = isObject(record.path_adapter) ? record.path_adapter.format : undefined;
483
+ if (adapterFormat !== undefined && adapterFormat !== 'json-pointer/1') throw Error('unsupported registration.path_adapter.format');
484
+ const isPointerAdaptedRecord = adapterFormat === 'json-pointer/1' || hasPointerAdapters || hasReqBindings;
485
+ const sourceConfigRefs = collectConfigAssetRefs(dir, configSourceRaw, isPointerAdaptedRecord);
486
+ if (adapterRows !== null) {
487
+ const expectedConfig = structuredClone(configSourceRaw);
488
+ const adaptedConfigPointers = new Set<string>();
489
+ for (const row of adapterRows.filter(r => r.doc === 'config')) {
490
+ if (row.kind === 'legacy-abs') {
491
+ const currentVal = row.pointer ? pointerValue(configSourceRaw, row.pointer) : undefined;
492
+ if (row.pointer && typeof currentVal === 'string' && absolute(currentVal) && currentVal === row.originalString) {
493
+ if (!row.sourcePath || !absolute(row.sourcePath) || !isValidAdaptedTarget(row.adaptedString)) {
494
+ add('RIG_PATH_ADAPTER_INVALID', 'registration.path_adapter', `config ${row.pointer}: invalid legacy adapter`);
495
+ throw Error(`config ${row.pointer}: invalid legacy adapter`);
496
+ }
497
+ setPointerValue(expectedConfig, row.pointer, row.adaptedString);
498
+ adaptedConfigPointers.add(row.pointer);
499
+ }
500
+ continue;
501
+ }
502
+ const ref = sourceConfigRefs.find(r => r.pointer === row.pointer);
503
+ if (!ref || pointerValue(configSourceRaw, row.pointer) !== row.originalString) {
504
+ add('RIG_PATH_ADAPTER_INVALID', 'registration.path_adapter', `config ${row.pointer}: adapter does not match config-source`);
505
+ throw Error(`config ${row.pointer}: adapter does not match config-source`);
506
+ }
507
+ adaptedConfigPointers.add(row.pointer);
508
+ setPointerValue(expectedConfig, row.pointer, row.adaptedString);
509
+ }
510
+ if (!isPointerAdaptedRecord) {
511
+ for (const ref of sourceConfigRefs) {
512
+ if (adaptedConfigPointers.has(ref.pointer)) continue;
513
+ if (/^assemble\.take\.\d+\.(?:run_dir|st_input)$|^assemble\.editor_receipts\.receipts\.\d+\.file$/.test(ref.dotPath)
514
+ && !absolute(ref.value)) {
515
+ setPointerValue(expectedConfig, ref.pointer, `inputs/${ref.dotPath.split('.').join('/')}`);
516
+ }
517
+ }
518
+ }
519
+ if (!isDeepStrictEqual(expectedConfig, portable)) {
520
+ throw Error('the retained runtime config differs from config-source outside declared path_adapter substitutions');
521
+ }
522
+ }
523
+ const adaptedPointersForCheck = new Set(
524
+ (adapterRows ?? []).filter(r => r.doc === 'config' && r.pointer).map(r => r.pointer),
525
+ );
526
+ for (const ref of collectConfigAssetRefs(dir, portable, isPointerAdaptedRecord)) {
527
+ if (!isPointerAdaptedRecord && !ref.pre394 && !adaptedPointersForCheck.has(ref.pointer)) continue;
528
+ if (!isValidAdaptedTarget(ref.value)) throw Error(`config.${ref.dotPath}: expected literal retained path`);
529
+ const targetPath = `${evidenceRoot}/${ref.value}`;
530
+ const fileBinding = files.find(f => isObject(f) && f.path === targetPath);
531
+ const dirBinding = files.some(f => isObject(f) && typeof f.path === 'string' && f.path.startsWith(`${targetPath}/`));
532
+ if (isPointerAdaptedRecord && ref.kind === 'dir' && isObject(fileBinding)) throw Error(`${targetPath} must be a retained directory`);
533
+ if (ref.kind === 'file' || ref.declaredSha256 !== undefined) {
534
+ if (!isObject(fileBinding)) throw Error(`${targetPath} is absent from the retained inventory`);
535
+ const bytes = bytesAt(dir, fileBinding);
536
+ if (ref.declaredSha256 !== undefined && sha(bytes) !== ref.declaredSha256) {
537
+ throw Error(`${targetPath} sha256 differs from config.${ref.dotPath} declaration`);
538
+ }
539
+ } else if (ref.kind === 'file-or-dir' && !isObject(fileBinding) && !dirBinding) {
540
+ throw Error(`${targetPath} is absent from the retained inventory`);
541
+ }
542
+ }
543
+ if (typeof portable.requirements !== 'string') {
544
+ if (hasReqBindings) throw Error('requirements bindings or adapter entries present without config.requirements');
545
+ } else {
546
+ const runtimeReqPath = `${evidenceRoot}/${portable.requirements}`;
547
+ const runtimeReqInFiles = files.find(f => isObject(f) && f.path === runtimeReqPath);
548
+ if (!isObject(runtimeReqInFiles)) throw Error(`${runtimeReqPath} is absent from the retained inventory`);
549
+ const runtimeReqObj = JSON.parse(bytesAt(dir, runtimeReqInFiles).toString()) as Json;
550
+ if (!isObject(runtimeReqObj)) throw Error('retained requirements must be a JSON object');
551
+ if (isPointerAdaptedRecord) checkPortable(runtimeReqObj, runtimeReqPath, true);
552
+ if (isPointerAdaptedRecord) {
553
+ if (!isObject(record.requirements_source) || !isObject(record.requirements)) {
554
+ throw Error('both requirements_source and requirements bindings are required when requirements are adapted');
555
+ }
556
+ for (const binding of [record.requirements_source, record.requirements]) {
557
+ if (!files.some(f => isObject(f) && f.path === binding.path && f.sha256 === binding.sha256)) {
558
+ throw Error('declared requirements input is absent from the retained inventory');
559
+ }
560
+ bytesAt(dir, binding);
561
+ }
562
+ if (record.requirements.path !== runtimeReqPath) throw Error('registration.requirements.path disagrees with config.requirements');
563
+ if (record.requirements_source.source_path !== undefined
564
+ && (typeof record.requirements_source.source_path !== 'string' || !literal(record.requirements_source.source_path))) {
565
+ throw Error('invalid registration.requirements_source.source_path');
566
+ }
567
+ const reqSourceObj = JSON.parse(bytesAt(dir, record.requirements_source).toString()) as Json;
568
+ if (!isObject(reqSourceObj)) throw Error('requirements-source must be a JSON object');
569
+ const sourceReqRefs = collectRequirementsAssetRefs(reqSourceObj, portable.requirements, true);
570
+ if (adapterRows !== null) {
571
+ const expectedReq = structuredClone(reqSourceObj);
572
+ for (const row of reqAdapters) {
573
+ const ref = sourceReqRefs.find(r => r.pointer === row.pointer);
574
+ if (!ref || pointerValue(reqSourceObj, row.pointer) !== row.originalString) {
575
+ add('RIG_PATH_ADAPTER_INVALID', 'registration.path_adapter', `requirements ${row.pointer}: adapter does not match requirements-source`);
576
+ throw Error(`requirements ${row.pointer}: adapter does not match requirements-source`);
577
+ }
578
+ setPointerValue(expectedReq, row.pointer, row.adaptedString);
579
+ }
580
+ if (!isDeepStrictEqual(expectedReq, runtimeReqObj)) {
581
+ throw Error('the retained runtime requirements differs from requirements-source outside declared path_adapter substitutions');
582
+ }
583
+ }
584
+ }
585
+ const reqParent = dirname(portable.requirements);
586
+ for (const ref of collectRequirementsAssetRefs(runtimeReqObj, portable.requirements, isPointerAdaptedRecord)) {
587
+ if (!isPointerAdaptedRecord && !ref.pre394) continue;
588
+ const relInEvidence = posix.normalize(reqParent === '.' ? ref.value : `${reqParent}/${ref.value}`);
589
+ if (absolute(ref.value) || (isPointerAdaptedRecord && !isValidAdaptedTarget(ref.value)) || !isValidAdaptedTarget(relInEvidence)) {
590
+ throw Error(`requirements.${ref.dotPath}: expected literal retained path`);
591
+ }
592
+ const assetTargetPath = `${evidenceRoot}/${relInEvidence}`;
593
+ const assetBinding = files.find(f => isObject(f) && f.path === assetTargetPath);
594
+ if (!isObject(assetBinding)) throw Error(`${assetTargetPath} is absent from the retained inventory`);
595
+ const assetBytes = bytesAt(dir, assetBinding);
596
+ if (ref.declaredSha256 !== undefined && sha(assetBytes) !== ref.declaredSha256) {
597
+ throw Error(`${assetTargetPath} sha256 differs from requirements.${ref.dotPath} declaration`);
598
+ }
599
+ }
600
+ }
601
+ if (!isObject(record.idle) || record.idle.path !== `outputs/v${String(version.n)}/idle/frames.json`) throw Error('idle manifest must be in the version output');
602
+ const manifest = JSON.parse(bytesAt(dir, record.idle, true).toString()) as Json;
603
+ if (!isObject(manifest) || manifest.spec !== 'rigc-frames/1' || !Array.isArray(manifest.sets)
604
+ || !manifest.sets.some(set => isObject(set) && set.animation === 'idle' && typeof set.fps === 'number' && set.fps > 0)) throw Error('idle requires the producer rigc-frames/1 manifest and an idle set');
605
+ if (!asArray(version.outputs).length) throw Error('empty output inventory');
606
+ for (const output of asArray(version.outputs)) {
607
+ if (!isObject(output) || typeof output.path !== 'string' || !literal(output.path) || !output.path.startsWith(`outputs/v${String(version.n)}/`)
608
+ || typeof output.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(output.sha256)) throw Error('invalid frozen output binding');
609
+ const bytes = readFileSync(literalFile(dir, output.path));
610
+ if (sha(bytes) !== output.sha256 || bytes.length !== output.bytes) throw Error(`${output.path} changed`);
611
+ }
612
+ const actual = treeBindings(dir, `outputs/v${String(version.n)}`).sort((a, b) => String(a.path).localeCompare(String(b.path), 'en'));
613
+ const expected = [...asArray(version.outputs)].sort((a, b) => String(isObject(a) ? a.path : '').localeCompare(String(isObject(b) ? b.path : ''), 'en'));
614
+ if (!isDeepStrictEqual(actual, expected)) throw Error('the frozen output tree differs from its complete inventory');
615
+ if (!isObject(record.copied) || record.copied.files !== actual.length || record.copied.bytes !== actual.reduce((sum, f) => sum + Number(f.bytes), 0)) throw Error('copied totals disagree with the frozen output inventory');
616
+ for (const [path, spec] of [['rig/rig.json', 'rigc-rig/1'], ['rig/motion.json', 'rigc-motion/1'], ['check/build/skeleton.model.json', 'rigc-compiled/3']]) {
617
+ const document = JSON.parse(readFileSync(literalFile(dir, `outputs/v${String(version.n)}/out/${path}`), 'utf8')) as Json;
618
+ if (!isObject(document) || document.spec !== spec) throw Error(`${path}: expected producer spec ${spec}`);
619
+ }
620
+ const idleRoot = isObject(record.idle) ? dirname(String(record.idle.path)) : '';
621
+ for (const set of manifest.sets.filter(isObject)) {
622
+ if (set.animation !== 'idle') continue;
623
+ if (typeof set.dir !== 'string' || !literal(set.dir) || !Number.isSafeInteger(set.written) || Number(set.written) < 1) throw Error('invalid idle set directory or written count');
624
+ const frames = asArray(version.outputs).filter(f => isObject(f) && typeof f.path === 'string'
625
+ && dirname(f.path) === `${idleRoot}/${set.dir}` && /^f\d+\.png$/.test(f.path.split('/').at(-1)!));
626
+ if (frames.length !== set.written) throw Error('idle frame inventory differs from the producer written count');
627
+ }
628
+ const compile = JSON.parse(bytesAt(dir, record.compile).toString()) as Json;
629
+ if (!isObject(compile) || compile.spec !== 'build-report/1' || compile.command !== 'build' || !Array.isArray(compile.gates)
630
+ || compile.gates.length === 0 || compile.gates.some(g => !isObject(g) || !Array.isArray(g.failures) || g.failures.length > 0)) throw Error('retain the successful producer build-report/1');
631
+ } catch (error) { add('RIG_REGISTRATION_FILE_INVALID', 'registration.files', String(error)); }
632
+ }
633
+ return findings;
634
+ }
635
+
636
+ export const RIG_VERSION_HELP = `rig-version <n> [<dir>] --parts <dir> --rig <dir> --build <dir> --idle <render dir>
637
+ --config <file> --path-adapter <file> --compile-report <file> --toolchain-lock <package-lock.json>
638
+ --at-source <dated JSON file> --at-pointer </pointer> --by <registrant> [--run-record <run.json>]... [--citations <sections JSON>] [--json]
639
+ Copy and hash frozen producer outputs. Retain an explicitly declared npm lock, producer run records and UTC not-after date.
640
+ Idle requires rigc render's frames.json. No build, acceptance or stage advance runs.
641
+ Repeat --answers <version>:<verdict> to answer earlier human bounced feedback.
642
+ Source arguments resolve against the project directory; external frozen sources are permitted.`;
643
+
644
+ interface SourceFile { source: string; bytes: Buffer }
645
+
646
+ export function rigVersionCommand(args: string[], now: number): { stdout: string; exit: number } {
647
+ const positional: string[] = [];
648
+ const options: Record<string, string> = {};
649
+ const runRecords: string[] = [];
650
+ const answerValues: string[] = [];
651
+ const flags = ['--parts', '--rig', '--build', '--idle', '--config', '--path-adapter', '--compile-report', '--toolchain-lock', '--at-source', '--at-pointer', '--by', '--citations'];
652
+ let json = false;
653
+ for (let i = 0; i < args.length; i++) {
654
+ const arg = args[i]!;
655
+ if (arg === '--json') json = true;
656
+ else if (arg === '--answers') {
657
+ const value = args[++i];
658
+ if (!value || value.startsWith('--')) fail('RIG_REGISTRATION_USAGE', 'versions', '--answers needs version:verdict');
659
+ answerValues.push(value);
660
+ } else if (arg === '--run-record') {
661
+ const value = args[++i];
662
+ if (!value || value.startsWith('--')) fail('RIG_REGISTRATION_USAGE', 'versions', '--run-record needs one value');
663
+ runRecords.push(value);
664
+ } else if (flags.includes(arg)) {
665
+ const value = args[++i];
666
+ if (!value || value.startsWith('--') || options[arg] !== undefined) fail('RIG_REGISTRATION_USAGE', 'versions', `${arg} needs one value`);
667
+ options[arg] = value;
668
+ } else if (arg.startsWith('-')) refuse('UNKNOWN_OPTION', `rig-version does not take ${arg}`, USAGE_EXIT);
669
+ else positional.push(arg);
670
+ }
671
+ const n = Number(positional[0]?.replace(/^v/, ''));
672
+ if (!Number.isSafeInteger(n) || n < 1 || positional.length > 2 || flags.filter(f => f !== '--citations').some(f => !options[f])) refuse('RIG_REGISTRATION_USAGE', RIG_VERSION_HELP, USAGE_EXIT);
673
+ const dir = realpathSync(resolve(positional[1] ?? process.cwd()));
674
+ let { file, project } = readProject(dir);
675
+ let original = readFileSync(file);
676
+ const lock = `${file}.rig-version.lock`;
677
+ let descriptor: number;
678
+ try { descriptor = openSync(lock, 'wx'); } catch { return fail('RIG_REGISTRATION_BUSY', 'versions', 'another registration holds the lock'); }
679
+ const out = `outputs/v${n}`;
680
+ const evidence = `evidence/v${n}/registration`;
681
+ const pkg = `derived/review/v${n}`;
682
+ const created: string[] = [];
683
+ const sources: SourceFile[] = [];
684
+ const sourceDirectories = new Map<string, string[]>();
685
+ const outputFiles: JsonObject[] = [];
686
+ let releasePublication = () => {};
687
+ let publicationIntent: PendingPublication | null = null;
688
+ try {
689
+ releasePublication = acquireBoundJobWrite(dir);
690
+ ({ file, project } = readProject(dir));
691
+ original = readFileSync(file);
692
+ if (project.schema !== 'rig-create/project@0') fail('SCHEMA_UNKNOWN', 'schema', 'rig-version reads rig-create/project@0');
693
+ if (!Array.isArray(project.versions) || asArray(project.versions).some(v => !isObject(v) || typeof v.n !== 'number')) fail('RIG_REGISTRATION_INVALID', 'versions', 'restore the version list');
694
+ const registeredNumbers = new Set(project.versions.map(v => (v as JsonObject).n as number));
695
+ const maxRegistered = Math.max(0, ...registeredNumbers);
696
+ if (n <= maxRegistered || registeredNumbers.has(n)) fail('RIG_REGISTRATION_INVALID', 'versions', 'use a version number after all registered versions');
697
+ const answersFeedback = parseFeedbackAnswers(project, n, answerValues);
698
+ // Refuse missing public idle provenance before copying any project input.
699
+ const idleSource = resolve(dir, options['--idle']!);
700
+ if (!existsSync(join(idleSource, 'frames.json'))) fail('RIG_IDLE_MANIFEST_MISSING', `versions[v${n}].registration.idle`, 'supply rigc render output with frames.json');
701
+ const invalidDestination = (target: string): never => fail('RIG_REGISTRATION_PATH_INVALID', target, 'destination must have real directory parents');
702
+ for (const target of [out, evidence, pkg]) {
703
+ if (destinationExists(dir, target, invalidDestination)) fail('RIG_REGISTRATION_EXISTS', target, 'preserve the existing destination');
704
+ }
705
+ const skipped: { n: number; destinations: string[] }[] = [];
706
+ for (let k = maxRegistered + 1; k < n; k++) {
707
+ if (registeredNumbers.has(k)) fail('RIG_REGISTRATION_INVALID', 'versions', `v${k} is already registered`);
708
+ const destinations = versionDestinations(k).filter(target => destinationExists(dir, target, invalidDestination));
709
+ if (!destinations.length) fail('RIG_REGISTRATION_INVALID', 'versions', `v${k} has no existing unregistered destination to skip`);
710
+ skipped.push({ n: k, destinations });
711
+ }
712
+ const systemRoots = [resolve(tmpdir()), '/tmp', '/var', dirname(resolve(positional[1] ?? process.cwd())), dirname(dir)];
713
+ const isSystemAncestor = (candidate: string): boolean =>
714
+ systemRoots.some(root => root === candidate || root.startsWith(`${candidate}${sep}`));
715
+ const sourceBytes = (path: string): Buffer => {
716
+ const absolutePath = resolve(dir, path);
717
+ // Check every ancestor inside the source tree, excluding system ancestors above it.
718
+ let current = absolutePath;
719
+ while (dirname(current) !== current && (current === absolutePath || !isSystemAncestor(current))) {
720
+ if (lstatSync(current).isSymbolicLink()) fail('RIG_REGISTRATION_PATH_INVALID', path, 'source symlinks are unsupported');
721
+ current = dirname(current);
722
+ }
723
+ if (!lstatSync(absolutePath).isFile()) fail('RIG_REGISTRATION_FILE_INVALID', path, 'expected a regular source file');
724
+ const bytes = readFileSync(absolutePath);
725
+ sources.push({ source: absolutePath, bytes });
726
+ return bytes;
727
+ };
728
+ const retain = (target: string, bytes: Buffer, outputs = false): JsonObject => {
729
+ destinationExists(dir, dirname(target), invalidDestination);
730
+ if (existsSync(join(dir, target)) && !lstatSync(join(dir, target)).isFile()) fail('RIG_REGISTRATION_PATH_INVALID', target, 'retained file target must be a regular file');
731
+ mkdirSync(dirname(join(dir, target)), { recursive: true });
732
+ if (existsSync(join(dir, target)) && !readFileSync(join(dir, target)).equals(bytes)) fail('RIG_REGISTRATION_PATH_INVALID', target, 'two distinct inputs collide');
733
+ writeFileSync(join(dir, target), bytes);
734
+ if (!readFileSync(join(dir, target)).equals(bytes)) fail('RIG_REGISTRATION_SOURCE_CHANGED', target, 'copy verification failed');
735
+ const binding = { path: target, sha256: sha(bytes), bytes: bytes.length };
736
+ if (outputs) outputFiles.push(binding);
737
+ return binding;
738
+ };
739
+ const input = (flagOrPath: string, name: string): JsonObject => retain(`${evidence}/${name}`, sourceBytes(options[flagOrPath] ?? flagOrPath));
740
+ // Validate the toolchain lock and run records before creating retained directories.
741
+ let versions: JsonObject;
742
+ try { versions = lockVersions(JSON.parse(sourceBytes(options['--toolchain-lock']!).toString())); }
743
+ catch (error) { if (error instanceof Refusal) throw error; return fail('RIG_TOOLCHAIN_LOCK_INVALID', `versions[v${n}].built_with`, String(error)); }
744
+ const registrationAt = new Date(now).toISOString();
745
+ const validatedRuns: { path: string; spec: RunSpec }[] = [];
746
+ const seenSpecs = new Set<RunSpec>();
747
+ for (const [i, recordPath] of runRecords.entries()) {
748
+ const entryPath = `versions[v${n}].registration.runs[${i}]`;
749
+ try {
750
+ const { spec } = validateRunRecord(sourceBytes(recordPath), entryPath);
751
+ if (seenSpecs.has(spec)) throw new RunRecordError('RUN_RECORD_MALFORMED', `${entryPath}.spec`, `duplicate run record for spec ${spec}; supply at most one --run-record per spec`);
752
+ seenSpecs.add(spec);
753
+ validatedRuns.push({ path: recordPath, spec });
754
+ } catch (error) {
755
+ if (error instanceof Refusal) throw error;
756
+ if (error instanceof RunRecordError) refuse(error.code, error.message);
757
+ return fail('RUN_RECORD_MALFORMED', entryPath, String(error));
758
+ }
759
+ }
760
+ if (validatedRuns.length === 0 && Date.parse(registrationAt) >= Date.parse(RUN_RECORD_SINCE)) {
761
+ return fail('RUN_RECORD_REQUIRED', `versions[v${n}].registration.runs`, 'version registered from the npm lock alone; retain producer run.json records with --run-record');
762
+ }
763
+ // Pre-existing refusals stay the project's; the candidate may not add a new one.
764
+ const baseline = baselineOf(checkProject(dir, project, { now }));
765
+ created.push(join(dir, evidence), join(dir, out));
766
+ mkdirSync(join(dir, evidence), { recursive: true });
767
+ const toolchain = input('--toolchain-lock', 'package-lock.json');
768
+ const runs = validatedRuns.map(({ path, spec }) => ({
769
+ spec,
770
+ source: input(path, `${spec.replace('/', '-')}.json`),
771
+ }));
772
+ let atSource: JsonObject;
773
+ let at: Json | undefined;
774
+ try {
775
+ atSource = { ...input('--at-source', 'at-source.json'), pointer: options['--at-pointer']! };
776
+ at = pointerValue(JSON.parse(bytesAt(dir, atSource).toString()), atSource.pointer);
777
+ } catch (error) { if (error instanceof Refusal) throw error; return fail('RIG_AT_SOURCE_INVALID', `versions[v${n}].at_source`, String(error)); }
778
+ if (!utcDate(at)) fail('RIG_AT_SOURCE_INVALID', `versions[v${n}].at_source`, 'the cited pointer needs a valid UTC timestamp');
779
+ const rawAdapterBytes = sourceBytes(options['--path-adapter']!);
780
+ let callerAdapter: NormalizedAdapterRow[];
781
+ try { callerAdapter = parsePathAdapter(JSON.parse(rawAdapterBytes.toString())); }
782
+ catch (error) { return fail('RIG_PATH_ADAPTER_INVALID', `versions[v${n}].registration.path_adapter`, String(error)); }
783
+ const configSource = input('--config', 'config-source.json');
784
+ const config = JSON.parse(bytesAt(dir, configSource).toString()) as Json;
785
+ if (!isObject(config)) fail('RIG_REGISTRATION_FILE_INVALID', 'config', 'expected a JSON object');
786
+ const copiedSources = new Map<string, string>();
787
+ const copyTree = (source: string, target: string, outputs: boolean): void => {
788
+ const path = resolve(dir, source);
789
+ const info = lstatSync(path);
790
+ if (info.isDirectory()) {
791
+ if (info.isSymbolicLink()) fail('RIG_REGISTRATION_PATH_INVALID', source, 'source symlinks are unsupported');
792
+ const names = readdirSync(path).sort();
793
+ sourceDirectories.set(path, names);
794
+ destinationExists(dir, target, invalidDestination);
795
+ mkdirSync(join(dir, target), { recursive: true });
796
+ for (const name of names) copyTree(join(path, name), `${target}/${name}`, outputs);
797
+ } else {
798
+ retain(target, sourceBytes(path), outputs);
799
+ copiedSources.set(target, path);
800
+ }
801
+ };
802
+ const checkSourceAncestors = (absolutePath: string, label: string, allowedRoot?: string): void => {
803
+ let current = absolutePath;
804
+ while (dirname(current) !== current && (current === absolutePath || !isSystemAncestor(current))) {
805
+ if (existsSync(current) && lstatSync(current).isSymbolicLink()) fail('RIG_REGISTRATION_PATH_INVALID', label, 'source symlinks are unsupported');
806
+ if (allowedRoot && current === allowedRoot) break;
807
+ current = dirname(current);
808
+ }
809
+ };
810
+ // Walk the lexical spelling before resolving system temp aliases or input symlinks.
811
+ const canonicalSourcePath = (path: string): string => {
812
+ let parent = path;
813
+ while (!existsSync(parent)) parent = dirname(parent);
814
+ return resolve(realpathSync(parent), relative(parent, path));
815
+ };
816
+ const configDir = dirname(realpathSync(resolve(dir, options['--config']!)));
817
+ let configRefs: AssetRefSpec[];
818
+ try { configRefs = collectConfigAssetRefs(dir, config); }
819
+ catch (error) { if (error instanceof Refusal) throw error; return fail('RIG_REGISTRATION_FILE_INVALID', 'config', String(error)); }
820
+ for (const row of callerAdapter.filter(r => r.kind === 'pointer' && r.doc === 'config')) {
821
+ const ref = configRefs.find(r => r.pointer === row.pointer);
822
+ if (!ref || ref.value !== row.originalString) {
823
+ return fail('RIG_PATH_ADAPTER_INVALID', `versions[v${n}].registration.path_adapter`, `config ${row.pointer}: adapter does not match config-source`);
824
+ }
825
+ }
826
+ const configUsesPointers = callerAdapter.some(row => row.kind === 'pointer')
827
+ || configRefs.some(ref => !absolute(ref.value) && ref.autoRelocate);
828
+ const configAllowedRoot = configUsesPointers || insideOrEqual(dir, configDir) ? dir : configDir;
829
+ const portable = structuredClone(config);
830
+ const effectiveAdapter: JsonObject[] = callerAdapter.map(r => r.raw);
831
+ const reservedTargets = new Set<string>(RESERVED_REGISTRATION_NAMES);
832
+ for (const { spec } of validatedRuns) reservedTargets.add(`${spec.replace('/', '-')}.json`);
833
+ const conflictsWithReserved = (candidate: string): boolean => {
834
+ for (const existing of reservedTargets) {
835
+ if (existing === candidate || existing.startsWith(`${candidate}/`) || candidate.startsWith(`${existing}/`)) return true;
836
+ }
837
+ return false;
838
+ };
839
+ let reqSourcePath: string | undefined;
840
+ let portableReqRel: string | undefined;
841
+ for (const ref of configRefs) {
842
+ const callerRow = callerAdapter.find(r => r.doc === 'config' && r.pointer === ref.pointer && (r.kind === 'pointer' || absolute(ref.value) && r.originalString === ref.value));
843
+ let sourcePath: string;
844
+ if (absolute(ref.value)) {
845
+ if (!callerRow || callerRow.kind !== 'legacy-abs' || !callerRow.pointer || !callerRow.sourcePath || !absolute(callerRow.sourcePath) || !isValidAdaptedTarget(callerRow.adaptedString)) {
846
+ return fail('RIG_RUNTIME_PATH_UNRESOLVED', `config.${ref.dotPath}`, 'absolute input has no exact recorded adapter');
847
+ }
848
+ sourcePath = resolve(dir, callerRow.sourcePath);
849
+ } else {
850
+ sourcePath = resolve(configDir, ref.value);
851
+ }
852
+ checkSourceAncestors(sourcePath, `config.${ref.dotPath}`, !absolute(ref.value) ? configAllowedRoot : undefined);
853
+ sourcePath = canonicalSourcePath(sourcePath);
854
+ if (!absolute(ref.value) && !insideOrEqual(configAllowedRoot, sourcePath)) {
855
+ return fail('RIG_REGISTRATION_PATH_INVALID', `config.${ref.dotPath}`, 'relative input escapes the project');
856
+ }
857
+ if (!existsSync(sourcePath)) {
858
+ return fail('RIG_REGISTRATION_FILE_INVALID', `config.${ref.dotPath}`, 'referenced source asset is missing');
859
+ }
860
+ const stat = lstatSync(sourcePath);
861
+ if (stat.isSymbolicLink()) return fail('RIG_REGISTRATION_PATH_INVALID', `config.${ref.dotPath}`, 'source symlinks are unsupported');
862
+ if (ref.kind === 'dir' && !stat.isDirectory()) return fail('RIG_REGISTRATION_FILE_INVALID', `config.${ref.dotPath}`, 'expected a source directory');
863
+ if (ref.kind === 'file' && !stat.isFile()) return fail('RIG_REGISTRATION_FILE_INVALID', `config.${ref.dotPath}`, 'expected a regular source file');
864
+ if (ref.declaredSha256 !== undefined) {
865
+ const bytes = sourceBytes(sourcePath);
866
+ if (sha(bytes) !== ref.declaredSha256) {
867
+ return fail('RIG_REGISTRATION_FILE_INVALID', `config.${ref.dotPath}`, 'source bytes differ from declared sha256');
868
+ }
869
+ }
870
+ let adaptedValue: string;
871
+ if (callerRow) {
872
+ adaptedValue = callerRow.adaptedString;
873
+ } else if (ref.autoRelocate) {
874
+ adaptedValue = `inputs/${ref.dotPath.split('.').join('/')}`;
875
+ effectiveAdapter.push({ doc: 'config', pointer: ref.pointer, before: ref.value, after: adaptedValue });
876
+ } else {
877
+ adaptedValue = ref.value;
878
+ }
879
+ if (!isValidAdaptedTarget(adaptedValue) || conflictsWithReserved(adaptedValue)) {
880
+ return fail('RIG_REGISTRATION_PATH_INVALID', `${evidence}/${adaptedValue}`, 'two distinct inputs collide or target is invalid');
881
+ }
882
+ if (adaptedValue !== ref.value) setPointerValue(portable, ref.pointer, adaptedValue);
883
+ if (ref.pointer === '/requirements') {
884
+ reqSourcePath = sourcePath;
885
+ portableReqRel = adaptedValue;
886
+ } else {
887
+ copyTree(sourcePath, `${evidence}/${adaptedValue}`, false);
888
+ }
889
+ }
890
+ let requirementsSourceBinding: JsonObject | undefined;
891
+ let requirementsRuntimeBinding: JsonObject | undefined;
892
+ if (typeof config.requirements !== 'string') {
893
+ if (callerAdapter.some(r => r.doc === 'requirements')) {
894
+ return fail('RIG_PATH_ADAPTER_INVALID', `versions[v${n}].registration.path_adapter`, 'requirements adapter supplied without config.requirements');
895
+ }
896
+ } else if (reqSourcePath && portableReqRel) {
897
+ const reqOriginalBytes = sourceBytes(reqSourcePath);
898
+ let originalReq: Json;
899
+ try { originalReq = JSON.parse(reqOriginalBytes.toString('utf8')); }
900
+ catch (error) { return fail('RIG_REGISTRATION_FILE_INVALID', 'requirements', String(error)); }
901
+ if (!isObject(originalReq)) return fail('RIG_REGISTRATION_FILE_INVALID', 'requirements', 'expected a JSON object');
902
+ let reqRefs: AssetRefSpec[];
903
+ try { reqRefs = collectRequirementsAssetRefs(originalReq, portableReqRel); }
904
+ catch (error) { return fail('RIG_REGISTRATION_FILE_INVALID', 'requirements', String(error)); }
905
+ for (const row of callerAdapter.filter(r => r.doc === 'requirements')) {
906
+ const ref = reqRefs.find(r => r.pointer === row.pointer);
907
+ if (!ref || ref.value !== row.originalString) {
908
+ return fail('RIG_PATH_ADAPTER_INVALID', `versions[v${n}].registration.path_adapter`, `requirements ${row.pointer}: adapter does not match requirements-source`);
909
+ }
910
+ }
911
+ const portableReq = structuredClone(originalReq);
912
+ const reqDir = dirname(reqSourcePath);
913
+ const reqUsesPointers = effectiveAdapter.some(row => row.pointer !== undefined)
914
+ || reqRefs.some(ref => ref.autoRelocate);
915
+ const reqAllowedRoot = reqUsesPointers || insideOrEqual(dir, reqDir) ? dir : configDir;
916
+ const reqParent = dirname(portableReqRel);
917
+ for (const ref of reqRefs) {
918
+ const callerRow = callerAdapter.find(r => r.doc === 'requirements' && r.pointer === ref.pointer);
919
+ if (absolute(ref.value)) {
920
+ return fail('RIG_RUNTIME_PATH_UNRESOLVED', `requirements.${ref.dotPath}`, 'requirements setup_painting path must be relative');
921
+ }
922
+ const lexicalSource = resolve(reqDir, ref.value);
923
+ checkSourceAncestors(lexicalSource, `requirements.${ref.dotPath}`, reqAllowedRoot);
924
+ const assetSource = canonicalSourcePath(lexicalSource);
925
+ if (!insideOrEqual(reqAllowedRoot, assetSource)) {
926
+ return fail('RIG_REGISTRATION_PATH_INVALID', `requirements.${ref.dotPath}`, 'relative input escapes the project');
927
+ }
928
+ if (!existsSync(assetSource)) {
929
+ return fail('RIG_REGISTRATION_FILE_INVALID', `requirements.${ref.dotPath}`, 'referenced source asset is missing');
930
+ }
931
+ const assetBytes = sourceBytes(assetSource);
932
+ if (ref.declaredSha256 !== undefined && sha(assetBytes) !== ref.declaredSha256) {
933
+ return fail('RIG_REGISTRATION_FILE_INVALID', `requirements.${ref.dotPath}`, 'source bytes differ from declared sha256');
934
+ }
935
+ let adaptedValue: string;
936
+ if (callerRow) {
937
+ adaptedValue = callerRow.adaptedString;
938
+ } else if (ref.autoRelocate) {
939
+ adaptedValue = reqParent === '.' ? `inputs/${ref.dotPath.split('.').join('/')}` : `${ref.dotPath.split('.').join('/')}`;
940
+ effectiveAdapter.push({ doc: 'requirements', pointer: ref.pointer, before: ref.value, after: adaptedValue });
941
+ } else {
942
+ adaptedValue = ref.value;
943
+ }
944
+ const relInEvidence = reqParent === '.' ? adaptedValue : `${reqParent}/${adaptedValue}`;
945
+ if (!isValidAdaptedTarget(adaptedValue) || !isValidAdaptedTarget(relInEvidence) || conflictsWithReserved(relInEvidence)) {
946
+ return fail('RIG_REGISTRATION_PATH_INVALID', `${evidence}/${relInEvidence}`, 'two distinct inputs collide or target is invalid');
947
+ }
948
+ retain(`${evidence}/${relInEvidence}`, assetBytes, false);
949
+ copiedSources.set(`${evidence}/${relInEvidence}`, assetSource);
950
+ if (adaptedValue !== ref.value) setPointerValue(portableReq, ref.pointer, adaptedValue);
951
+ }
952
+ if (effectiveAdapter.some(row => row.pointer !== undefined)) {
953
+ const reqSourceRel = insideOrEqual(dir, reqSourcePath) ? relative(dir, reqSourcePath).split(sep).join('/') : undefined;
954
+ requirementsSourceBinding = {
955
+ ...retain(`${evidence}/requirements-source.json`, reqOriginalBytes),
956
+ ...(reqSourceRel && literal(reqSourceRel) ? { source_path: reqSourceRel } : {}),
957
+ };
958
+ const runtimeReqBytes = isDeepStrictEqual(originalReq, portableReq)
959
+ ? reqOriginalBytes : Buffer.from(`${JSON.stringify(portableReq, null, 2)}\n`);
960
+ requirementsRuntimeBinding = retain(`${evidence}/${portableReqRel}`, runtimeReqBytes);
961
+ } else {
962
+ retain(`${evidence}/${portableReqRel}`, reqOriginalBytes);
963
+ }
964
+ copiedSources.set(`${evidence}/${portableReqRel}`, reqSourcePath);
965
+ }
966
+ const pointerAdapted = effectiveAdapter.some(row => row.pointer !== undefined);
967
+ const adapterBinding = retain(
968
+ `${evidence}/path-adapter.json`,
969
+ effectiveAdapter.length === callerAdapter.length ? rawAdapterBytes : Buffer.from(`${JSON.stringify(effectiveAdapter, null, 2)}\n`),
970
+ );
971
+ const configBinding = retain(`${evidence}/config.json`, Buffer.from(`${JSON.stringify(portable, null, 2)}\n`));
972
+ const compile = input('--compile-report', 'compile-report.json');
973
+ const partsDir = resolve(dir, options['--parts']!);
974
+ copyTree(join(partsDir, 'parts.json'), `${out}/out/parts.json`, true);
975
+ copyTree(join(partsDir, 'parts'), `${out}/out/parts`, true);
976
+ const partReferences = partsFileReferences(partsDir);
977
+ for (const reference of partReferences) {
978
+ const target = `${out}/out/${reference.path}`;
979
+ if (!copiedSources.has(target)) copyTree(join(partsDir, reference.path), target, true);
980
+ }
981
+ copyTree(options['--rig']!, `${out}/out/rig`, true);
982
+ copyTree(options['--build']!, `${out}/out/check/build`, true);
983
+ copyTree(idleSource, `${out}/idle`, true);
984
+ const idleBytes = readFileSync(join(dir, out, 'idle/frames.json'));
985
+ const idle = { path: `${out}/idle/frames.json`, sha256: sha(idleBytes) };
986
+ const retained: JsonObject[] = [];
987
+ const inventory = (root: string): void => {
988
+ for (const name of readdirSync(join(dir, root)).sort()) {
989
+ const path = `${root}/${name}`;
990
+ if (lstatSync(join(dir, path)).isDirectory()) inventory(path);
991
+ else retained.push({ path, sha256: sha(readFileSync(join(dir, path))) });
992
+ }
993
+ };
994
+ inventory(evidence);
995
+ let sections: Json = {};
996
+ if (options['--citations']) {
997
+ const binding = input('--citations', 'citations.json');
998
+ retained.push(binding);
999
+ sections = JSON.parse(bytesAt(dir, binding).toString()) as Json;
1000
+ if (!isObject(sections)) fail('RIG_REGISTRATION_FILE_INVALID', 'provenance.sections', 'citations must be a section map');
1001
+ }
1002
+ const structureChanges = approvedStructureDelta(dir, project, Date.parse(String(at)), portable);
1003
+ const version: JsonObject = { n, kind: 'rig', rig_version: null, at, at_basis: 'not-after', at_source: atSource,
1004
+ built_by: options['--by']!, built_with: versions, outputs: outputFiles, evidence: [], verdicts: [], answers_feedback: answersFeedback, review_origin: null,
1005
+ changed: 'Registered frozen producer outputs', provenance: { config: configBinding, sections, ...(structureChanges.length ? { structure_delta: structureChanges } : {}) },
1006
+ registration: { kind: 'frozen-rig/1', at: registrationAt, toolchain: { kind: 'npm-lock-v3', source: toolchain },
1007
+ ...(runs.length ? { runs } : {}),
1008
+ ...(skipped.length ? { skipped } : {}),
1009
+ files: retained, config_source: configSource,
1010
+ ...(requirementsSourceBinding ? { requirements_source: requirementsSourceBinding } : {}),
1011
+ ...(requirementsRuntimeBinding ? { requirements: requirementsRuntimeBinding } : {}),
1012
+ path_adapter: { ...adapterBinding, ...(pointerAdapted ? { format: 'json-pointer/1' } : {}) }, idle, compile,
1013
+ copied: { files: outputFiles.length, bytes: outputFiles.reduce((total, f) => total + Number(f.bytes), 0) } } };
1014
+ const candidate = { ...project, versions: [...project.versions, version] };
1015
+ const problem = rigRegistrationFindings(dir, candidate)[0];
1016
+ if (problem) refuse(problem.code, problem.message);
1017
+ created.push(join(dir, pkg));
1018
+ prepareRigReviewPackage([String(n), dir, '--config', String(configBinding.path), '--from', `${out}/out`], candidate, partReferences.map(reference => reference.path));
1019
+ // The record must pass the same check as every other writer's before it is renamed into place.
1020
+ const fresh = newFindings(baseline, checkProject(dir, candidate, { now }).refusals)[0];
1021
+ if (fresh) refuse(fresh.code, fresh.message);
1022
+ for (const entry of skipped) {
1023
+ const currentDestinations = versionDestinations(entry.n).filter(target => destinationExists(dir, target, invalidDestination));
1024
+ if (!isDeepStrictEqual(currentDestinations, entry.destinations)) fail('RIG_REGISTRATION_SOURCE_CHANGED', `versions[v${n}].registration.skipped`, `v${entry.n} occupying destinations changed during registration`);
1025
+ }
1026
+ for (const [path, names] of sourceDirectories) if (!isDeepStrictEqual(readdirSync(path).sort(), names)) fail('RIG_REGISTRATION_SOURCE_CHANGED', path, 'source directory changed during registration');
1027
+ for (const source of sources) if (!readFileSync(source.source).equals(source.bytes)) fail('RIG_REGISTRATION_SOURCE_CHANGED', source.source, 'source changed during registration');
1028
+ if (!readFileSync(file).equals(original)) fail('RIG_REGISTRATION_SOURCE_CHANGED', 'project.json', 'record changed during registration');
1029
+ const pending = join(dir, evidence, 'project.pending');
1030
+ const recordBytes = Buffer.from(`${JSON.stringify(candidate, null, 2)}\n`);
1031
+ publicationIntent = prepareVersionIntent(dir, recordBytes, n);
1032
+ writeFileSync(pending, recordBytes);
1033
+ renameSync(pending, file);
1034
+ created.length = 0;
1035
+ publishCommittedVersion(dir, n);
1036
+ return { stdout: json ? JSON.stringify(version, null, 2) : `rig-version: v${n} registered; ${String((version.review_package as JsonObject).open)}`, exit: 0 };
1037
+ } catch (error) {
1038
+ if (error instanceof Refusal) throw error;
1039
+ return fail('RIG_REGISTRATION_FILE_INVALID', `versions[v${n}].registration`, error instanceof Error ? error.message : String(error));
1040
+ } finally {
1041
+ cancelUncommittedIntent(dir, publicationIntent);
1042
+ for (const path of created.reverse()) rmSync(path, { recursive: true, force: true });
1043
+ closeSync(descriptor);
1044
+ rmSync(lock);
1045
+ releasePublication();
1046
+ }
1047
+ }