@rig-c/create 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
package/src/choices.ts ADDED
@@ -0,0 +1,378 @@
1
+ import { SceneError, currentScene, sceneCurrentAt, sceneReference } from './scene.ts';
2
+ import { humanReviewVerdicts } from './review.ts';
3
+ import { intakeContext } from './intake.ts';
4
+ /** Per-ask proposals and human selections. No producer is run or measurement derived here. */
5
+ import { createHash } from 'node:crypto';
6
+ import { lstatSync, readFileSync, realpathSync } from './check-reads.ts';
7
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
8
+ import { isDeepStrictEqual as equal } from 'node:util';
9
+ import { asArray, collectItems, collectSignoffs, collectionQuestion, loadCollection, isObject, type Ctx, type Finding, type Item } from './check.ts';
10
+ import { draftFileUnavailable } from './drafts.ts';
11
+ import { collectReopens, liveSignoff } from './reopen.ts';
12
+ import { latestPartsApproval, structurePathIsIgnored } from './structure.ts';
13
+ import { CHOICE_ORDER_SPEC, approvalStageOfGuide, hasStageOrder, interviewStageOfGuide, stageIndex } from './stages.ts';
14
+ import { WAYPOINT_ASKS } from './waypoints.ts';
15
+ import type { Json, JsonObject } from './templates.ts';
16
+
17
+ export const OPTIONS_SPEC = 'rig-create/interview-options@1';
18
+ export const CHOICE_SPEC = 'rig-create/interview-choice@1';
19
+ export { CHOICE_ORDER_SPEC } from './stages.ts';
20
+ /** Labels legacy warnings; dates alone never adopt choices. Correct at landing. */
21
+ export const CHOICE_ANSWERS_SINCE = '2026-11-06T00:00:00Z';
22
+ /** Scene-kind option sets drafted earlier keep the rule of their day: the latest scene, current or not. */
23
+ export const SCENE_CONTEXT_SINCE = '2026-11-08T00:00:00Z';
24
+ export const sha256 = (bytes: Uint8Array): string => createHash('sha256').update(bytes).digest('hex');
25
+ const text = (v: Json | undefined): v is string => typeof v === 'string' && v.trim() !== '';
26
+ const date = (v: Json | undefined): number => typeof v === 'string' ? Date.parse(v) : NaN;
27
+ const id = (v: Json | undefined): v is string => typeof v === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(v);
28
+ const objects = (v: Json | undefined): JsonObject[] => asArray(v).filter(isObject);
29
+ export class ChoiceError extends Error {
30
+ constructor(readonly code: string, message: string) { super(message); }
31
+ }
32
+ function fail(code: string, why: string): never { throw new ChoiceError(code, why); }
33
+ function invalid(why: string): never { return fail('INTERVIEW_OPTIONS_INVALID', why); }
34
+ function badChoice(why: string): never { return fail('INTERVIEW_CHOICE_INVALID', why); }
35
+ export function choiceAdoption(project: JsonObject, item?: Item): number | null {
36
+ const interview = isObject(project.interview) ? project.interview : {};
37
+ if (item?.kind === 'post' && isObject(interview.post) && interview.post.choices_adopted_at !== undefined) return date(interview.post.choices_adopted_at);
38
+ if (!isObject(interview.order) || interview.order.spec !== CHOICE_ORDER_SPEC) return null;
39
+ return date(interview.order.choices_adopted_at);
40
+ }
41
+
42
+ /** Read exact retained bytes, rejecting both lexical and canonical ignored roots. */
43
+ export function literalFile(dir: string, path: string): string {
44
+ if (isAbsolute(path) || path.split('/').some(p => !p || p === '.' || p === '..')) throw Error('expected literal project-relative file path');
45
+ let file = realpathSync(dir);
46
+ const segments = path.split('/');
47
+ for (const [i, segment] of segments.entries()) {
48
+ file = resolve(file, segment);
49
+ const stat = lstatSync(file);
50
+ if (stat.isSymbolicLink() || (i < segments.length-1 && !stat.isDirectory()) || (i === segments.length-1 && !stat.isFile())) throw Error('symlinks and nonregular files are not supported');
51
+ }
52
+ return file;
53
+ }
54
+ export function optionFile(dir: string, binding: Json | undefined): { set: JsonObject; bytes: Buffer } {
55
+ try {
56
+ if (!isObject(binding) || !text(binding.path) || isAbsolute(binding.path) || binding.path.split('/').some(p => !p || p === '.' || p === '..')
57
+ || typeof binding.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(binding.sha256)) throw Error('expected retained project-relative path and raw sha256');
58
+ const file = literalFile(dir, binding.path);
59
+ const rel = relative(realpathSync(dir), realpathSync(file));
60
+ if (rel === '' || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel) || structurePathIsIgnored(dir, binding.path) || !lstatSync(file).isFile()) throw Error('expected retained regular file within the project');
61
+ const bytes = readFileSync(file);
62
+ if (sha256(bytes) !== binding.sha256) throw Error('retained option bytes changed');
63
+ const parsed: Json = JSON.parse(bytes.toString('utf8'));
64
+ if (!isObject(parsed)) throw Error('expected option object');
65
+ return { set: parsed, bytes };
66
+ } catch (e) { fail('INTERVIEW_OPTIONS_FILE', `${e instanceof Error ? e.message : String(e)}; restore the original retained option file`); }
67
+ }
68
+
69
+ function resolvedValue(project: JsonObject, question: string, collection: JsonObject | null = null): Json | undefined {
70
+ const item = collectItems(project, { collection }).find(i => i.id === question);
71
+ if (item?.state.state === 'user') return item.state.answer.value;
72
+ if (item?.state.state === 'delegated' && isObject(item.state.answer.agent_decision)) return item.state.answer.agent_decision.value;
73
+ return undefined;
74
+ }
75
+ /** Ground is applicable if any offered projection can choose visible; a core pick must explicitly constrain it. */
76
+ export function applicableAsks(project: JsonObject, item: Item, set?: JsonObject, collection: JsonObject | null = null): string[] {
77
+ if (item.kind !== 'waypoint') return [item.id];
78
+ const rig = resolvedValue(project, 'rig_type',collection);
79
+ const projection = objects(set?.asks).find(a => a.id === 'projection');
80
+ const offeredVisible = objects(projection?.options).some(o => isObject(o.fragment) && o.fragment.project === 'visible');
81
+ const currentAssemble = resolvedValue(project, 'assemble',collection);
82
+ return (WAYPOINT_ASKS[item.id] ?? []).filter(a => (a.required_for === undefined || (a.required_for === 'fullscreen.game_logic' ? rig === 'fullscreen.game_logic' : rig !== 'fullscreen.game_logic'))
83
+ && (a.id !== 'ground' || item.id !== 'assemble' || offeredVisible || (isObject(currentAssemble) && currentAssemble.project === 'visible'))).map(a => a.id);
84
+ }
85
+
86
+ export function assembleFragments(rule: string, fragments: Json[]): Json {
87
+ if (rule === 'single') {
88
+ if (fragments.length !== 1) badChoice('single needs exactly one contribution');
89
+ return fragments[0] as Json;
90
+ }
91
+ if (rule === 'concat') {
92
+ if (fragments.some(f => !Array.isArray(f))) badChoice('concat contributions must be arrays');
93
+ return fragments.flatMap(f => f as Json[]);
94
+ }
95
+ if (rule === 'merge') {
96
+ const result: JsonObject = {};
97
+ for (const fragment of fragments) {
98
+ if (!isObject(fragment)) badChoice('merge contributions must be objects');
99
+ for (const [key, value] of Object.entries(fragment)) {
100
+ if (Object.hasOwn(result, key)) badChoice(`merge key collision: ${key}`);
101
+ Object.defineProperty(result, key, { value, enumerable: true, writable: true, configurable: true });
102
+ }
103
+ }
104
+ return result;
105
+ }
106
+ return invalid('assembly.rule must be single, concat or merge');
107
+ }
108
+
109
+ export function validateSet(project: JsonObject, item: Item, set: JsonObject, collection: JsonObject | null = null): void {
110
+ if (set.schema !== OPTIONS_SPEC || !id(set.id) || set.question !== item.id || set.stage !== (item.path === 'intake.choice.answer' ? 'parts' : interviewStageOfGuide(item.id, item.kind === 'post', hasStageOrder(project)))
111
+ || !text(set.drafted_by) || !Number.isFinite(date(set.drafted_at)) || !isObject(set.context) || !Array.isArray(set.asks)
112
+ || !isObject(set.assembly) || !['single', 'concat', 'merge'].includes(String(set.assembly.rule))) invalid('set identity, author, clock, question, stage, context, asks and assembly are required');
113
+ const asks = objects(set.asks);
114
+ const expected = applicableAsks(project, item, set,collection);
115
+ if (asks.length !== set.asks.length || !equal(asks.map(a => a.id), expected) || (set.assembly.rule === 'single' && asks.length !== 1)) invalid('asks must list exactly the applicable asks in questionnaire order; single is only for one ask');
116
+ for (const ask of asks) {
117
+ const options = objects(ask.options);
118
+ if (!text(ask.prompt) || !Array.isArray(ask.options) || options.length !== ask.options.length || new Set(options.map(o => o.id)).size !== options.length) invalid('each ask needs prompt and unique option ids');
119
+ let recommended = 0;
120
+ for (const option of options) {
121
+ if (!id(option.id) || !text(option.label) || !text(option.effect) || typeof option.recommended !== 'boolean' || !['value', 'other', 'auto'].includes(String(option.kind))) invalid('option needs id, kind, label, effect and explicit recommended boolean');
122
+ if (option.kind === 'value') {
123
+ if (!('fragment' in option)) invalid('value options require fragment');
124
+ if ((set.assembly.rule === 'concat' && !Array.isArray(option.fragment)) || (set.assembly.rule === 'merge' && !isObject(option.fragment))) invalid('fragment type does not match assembly rule');
125
+ } else if ('fragment' in option || option.recommended) invalid('Other and auto have no preset fragment and cannot be recommended');
126
+ if (option.recommended) { recommended++; if (!text(option.reason)) invalid('recommended option requires a reason'); }
127
+ }
128
+ if (recommended !== 1 || options.filter(o => o.kind === 'value').length < 2 || options.filter(o => o.kind === 'other').length !== 1 || options.filter(o => o.kind === 'auto').length !== 1) invalid('each ask needs two value options, one recommendation, one Other and one auto');
129
+ }
130
+ if (set.constraints !== undefined) {
131
+ if (!Array.isArray(set.constraints)) invalid('constraints must be a list');
132
+ for (const constraint of set.constraints) {
133
+ if (!isObject(constraint) || !Array.isArray(constraint.asks) || constraint.asks.length < 2 || new Set(constraint.asks).size !== constraint.asks.length
134
+ || constraint.asks.some(a => typeof a !== 'string' || !expected.includes(a)) || !Array.isArray(constraint.allowed) || !constraint.allowed.length) invalid('constraint needs distinct known asks and allowed tuples');
135
+ for (const tuple of constraint.allowed) {
136
+ if (!Array.isArray(tuple) || tuple.length !== constraint.asks.length || tuple.some((opt, i) => !objects(asks.find(a => a.id === (constraint.asks as Json[])[i])?.options).some(o => o.kind === 'value' && o.id === opt))) invalid('constraint tuple must name a value option on each corresponding ask');
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ /**
143
+ * A context is a projection of existing identities, never a producer report reconstructed by create.
144
+ * A post set takes the scene kind only while the scene is current at `at`, which reads the scene
145
+ * document under `dir`; `legacyScene` is the rule before SCENE_CONTEXT_SINCE, where any latest scene was taken.
146
+ */
147
+ export function optionContext(project: JsonObject, stage: string, at: number, dir?: string, legacyScene = false): JsonObject {
148
+ if (stage === 'parts') {
149
+ try { return intakeContext(project, at); } catch (e) { fail('INTERVIEW_OPTIONS_STALE', `${String(e)}; record the preflight before showing options`); }
150
+ }
151
+ const reopens = collectReopens(project, []).filter(r => r.at <= at);
152
+ if (stage === 'interview') {
153
+ const files = objects(project.inputs).filter(f => f.at === undefined || date(f.at) <= at).map(f => ({ path: f.path as Json, sha256: f.sha256 as Json }));
154
+ if (!files.length) fail('INTERVIEW_OPTIONS_STALE', 'source inputs must be recorded before drafting options');
155
+ return { kind: 'inputs', files };
156
+ }
157
+ if (stage === 'rigging-interview') {
158
+ const approval = latestPartsApproval(project, at);
159
+ const draft = objects(project.drafts).find(d => d.id === approval?.draft_id);
160
+ if (!approval || !draft || date(draft.at) > at || !isObject(approval.evidence) || !isObject(approval.structure) || !isObject(draft.parts)) fail('INTERVIEW_OPTIONS_STALE', 'review and retain parts/structure before drafting rigging options');
161
+ return { kind: 'parts', accepted_parts: { signoff_at: approval.at as Json, evidence: approval.evidence }, structure: approval.structure, draft_id: draft.id as Json, parts: draft.parts };
162
+ }
163
+ try {
164
+ const candidate = currentScene(project, at);
165
+ if (candidate && !legacyScene && dir === undefined) fail('INTERVIEW_OPTIONS_STALE', `${String(candidate.path)}: the scene's currentness needs the project folder; pass dir to optionContext`);
166
+ const scene = legacyScene || !candidate ? candidate : sceneCurrentAt(dir!, project, at);
167
+ if (scene) return { kind: 'scene', scene: sceneReference(scene) };
168
+ } catch (error) {
169
+ if (error instanceof SceneError) fail('INTERVIEW_OPTIONS_STALE', error.message);
170
+ throw error;
171
+ }
172
+ const signoff = collectSignoffs(project, []).filter(s => s.stage === 'rig-review' && s.checkpoint === null && s.at <= at && liveSignoff(reopens, s.stage, s.at)).sort((a,b) => b.at - a.at)[0];
173
+ const rigs = objects(project.versions).filter(v => v.kind === 'rig' && date(v.at) <= at && liveSignoff(reopens, 'rig-review', date(v.at)))
174
+ .filter(v => { const verdicts = humanReviewVerdicts(v.verdicts, 'rig-review').map(v => v.record).filter(d => date(d.at) <= at); return verdicts.at(-1)?.verdict === 'accepted'; }).sort((a,b) => Number(b.n) - Number(a.n));
175
+ const rig = rigs[0];
176
+ const provenance = isObject(rig?.provenance) ? rig.provenance : {};
177
+ if (!signoff || !rig || !isObject(provenance.config)) fail('INTERVIEW_OPTIONS_STALE', 'accept a rig with retained config before drafting post options');
178
+ const approval = latestPartsApproval(project, date(rig.at));
179
+ const draft = objects(project.drafts).find(d => d.id === approval?.draft_id);
180
+ if (!draft || !isObject(draft.parts)) fail('INTERVIEW_OPTIONS_STALE', 'retain the accepted rig parts identity before drafting post options');
181
+ return { kind: 'rig', rig_version: rig.n as Json, signoff_at: signoff.record.at as Json, config: provenance.config, parts: draft.parts };
182
+ }
183
+
184
+ function verifyBinding(dir: string, binding: Json, fresh: boolean): void {
185
+ if (!isObject(binding) || !text(binding.path) || typeof binding.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(binding.sha256) || isAbsolute(binding.path)) fail('INTERVIEW_OPTIONS_STALE', 'invalid context file binding');
186
+ try {
187
+ if (binding.path.startsWith('derived/drafts/')) {
188
+ const root = binding.path.split('/').slice(0,3).join('/');
189
+ if (draftFileUnavailable(dir, binding.path, root)) { if (fresh) throw Error('viewed draft bytes unavailable'); else return; }
190
+ }
191
+ const file = literalFile(dir, binding.path);
192
+ const rel = relative(realpathSync(dir), realpathSync(file));
193
+ if (rel === '' || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel) || !lstatSync(file).isFile() || sha256(readFileSync(file)) !== binding.sha256) throw Error('context file changed or escaped');
194
+ } catch (e) { fail('INTERVIEW_OPTIONS_STALE', `${String(binding.path)}: ${e instanceof Error ? e.message : String(e)}; redraft against current retained context`); }
195
+ }
196
+ /** `warn` receives the reason when a set drafted before SCENE_CONTEXT_SINCE holds only under that day's rule. */
197
+ export function validateContext(dir: string, project: JsonObject, set: JsonObject, at: number, fresh = false, warn?: (why: string) => void): void {
198
+ const drafted = date(set.drafted_at);
199
+ if (!Number.isFinite(at) || drafted > at) fail('INTERVIEW_OPTIONS_STALE','selection must follow drafting; redraft against current accepted inputs');
200
+ const contextProject = isObject(set.context) && set.context.kind === 'rig' ? { ...project, scenes: [] } : project;
201
+ if (isObject(set.context) && set.context.kind === 'inputs' && !fresh) {
202
+ // Inputs are append-only and older declarations need not carry their own clock.
203
+ // A later additional input must not rewrite which immutable input an earlier person saw.
204
+ const declared = objects(project.inputs);
205
+ if (!Array.isArray(set.context.files) || !set.context.files.length || set.context.files.some(f => !isObject(f) || !declared.some(d => d.path === f.path && d.sha256 === f.sha256 && (d.at === undefined || date(d.at) <= drafted)))) fail('INTERVIEW_OPTIONS_STALE','shown source identity is no longer declared; restore the original inputs');
206
+ } else {
207
+ const live = (legacyScene: boolean): void => {
208
+ if (!equal(optionContext(contextProject, String(set.stage), drafted, dir, legacyScene), set.context) || !equal(optionContext(contextProject, String(set.stage), at, dir, legacyScene), set.context)) fail('INTERVIEW_OPTIONS_STALE', 'options no longer match the live context; redraft against current accepted inputs');
209
+ };
210
+ if (!isObject(set.context) || set.context.kind !== 'scene' || !(drafted < Date.parse(SCENE_CONTEXT_SINCE))) live(false);
211
+ else {
212
+ try { live(false); }
213
+ catch (error) {
214
+ if (!(error instanceof ChoiceError)) throw error;
215
+ live(true);
216
+ warn?.(`drafted before ${SCENE_CONTEXT_SINCE} on a scene that was not current (${error.message})`);
217
+ }
218
+ }
219
+ }
220
+ if (collectReopens(project, []).some(r => r.at > drafted && r.at <= at && stageIndex(r.stage) <= stageIndex(String(set.stage)))) fail('INTERVIEW_OPTIONS_STALE', 'context was reopened after drafting; redraft the option set');
221
+ for (const file of optionContextFiles(set.context as JsonObject)) verifyBinding(dir, file, fresh);
222
+ }
223
+
224
+ /** The exact retained bindings read by validateContext, shared with publication. */
225
+ export function optionContextFiles(context: JsonObject): Json[] {
226
+ const files: Json[] = [...(context.kind === 'inputs' ? asArray(context.files) : isObject(context.parts) ? asArray(context.parts.files) : [])];
227
+ if (context.kind === 'parts') files.push(context.structure as Json, (context.accepted_parts as JsonObject).evidence as Json);
228
+ if (context.kind === 'rig') files.push(context.config as Json);
229
+ if (context.kind === 'scene') {
230
+ if (!isObject(context.scene)) fail('INTERVIEW_OPTIONS_STALE', 'retain the scene reference');
231
+ files.push({ path: context.scene.path as Json, sha256: context.scene.sha256 as Json });
232
+ }
233
+ if (context.kind === 'intake') files.push(context.report as Json);
234
+ return files;
235
+ }
236
+
237
+ export function choiceVerbatim(picks: JsonObject[], note?: Json): string {
238
+ const body = picks.map(p => { const shown = p.shown as JsonObject; return `${String(shown.label)}\n${String(shown.effect)}${p.other_text === undefined ? '' : `\n${String(p.other_text)}`}`; }).join('\n\n');
239
+ return body + (note === undefined ? '' : `\n${String(note)}`);
240
+ }
241
+
242
+ export interface Selection { value?: Json; delegated: boolean; asks: JsonObject[]; autos: string[]; pending: string[]; fragments: Map<string, Json>; }
243
+ export function selection(set: JsonObject, choice: JsonObject): Selection {
244
+ if (choice.spec !== CHOICE_SPEC || !Array.isArray(choice.picks) || (choice.note !== undefined && typeof choice.note !== 'string')) badChoice('choice needs spec, ordered picks and optional exact note');
245
+ const asks = objects(set.asks); const picks = objects(choice.picks);
246
+ if (picks.length !== choice.picks.length || !equal(picks.map(p => p.ask), asks.map(a => a.id))) badChoice('pick exactly once per ask in questionnaire order');
247
+ const fragments = new Map<string, Json>(); const autos: string[] = []; const pending: string[] = []; const coverage: JsonObject[] = [];
248
+ for (const [i, pick] of picks.entries()) {
249
+ const ask = asks[i] as JsonObject; const option = objects(ask.options).find(o => o.id === pick.option_id);
250
+ if (!option || !equal(pick.shown, { label: option.label, effect: option.effect })) badChoice('unknown pick or shown text differs from the retained option');
251
+ if (option.kind === 'other') {
252
+ if (!text(pick.other_text)) badChoice('Other requires exact nonempty text');
253
+ if ('other_fragment' in pick) fragments.set(String(ask.id), pick.other_fragment as Json);
254
+ else pending.push(String(ask.id));
255
+ } else {
256
+ if ('other_text' in pick || 'other_fragment' in pick) badChoice('only Other has other_text/other_fragment');
257
+ if (option.kind === 'auto') autos.push(String(ask.id)); else fragments.set(String(ask.id), option.fragment as Json);
258
+ }
259
+ coverage.push({ id: ask.id as Json, state: option.kind === 'auto' ? 'delegated' : pending.includes(String(ask.id)) ? 'other-pending' : 'user' });
260
+ }
261
+ const rule = String((set.assembly as JsonObject).rule);
262
+ const user = [...fragments.values()];
263
+ const userValue = user.length ? assembleFragments(rule,user) : undefined;
264
+ return { value: pending.length ? null : userValue, delegated: autos.length === asks.length, asks: coverage, autos, pending, fragments };
265
+ }
266
+
267
+ export function checkChoiceConstraints(set: JsonObject, choice: JsonObject, fragments: Map<string, Json>): void {
268
+ const asks = objects(set.asks); const picks = objects(choice.picks);
269
+ for (const constraint of objects(set.constraints)) {
270
+ const names = asArray(constraint.asks).map(String);
271
+ const tuple = names.map(name => {
272
+ if (!fragments.has(name)) return undefined; // Check every landed contribution even while another is pending.
273
+ const ask = asks.find(a => a.id === name) as JsonObject;
274
+ const pick = picks.find(p => p.ask === name) as JsonObject;
275
+ const option = objects(ask.options).find(o => o.id === pick.option_id) as JsonObject;
276
+ if (option.kind === 'value') return option.id;
277
+ const matches = objects(ask.options).filter(o => o.kind === 'value' && equal(o.fragment, fragments.get(name)));
278
+ if (matches.length !== 1) fail('INTERVIEW_CHOICE_CONSTRAINT', `ask ${name}: fragment resolves to ${matches.length} value options; redraft the set to offer that option`);
279
+ return matches[0]?.id;
280
+ });
281
+ if (tuple.some(id => id === undefined)) continue;
282
+ if (!asArray(constraint.allowed).some(t => equal(t, tuple))) fail('INTERVIEW_CHOICE_CONSTRAINT', 'chosen combination is not allowed; choose an offered allowed combination or redraft the set to offer that option');
283
+ }
284
+ }
285
+
286
+ function answersAt(project: JsonObject, at: number, collection: JsonObject | null = null): JsonObject {
287
+ const snapshot = structuredClone(project);
288
+ for (const item of collectItems(snapshot, { collection })) {
289
+ const field = item.kind === 'framing' ? 'answer' : 'guide';
290
+ const all = [...objects(item.record.history), ...(isObject(item.raw) ? [item.raw] : [])];
291
+ if (isObject(item.raw) && item.raw.basis === 'inherited' && collection && isObject(item.raw.from) && item.raw.from.collection === collection.name && typeof item.raw.from.question === 'string') {
292
+ const source = collectionQuestion(collection,item.raw.from.question);
293
+ if (source) all.push(...objects(source.history));
294
+ if (item.state.state === 'user' || item.state.state === 'delegated') all.push(item.state.answer);
295
+ }
296
+ item.record[field] = all.filter(a => date(a.at) <= at).sort((a,b) => date(b.at) - date(a.at))[0] ?? null;
297
+ }
298
+ return snapshot;
299
+ }
300
+
301
+ /** Validate the transition allowance independently of the decision being replaced. */
302
+ export function refusalBaseline(answer: JsonObject): JsonObject[] {
303
+ if (answer.refusal_baseline !== undefined && (!Array.isArray(answer.refusal_baseline) || answer.refusal_baseline.some(entry =>
304
+ !isObject(entry) || Object.keys(entry).length !== 3 || !text(entry.code) || !text(entry.path)
305
+ || !Number.isSafeInteger(entry.count) || Number(entry.count) <= 0))) badChoice('refusal_baseline must contain only code, path and positive integer count');
306
+ return (answer.refusal_baseline ?? []) as JsonObject[];
307
+ }
308
+
309
+ export function validateChoice(dir: string, project: JsonObject, item: Item, answer: JsonObject, warn?: (why: string) => void): void {
310
+ if (!isObject(answer.choice)) badChoice('choice must be an object');
311
+ refusalBaseline(answer);
312
+ const choice = answer.choice;
313
+ const { set } = optionFile(dir, choice.set);
314
+ validateSet(answersAt(project, date(set.drafted_at),loadCollection(dir)), item, set); validateContext(dir, project, set, date(answer.at), false, warn);
315
+ if (!text(answer.by) || answer.by.toLowerCase() === 'agent' || !Number.isFinite(date(answer.at))) badChoice('a selection needs a human identity and valid clock');
316
+ const projected = selection(set, choice);
317
+ if (answer.delegated !== projected.delegated || !equal(answer.asks, projected.asks) || answer.verbatim !== choiceVerbatim(objects(choice.picks), choice.note)
318
+ || (projected.value === undefined ? 'value' in answer : !equal(answer.value, projected.value))) badChoice('answer state, asks, value or verbatim differs from selected contributions');
319
+ if (projected.pending.length && item.path !== 'intake.choice.answer') {
320
+ const stage = approvalStageOfGuide(item.id,item.kind === 'post',true);
321
+ const reopens = collectReopens(project,[]).filter(r => r.at <= date(answer.at));
322
+ // History does not become invalid merely because a later resolved answer was signed.
323
+ const end = objects(item.record.history).concat(isObject(item.raw) ? [item.raw] : []).filter(a => date(a.at) > date(answer.at)).reduce((n,a) => Math.min(n,date(a.at)),Infinity);
324
+ if (collectSignoffs(project,[]).some(s => (s.stage === stage || s.stage === interviewStageOfGuide(item.id,item.kind === 'post',true)) && s.checkpoint === null && s.at >= date(answer.at) && s.at < end && liveSignoff(reopens,s.stage,s.at))) fail('INTERVIEW_CHOICE_REQUIRED','Other is pending; redraft with a tailored value option and have the person explicitly pick it before approval');
325
+ }
326
+ const decision = answer.agent_decision;
327
+ if (isObject(decision) && equal(Object.keys(decision),['covers']) && decision.covers === projected.autos.join(',') && projected.autos.length) { checkChoiceConstraints(set,choice,projected.fragments); return; }
328
+ if (decision !== undefined) {
329
+ if (!isObject(decision) || !projected.autos.length || !text(decision.by) || !text(decision.reasoning)
330
+ || date(decision.at) < date(answer.at) || !Number.isFinite(date(decision.at)) || decision.covers !== projected.autos.join(',')
331
+ || !Array.isArray(decision.fragments) || !equal(objects(decision.fragments).map(f => f.ask), projected.autos)
332
+ || objects(decision.fragments).length !== decision.fragments.length || objects(decision.fragments).some(f => !('fragment' in f))) badChoice('decision must record exactly the auto asks in order, exact covers, author, clock and reasoning');
333
+ validateContext(dir, project, set, date(decision.at), false, warn);
334
+ const entries = objects(decision.fragments);
335
+ const assembled = assembleFragments(String((set.assembly as JsonObject).rule), entries.map(f => f.fragment as Json));
336
+ if (!equal(decision.value, assembled)) badChoice('agent_decision.value differs from its per-ask fragments');
337
+ for (const entry of entries) projected.fragments.set(String(entry.ask), entry.fragment as Json);
338
+ }
339
+ if (projected.fragments.size === objects(set.asks).length) assembleFragments(String((set.assembly as JsonObject).rule), objects(set.asks).map(a => projected.fragments.get(String(a.id)) as Json));
340
+ checkChoiceConstraints(set, choice, projected.fragments);
341
+ }
342
+
343
+ function finding(e: unknown, path: string): Finding {
344
+ return { code: e instanceof ChoiceError ? e.code : 'INTERVIEW_CHOICE_INVALID', path, message: `${path}: ${e instanceof Error ? e.message : String(e)}; retain the shown set, explicitly pick each ask, and record the exact contributions` };
345
+ }
346
+
347
+ /** One WARN per record path, however many of its clocks the earlier rule had to hold. */
348
+ function legacyScene(ctx: Ctx, path: string): (why: string) => void {
349
+ return why => {
350
+ if (ctx.warnings.some(w => w.code === 'INTERVIEW_OPTIONS_SCENE_LEGACY' && w.path === path)) return;
351
+ ctx.warnings.push({ code: 'INTERVIEW_OPTIONS_SCENE_LEGACY', path, message: `${path}: option set ${why}; preserve the record and redraft against the current context when this question is asked again` });
352
+ };
353
+ }
354
+ export function checkChoices(ctx: Ctx): void {
355
+ const adopted = choiceAdoption(ctx.project);
356
+ if (adopted !== null && (!Number.isFinite(adopted) || !hasStageOrder(ctx.project))) ctx.refusals.push(finding(new ChoiceError('INTERVIEW_CHOICE_INVALID', 'order@2 requires choices_adopted_at'), 'interview.order'));
357
+ if (adopted === null) ctx.warnings.push({ code: 'CHOICE_ANSWERS_LEGACY', path: 'interview.order', message: `interview.order: choices are not adopted (${CHOICE_ANSWERS_SINCE} legacy label); preserve answers and adopt order@2 explicitly before asking fresh choices` });
358
+ const post = isObject(ctx.project.interview) && isObject(ctx.project.interview.post) ? ctx.project.interview.post : null;
359
+ if (post?.choices_adopted_at !== undefined && !Number.isFinite(date(post.choices_adopted_at))) ctx.refusals.push(finding(new ChoiceError('INTERVIEW_CHOICE_INVALID', 'post choices require a valid choices_adopted_at'), 'interview.post'));
360
+ for (const item of ctx.items) {
361
+ const itemAdopted = choiceAdoption(ctx.project, item);
362
+ if (item.record.pending_options !== undefined) {
363
+ try { const { set } = optionFile(ctx.dir, item.record.pending_options); validateSet(ctx.project, item, set,ctx.collection); validateContext(ctx.dir, ctx.project, set, date(set.drafted_at), false, legacyScene(ctx, `${item.path}.pending_options`)); }
364
+ catch (e) { ctx.refusals.push(finding(e, `${item.path}.pending_options`)); }
365
+ }
366
+ const answers: { answer: JsonObject; path: string; current: boolean }[] = objects(item.record.history).map((answer,i) => ({ answer, path: `${item.path}.history[${i}]`, current: false }));
367
+ if (item.state.state === 'user' || item.state.state === 'delegated') answers.push({ answer: item.state.answer, path: item.path, current: true });
368
+ for (const { answer, path, current } of answers) {
369
+ if (answer.choice !== undefined) { try { validateChoice(ctx.dir, ctx.project, item, answer, legacyScene(ctx, path)); } catch (e) { ctx.refusals.push(finding(e, path)); } }
370
+ else if (current && itemAdopted !== null) {
371
+ // An inherited collection answer is historical evidence, never a fabricated member click.
372
+ const inherited = isObject(item.raw) && item.raw.basis === 'inherited';
373
+ if (!inherited && date(answer.at) >= itemAdopted) ctx.refusals.push(finding(new ChoiceError('INTERVIEW_CHOICE_REQUIRED', 'new adopted answer requires explicit per-ask choices'), path));
374
+ else ctx.warnings.push({ code: 'CHOICE_ANSWERS_LEGACY', path, message: `${path}: grandfathered sentence answer; preserve original words/date and show them as a tailored option when this question is asked again` });
375
+ }
376
+ }
377
+ }
378
+ }