@mnstry/atelier 0.2.0-alpha.12 → 0.2.0-alpha.14

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 (531) hide show
  1. package/CHANGELOG.md +894 -0
  2. package/README.md +159 -6
  3. package/contracts/atelier-adoption-policy.v2.schema.json +55 -0
  4. package/contracts/atelier-architecture.v1.schema.json +258 -0
  5. package/contracts/atelier-boundary-delegations.v1.schema.json +161 -0
  6. package/contracts/atelier-boundary-owner-keys.v1.schema.json +98 -0
  7. package/contracts/atelier-build.v1.schema.json +1076 -0
  8. package/contracts/atelier-capability.v1.schema.json +1252 -0
  9. package/contracts/atelier-coordination.v1.schema.json +1050 -0
  10. package/contracts/atelier-evidence-navigation.v1.schema.json +603 -0
  11. package/contracts/atelier-harness.v1.schema.json +234 -0
  12. package/contracts/atelier-ingestion.v1.schema.json +806 -0
  13. package/contracts/atelier-inquiry.v1.schema.json +1772 -0
  14. package/contracts/atelier-instruction-adoption.v1.schema.json +490 -0
  15. package/contracts/atelier-interaction.v1.schema.json +553 -0
  16. package/contracts/atelier-judgment.v1.schema.json +690 -0
  17. package/contracts/atelier-knowledge.v1.schema.json +1512 -0
  18. package/contracts/atelier-learning.v1.schema.json +1095 -0
  19. package/contracts/atelier-migration.v3.schema.json +64 -0
  20. package/contracts/atelier-personal-workspace-manifest.v1.schema.json +87 -0
  21. package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
  22. package/contracts/atelier-template-adoption.v1.schema.json +223 -0
  23. package/contracts/atelier-template-profile.v1.schema.json +1168 -0
  24. package/contracts/atelier-trackable.v1.schema.json +781 -0
  25. package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
  26. package/docs/architecture.md +89 -0
  27. package/docs/assurance-controls.md +2 -2
  28. package/docs/blocks/capability-stewardship.md +17 -0
  29. package/docs/blocks/claims.md +16 -3
  30. package/docs/blocks/inquiry-harnesses.md +19 -0
  31. package/docs/blocks/learning-harnesses.md +17 -0
  32. package/docs/capability-stewardship.md +267 -0
  33. package/docs/coordination.md +51 -0
  34. package/docs/decision-practices.md +111 -0
  35. package/docs/decisions.md +29 -0
  36. package/docs/evidence-navigation.md +237 -0
  37. package/docs/exact-upgrades.md +3 -2
  38. package/docs/ingestion.md +181 -0
  39. package/docs/inquiry-harnesses.md +210 -0
  40. package/docs/install.md +80 -3
  41. package/docs/intake-and-guides.md +5 -0
  42. package/docs/integration-contract-decisions.md +24 -2
  43. package/docs/interaction-and-reflection.md +72 -0
  44. package/docs/knowledge-graph.md +20 -0
  45. package/docs/knowledge-health/api.md +30 -0
  46. package/docs/knowledge-health/assessment/api.md +96 -0
  47. package/docs/knowledge-health/assessment/capabilities.md +40 -0
  48. package/docs/knowledge-health/assessment/evaluation.md +27 -0
  49. package/docs/knowledge-health/assessment/facilitator.md +30 -0
  50. package/docs/knowledge-health/assessment/quickstart.md +61 -0
  51. package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
  52. package/docs/knowledge-health/devday/facilitator.md +16 -0
  53. package/docs/knowledge-health/devday/participant-response.md +22 -0
  54. package/docs/knowledge-health/devday/public-default.md +107 -0
  55. package/docs/knowledge-health/devday/quickstart.md +18 -0
  56. package/docs/knowledge-health/devday/support.md +22 -0
  57. package/docs/knowledge-health/exercise.md +17 -0
  58. package/docs/knowledge-health/facilitator.md +20 -0
  59. package/docs/knowledge-health/quickstart.md +94 -0
  60. package/docs/knowledge-health/readme.md +15 -0
  61. package/docs/knowledge-health/support.md +34 -0
  62. package/docs/knowledge-setup.md +248 -0
  63. package/docs/knowledge-workspace.md +264 -0
  64. package/docs/learning-harnesses.md +237 -0
  65. package/docs/learning.md +215 -0
  66. package/docs/local-services.md +223 -19
  67. package/docs/obsidian-contract.md +246 -15
  68. package/docs/obsidian-plugin.md +252 -7
  69. package/docs/obsidian.md +874 -52
  70. package/docs/optional-ingestion.md +83 -0
  71. package/docs/personal-workspace.md +266 -0
  72. package/docs/practical-judgment.md +92 -0
  73. package/docs/progressive-ingestion-acceptance.md +89 -0
  74. package/docs/progressive-ingestion.md +235 -0
  75. package/docs/project-command-inventory.md +1 -0
  76. package/docs/release-engineering.md +50 -0
  77. package/docs/repo-boundary-guard.md +120 -3
  78. package/docs/research-starter-quickstart.md +66 -0
  79. package/docs/research-starter.md +114 -0
  80. package/docs/semantic-ingestion.md +198 -0
  81. package/docs/semantic-operation.md +318 -0
  82. package/docs/skill-steward.md +6 -0
  83. package/docs/template-profile.md +164 -0
  84. package/docs/template-upgrades.md +114 -0
  85. package/docs/trackables.md +111 -0
  86. package/docs/upgrade.md +219 -2
  87. package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
  88. package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
  89. package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
  90. package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
  91. package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
  92. package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
  93. package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
  94. package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
  95. package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
  96. package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
  97. package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
  98. package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
  99. package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
  100. package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
  101. package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
  102. package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
  103. package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
  104. package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
  105. package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
  106. package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
  107. package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
  108. package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
  109. package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
  110. package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
  111. package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
  112. package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
  113. package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
  114. package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
  115. package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
  116. package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
  117. package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
  118. package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
  119. package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
  120. package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
  121. package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
  122. package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
  123. package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
  124. package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
  125. package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
  126. package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
  127. package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
  128. package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
  129. package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
  130. package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
  131. package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
  132. package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
  133. package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
  134. package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
  135. package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
  136. package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
  137. package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
  138. package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
  139. package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
  140. package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
  141. package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
  142. package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
  143. package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
  144. package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
  145. package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
  146. package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
  147. package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
  148. package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
  149. package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
  150. package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
  151. package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
  152. package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
  153. package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
  154. package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
  155. package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
  156. package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
  157. package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
  158. package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
  159. package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
  160. package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
  161. package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
  162. package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
  163. package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
  164. package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
  165. package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
  166. package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
  167. package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
  168. package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
  169. package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
  170. package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
  171. package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
  172. package/fixtures/atelier-interaction/act/valid/document.json +24 -0
  173. package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
  174. package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
  175. package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
  176. package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
  177. package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
  178. package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
  179. package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
  180. package/fixtures/atelier-judgment/case/valid/document.json +75 -0
  181. package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
  182. package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
  183. package/fixtures/atelier-judgment/native-decision.md +3 -0
  184. package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
  185. package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
  186. package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
  187. package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
  188. package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
  189. package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
  190. package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
  191. package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
  192. package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
  193. package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
  194. package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
  195. package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
  196. package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
  197. package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
  198. package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
  199. package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
  200. package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
  201. package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
  202. package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
  203. package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
  204. package/fixtures/atelier-learning/invalid/authority.json +14 -0
  205. package/fixtures/atelier-learning/valid/capture.json +13 -0
  206. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
  207. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
  208. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
  209. package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
  210. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
  211. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
  212. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
  213. package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
  214. package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
  215. package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
  216. package/fixtures/atelier-trackable/request/valid/document.json +22 -0
  217. package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
  218. package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
  219. package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
  220. package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
  221. package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
  222. package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
  223. package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
  224. package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
  225. package/fixtures/capability-packages/adoption.example.json +70 -0
  226. package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
  227. package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
  228. package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
  229. package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
  230. package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
  231. package/fixtures/capability-packages/research/capability-package.json +88 -0
  232. package/fixtures/capability-packages/research/capability-release.json +134 -0
  233. package/fixtures/capability-packages/research/evaluation.md +15 -0
  234. package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
  235. package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
  236. package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
  237. package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
  238. package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
  239. package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
  240. package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
  241. package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
  242. package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
  243. package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
  244. package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
  245. package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
  246. package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
  247. package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
  248. package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
  249. package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
  250. package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
  251. package/fixtures/evidence-navigation/valid/claim.json +36 -0
  252. package/fixtures/evidence-navigation/valid/host.json +16 -0
  253. package/fixtures/evidence-navigation/valid/profile.json +16 -0
  254. package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
  255. package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
  256. package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
  257. package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
  258. package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
  259. package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
  260. package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
  261. package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
  262. package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
  263. package/fixtures/harnesses/learning-cycle.json +780 -0
  264. package/fixtures/inquiry/workshop.json +675 -0
  265. package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
  266. package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
  267. package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
  268. package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
  269. package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
  270. package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
  271. package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
  272. package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
  273. package/fixtures/knowledge-health/calibration-suite.json +26 -0
  274. package/fixtures/knowledge-health/calibration-trial.json +37 -0
  275. package/fixtures/knowledge-health/corrected/checklist.md +18 -0
  276. package/fixtures/knowledge-health/devday/participant-record.json +19 -0
  277. package/fixtures/knowledge-health/expected-summary.json +27 -0
  278. package/fixtures/knowledge-health/workshop/README.md +20 -0
  279. package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
  280. package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
  281. package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
  282. package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
  283. package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
  284. package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
  285. package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
  286. package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
  287. package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
  288. package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
  289. package/fixtures/personal-workspace/overlay.json +24 -0
  290. package/fixtures/templates/local-library.v1.json +76 -0
  291. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
  292. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
  293. package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
  294. package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
  295. package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
  296. package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
  297. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
  298. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
  299. package/package.json +60 -5
  300. package/plugins/obsidian/main.js +57 -8
  301. package/plugins/obsidian/manifest.json +1 -1
  302. package/skills/claude/atelier-build-harness/SKILL.md +59 -0
  303. package/skills/claude/atelier-coordination/SKILL.md +32 -0
  304. package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
  305. package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
  306. package/skills/claude/atelier-interaction/SKILL.md +34 -0
  307. package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
  308. package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
  309. package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
  310. package/skills/claude/atelier-research-harness/SKILL.md +43 -0
  311. package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
  312. package/skills/claude/atelier-trackables/SKILL.md +40 -0
  313. package/skills/codex/atelier-build-harness/SKILL.md +59 -0
  314. package/skills/codex/atelier-coordination/SKILL.md +32 -0
  315. package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
  316. package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
  317. package/skills/codex/atelier-interaction/SKILL.md +34 -0
  318. package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
  319. package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
  320. package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
  321. package/skills/codex/atelier-research-harness/SKILL.md +43 -0
  322. package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
  323. package/skills/codex/atelier-trackables/SKILL.md +40 -0
  324. package/src/architecture/catalog.mjs +355 -0
  325. package/src/architecture/index.mjs +93 -0
  326. package/src/boundary/content-rules.mjs +536 -69
  327. package/src/boundary/delegation-command.mjs +158 -0
  328. package/src/boundary/delegation.mjs +396 -0
  329. package/src/boundary/policy.mjs +185 -64
  330. package/src/build/git.mjs +18 -0
  331. package/src/build/index.mjs +3 -0
  332. package/src/build/ledger.mjs +88 -0
  333. package/src/capabilities/adoption.mjs +381 -0
  334. package/src/capabilities/evidence.mjs +140 -0
  335. package/src/capabilities/files.mjs +124 -0
  336. package/src/capabilities/index.mjs +4 -0
  337. package/src/capabilities/instructions.mjs +250 -0
  338. package/src/capabilities/package.mjs +136 -0
  339. package/src/cli/command-failure.mjs +36 -0
  340. package/src/cli/execute-command.mjs +16 -2
  341. package/src/cli/run.mjs +66 -9
  342. package/src/coauthor/store.mjs +43 -2
  343. package/src/collaboration/proposals.mjs +189 -0
  344. package/src/commands/architecture.mjs +27 -0
  345. package/src/commands/boundary.mjs +3 -0
  346. package/src/commands/capability.mjs +73 -0
  347. package/src/commands/enroll.mjs +3 -0
  348. package/src/commands/harness.mjs +79 -0
  349. package/src/commands/ingest.mjs +39 -0
  350. package/src/commands/init.mjs +4 -1
  351. package/src/commands/inquiry.mjs +66 -0
  352. package/src/commands/knowledge-semantic.mjs +128 -0
  353. package/src/commands/knowledge.mjs +119 -0
  354. package/src/commands/learn.mjs +61 -0
  355. package/src/commands/obsidian.mjs +772 -65
  356. package/src/commands/practice.mjs +24 -0
  357. package/src/commands/setup.mjs +22 -2
  358. package/src/commands/trackable.mjs +19 -0
  359. package/src/contracts/corpus.mjs +30 -0
  360. package/src/coordination/index.mjs +110 -0
  361. package/src/evidence-navigation/contracts.mjs +60 -0
  362. package/src/evidence-navigation/evaluate.mjs +69 -0
  363. package/src/evidence-navigation/generation.mjs +22 -0
  364. package/src/evidence-navigation/index.mjs +2 -0
  365. package/src/evidence-navigation/local.mjs +167 -0
  366. package/src/evidence-navigation/schema.generated.mjs +604 -0
  367. package/src/evidence-navigation/types.mjs +12 -0
  368. package/src/export/atelier-export-contract.mjs +35 -8
  369. package/src/graph/enroll.mjs +248 -0
  370. package/src/graph/graph.mjs +30 -6
  371. package/src/graph/knowledge-graph.mjs +77 -9
  372. package/src/harnesses/contracts.mjs +41 -0
  373. package/src/harnesses/exchange.mjs +154 -0
  374. package/src/harnesses/feedback.mjs +16 -0
  375. package/src/harnesses/history.mjs +37 -0
  376. package/src/harnesses/index.mjs +4 -0
  377. package/src/harnesses/store.mjs +29 -0
  378. package/src/ingestion/contracts.mjs +41 -0
  379. package/src/ingestion/evaluation.mjs +119 -0
  380. package/src/ingestion/optional/composition/index.mjs +171 -0
  381. package/src/ingestion/optional/evaluation/index.mjs +219 -0
  382. package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
  383. package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
  384. package/src/ingestion/optional/graphify/graphify_supplier.py +938 -0
  385. package/src/ingestion/optional/graphify/json_bridge.py +163 -0
  386. package/src/ingestion/optional/graphify/native-closure-pins.json +275 -0
  387. package/src/ingestion/optional/graphify/native-pins.json +306 -0
  388. package/src/ingestion/optional/graphify/query_receiving.py +209 -0
  389. package/src/ingestion/optional/index.mjs +9 -0
  390. package/src/ingestion/optional/jev/bridge.mjs +166 -0
  391. package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
  392. package/src/ingestion/optional/jev/judgments.mjs +174 -0
  393. package/src/ingestion/optional/jev/native-translation.mjs +32 -0
  394. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
  395. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
  396. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
  397. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
  398. package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
  399. package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
  400. package/src/ingestion/optional/vanilla/index.mjs +218 -0
  401. package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
  402. package/src/ingestion/processors.mjs +218 -0
  403. package/src/ingestion/semantic.mjs +203 -0
  404. package/src/ingestion/store.mjs +372 -0
  405. package/src/inquiry/beliefs.mjs +32 -0
  406. package/src/inquiry/contracts.mjs +23 -0
  407. package/src/inquiry/index.mjs +5 -0
  408. package/src/inquiry/ledger.mjs +135 -0
  409. package/src/inquiry/lenses.mjs +15 -0
  410. package/src/inquiry/projection.mjs +77 -0
  411. package/src/inquiry/store.mjs +35 -0
  412. package/src/intake/read-scope.mjs +12 -0
  413. package/src/intake/store.mjs +69 -6
  414. package/src/interaction/index.mjs +91 -0
  415. package/src/judgment/index.mjs +82 -0
  416. package/src/judgment/practice-evaluation.mjs +70 -0
  417. package/src/judgment/practice.mjs +144 -0
  418. package/src/knowledge/context.mjs +217 -0
  419. package/src/knowledge/index.mjs +5 -0
  420. package/src/knowledge/ingestion.mjs +79 -0
  421. package/src/knowledge/intake.mjs +33 -0
  422. package/src/knowledge/ledger.mjs +110 -0
  423. package/src/knowledge/participatory/guidance.mjs +36 -0
  424. package/src/knowledge/participatory/guided-contribution.mjs +44 -0
  425. package/src/knowledge/participatory/index.mjs +2 -0
  426. package/src/knowledge/participatory/installed-workshop.mjs +108 -0
  427. package/src/knowledge/plan.mjs +67 -0
  428. package/src/knowledge/projection.mjs +43 -0
  429. package/src/knowledge/semantic-operation.mjs +567 -0
  430. package/src/knowledge/sessions.mjs +467 -0
  431. package/src/knowledge/workspace.mjs +295 -0
  432. package/src/knowledge-health/assessment.mjs +165 -0
  433. package/src/knowledge-health/capabilities.mjs +350 -0
  434. package/src/knowledge-health/context.mjs +62 -0
  435. package/src/knowledge-health/example.mjs +47 -0
  436. package/src/knowledge-health/index.mjs +10 -0
  437. package/src/knowledge-health/owner-join.mjs +88 -0
  438. package/src/knowledge-health/proof/common.mjs +24 -0
  439. package/src/knowledge-health/proof/installed-check.mjs +100 -0
  440. package/src/knowledge-health/proof/native-readback.mjs +37 -0
  441. package/src/knowledge-health/proof/rehearse.mjs +68 -0
  442. package/src/knowledge-health/proof/validate-participant.mjs +17 -0
  443. package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
  444. package/src/knowledge-health/proof/workshop-check.mjs +36 -0
  445. package/src/knowledge-health/rule-identity.mjs +49 -0
  446. package/src/knowledge-health/workshop-composition.mjs +19 -0
  447. package/src/knowledge-health/workshop-profile.mjs +176 -0
  448. package/src/knowledge-health/workshop.mjs +99 -0
  449. package/src/learning/adapters.mjs +68 -0
  450. package/src/learning/assistance.mjs +127 -0
  451. package/src/learning/contracts.mjs +65 -0
  452. package/src/learning/store.mjs +272 -0
  453. package/src/personal-workspace/index.mjs +391 -0
  454. package/src/personal-workspace/selection.mjs +401 -0
  455. package/src/project/config.mjs +8 -0
  456. package/src/project/durable-state.mjs +3 -1
  457. package/src/project/file-class.mjs +1 -1
  458. package/src/project/path-match.mjs +2 -1
  459. package/src/projection/obsidian/edits/apply.mjs +55 -16
  460. package/src/projection/obsidian/materialize/prepare-view.mjs +87 -5
  461. package/src/projection/obsidian/personal-workspace.mjs +519 -0
  462. package/src/projection/obsidian/proposals/adapter.mjs +79 -17
  463. package/src/projection/obsidian/proposals/observation.mjs +50 -4
  464. package/src/projection/obsidian/publication/publisher.mjs +214 -18
  465. package/src/projection/obsidian/publication/vault-list.mjs +4 -3
  466. package/src/projection/obsidian/recovery/index.mjs +4 -1
  467. package/src/projection/obsidian/recovery/store.mjs +160 -7
  468. package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
  469. package/src/projection/project.mjs +3 -2
  470. package/src/readiness/readiness.mjs +10 -3
  471. package/src/reflection/index.mjs +56 -0
  472. package/src/runtime/obsidian/app-production-seams.mjs +48 -16
  473. package/src/runtime/obsidian/app-registration.mjs +175 -28
  474. package/src/runtime/obsidian/app-restart.mjs +154 -0
  475. package/src/runtime/obsidian/engine.mjs +437 -56
  476. package/src/runtime/obsidian/extension-points.mjs +1 -1
  477. package/src/runtime/obsidian/index.mjs +10 -7
  478. package/src/runtime/obsidian/launch-plan.mjs +7 -4
  479. package/src/runtime/obsidian/lifecycle.mjs +108 -23
  480. package/src/runtime/obsidian/login-item.mjs +335 -0
  481. package/src/runtime/obsidian/machine-settings.mjs +119 -12
  482. package/src/runtime/obsidian/observation.mjs +36 -4
  483. package/src/runtime/obsidian/opening.mjs +173 -38
  484. package/src/runtime/obsidian/personal-selection.mjs +125 -0
  485. package/src/runtime/obsidian/pipeline.mjs +115 -6
  486. package/src/runtime/obsidian/plugin-channel.mjs +11 -3
  487. package/src/runtime/obsidian/plugin-choice.mjs +16 -4
  488. package/src/runtime/obsidian/plugin-drift.mjs +59 -11
  489. package/src/runtime/obsidian/plugin-presence.mjs +14 -1
  490. package/src/runtime/obsidian/private-lock.mjs +39 -4
  491. package/src/runtime/obsidian/process-identity.mjs +20 -10
  492. package/src/runtime/obsidian/project-views.mjs +282 -0
  493. package/src/runtime/obsidian/questions.mjs +48 -0
  494. package/src/runtime/obsidian/release-watch.mjs +56 -0
  495. package/src/runtime/obsidian/service-main.mjs +148 -19
  496. package/src/runtime/obsidian/service-manager-production.mjs +61 -0
  497. package/src/runtime/obsidian/service-managers.mjs +275 -0
  498. package/src/runtime/obsidian/service-record.mjs +87 -23
  499. package/src/runtime/obsidian/service.mjs +102 -9
  500. package/src/runtime/obsidian/startup-adapters.mjs +59 -21
  501. package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
  502. package/src/runtime/obsidian/vault-location.mjs +300 -0
  503. package/src/runtime/obsidian/view-counts.mjs +34 -0
  504. package/src/runtime/obsidian/view-permission.mjs +334 -0
  505. package/src/server/local-sidecar.mjs +169 -2
  506. package/src/server/server.mjs +28 -2
  507. package/src/template-bindings/project.mjs +92 -0
  508. package/src/templates/profile.mjs +225 -0
  509. package/src/trackables/domain.mjs +175 -0
  510. package/src/trackables/store.mjs +114 -0
  511. package/src/ui/control-system.mjs +201 -0
  512. package/src/ui/html-primitives.mjs +3 -0
  513. package/src/ui/knowledge-page.mjs +1388 -0
  514. package/src/upgrade/closure-diagnostics.mjs +135 -0
  515. package/src/upgrade/closure.mjs +649 -0
  516. package/src/upgrade/explanation.mjs +1 -1
  517. package/src/upgrade/template-participant.mjs +171 -0
  518. package/src/upgrade/transaction.mjs +72 -25
  519. package/src/upgrade/upgrade.mjs +20 -7
  520. package/templates/external-project-workspace/pack-lifecycle.json +7 -1
  521. package/templates/knowledge-workspace/.gitattributes +2 -0
  522. package/templates/knowledge-workspace/AGENTS.md +27 -0
  523. package/templates/knowledge-workspace/README.md +44 -0
  524. package/templates/knowledge-workspace/atelier.project.json +22 -0
  525. package/templates/knowledge-workspace/gitignore +6 -0
  526. package/templates/knowledge-workspace/knowledge-plan.json +111 -0
  527. package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
  528. package/templates/knowledge-workspace/records/forecast.md +22 -0
  529. package/templates/knowledge-workspace/records/inspection.md +25 -0
  530. package/templates/knowledge-workspace/records/telescope.md +24 -0
  531. package/templates/knowledge-workspace/repo-access.v1.json +9 -0
@@ -7,6 +7,9 @@ import { matchesPathPattern } from '../project/path-match.mjs'
7
7
  import { sanitizedGitEnvironment } from '../runtime/git-adapter.mjs'
8
8
  import {
9
9
  CHECK_AGGREGATE_MAX_BYTES,
10
+ canonicalDiffArgs,
11
+ gitOutputResult,
12
+ parseDiffSections,
10
13
  parsePushRefInput,
11
14
  resolveContentRules,
12
15
  scanPushUpdate,
@@ -15,6 +18,7 @@ import {
15
18
  validateContentRuleExceptions,
16
19
  validateContentRules,
17
20
  } from './content-rules.mjs'
21
+ import { delegationsPathFor, loadDelegationsFile, ownerKeysForCommand, resolveDelegation, validateDelegationsDocument } from './delegation.mjs'
18
22
 
19
23
  export const BOUNDARY_POLICY_SCHEMA = 'mnstry.atelier-boundary-policy@v1'
20
24
  export const PROMOTE_EVENT_SCHEMA = 'git-promote-event@v1'
@@ -115,6 +119,12 @@ function unknownKeys(value, label, allowed) {
115
119
  .map((key) => `${label} must not include additional property ${key}`)
116
120
  }
117
121
 
122
+ // The schema declares `ext` as a reserved extension container that consumers
123
+ // must ignore. It is accepted, never read, and never carries authority.
124
+ function extErrors(value, label) {
125
+ return value?.ext != null && !isObject(value.ext) ? [`${label}.ext must be an object`] : []
126
+ }
127
+
118
128
  function validAudienceList(value, label, required = false) {
119
129
  if (value == null && !required) return []
120
130
  if (!Array.isArray(value)) return [`${label} must be a list`]
@@ -128,8 +138,12 @@ function validAudienceList(value, label, required = false) {
128
138
  export function validateBoundaryPolicy(policy, project = null) {
129
139
  const errors = []
130
140
  if (!isObject(policy)) return ['boundary policy must be a JSON object']
131
- errors.push(...unknownKeys(policy, '/', new Set(['schema', 'mode', 'actors', 'repos', 'promotion', 'forbiddenPaths', 'contentRules', 'contentRuleExceptions', 'governanceLedgerPath'])))
141
+ errors.push(...unknownKeys(policy, '/', new Set(['schema', 'contractVersion', 'mode', 'actors', 'repos', 'promotion', 'forbiddenPaths', 'contentRules', 'contentRuleExceptions', 'governanceLedgerPath', 'ext'])))
132
142
  if (policy.schema !== BOUNDARY_POLICY_SCHEMA) errors.push(`schema must be ${BOUNDARY_POLICY_SCHEMA}`)
143
+ if (policy.contractVersion != null && !(typeof policy.contractVersion === 'string' && /^1\.[0-9]+\.[0-9]+$/.test(policy.contractVersion))) {
144
+ errors.push('contractVersion must be a 1.x.y version')
145
+ }
146
+ errors.push(...extErrors(policy, '/'))
133
147
  if (!VALID_BOUNDARY_MODES.has(policy.mode)) errors.push('mode must be strict or legacy-warning')
134
148
 
135
149
  const actors = policy.actors
@@ -138,7 +152,8 @@ export function validateBoundaryPolicy(policy, project = null) {
138
152
  if (!isObject(actors)) errors.push('actors must be an object')
139
153
  if (!isObject(repos)) errors.push('repos must be an object')
140
154
  if (policy.promotion != null && !isObject(policy.promotion)) errors.push('promotion must be an object')
141
- errors.push(...unknownKeys(promotion, 'promotion', new Set(['requiresGitPromote', 'recordsPath'])))
155
+ errors.push(...unknownKeys(promotion, 'promotion', new Set(['requiresGitPromote', 'recordsPath', 'ext'])))
156
+ errors.push(...extErrors(promotion, 'promotion'))
142
157
  if (policy.forbiddenPaths != null && !Array.isArray(policy.forbiddenPaths)) errors.push('forbiddenPaths must be a list')
143
158
  if (policy.contentRules != null) errors.push(...validateContentRules(policy.contentRules))
144
159
  if (policy.contentRuleExceptions != null) {
@@ -153,11 +168,15 @@ export function validateBoundaryPolicy(policy, project = null) {
153
168
  }
154
169
 
155
170
  for (const [actorId, actor] of Object.entries(isObject(actors) ? actors : {})) {
156
- errors.push(...unknownKeys(actor, `actors.${actorId}`, new Set(['githubLogin', 'gitEmails', 'privateDomainRepo'])))
171
+ errors.push(...unknownKeys(actor, `actors.${actorId}`, new Set(['githubLogin', 'gitEmails', 'privateDomainRepo', 'ext'])))
172
+ errors.push(...extErrors(actor, `actors.${actorId}`))
157
173
  if (!isObject(actor)) {
158
174
  errors.push(`actors.${actorId} must be an object`)
159
175
  continue
160
176
  }
177
+ // The schema types these fields; a non-string login would still match through String().
178
+ if (actor.githubLogin != null && typeof actor.githubLogin !== 'string') errors.push(`actors.${actorId}.githubLogin must be a string`)
179
+ if (actor.gitEmails != null && !Array.isArray(actor.gitEmails)) errors.push(`actors.${actorId}.gitEmails must be a list`)
161
180
  if (!firstString(actor.githubLogin) && !asArray(actor.gitEmails).length) {
162
181
  errors.push(`actors.${actorId} must declare githubLogin or gitEmails`)
163
182
  }
@@ -176,7 +195,8 @@ export function validateBoundaryPolicy(policy, project = null) {
176
195
  }
177
196
 
178
197
  for (const [repoName, repo] of Object.entries(isObject(repos) ? repos : {})) {
179
- errors.push(...unknownKeys(repo, `repos.${repoName}`, new Set(['kind', 'ownerActor', 'readBoundary', 'allowedAudiences', 'forbiddenAudiences', 'autoCommit'])))
198
+ errors.push(...unknownKeys(repo, `repos.${repoName}`, new Set(['kind', 'ownerActor', 'readBoundary', 'allowedAudiences', 'forbiddenAudiences', 'autoCommit', 'ext'])))
199
+ errors.push(...extErrors(repo, `repos.${repoName}`))
180
200
  if (!isObject(repo)) {
181
201
  errors.push(`repos.${repoName} must be an object`)
182
202
  continue
@@ -306,7 +326,7 @@ function nodePlacementFindings({ node, policy }) {
306
326
  return findings
307
327
  }
308
328
 
309
- function actorFindings({ policy, project, actor, gitExecutable, allowNetworkActorResolution, allowHistoryActorResolution, forceActorErrors }) {
329
+ function actorFindings({ policy, project, actor, gitExecutable, allowNetworkActorResolution, allowHistoryActorResolution, forceActorErrors, operation, delegations, ownerKeys, now }) {
310
330
  const findings = []
311
331
  const operatedRepos = new Set(managedRepos(project).map((repo) => repo.name))
312
332
  const ownedRepos = Object.entries(policy.repos ?? {}).filter(([name, repo]) =>
@@ -322,23 +342,102 @@ function actorFindings({ policy, project, actor, gitExecutable, allowNetworkActo
322
342
  findings.push(finding({ severity, code: 'private-domain-actor-unverified', repo: repoName,
323
343
  message: `${repoName}: could not verify local actor for private domain repo owned by ${repo.ownerActor}${current.reason ? ` (${current.reason})` : ''}` }))
324
344
  } else if (current.actorId !== repo.ownerActor) {
325
- findings.push(finding({ severity, code: 'private-domain-actor-mismatch', repo: repoName, message: `${repoName}: private domain repo is owned by ${repo.ownerActor}, but current actor is ${current.actorId}` }))
345
+ // Only an owner-signed delegation, verified against a host-supplied key,
346
+ // changes this outcome, and only for its operator, repos and operation.
347
+ const delegated = resolveDelegation({ policy, project, delegations, repoName, repo, actorId: current.actorId, operation, ownerKeys, now })
348
+ if (delegated.applies) {
349
+ const { id, owner, expiresAt } = delegated.delegation
350
+ findings.push(finding({ severity: 'info', code: 'private-domain-delegated-operator', repo: repoName,
351
+ message: `${repoName}: ${current.actorId} operates under delegation ${id} from owner ${owner} until ${expiresAt}`,
352
+ details: { actorId: current.actorId, owner, delegationId: id, expiresAt, operation } }))
353
+ } else {
354
+ const note = delegated.reason === 'delegation-missing' ? '' : ` (${delegated.reason})`
355
+ findings.push(finding({ severity, code: 'private-domain-actor-mismatch', repo: repoName,
356
+ message: `${repoName}: private domain repo is owned by ${repo.ownerActor}, but current actor is ${current.actorId}${note}`,
357
+ details: { delegationReason: delegated.reason } }))
358
+ }
326
359
  }
327
360
  }
328
361
  return findings
329
362
  }
330
363
 
331
- export function stagedPathsForProject(project, { gitExecutable = 'git' } = {}) {
364
+ /**
365
+ * The index a pre-commit hook is judging. Git names it in GIT_INDEX_FILE (a
366
+ * temporary index under `git commit -a` or `git commit <paths>`, a linked
367
+ * worktree's own index, or `.git/index` relative to the work tree's top) and
368
+ * runs the hook at that top, though a chained hook may have moved. The work
369
+ * tree is found by Git's own discovery from where the hook runs; the hook's
370
+ * GIT_DIR is not used, because in a linked worktree Git exports it without a
371
+ * work tree, which would make any subdirectory look like the top. A work tree
372
+ * discovery cannot find is unresolved, and blocks. Returns null when no index
373
+ * is named.
374
+ */
375
+ export function hookIndexFromEnvironment({ env = process.env, cwd = process.cwd(), gitExecutable = 'git' } = {}) {
376
+ const named = typeof env.GIT_INDEX_FILE === 'string' ? env.GIT_INDEX_FILE : ''
377
+ if (!named) return null
378
+ const workTree = gitPathOutput(cwd, ['rev-parse', '--show-toplevel'], sanitizedGitEnvironment(), gitExecutable)
379
+ const commonDir = gitPathOutput(cwd, ['rev-parse', '--git-common-dir'], sanitizedGitEnvironment(), gitExecutable)
380
+ return { workTree, indexFile: path.resolve(workTree ?? cwd, named), commonDir }
381
+ }
382
+
383
+ function gitPathOutput(cwd, args, env, gitExecutable = 'git') {
384
+ const result = spawnSync(gitExecutable, ['-C', cwd, ...args], { encoding: 'utf8', env })
385
+ const output = result.status === 0 ? String(result.stdout ?? '').replace(/\r?\n$/, '') : ''
386
+ return output ? nativeRealPath(path.resolve(cwd, output)) : null
387
+ }
388
+
389
+ // The file system's own spelling of a path, so a configured path that differs
390
+ // only in case (macOS, Windows) still names the same repo.
391
+ function nativeRealPath(value) {
392
+ try {
393
+ return fs.realpathSync.native(path.resolve(value))
394
+ } catch {
395
+ return path.resolve(value)
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Where each managed repo's staged state is read. The repo the hook is
401
+ * committing is read from the hook's work tree and index: the managed repo
402
+ * configured at that work tree, or the one managed repo sharing its Git
403
+ * directory (a linked worktree of it). Every other repo is read as configured.
404
+ * When an index is named but no single managed repo is being committed, the
405
+ * guard cannot tell what it is judging, and says so.
406
+ */
407
+ function stagedTargets(project, hookIndex, gitExecutable = 'git') {
408
+ const repos = managedRepos(project)
409
+ const targets = new Map(repos.map((repo) => [repo, { root: repo.path, indexFile: null }]))
410
+ if (!hookIndex) return { targets, unresolved: null }
411
+ let committing = hookIndex.workTree ? repos.filter((repo) => repo.path && nativeRealPath(repo.path) === hookIndex.workTree) : []
412
+ if (committing.length === 0 && hookIndex.commonDir) {
413
+ committing = repos.filter((repo) => repo.path && fs.existsSync(repo.path)
414
+ && gitPathOutput(repo.path, ['rev-parse', '--git-common-dir'], sanitizedGitEnvironment(), gitExecutable) === hookIndex.commonDir)
415
+ }
416
+ if (committing.length !== 1) return { targets, unresolved: hookIndex.workTree ?? '(an unresolved work tree)' }
417
+ targets.set(committing[0], { root: hookIndex.workTree, indexFile: hookIndex.indexFile })
418
+ return { targets, unresolved: null }
419
+ }
420
+
421
+ function stagedPathsResult(targets, { gitExecutable = 'git' } = {}) {
332
422
  const paths = []
333
- for (const repo of managedRepos(project)) {
334
- if (!repo.path || !fs.existsSync(path.join(repo.path, '.git'))) continue
335
- const result = spawnSync(gitExecutable, ['-C', repo.path, 'diff', '--cached', '--name-only', '--diff-filter=ACMR'], { encoding: 'utf8', env: sanitizedGitEnvironment() })
336
- if (result.status !== 0) continue
337
- for (const rel of result.stdout.split('\n').map((line) => line.trim()).filter(Boolean)) {
338
- paths.push({ repo: repo.name, repoRoot: repo.path, path: normalize(rel) })
423
+ const failures = []
424
+ for (const [repo, target] of targets) {
425
+ if (!target.root || !fs.existsSync(path.join(target.root, '.git'))) continue
426
+ // NUL-separated names are exact: a quoted name could never match a forbidden-path glob.
427
+ const result = gitOutputResult(target.root, ['diff', '--cached', '--name-only', '-z', '--no-relative', '--diff-filter=ACMR'], { gitExecutable, indexFile: target.indexFile })
428
+ if (!result.ok) {
429
+ failures.push({ repo, reason: result.code })
430
+ continue
431
+ }
432
+ for (const rel of result.stdout.split('\0').filter(Boolean)) {
433
+ paths.push({ repo: repo.name, repoRoot: target.root, path: normalize(rel) })
339
434
  }
340
435
  }
341
- return paths
436
+ return { paths, failures }
437
+ }
438
+
439
+ export function stagedPathsForProject(project, { gitExecutable = 'git', hookIndex = null } = {}) {
440
+ return stagedPathsResult(stagedTargets(project, hookIndex, gitExecutable).targets, { gitExecutable }).paths
342
441
  }
343
442
 
344
443
  function forbiddenPathFindings({ policy, stagedPaths }) {
@@ -380,30 +479,14 @@ function isInitializationDefault(field, value) {
380
479
  return Array.isArray(defaults) && defaults.includes(value)
381
480
  }
382
481
 
482
+ /** Diff sections with their hunks, read by the same parser as the content scan. */
383
483
  export function diffFileSections(diff) {
384
- const files = []
385
- let current = null
386
- let hunk = null
387
- for (const line of String(diff ?? '').split('\n')) {
388
- if (line.startsWith('diff --git ')) {
389
- const header = line.match(/^diff --git a\/(.*) b\/(.*)$/)
390
- current = { path: header ? normalize(header[2]) : null, hunks: [], lines: [] }
391
- files.push(current)
392
- hunk = null
393
- continue
394
- }
395
- if (!current) continue
396
- current.lines.push(line)
397
- if (line.startsWith('@@')) {
398
- hunk = { added: [], removed: [] }
399
- current.hunks.push(hunk)
400
- continue
401
- }
402
- if (!hunk) continue
403
- if (/^\+(?!\+)/.test(line)) hunk.added.push(line.slice(1))
404
- else if (/^-(?!-)/.test(line)) hunk.removed.push(line.slice(1))
405
- }
406
- return files
484
+ return parseDiffSections(diff, { detail: true }).map((section) => ({
485
+ path: section.path,
486
+ hunks: section.hunks,
487
+ lines: section.lines,
488
+ incomplete: section.incomplete,
489
+ }))
407
490
  }
408
491
 
409
492
  export function semanticChangesInFile(file) {
@@ -426,26 +509,38 @@ export function semanticChangesInFile(file) {
426
509
  return changes
427
510
  }
428
511
 
429
- function semanticDiffFindings({ policy, project, gitExecutable = 'git' }) {
512
+ function semanticDiffFindings({ targets, gitExecutable = 'git' }) {
430
513
  const findings = []
431
514
  const severity = 'error'
432
- for (const repo of managedRepos(project)) {
433
- if (!repo.path || !fs.existsSync(path.join(repo.path, '.git'))) {
515
+ for (const [repo, target] of targets) {
516
+ if (!target.root || !fs.existsSync(path.join(target.root, '.git'))) {
434
517
  findings.push(unscannableRepoFinding(repo, 'configured path is absent or is not a Git checkout'))
435
518
  continue
436
519
  }
437
- const result = spawnSync(gitExecutable, ['-C', repo.path, 'diff', '--cached', '--unified=0', '--', '*.md', '*.kg.json'], { encoding: 'utf8', env: sanitizedGitEnvironment() })
438
- if (result.status !== 0) {
520
+ // --text: a document declared binary, or holding a NUL byte, still has its fields read.
521
+ const result = gitOutputResult(target.root, [...canonicalDiffArgs(['--cached'], ['--text']), '--', '*.md', '*.kg.json'], { gitExecutable, indexFile: target.indexFile })
522
+ if (!result.ok) {
439
523
  findings.push(unscannableRepoFinding(repo, 'git diff --cached failed'))
440
524
  continue
441
525
  }
442
526
  if (!result.stdout.trim()) continue
443
527
  for (const file of diffFileSections(result.stdout)) {
528
+ if (file.incomplete) {
529
+ findings.push(finding({
530
+ severity,
531
+ code: 'semantic-field-scan-incomplete',
532
+ repo: repo.name,
533
+ path: file.path,
534
+ message: `${repo.name}/${file.path ?? '(unknown path)'}: the staged diff could not be read, so its semantic fields were not checked`,
535
+ details: { reason: file.incomplete },
536
+ }))
537
+ continue
538
+ }
444
539
  const changes = semanticChangesInFile(file)
445
540
  if (!changes.length) continue
446
- // The marker has to travel in the same file's diff as the change it approves —
447
- // a marker elsewhere in the commit approves nothing.
448
- if (REVIEW_MARKER_RE.test(file.lines.join('\n'))) continue
541
+ // The marker has to be added in the same file's diff as the change it approves:
542
+ // a marker elsewhere in the commit, a removed one, or one in a file name approves nothing.
543
+ if (file.hunks.some((hunk) => hunk.added.some((line) => REVIEW_MARKER_RE.test(line)))) continue
449
544
  const label = `${repo.name}/${file.path ?? '(unknown path)'}`
450
545
  findings.push(
451
546
  finding({
@@ -462,17 +557,17 @@ function semanticDiffFindings({ policy, project, gitExecutable = 'git' }) {
462
557
  return findings
463
558
  }
464
559
 
465
- function stagedContentFindings({ policy, project, gitExecutable = 'git' }) {
560
+ function stagedContentFindings({ policy, targets, gitExecutable = 'git' }) {
466
561
  const rules = resolveContentRules(policy)
467
562
  const exceptions = asArray(policy?.contentRuleExceptions)
468
563
  const findings = []
469
564
  let totalBytes = 0
470
- for (const repo of managedRepos(project)) {
471
- if (!repo.path || !fs.existsSync(path.join(repo.path, '.git'))) {
565
+ for (const [repo, target] of targets) {
566
+ if (!target.root || !fs.existsSync(path.join(target.root, '.git'))) {
472
567
  findings.push(unscannableRepoFinding(repo, 'configured path is absent or is not a Git checkout'))
473
568
  continue
474
569
  }
475
- const result = scanStagedRepository({ repoRoot: repo.path, rules, exceptions, repo: repo.name, gitExecutable })
570
+ const result = scanStagedRepository({ repoRoot: target.root, rules, exceptions, repo: repo.name, gitExecutable, indexFile: target.indexFile })
476
571
  findings.push(...result.findings, ...result.diagnostics)
477
572
  totalBytes += result.bytes
478
573
  if (totalBytes > CHECK_AGGREGATE_MAX_BYTES) {
@@ -515,7 +610,7 @@ function repoForCwd(project, cwd) {
515
610
  * which reports without blocking, so an accepted usage elsewhere in the repo can
516
611
  * never strand unrelated work on the machine.
517
612
  */
518
- export function checkPushContent({ project, policy, repo, updates, cwd = process.cwd(), gitRunner = spawnSync } = {}) {
613
+ export function checkPushContent({ project, policy, repo, updates, remote = null, cwd = process.cwd(), gitRunner = spawnSync } = {}) {
519
614
  const target = repo ?? repoForCwd(project, cwd)
520
615
  if (!target) {
521
616
  // Fail closed. This hook is only installed into managed repos, so failing to
@@ -533,15 +628,17 @@ export function checkPushContent({ project, policy, repo, updates, cwd = process
533
628
  const findings = []
534
629
  let totalBytes = 0
535
630
  for (const update of updates) {
536
- const result = scanPushUpdate({ repoRoot: target.path, update, rules, exceptions, repo: target.name, gitRunner })
631
+ const result = scanPushUpdate({ repoRoot: target.path, update, rules, exceptions, repo: target.name, remote, gitRunner, maxBytes: CHECK_AGGREGATE_MAX_BYTES - totalBytes })
537
632
  findings.push(...result.findings, ...result.diagnostics)
538
633
  totalBytes += result.bytes
634
+ // The per-commit scan reports its own budget refusal; say it once.
635
+ if (result.diagnostics.some((item) => item.details?.reason === 'push-evidence-too-large')) break
539
636
  if (totalBytes > CHECK_AGGREGATE_MAX_BYTES) {
540
637
  findings.push(finding({
541
638
  severity: 'error',
542
639
  code: 'content-scan-incomplete',
543
640
  repo: target.name,
544
- message: `push evidence exceeds the ${CHECK_AGGREGATE_MAX_BYTES}-byte aggregate limit`,
641
+ message: `push evidence exceeds the ${CHECK_AGGREGATE_MAX_BYTES}-byte aggregate limit; push it in smaller parts`,
545
642
  }))
546
643
  break
547
644
  }
@@ -635,7 +732,7 @@ function promotionFindings({ policy, project, graph }) {
635
732
  return findings
636
733
  }
637
734
 
638
- export function checkBoundaryPolicy({ project, policy, staged = false, stagedOnly = false, actor = null, gitExecutable = 'git', allowNetworkActorResolution = true, allowHistoryActorResolution = false, forceActorErrors = false } = {}) {
735
+ export function checkBoundaryPolicy({ project, policy, staged = false, stagedOnly = false, actor = null, gitExecutable = 'git', allowNetworkActorResolution = true, allowHistoryActorResolution = false, forceActorErrors = false, delegations = null, ownerKeys = null, now = Date.now(), hookIndex = null } = {}) {
639
736
  const validationErrors = validateBoundaryPolicy(policy, project)
640
737
  let graph = null
641
738
  const findings = validationErrors.map((message) => finding({ severity: 'error', code: 'boundary-policy-invalid', message }))
@@ -645,12 +742,27 @@ export function checkBoundaryPolicy({ project, policy, staged = false, stagedOnl
645
742
  for (const node of graph.nodes ?? []) findings.push(...nodePlacementFindings({ node, policy }))
646
743
  findings.push(...promotionFindings({ policy, project, graph }))
647
744
  }
648
- findings.push(...actorFindings({ policy, project, actor, gitExecutable, allowNetworkActorResolution, allowHistoryActorResolution, forceActorErrors }))
745
+ // Delegations come from their own document, never from the policy. An invalid
746
+ // document is an error and grants nothing.
747
+ const delegationErrors = delegations == null ? [] : validateDelegationsDocument(delegations, policy)
748
+ findings.push(...delegationErrors.map((message) => finding({ severity: 'error', code: 'boundary-delegations-invalid', message })))
749
+ const delegationList = delegations != null && !delegationErrors.length ? delegations.delegations : []
750
+ // A staged check is the commit path (the installed pre-commit hook runs it).
751
+ const operation = staged ? 'pre-commit' : 'boundary-check'
752
+ findings.push(...actorFindings({ policy, project, actor, gitExecutable, allowNetworkActorResolution, allowHistoryActorResolution, forceActorErrors, operation, delegations: delegationList, ownerKeys, now }))
649
753
  if (staged) {
650
- const stagedPaths = stagedPathsForProject(project, { gitExecutable })
651
- findings.push(...forbiddenPathFindings({ policy, stagedPaths }))
652
- findings.push(...semanticDiffFindings({ policy, project, gitExecutable }))
653
- findings.push(...stagedContentFindings({ policy, project, gitExecutable }))
754
+ const { targets, unresolved } = stagedTargets(project, hookIndex, gitExecutable)
755
+ if (unresolved) {
756
+ findings.push(finding({
757
+ code: 'pre-commit-repo-unresolved',
758
+ message: `${unresolved} is not a repo declared in ${project?.configPath ?? 'the project config'}, or several declared repos share its Git directory; the pre-commit guard cannot tell what is being committed`,
759
+ }))
760
+ }
761
+ const listed = stagedPathsResult(targets, { gitExecutable })
762
+ findings.push(...listed.failures.map(({ repo, reason }) => unscannableRepoFinding(repo, `staged paths could not be listed (${reason})`)))
763
+ findings.push(...forbiddenPathFindings({ policy, stagedPaths: listed.paths }))
764
+ findings.push(...semanticDiffFindings({ targets, gitExecutable }))
765
+ findings.push(...stagedContentFindings({ policy, targets, gitExecutable }))
654
766
  }
655
767
  const errors = findings.filter((item) => item.severity === 'error')
656
768
  const warnings = findings.filter((item) => item.severity !== 'error')
@@ -673,9 +785,14 @@ export function runBoundaryCheckCommand(argv = process.argv.slice(2)) {
673
785
  const project = commandProject({ argv })
674
786
  const loaded = loadBoundaryPolicy(project)
675
787
  const missingFindings = loaded.errors?.map((message) => finding({ code: 'boundary-policy-missing', message })) ?? []
676
- const report = loaded.ok
677
- ? checkBoundaryPolicy({ project, policy: loaded.policy, staged, stagedOnly, actor: firstString(args.actor) })
788
+ const sidecar = loaded.ok ? loadDelegationsFile(delegationsPathFor(loaded.policyPath)) : { present: false, document: null, errors: [] }
789
+ const sidecarFindings = sidecar.errors.map((message) => finding({ code: 'boundary-delegations-invalid', message }))
790
+ // Owner keys are read only when a delegations document exists.
791
+ const ownerKeys = sidecar.present && sidecar.document ? ownerKeysForCommand() : null
792
+ let report = loaded.ok
793
+ ? checkBoundaryPolicy({ project, policy: loaded.policy, staged, stagedOnly, actor: firstString(args.actor), delegations: sidecar.document, ownerKeys, hookIndex: staged ? hookIndexFromEnvironment() : null })
678
794
  : { ok: false, mode: null, schema: BOUNDARY_POLICY_SCHEMA, graphCounts: null, findings: missingFindings, errors: missingFindings, warnings: [] }
795
+ if (sidecarFindings.length) report = { ...report, ok: false, findings: [...sidecarFindings, ...report.findings], errors: [...sidecarFindings, ...report.errors] }
679
796
 
680
797
  if (json) {
681
798
  console.log(JSON.stringify(report, null, 2))
@@ -742,7 +859,7 @@ ATELIER_HOOK_PROJECT_CONFIG="$ATELIER_HOOK_REPO_ROOT"/${shellSingleQuote(portabl
742
859
  // pre-push reads the ref updates git writes to stdin and judges only that range;
743
860
  // pre-commit judges the staged diff. Neither scans the whole tree — that view is
744
861
  // `atelier boundary audit`, which reports without blocking.
745
- const invocation = hookName === 'pre-push' ? `boundary push-check ${config}` : `boundary check --staged ${config}`
862
+ const invocation = hookName === 'pre-push' ? `boundary push-check ${config} --remote="\${1:-}"` : `boundary check --staged ${config}`
746
863
  return `#!/usr/bin/env bash
747
864
  # MNSTRY_ATELIER_BOUNDARY_GUARD ${hookName}
748
865
  set -euo pipefail
@@ -788,13 +905,17 @@ export function runBoundaryPushCheckCommand(argv = process.argv.slice(2), { stdi
788
905
  console.log('[boundary:push-check] no ref updates on stdin · nothing to judge')
789
906
  process.exit(0)
790
907
  }
791
- const report = checkPushContent({ project, policy: loaded.policy, updates: parsed.updates, cwd: process.cwd() })
908
+ // The installed hook passes the remote Git names as its first argument.
909
+ const report = checkPushContent({ project, policy: loaded.policy, updates: parsed.updates, remote: firstString(args.remote), cwd: process.cwd() })
792
910
  if (args.json) {
793
911
  console.log(JSON.stringify(report, null, 2))
794
912
  } else {
795
913
  console.log(`[boundary:push-check] ${report.ok ? 'passed' : 'blocked'} · ${report.repo ?? 'unknown repo'} · ${report.errors.length} errors · ${report.warnings.length} warnings`)
796
- for (const item of report.findings.slice(0, 50)) console.log(`${item.severity} ${item.rule ?? item.code}: ${item.message}`)
797
- if (!report.ok && report.repo) {
914
+ for (const item of report.findings.slice(0, 50)) {
915
+ const also = item.commits?.length ? ` (also in commits ${item.commits.map((commit) => commit.slice(0, 12)).join(', ')})` : ''
916
+ console.log(`${item.severity} ${item.rule ?? item.code}: ${item.message}${also}`)
917
+ }
918
+ if (!report.ok && report.repo && report.errors.some((item) => item.code === 'content-rule-violation')) {
798
919
  console.log('')
799
920
  console.log('If this usage is intentional, declare it in the boundary policy:')
800
921
  console.log(' contentRuleExceptions: [{ rule, repo, paths: [...], reason: "why this is accepted" }]')
@@ -0,0 +1,18 @@
1
+ import { execFileSync } from 'node:child_process'
2
+ import { bytesAt, workspaceRoot, digest } from '../capabilities/files.mjs'
3
+ import { harnessRef } from '../harnesses/contracts.mjs'
4
+ import { inspectBuild } from './ledger.mjs'
5
+
6
+ // Read-only local adapter. A host still owns commands, writer custody and CI.
7
+ export function prepareGitCandidate({ workspaceRoot: input, records, artifact, writer }) {
8
+ const root = workspaceRoot(input), state = inspectBuild(records)
9
+ if (!state.establishment) throw new Error('build objective missing')
10
+ const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.toUpperCase().startsWith('GIT_')))
11
+ const git = args => execFileSync('git', ['-C', root, ...args], { env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim()
12
+ if (git(['status', '--porcelain', '--untracked-files=all'])) throw new Error('candidate requires clean repository sources')
13
+ const commit = git(['rev-parse', 'HEAD']), tree = git(['rev-parse', 'HEAD^{tree}'])
14
+ const artifactDigest = digest(bytesAt(root, artifact))
15
+ if (git(['rev-parse', 'HEAD']) !== commit || git(['status', '--porcelain', '--untracked-files=all']) || digest(bytesAt(root, artifact)) !== artifactDigest) throw new Error('candidate changed during capture')
16
+ return { data: { objective: harnessRef(state.establishment), repository: state.establishment.data.repository, commit, tree, artifactDigest, writer,
17
+ ...(state.candidate ? { supersedes: harnessRef(state.candidate) } : {}) }, artifact, assurance: 'local-bytes-and-git-observation; writer-is-caller-reported', authority: 'none' }
18
+ }
@@ -0,0 +1,3 @@
1
+ export { inspectBuild } from './ledger.mjs'
2
+ export { prepareGitCandidate } from './git.mjs'
3
+ export { buildReadiness, buildCoordinationProposal } from '../harnesses/exchange.mjs'
@@ -0,0 +1,88 @@
1
+ import { replay, unique } from '../harnesses/history.mjs'
2
+ import { assertAudience, assertHandoff } from '../harnesses/contracts.mjs'
3
+ const terminal = new Set(['completed', 'failed', 'cancelled'])
4
+ export function inspectBuild(records) {
5
+ const attempts = new Map(), gates = new Map(), decisions = []
6
+ let candidate
7
+ const state = replay(records, 'build', 'objective', (record, ctx) => {
8
+ const d = record.data, refs = []
9
+ const resolve = (pin, kinds) => { const r = ctx.resolve(pin, kinds); refs.push(r.id); return r }
10
+ const objective = ctx.first.data
11
+ switch (record.kind) {
12
+ case 'objective':
13
+ unique(d.gates.map(g => g.id), 'build gate'); unique(d.dependencies.map(d => d.id), 'build dependency'); unique(d.allowedEffects, 'allowed effect')
14
+ if (!d.gates.some(g => g.required)) throw new Error('build needs a required gate')
15
+ for (const dependency of d.dependencies) {
16
+ const h = assertHandoff(dependency.handoff)
17
+ if (h.source.profile === 'build' || h.target.profile !== 'build' || h.target.repository !== d.repository) throw new Error('build dependency requires knowledge or inquiry handoff to this repository')
18
+ assertAudience(d.audience, h.audience)
19
+ }
20
+ break
21
+ case 'candidate':
22
+ resolve(d.objective, ['objective'])
23
+ if (d.repository !== objective.repository) throw new Error('candidate repository mismatch')
24
+ if (candidate) {
25
+ if (!d.supersedes || ctx.resolve(d.supersedes, ['candidate']).id !== candidate.id) throw new Error('candidate must supersede current candidate')
26
+ if ([...attempts.values()].some(a => a.candidate === candidate.id && !terminal.has(a.state))) throw new Error('unsettled attempt prevents candidate replacement')
27
+ ctx.invalidate(candidate.id, `candidate-replaced:${record.id}`)
28
+ } else if (d.supersedes) throw new Error('initial candidate cannot supersede missing work')
29
+ candidate = record
30
+ break
31
+ case 'attempt':
32
+ resolve(d.candidate, ['candidate']); unique(d.effects, 'attempt effect')
33
+ if (d.effects.some(effect => !objective.allowedEffects.includes(effect))) throw new Error('attempt effect is outside declared scope')
34
+ if (attempts.size >= objective.maxAttempts) throw new Error('build attempt budget exhausted')
35
+ if ([...attempts.values()].some(a => a.candidate === d.candidate.id && a.operation === d.operation && !['failed', 'cancelled'].includes(a.state))) throw new Error('operation already completed or unsettled; reconcile before retry')
36
+ attempts.set(record.id, { candidate: d.candidate.id, operation: d.operation, state: 'intent', last: record.id })
37
+ break
38
+ case 'progress': {
39
+ const original = resolve(d.attempt, ['attempt']), previous = attempts.get(original.id)
40
+ const allowed = { intent: ['running', 'uncertain', 'completed', 'failed', 'cancelled'], running: ['uncertain', 'completed', 'failed', 'cancelled'], uncertain: ['completed', 'failed', 'cancelled'] }
41
+ if (!allowed[previous.state]?.includes(d.state)) throw new Error('invalid or terminal attempt transition')
42
+ refs.push(previous.last)
43
+ attempts.set(original.id, { ...previous, state: d.state, last: record.id })
44
+ break
45
+ }
46
+ case 'gate': {
47
+ resolve(d.candidate, ['candidate'])
48
+ if (!objective.gates.some(g => g.id === d.gate)) throw new Error('undeclared build gate')
49
+ if (d.attempt) {
50
+ const a = resolve(d.attempt, ['attempt']), status = attempts.get(a.id)
51
+ if (a.data.candidate.id !== d.candidate.id || (d.status === 'passed' && status.state !== 'completed')) throw new Error('gate requires completed attempt on exact candidate')
52
+ refs.push(status.last)
53
+ }
54
+ const key = `${d.candidate.id}/${d.gate}`, old = gates.get(key)
55
+ if (old) ctx.invalidate(old.id, `gate-replaced:${record.id}`)
56
+ gates.set(key, record)
57
+ break
58
+ }
59
+ case 'decision': {
60
+ resolve(d.candidate, ['candidate']); unique(d.gates.map(g => g.id), 'decision gate')
61
+ const named = d.gates.map(pin => resolve(pin, ['gate']))
62
+ if (named.some(g => g.data.candidate.id !== d.candidate.id)) throw new Error('gate names another candidate')
63
+ if (d.disposition === 'accepted') {
64
+ for (const required of objective.gates.filter(g => g.required)) {
65
+ const gate = gates.get(`${d.candidate.id}/${required.id}`)
66
+ if (!gate || gate.data.status !== 'passed' || !named.some(r => r.id === gate.id)) throw new Error('accepted build requires all current required gates')
67
+ }
68
+ if ([...attempts.values()].some(a => a.candidate === d.candidate.id && !terminal.has(a.state))) throw new Error('unsettled attempt prevents acceptance')
69
+ }
70
+ if (decisions.length) ctx.invalidate(decisions.at(-1).id, `decision-replaced:${record.id}`)
71
+ decisions.push(record)
72
+ break
73
+ }
74
+ case 'delivery': {
75
+ const decision = resolve(d.decision, ['decision'])
76
+ if (decision.data.disposition !== 'accepted' || decisions.at(-1)?.id !== decision.id) throw new Error('delivery requires latest accepted decision')
77
+ break
78
+ }
79
+ }
80
+ return refs
81
+ })
82
+ const stale = new Set(state.reconsider.map(r => r.id))
83
+ const decision = decisions.filter(r => !stale.has(r.id)).at(-1)
84
+ return { ...state, candidate: candidate ?? null, attempts: Object.fromEntries(attempts),
85
+ gates: [...gates.values()].filter(r => !stale.has(r.id)), decision: decision ?? null,
86
+ reportedAccepted: decision?.data.disposition === 'accepted',
87
+ dependencyFreshness: 'unverified-until-snapshots-supplied', executionAuthorized: false, authenticatedAcceptance: false }
88
+ }