@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
@@ -0,0 +1,158 @@
1
+ import fs from 'node:fs'
2
+ import { loadSigningKey, safeLabel, signDocument, verifyDocument } from '../attestation/sign.mjs'
3
+ import { commandProject, firstString, parseArgs } from '../project/config.mjs'
4
+ import { HOST_OWNER_KEYS_PATH, delegationBindings, draftDelegation, loadOwnerKeysFile, validateDelegation } from './delegation.mjs'
5
+ import { loadBoundaryPolicy, validateBoundaryPolicy } from './policy.mjs'
6
+
7
+ export const DELEGATION_USAGE = `Usage: atelier boundary delegation <draft|sign|verify>
8
+
9
+ draft --id ID --owner ACTOR --operator ACTOR --repos A[,B] --operations pre-commit[,boundary-check]
10
+ --expires RFC3339 [--not-before RFC3339] [--out FILE] [--project-config FILE]
11
+ Write an unsigned delegation. It binds the current boundary policy, the
12
+ managed repository set and each listed repository's root commit.
13
+
14
+ sign <delegation.json> --key FILE [--out FILE] [--project-config FILE]
15
+ The OWNER signs with their own private key file (from attestation keygen).
16
+ The project is required: sign recomputes the bindings from it, refuses a
17
+ draft that does not match, and prints what is being authorized.
18
+
19
+ verify <delegation.json> [--owner-keys FILE] [--json]
20
+ Report the signature against the host owner-keys file
21
+ (${HOST_OWNER_KEYS_PATH}), or against FILE to inspect a candidate host
22
+ file, together with that file's trust result and revocation. This is a
23
+ diagnostic; boundary check decides whether a delegation applies.
24
+
25
+ Exit codes: 0 success or valid, 1 invalid, 2 usage or input error.`
26
+
27
+ function fail(message) {
28
+ console.error(message)
29
+ process.exit(2)
30
+ }
31
+
32
+ const list = (value) => (typeof value === 'string' ? value.split(',').map((item) => item.trim()).filter(Boolean) : [])
33
+
34
+ function readJsonFile(file, label) {
35
+ try {
36
+ return JSON.parse(fs.readFileSync(file, 'utf8'))
37
+ } catch {
38
+ fail(`cannot read ${label} JSON: ${file}`)
39
+ }
40
+ return null
41
+ }
42
+
43
+ function writeOutput(out, value) {
44
+ const text = `${JSON.stringify(value, null, 2)}\n`
45
+ if (!out) return process.stdout.write(text)
46
+ try {
47
+ fs.writeFileSync(out, text, { flag: 'wx' })
48
+ } catch {
49
+ fail(`refusing to write ${out}: it exists or cannot be created`)
50
+ }
51
+ return console.error(`written to ${out}`)
52
+ }
53
+
54
+ function projectAndPolicy(argv) {
55
+ const project = commandProject({ argv })
56
+ const loaded = loadBoundaryPolicy(project)
57
+ if (!loaded.ok) fail(loaded.errors.join('\n'))
58
+ // Never draft or sign against a policy the check itself would reject.
59
+ const invalid = validateBoundaryPolicy(loaded.policy, project)
60
+ if (invalid.length) fail(`the boundary policy is invalid:\n${invalid.join('\n')}`)
61
+ return { project, policy: loaded.policy }
62
+ }
63
+
64
+ function runDraft(args, argv) {
65
+ const { project, policy } = projectAndPolicy(argv)
66
+ const expiresAt = firstString(args.expires)
67
+ if (!expiresAt) fail(`delegation draft requires --expires\n\n${DELEGATION_USAGE}`)
68
+ const draft = draftDelegation({
69
+ policy,
70
+ project,
71
+ id: firstString(args.id),
72
+ owner: firstString(args.owner),
73
+ operator: firstString(args.operator),
74
+ repos: list(args.repos),
75
+ operations: list(args.operations),
76
+ notBefore: firstString(args['not-before']) ?? new Date().toISOString().replace(/\.\d{3}Z$/, 'Z'),
77
+ expiresAt,
78
+ })
79
+ if (!draft.ok && !draft.errors.every((message) => /repoRoots/.test(message))) fail(draft.errors.join('\n'))
80
+ if (Object.values(draft.delegation.repoRoots).some((root) => !root)) fail('every listed repository needs at least one commit before it can be delegated')
81
+ if (!draft.ok) fail(draft.errors.join('\n'))
82
+ writeOutput(firstString(args.out), draft.delegation)
83
+ }
84
+
85
+ function runSign(args, argv) {
86
+ const [file] = args._
87
+ if (!file) fail(`delegation sign requires a delegation file\n\n${DELEGATION_USAGE}`)
88
+ const keyPath = firstString(args.key)
89
+ if (!keyPath) fail(`delegation sign requires --key FILE (the owner's own private key)\n\n${DELEGATION_USAGE}`)
90
+ const doc = readJsonFile(file, 'delegation')
91
+ if (doc?.signature !== null) fail('delegation sign requires an unsigned delegation (signature: null)')
92
+ const { project, policy } = projectAndPolicy(argv)
93
+ const shapeErrors = validateDelegation({ ...doc, signature: { algorithm: 'ed25519', keyId: 'unsigned', value: 'unsigned' } }, policy)
94
+ if (shapeErrors.length) fail(shapeErrors.join('\n'))
95
+ // The owner signs what the current project binds, never an opaque digest.
96
+ const expected = delegationBindings({ policy, project, repos: doc.repos })
97
+ if (doc.policyDigest !== expected.policyDigest) fail('the draft does not bind the current boundary policy; draft it again from this project')
98
+ if (doc.repos.some((name) => !expected.repoRoots[name] || doc.repoRoots[name] !== expected.repoRoots[name])) {
99
+ fail('the draft does not bind these repositories\' root commits; draft it again from this project')
100
+ }
101
+ // Every value is printed through safeLabel: ids come from operator-editable files.
102
+ const operatorActor = policy.actors[doc.operator] ?? {}
103
+ const label = (value) => safeLabel(value, 160)
104
+ console.error([
105
+ `Authorizing delegation ${label(doc.id)}:`,
106
+ ` operator ${label(doc.operator)} may pass the actor check owned by ${label(doc.owner)}`,
107
+ ` the operator is recognised by githubLogin ${label(operatorActor.githubLogin ?? '(none)')} and gitEmails ${(operatorActor.gitEmails ?? []).map((email) => label(email)).join(', ') || '(none)'}`,
108
+ ...doc.repos.map((name) => ` repository ${label(name)} with root commit ${label(doc.repoRoots[name])}`),
109
+ ` operations: ${doc.operations.map((op) => label(op)).join(', ')}`,
110
+ ` from ${label(new Date(Date.parse(doc.notBefore)).toISOString())} until ${label(new Date(Date.parse(doc.expiresAt)).toISOString())}`,
111
+ ` while the boundary policy and repository set stay ${label(doc.policyDigest)}`,
112
+ ].join('\n'))
113
+ let signed
114
+ try {
115
+ // An explicit key file only: the environment and working-directory fallbacks are not used for delegations.
116
+ const key = loadSigningKey({ keyPath, env: {}, cwd: '/nonexistent' })
117
+ if (key.algorithm !== 'ed25519') fail('delegations are signed with ed25519 keys')
118
+ signed = signDocument(doc, { privateKey: key.privateKeyJwk, keyId: key.keyId, algorithm: key.algorithm })
119
+ } catch (error) {
120
+ fail(error.message)
121
+ }
122
+ writeOutput(firstString(args.out), signed)
123
+ }
124
+
125
+ function runVerify(args) {
126
+ const [file] = args._
127
+ if (!file) fail(`delegation verify requires a delegation file\n\n${DELEGATION_USAGE}`)
128
+ const doc = readJsonFile(file, 'delegation')
129
+ const keysFile = firstString(args['owner-keys']) ?? HOST_OWNER_KEYS_PATH
130
+ const ownerKeys = loadOwnerKeysFile(keysFile)
131
+ const report = { ownerKeysFile: keysFile, hostTrust: ownerKeys.ok ? { ok: true } : { ok: false, reason: ownerKeys.reason }, signatureValid: false, revoked: false, reasons: [] }
132
+ if (ownerKeys.ok) {
133
+ const key = ownerKeys.keys.find((item) => item.actorId === doc?.owner && item.keyId === doc?.signature?.keyId)
134
+ if (key) {
135
+ const checked = verifyDocument(doc, { publicKey: key })
136
+ report.signatureValid = checked.valid
137
+ report.reasons.push(...checked.reasons)
138
+ report.keyId = key.keyId
139
+ report.owner = key.actorId
140
+ } else report.reasons.push({ code: 'delegation-owner-key-missing', message: 'no host key for this owner and keyId' })
141
+ report.revoked = ownerKeys.revoked.has(doc?.id)
142
+ } else report.reasons.push({ code: ownerKeys.reason, message: ownerKeys.errors.join('; ') })
143
+ report.valid = report.hostTrust.ok && report.signatureValid && !report.revoked
144
+ if (args.json) console.log(JSON.stringify(report, null, 2))
145
+ else console.log(report.valid ? `signature valid for ${report.owner} key ${report.keyId}; scope, time and bindings are decided by boundary check` : `invalid: ${report.reasons.map((item) => item.code).join(', ') || (report.revoked ? 'delegation-revoked' : 'unknown')}`)
146
+ process.exit(report.valid ? 0 : 1)
147
+ }
148
+
149
+ export function runBoundaryDelegationCommand(argv = process.argv.slice(2)) {
150
+ const args = parseArgs(argv)
151
+ const [subcommand, ...rest] = args._
152
+ const sub = { ...args, _: rest }
153
+ if (args.help || args.h) return console.log(DELEGATION_USAGE)
154
+ if (subcommand === 'draft') return runDraft(sub, argv)
155
+ if (subcommand === 'sign') return runSign(sub, argv)
156
+ if (subcommand === 'verify') return runVerify(sub)
157
+ return fail(DELEGATION_USAGE)
158
+ }
@@ -0,0 +1,396 @@
1
+ import { spawnSync } from 'node:child_process'
2
+ import crypto from 'node:crypto'
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import { canonicalize } from '../attestation/jcs.mjs'
6
+ import { verifyDocument } from '../attestation/sign.mjs'
7
+ import { sanitizedGitEnvironment } from '../runtime/git-adapter.mjs'
8
+
9
+ // Owner-signed delegation of the local boundary actor check (FD-BOUND-1).
10
+ // A delegation lets one declared operator pass the private-domain actor check
11
+ // for named repositories and operations. It never changes ownership, audiences,
12
+ // paths, content rules, promotion, push-content approval or publication.
13
+ //
14
+ // Guarantee: attribution integrity for a cooperating operator. The actor check
15
+ // itself stays attribution-grade: an operator can still declare another actor
16
+ // (--actor, MNSTRY_ATELIER_ACTOR), edit the policy or project configuration, or
17
+ // control the process that runs the check. A delegation adds that an owner's
18
+ // signed, current, host-anchored consent is recorded when someone else commits.
19
+ //
20
+ // Trust: the policy and the delegations document are operator-writable, so a
21
+ // delegation is authority only when its signature verifies against an owner key
22
+ // in the host file at a fixed path, owned by root in root-owned directories. That
23
+ // file is also the only source of revocation. Each delegation binds the whole
24
+ // policy (ext excluded), the managed repository set and each repository's root
25
+ // commit, so it cannot be loosened in place or replayed into an independently
26
+ // created repository. Forks sharing the root commit are not distinguished.
27
+ // `ext` is accepted and never read.
28
+
29
+ export const DELEGATION_SCHEMA = 'atelier-boundary-delegation@v1'
30
+ export const DELEGATIONS_DOCUMENT_SCHEMA = 'atelier-boundary-delegations@v1'
31
+ export const OWNER_KEYS_SCHEMA = 'atelier-boundary-owner-keys@v1'
32
+ export const DELEGATION_OPERATIONS = Object.freeze(['boundary-check', 'pre-commit'])
33
+ export const MAX_DELEGATION_SPAN_MS = 90 * 24 * 60 * 60 * 1000
34
+ export const HOST_OWNER_KEYS_PATH = '/etc/atelier/boundary-owner-keys.json'
35
+
36
+ const DELEGATION_KEYS = ['schema', 'id', 'owner', 'operator', 'repos', 'operations', 'notBefore', 'expiresAt', 'policyDigest', 'repoRoots', 'signature', 'ext']
37
+ const VERSION_RE = /^1\.[0-9]+\.[0-9]+$/
38
+ const ID_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/
39
+ const REPO_RE = /^[a-z0-9][a-z0-9._-]*$/
40
+ // Owner and operator ids are shown to the signing owner, so they are restricted
41
+ // to printable identifiers: no control characters, spaces or lookalikes.
42
+ export const DELEGATION_ACTOR_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/
43
+ const UTC_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/
44
+ const DIGEST_RE = /^sha256:[0-9a-f]{64}$/
45
+ const COMMIT_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/
46
+ const JWK_X_RE = /^[A-Za-z0-9_-]{43}$/
47
+ const OWNER_KEYS_MAX_BYTES = 64 * 1024
48
+ const MAX_DELEGATIONS = 64
49
+ const MAX_REPOS = 64
50
+ const MAX_KEYS = 64
51
+ const MAX_REVOKED = 1024
52
+
53
+ const isObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)
54
+ const boundedString = (value, max = 128) => typeof value === 'string' && value.length > 0 && value.length <= max
55
+ // A time must be a real calendar instant that round-trips: 2026-02-31 is refused,
56
+ // never rolled over to March.
57
+ const timeOf = (value) => {
58
+ if (typeof value !== 'string' || !UTC_RE.test(value)) return Number.NaN
59
+ const ms = Date.parse(value)
60
+ if (Number.isNaN(ms)) return Number.NaN
61
+ const iso = new Date(ms).toISOString()
62
+ return iso === value || iso.replace(/\.000Z$/, 'Z') === value || iso.replace(/0*Z$/, 'Z').replace(/\.Z$/, 'Z') === value ? ms : Number.NaN
63
+ }
64
+
65
+ // `ext` is never read, so it is removed before hashing, but only at the
66
+ // extension positions the schema declares. A repository or actor that happens
67
+ // to be named `ext` is ordinary policy and stays bound.
68
+ const dropExt = (value) => {
69
+ if (!isObject(value)) return value
70
+ const { ext, ...rest } = value
71
+ return rest
72
+ }
73
+ function withoutExt(policy) {
74
+ if (!isObject(policy)) return policy ?? null
75
+ const out = dropExt(policy)
76
+ const each = (map) => (isObject(map) ? Object.fromEntries(Object.entries(map).map(([key, item]) => [key, dropExt(item)])) : map)
77
+ if (Object.hasOwn(out, 'repos')) out.repos = each(out.repos)
78
+ if (Object.hasOwn(out, 'actors')) out.actors = each(out.actors)
79
+ if (Object.hasOwn(out, 'promotion')) out.promotion = dropExt(out.promotion)
80
+ if (Array.isArray(out.contentRules)) out.contentRules = out.contentRules.map(dropExt)
81
+ if (Array.isArray(out.contentRuleExceptions)) out.contentRuleExceptions = out.contentRuleExceptions.map(dropExt)
82
+ return out
83
+ }
84
+
85
+ const managedRepoNames = (project) => (project?.repos ?? []).filter((repo) => !repo.external).map((repo) => repo.name).sort()
86
+
87
+ /**
88
+ * The digest a delegation binds: the whole boundary policy (ext excluded) and the
89
+ * project's managed repository names. Any later edit to them voids every
90
+ * delegation until the owner signs again. Other project configuration (repo
91
+ * access, graph settings, repository paths) is not bound.
92
+ */
93
+ export function policyDigest(policy, project) {
94
+ const bound = { policy: withoutExt(policy), managedRepos: managedRepoNames(project) }
95
+ return `sha256:${crypto.createHash('sha256').update(canonicalize(bound), 'utf8').digest('hex')}`
96
+ }
97
+
98
+ /**
99
+ * The repository's identity: the lowest-sorting root commit of HEAD, ignoring
100
+ * replace refs, or null when it has none. Forks and histories built on the same
101
+ * root share it; independently created repositories do not.
102
+ */
103
+ export function repoRootCommit(repoPath, { gitExecutable = 'git' } = {}) {
104
+ if (!repoPath) return null
105
+ const result = spawnSync(gitExecutable, ['--no-replace-objects', '-C', repoPath, 'rev-list', '--max-parents=0', 'HEAD', '--'], { encoding: 'utf8', env: sanitizedGitEnvironment() })
106
+ if (result.status !== 0) return null
107
+ const roots = result.stdout.split('\n').map((line) => line.trim()).filter((line) => COMMIT_RE.test(line)).sort()
108
+ return roots[0] ?? null
109
+ }
110
+
111
+ function repoPathFor(project, name) {
112
+ return (project?.repos ?? []).find((repo) => repo.name === name && !repo.external)?.path ?? null
113
+ }
114
+
115
+ function delegationErrors(item, at, { actors, repos }) {
116
+ if (!isObject(item)) return [`${at} must be an object`]
117
+ const errors = []
118
+ for (const key of Object.keys(item)) {
119
+ if (!DELEGATION_KEYS.includes(key)) errors.push(`${at} must not include additional property ${key}`)
120
+ }
121
+ if (item.ext != null && !isObject(item.ext)) errors.push(`${at}.ext must be an object`)
122
+ if (item.schema !== DELEGATION_SCHEMA) errors.push(`${at}.schema must be ${DELEGATION_SCHEMA}`)
123
+ if (typeof item.id !== 'string' || !ID_RE.test(item.id)) errors.push(`${at}.id is invalid`)
124
+ if (typeof item.owner !== 'string' || !DELEGATION_ACTOR_RE.test(item.owner) || !Object.hasOwn(actors, item.owner)) errors.push(`${at}.owner must be a declared actor with a plain identifier`)
125
+ if (typeof item.operator !== 'string' || !DELEGATION_ACTOR_RE.test(item.operator) || !Object.hasOwn(actors, item.operator)) errors.push(`${at}.operator must be a declared actor with a plain identifier`)
126
+ if (item.owner === item.operator) errors.push(`${at}.operator must differ from the owner`)
127
+ const repoNames = Array.isArray(item.repos) ? item.repos : []
128
+ if (!repoNames.length || repoNames.length > MAX_REPOS || new Set(repoNames).size !== repoNames.length) {
129
+ errors.push(`${at}.repos must be a non-empty list of at most ${MAX_REPOS} without duplicates`)
130
+ }
131
+ for (const name of repoNames) {
132
+ const repo = typeof name === 'string' && REPO_RE.test(name) && Object.hasOwn(repos, name) ? repos[name] : null
133
+ if (!repo || repo.kind !== 'private_domain') errors.push(`${at}.repos ${String(name)} must be a declared private_domain repo`)
134
+ else if (repo.ownerActor !== item.owner) errors.push(`${at}.repos ${name} is not owned by ${String(item.owner)}`)
135
+ }
136
+ const operations = Array.isArray(item.operations) ? item.operations : []
137
+ if (!operations.length || new Set(operations).size !== operations.length || operations.some((op) => !DELEGATION_OPERATIONS.includes(op))) {
138
+ errors.push(`${at}.operations must be a non-empty list of ${DELEGATION_OPERATIONS.join(', ')}`)
139
+ }
140
+ const notBefore = timeOf(item.notBefore)
141
+ const expiresAt = timeOf(item.expiresAt)
142
+ if (Number.isNaN(notBefore)) errors.push(`${at}.notBefore must be an RFC 3339 UTC time`)
143
+ if (Number.isNaN(expiresAt)) errors.push(`${at}.expiresAt must be an RFC 3339 UTC time`)
144
+ if (!Number.isNaN(notBefore) && !Number.isNaN(expiresAt) && !(expiresAt > notBefore && expiresAt - notBefore <= MAX_DELEGATION_SPAN_MS)) {
145
+ errors.push(`${at}.expiresAt must be after notBefore and at most 90 days later`)
146
+ }
147
+ if (typeof item.policyDigest !== 'string' || !DIGEST_RE.test(item.policyDigest)) errors.push(`${at}.policyDigest must be a sha256 digest`)
148
+ const roots = item.repoRoots
149
+ if (!isObject(roots) || Object.keys(roots).length !== repoNames.length ||
150
+ !repoNames.every((name) => typeof name === 'string' && Object.hasOwn(roots, name) && typeof roots[name] === 'string' && COMMIT_RE.test(roots[name]))) {
151
+ errors.push(`${at}.repoRoots must hold one root commit per listed repo`)
152
+ }
153
+ const signature = item.signature
154
+ if (!isObject(signature) || signature.algorithm !== 'ed25519' || !boundedString(signature.keyId) || !boundedString(signature.value, 512) ||
155
+ Object.keys(signature).some((key) => !['algorithm', 'keyId', 'value', 'ext'].includes(key))) {
156
+ errors.push(`${at}.signature must be an ed25519 signature; an unsigned delegation grants nothing`)
157
+ }
158
+ return errors
159
+ }
160
+
161
+ /** Errors for one delegation checked against a policy (used before signing). */
162
+ export function validateDelegation(item, policy) {
163
+ return delegationErrors(item, 'delegation', { actors: isObject(policy?.actors) ? policy.actors : {}, repos: isObject(policy?.repos) ? policy.repos : {} })
164
+ }
165
+
166
+ /**
167
+ * Errors for a delegations document, checked against the boundary policy it
168
+ * delegates under. Delegations live in their own document beside the policy,
169
+ * so the policy contract is not widened.
170
+ */
171
+ export function validateDelegationsDocument(doc, policy) {
172
+ if (!isObject(doc)) return ['delegations document must be a JSON object']
173
+ const errors = []
174
+ for (const key of Object.keys(doc)) {
175
+ if (!['schema', 'contractVersion', 'delegations', 'ext'].includes(key)) errors.push(`delegations document must not include additional property ${key}`)
176
+ }
177
+ if (doc.schema !== DELEGATIONS_DOCUMENT_SCHEMA) errors.push(`delegations document schema must be ${DELEGATIONS_DOCUMENT_SCHEMA}`)
178
+ if (doc.contractVersion != null && !(typeof doc.contractVersion === 'string' && VERSION_RE.test(doc.contractVersion))) errors.push('delegations document contractVersion must be a 1.x.y version')
179
+ if (doc.ext != null && !isObject(doc.ext)) errors.push('delegations document ext must be an object')
180
+ if (!Array.isArray(doc.delegations)) return [...errors, 'delegations document must list delegations']
181
+ if (doc.delegations.length > MAX_DELEGATIONS) errors.push(`delegations document lists more than ${MAX_DELEGATIONS} delegations`)
182
+ const context = { actors: isObject(policy?.actors) ? policy.actors : {}, repos: isObject(policy?.repos) ? policy.repos : {} }
183
+ const seen = new Set()
184
+ for (const [index, item] of doc.delegations.entries()) {
185
+ const at = `delegations[${index}]`
186
+ errors.push(...delegationErrors(item, at, context))
187
+ if (isObject(item) && typeof item.id === 'string') {
188
+ if (seen.has(item.id)) errors.push(`${at}.id ${item.id} is duplicated`)
189
+ seen.add(item.id)
190
+ }
191
+ }
192
+ return errors
193
+ }
194
+
195
+ function ownerKeysErrors(doc) {
196
+ if (!isObject(doc)) return ['owner keys must be a JSON object']
197
+ const errors = []
198
+ for (const key of Object.keys(doc)) {
199
+ if (!['schema', 'contractVersion', 'keys', 'revokedDelegations', 'ext'].includes(key)) errors.push(`owner keys must not include additional property ${key}`)
200
+ }
201
+ if (doc.schema !== OWNER_KEYS_SCHEMA) errors.push(`owner keys schema must be ${OWNER_KEYS_SCHEMA}`)
202
+ if (doc.contractVersion != null && !(typeof doc.contractVersion === 'string' && VERSION_RE.test(doc.contractVersion))) errors.push('owner keys contractVersion must be a 1.x.y version')
203
+ if (doc.ext != null && !isObject(doc.ext)) errors.push('owner keys ext must be an object')
204
+ if (!Array.isArray(doc.keys) || doc.keys.length > MAX_KEYS) errors.push(`owner keys must list at most ${MAX_KEYS} keys`)
205
+ const seen = new Set()
206
+ for (const [index, key] of (Array.isArray(doc.keys) ? doc.keys : []).entries()) {
207
+ if (!isObject(key) || Object.keys(key).some((name) => !['actorId', 'keyId', 'algorithm', 'publicKeyJwk', 'ext'].includes(name)) ||
208
+ (key.ext != null && !isObject(key.ext)) || !boundedString(key.actorId) || !boundedString(key.keyId) || key.algorithm !== 'ed25519' ||
209
+ !isObject(key.publicKeyJwk) || key.publicKeyJwk.kty !== 'OKP' || key.publicKeyJwk.crv !== 'Ed25519' ||
210
+ typeof key.publicKeyJwk.x !== 'string' || !JWK_X_RE.test(key.publicKeyJwk.x) || Object.hasOwn(key.publicKeyJwk, 'd')) {
211
+ errors.push(`owner keys keys[${index}] must be an ed25519 public key with actorId and keyId`)
212
+ continue
213
+ }
214
+ const identity = `${key.actorId}\u0000${key.keyId}`
215
+ if (seen.has(identity)) errors.push(`owner keys keys[${index}] duplicates ${key.actorId}/${key.keyId}`)
216
+ seen.add(identity)
217
+ }
218
+ const revoked = doc.revokedDelegations
219
+ if (revoked != null && (!Array.isArray(revoked) || revoked.length > MAX_REVOKED || new Set(revoked).size !== revoked.length ||
220
+ revoked.some((id) => typeof id !== 'string' || !ID_RE.test(id)))) {
221
+ errors.push('owner keys revokedDelegations must be a list of distinct delegation ids')
222
+ }
223
+ return errors
224
+ }
225
+
226
+ /** Normalize a host-supplied owner-keys document. The caller is the trusted host. */
227
+ export function ownerKeysFromDocument(doc) {
228
+ const errors = ownerKeysErrors(doc)
229
+ if (errors.length) return { ok: false, reason: 'delegation-owner-keys-invalid', errors }
230
+ return { ok: true, keys: doc.keys, revoked: new Set(doc.revokedDelegations ?? []) }
231
+ }
232
+
233
+ /** The normalized form of a host `ownerKeys` value, re-validated. */
234
+ function normalizedOwnerKeys(ownerKeys) {
235
+ if (!isObject(ownerKeys)) return null
236
+ if (ownerKeys.ok === false) return ownerKeys
237
+ if (ownerKeys.ok === true && Array.isArray(ownerKeys.keys) && ownerKeys.revoked instanceof Set) {
238
+ return ownerKeysFromDocument({ schema: OWNER_KEYS_SCHEMA, keys: ownerKeys.keys, revokedDelegations: [...ownerKeys.revoked] })
239
+ }
240
+ return ownerKeysFromDocument(ownerKeys)
241
+ }
242
+
243
+ /**
244
+ * Load the host owner-keys file. It is trusted only when it is a regular file
245
+ * owned by the trusted account (root), not group- or world-writable, opened
246
+ * without following links, and every directory above it is owned by that
247
+ * account (or root) and not group- or world-writable. The checks run on the open
248
+ * descriptor, which is also what is read. The current account must not be the
249
+ * trusted one. Windows is refused until an owner and DACL check exists.
250
+ */
251
+ export function loadOwnerKeysFile(file = HOST_OWNER_KEYS_PATH, { platform = process.platform, trustedUid = 0, currentUid = process.getuid?.() } = {}) {
252
+ const untrusted = (detail) => ({ ok: false, reason: 'delegation-owner-keys-untrusted', errors: [detail] })
253
+ if (platform === 'win32') return untrusted('owner keys are not supported on Windows')
254
+ if (typeof currentUid !== 'number') return untrusted('the current account cannot be determined')
255
+ if (currentUid === 0) return untrusted('owner keys cannot be trusted for the root account')
256
+ if (currentUid === trustedUid) return untrusted('the current account must not own the owner keys')
257
+ const resolved = path.resolve(file)
258
+ let fd
259
+ try {
260
+ fd = fs.openSync(resolved, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0) | (fs.constants.O_NONBLOCK ?? 0))
261
+ } catch (error) {
262
+ if (error?.code === 'ENOENT') return { ok: false, reason: 'delegation-owner-key-missing', errors: ['owner keys file not found'] }
263
+ if (error?.code === 'ELOOP') return untrusted('owner keys file must not be a symlink')
264
+ return untrusted('owner keys file cannot be opened')
265
+ }
266
+ try {
267
+ const stat = fs.fstatSync(fd)
268
+ if (!stat.isFile()) return untrusted('owner keys file must be a regular file')
269
+ if (stat.size > OWNER_KEYS_MAX_BYTES) return untrusted('owner keys file is too large')
270
+ if (stat.uid !== trustedUid) return untrusted('owner keys file must be owned by the trusted account')
271
+ if ((stat.mode & 0o022) !== 0) return untrusted('owner keys file must not be group- or world-writable')
272
+ let real
273
+ try {
274
+ real = fs.realpathSync(resolved)
275
+ const named = fs.lstatSync(real)
276
+ if (named.dev !== stat.dev || named.ino !== stat.ino) return untrusted('owner keys file changed while it was checked')
277
+ } catch {
278
+ return untrusted('owner keys file changed while it was checked')
279
+ }
280
+ for (let dir = path.dirname(real); ; dir = path.dirname(dir)) {
281
+ let info
282
+ try {
283
+ info = fs.statSync(dir)
284
+ } catch {
285
+ return untrusted('owner keys file directories changed while they were checked')
286
+ }
287
+ if ((info.uid !== trustedUid && info.uid !== 0) || (info.mode & 0o022) !== 0) {
288
+ return untrusted('owner keys file must be in directories owned by the trusted account and not group- or world-writable')
289
+ }
290
+ if (path.dirname(dir) === dir) break
291
+ }
292
+ let doc
293
+ try {
294
+ doc = JSON.parse(fs.readFileSync(fd, 'utf8'))
295
+ } catch {
296
+ return { ok: false, reason: 'delegation-owner-keys-invalid', errors: ['owner keys file is not valid JSON'] }
297
+ }
298
+ return ownerKeysFromDocument(doc)
299
+ } finally {
300
+ fs.closeSync(fd)
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Owner keys for a CLI check: only the host file at its fixed path. The command
306
+ * line and environment cannot choose another file, so the host's keys and
307
+ * revocations always apply. Returns null when the host has none.
308
+ */
309
+ export function ownerKeysForCommand() {
310
+ return fs.existsSync(HOST_OWNER_KEYS_PATH) || isSymlink(HOST_OWNER_KEYS_PATH) ? loadOwnerKeysFile(HOST_OWNER_KEYS_PATH) : null
311
+ }
312
+
313
+ function isSymlink(file) {
314
+ try {
315
+ return fs.lstatSync(file).isSymbolicLink()
316
+ } catch {
317
+ return false
318
+ }
319
+ }
320
+
321
+ /** The delegations document beside a boundary policy file. */
322
+ export function delegationsPathFor(policyPath) {
323
+ return path.join(path.dirname(policyPath), 'boundary-delegations.v1.json')
324
+ }
325
+
326
+ /** Read the delegations document beside the policy, if one exists. */
327
+ export function loadDelegationsFile(file) {
328
+ if (!fs.existsSync(file)) return { present: false, document: null, errors: [] }
329
+ let document
330
+ try {
331
+ document = JSON.parse(fs.readFileSync(file, 'utf8'))
332
+ } catch {
333
+ return { present: true, document: null, errors: ['delegations document is not valid JSON'] }
334
+ }
335
+ if (!isObject(document)) return { present: true, document: null, errors: ['delegations document must be a JSON object'] }
336
+ return { present: true, document, errors: [] }
337
+ }
338
+
339
+ /**
340
+ * Decide whether a delegation lets `actorId` pass the actor check for one
341
+ * repository and operation. Returns `{ applies: true, delegation }` or
342
+ * `{ applies: false, reason }`; it never widens anything else.
343
+ */
344
+ export function resolveDelegation({ policy, project, delegations = [], repoName, repo, actorId, operation, ownerKeys = null, now = Date.now(), rootCommitOf = repoRootCommit }) {
345
+ const deny = (reason) => ({ applies: false, reason })
346
+ const all = Array.isArray(delegations) ? delegations.filter(isObject) : []
347
+ const forOperator = all.filter((item) => item.operator === actorId)
348
+ if (!forOperator.length) return deny('delegation-missing')
349
+ const forRepo = forOperator.filter((item) => Array.isArray(item.repos) && item.repos.includes(repoName))
350
+ const inScope = forRepo.filter((item) => Array.isArray(item.operations) && item.operations.includes(operation))
351
+ if (!inScope.length) return deny('delegation-scope')
352
+ if (inScope.length > 1) return deny('delegation-ambiguous')
353
+ const [delegation] = inScope
354
+ if (validateDelegation(delegation, policy).length || delegation.owner !== repo?.ownerActor) return deny('delegation-malformed')
355
+ const keys = normalizedOwnerKeys(ownerKeys)
356
+ if (!keys) return deny('delegation-owner-key-missing')
357
+ if (!keys.ok) return deny(keys.reason)
358
+ // The key must be registered for the delegation's owner, not merely present.
359
+ const key = keys.keys.find((item) => item.actorId === delegation.owner && item.keyId === delegation.signature.keyId)
360
+ if (!key) return deny('delegation-owner-key-missing')
361
+ if (!verifyDocument(delegation, { publicKey: key }).valid) return deny('delegation-signature-invalid')
362
+ if (keys.revoked.has(delegation.id)) return deny('delegation-revoked')
363
+ if (now < Date.parse(delegation.notBefore)) return deny('delegation-not-yet-valid')
364
+ if (now >= Date.parse(delegation.expiresAt)) return deny('delegation-expired')
365
+ if (delegation.policyDigest !== policyDigest(policy, project)) return deny('delegation-policy-changed')
366
+ for (const name of delegation.repos) {
367
+ if (rootCommitOf(repoPathFor(project, name)) !== delegation.repoRoots[name]) return deny('delegation-repository-changed')
368
+ }
369
+ return { applies: true, delegation }
370
+ }
371
+
372
+ /** The bound values a delegation must carry for this policy and project. */
373
+ export function delegationBindings({ policy, project, repos, rootCommitOf = repoRootCommit }) {
374
+ return {
375
+ policyDigest: policyDigest(policy, project),
376
+ repoRoots: Object.fromEntries((Array.isArray(repos) ? repos : []).map((name) => [name, rootCommitOf(repoPathFor(project, name))])),
377
+ }
378
+ }
379
+
380
+ /** An unsigned delegation document for the owner to read and sign. */
381
+ export function draftDelegation({ policy, project, id, owner, operator, repos, operations, notBefore, expiresAt, rootCommitOf = repoRootCommit }) {
382
+ const doc = {
383
+ schema: DELEGATION_SCHEMA,
384
+ id,
385
+ owner,
386
+ operator,
387
+ repos,
388
+ operations,
389
+ notBefore,
390
+ expiresAt,
391
+ ...delegationBindings({ policy, project, repos, rootCommitOf }),
392
+ signature: null,
393
+ }
394
+ const errors = validateDelegation({ ...doc, signature: { algorithm: 'ed25519', keyId: 'unsigned', value: 'unsigned' } }, policy)
395
+ return { ok: errors.length === 0, delegation: doc, errors }
396
+ }