@mnstry/atelier 0.2.0-alpha.11 → 0.2.0-alpha.13

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 (560) hide show
  1. package/CHANGELOG.md +1005 -0
  2. package/README.md +149 -9
  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-obsidian-generation-manifest.v2.schema.json +372 -0
  21. package/contracts/atelier-personal-workspace-manifest.v1.schema.json +87 -0
  22. package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
  23. package/contracts/atelier-template-adoption.v1.schema.json +223 -0
  24. package/contracts/atelier-template-profile.v1.schema.json +1168 -0
  25. package/contracts/atelier-trackable.v1.schema.json +781 -0
  26. package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
  27. package/contracts/compat-baseline.json +1 -1
  28. package/docs/architecture.md +89 -0
  29. package/docs/assurance-controls.md +4 -3
  30. package/docs/blocks/capability-stewardship.md +17 -0
  31. package/docs/blocks/claims.md +16 -3
  32. package/docs/blocks/inquiry-harnesses.md +19 -0
  33. package/docs/blocks/learning-harnesses.md +17 -0
  34. package/docs/capability-stewardship.md +269 -0
  35. package/docs/coordination.md +51 -0
  36. package/docs/decision-practices.md +111 -0
  37. package/docs/decisions.md +29 -0
  38. package/docs/evidence-navigation.md +237 -0
  39. package/docs/exact-upgrades.md +3 -2
  40. package/docs/ingestion.md +181 -0
  41. package/docs/inquiry-harnesses.md +211 -0
  42. package/docs/install.md +80 -3
  43. package/docs/intake-and-guides.md +5 -0
  44. package/docs/integration-contract-decisions.md +24 -2
  45. package/docs/interaction-and-reflection.md +72 -0
  46. package/docs/knowledge-graph.md +20 -0
  47. package/docs/knowledge-health/api.md +30 -0
  48. package/docs/knowledge-health/assessment/api.md +96 -0
  49. package/docs/knowledge-health/assessment/capabilities.md +40 -0
  50. package/docs/knowledge-health/assessment/evaluation.md +27 -0
  51. package/docs/knowledge-health/assessment/facilitator.md +30 -0
  52. package/docs/knowledge-health/assessment/quickstart.md +61 -0
  53. package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
  54. package/docs/knowledge-health/devday/facilitator.md +16 -0
  55. package/docs/knowledge-health/devday/participant-response.md +22 -0
  56. package/docs/knowledge-health/devday/public-default.md +107 -0
  57. package/docs/knowledge-health/devday/quickstart.md +18 -0
  58. package/docs/knowledge-health/devday/support.md +22 -0
  59. package/docs/knowledge-health/exercise.md +17 -0
  60. package/docs/knowledge-health/facilitator.md +20 -0
  61. package/docs/knowledge-health/quickstart.md +94 -0
  62. package/docs/knowledge-health/readme.md +15 -0
  63. package/docs/knowledge-health/support.md +34 -0
  64. package/docs/knowledge-setup.md +248 -0
  65. package/docs/knowledge-workspace.md +264 -0
  66. package/docs/learning-harnesses.md +238 -0
  67. package/docs/learning.md +215 -0
  68. package/docs/local-services.md +242 -26
  69. package/docs/obsidian-contract.md +878 -36
  70. package/docs/obsidian-plugin.md +619 -0
  71. package/docs/obsidian.md +809 -64
  72. package/docs/optional-ingestion.md +83 -0
  73. package/docs/personal-workspace.md +257 -0
  74. package/docs/practical-judgment.md +92 -0
  75. package/docs/progressive-ingestion-acceptance.md +89 -0
  76. package/docs/progressive-ingestion.md +235 -0
  77. package/docs/project-command-inventory.md +1 -0
  78. package/docs/release-engineering.md +56 -1
  79. package/docs/repo-boundary-guard.md +94 -0
  80. package/docs/research-starter-quickstart.md +66 -0
  81. package/docs/research-starter.md +114 -0
  82. package/docs/semantic-ingestion.md +198 -0
  83. package/docs/semantic-operation.md +317 -0
  84. package/docs/skill-steward.md +6 -0
  85. package/docs/template-profile.md +164 -0
  86. package/docs/template-upgrades.md +114 -0
  87. package/docs/trackables.md +111 -0
  88. package/docs/upgrade.md +68 -3
  89. package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
  90. package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
  91. package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
  92. package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
  93. package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
  94. package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
  95. package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
  96. package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
  97. package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
  98. package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
  99. package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
  100. package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
  101. package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
  102. package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
  103. package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
  104. package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
  105. package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
  106. package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
  107. package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
  108. package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
  109. package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
  110. package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
  111. package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
  112. package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
  113. package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
  114. package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
  115. package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
  116. package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
  117. package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
  118. package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
  119. package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
  120. package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
  121. package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
  122. package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
  123. package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
  124. package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
  125. package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
  126. package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
  127. package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
  128. package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
  129. package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
  130. package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
  131. package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
  132. package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
  133. package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
  134. package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
  135. package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
  136. package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
  137. package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
  138. package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
  139. package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
  140. package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
  141. package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
  142. package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
  143. package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
  144. package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
  145. package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
  146. package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
  147. package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
  148. package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
  149. package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
  150. package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
  151. package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
  152. package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
  153. package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
  154. package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
  155. package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
  156. package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
  157. package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
  158. package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
  159. package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
  160. package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
  161. package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
  162. package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
  163. package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
  164. package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
  165. package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
  166. package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
  167. package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
  168. package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
  169. package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
  170. package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
  171. package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
  172. package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
  173. package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
  174. package/fixtures/atelier-interaction/act/valid/document.json +24 -0
  175. package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
  176. package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
  177. package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
  178. package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
  179. package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
  180. package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
  181. package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
  182. package/fixtures/atelier-judgment/case/valid/document.json +75 -0
  183. package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
  184. package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
  185. package/fixtures/atelier-judgment/native-decision.md +3 -0
  186. package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
  187. package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
  188. package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
  189. package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
  190. package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
  191. package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
  192. package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
  193. package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
  194. package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
  195. package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
  196. package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
  197. package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
  198. package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
  199. package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
  200. package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
  201. package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
  202. package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
  203. package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
  204. package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
  205. package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
  206. package/fixtures/atelier-learning/invalid/authority.json +14 -0
  207. package/fixtures/atelier-learning/valid/capture.json +13 -0
  208. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
  209. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
  210. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
  211. package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
  212. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
  213. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
  214. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
  215. package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
  216. package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
  217. package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
  218. package/fixtures/atelier-trackable/request/valid/document.json +22 -0
  219. package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
  220. package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
  221. package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
  222. package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
  223. package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
  224. package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
  225. package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
  226. package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
  227. package/fixtures/capability-packages/adoption.example.json +70 -0
  228. package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
  229. package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
  230. package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
  231. package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
  232. package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
  233. package/fixtures/capability-packages/research/capability-package.json +88 -0
  234. package/fixtures/capability-packages/research/capability-release.json +134 -0
  235. package/fixtures/capability-packages/research/evaluation.md +15 -0
  236. package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
  237. package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
  238. package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
  239. package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
  240. package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
  241. package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
  242. package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
  243. package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
  244. package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
  245. package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
  246. package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
  247. package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
  248. package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
  249. package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
  250. package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
  251. package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
  252. package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
  253. package/fixtures/evidence-navigation/valid/claim.json +36 -0
  254. package/fixtures/evidence-navigation/valid/host.json +16 -0
  255. package/fixtures/evidence-navigation/valid/profile.json +16 -0
  256. package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
  257. package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
  258. package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
  259. package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
  260. package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
  261. package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
  262. package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
  263. package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
  264. package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
  265. package/fixtures/harnesses/learning-cycle.json +780 -0
  266. package/fixtures/inquiry/workshop.json +675 -0
  267. package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
  268. package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
  269. package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
  270. package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
  271. package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
  272. package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
  273. package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
  274. package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
  275. package/fixtures/knowledge-health/calibration-suite.json +26 -0
  276. package/fixtures/knowledge-health/calibration-trial.json +37 -0
  277. package/fixtures/knowledge-health/corrected/checklist.md +18 -0
  278. package/fixtures/knowledge-health/devday/participant-record.json +19 -0
  279. package/fixtures/knowledge-health/expected-summary.json +27 -0
  280. package/fixtures/knowledge-health/workshop/README.md +20 -0
  281. package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
  282. package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
  283. package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
  284. package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
  285. package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
  286. package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
  287. package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
  288. package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
  289. package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
  290. package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
  291. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/case-only-collision.v2.json +160 -0
  292. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/flat-note-path.v2.json +160 -0
  293. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/hidden-folder.v2.json +160 -0
  294. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/identity-in-the-body.v2.json +160 -0
  295. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/link-breaking-name.v2.json +160 -0
  296. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-identity-region.v2.json +156 -0
  297. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-layout-version.v2.json +159 -0
  298. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/note-named-like-a-folder.v2.json +160 -0
  299. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/unknown-region-kind.v2.json +160 -0
  300. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/v1-contract-version.v2.json +161 -0
  301. package/fixtures/obsidian/contracts/generation-manifest-v2/valid/three-notes.v2.json +160 -0
  302. package/fixtures/obsidian/edits/workspace.json +1 -1
  303. package/fixtures/obsidian/materialization/expected-full.json +21 -21
  304. package/fixtures/obsidian/materialization/expected-full.layout-1.json +99 -0
  305. package/fixtures/obsidian/materialization/expected-scoped.json +7 -7
  306. package/fixtures/obsidian/materialization/expected-scoped.layout-1.json +39 -0
  307. package/fixtures/personal-workspace/overlay.json +24 -0
  308. package/fixtures/templates/local-library.v1.json +75 -0
  309. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
  310. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
  311. package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
  312. package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
  313. package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
  314. package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
  315. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
  316. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
  317. package/package.json +64 -6
  318. package/plugins/obsidian/main.js +444 -0
  319. package/plugins/obsidian/manifest.json +9 -0
  320. package/plugins/obsidian/styles.css +22 -0
  321. package/skills/claude/atelier-build-harness/SKILL.md +59 -0
  322. package/skills/claude/atelier-coordination/SKILL.md +32 -0
  323. package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
  324. package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
  325. package/skills/claude/atelier-interaction/SKILL.md +34 -0
  326. package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
  327. package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
  328. package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
  329. package/skills/claude/atelier-research-harness/SKILL.md +43 -0
  330. package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
  331. package/skills/claude/atelier-trackables/SKILL.md +40 -0
  332. package/skills/codex/atelier-build-harness/SKILL.md +59 -0
  333. package/skills/codex/atelier-coordination/SKILL.md +32 -0
  334. package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
  335. package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
  336. package/skills/codex/atelier-interaction/SKILL.md +34 -0
  337. package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
  338. package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
  339. package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
  340. package/skills/codex/atelier-research-harness/SKILL.md +43 -0
  341. package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
  342. package/skills/codex/atelier-trackables/SKILL.md +40 -0
  343. package/src/architecture/catalog.mjs +355 -0
  344. package/src/architecture/index.mjs +93 -0
  345. package/src/boundary/content-rules.mjs +4 -2
  346. package/src/boundary/delegation-command.mjs +158 -0
  347. package/src/boundary/delegation.mjs +396 -0
  348. package/src/boundary/policy.mjs +52 -10
  349. package/src/build/git.mjs +18 -0
  350. package/src/build/index.mjs +3 -0
  351. package/src/build/ledger.mjs +88 -0
  352. package/src/capabilities/adoption.mjs +381 -0
  353. package/src/capabilities/evidence.mjs +140 -0
  354. package/src/capabilities/files.mjs +124 -0
  355. package/src/capabilities/index.mjs +4 -0
  356. package/src/capabilities/instructions.mjs +250 -0
  357. package/src/capabilities/package.mjs +136 -0
  358. package/src/cli/command-failure.mjs +36 -0
  359. package/src/cli/execute-command.mjs +16 -2
  360. package/src/cli/run.mjs +65 -9
  361. package/src/coauthor/store.mjs +43 -2
  362. package/src/collaboration/proposals.mjs +189 -0
  363. package/src/commands/architecture.mjs +27 -0
  364. package/src/commands/boundary.mjs +3 -0
  365. package/src/commands/capability.mjs +73 -0
  366. package/src/commands/enroll.mjs +3 -0
  367. package/src/commands/harness.mjs +79 -0
  368. package/src/commands/ingest.mjs +39 -0
  369. package/src/commands/init.mjs +4 -1
  370. package/src/commands/inquiry.mjs +66 -0
  371. package/src/commands/knowledge-semantic.mjs +128 -0
  372. package/src/commands/knowledge.mjs +119 -0
  373. package/src/commands/learn.mjs +61 -0
  374. package/src/commands/obsidian.mjs +832 -62
  375. package/src/commands/practice.mjs +24 -0
  376. package/src/commands/setup.mjs +22 -2
  377. package/src/commands/trackable.mjs +19 -0
  378. package/src/contracts/corpus.mjs +36 -0
  379. package/src/coordination/index.mjs +110 -0
  380. package/src/egress/forbidden-egress.mjs +2 -0
  381. package/src/evidence-navigation/contracts.mjs +60 -0
  382. package/src/evidence-navigation/evaluate.mjs +69 -0
  383. package/src/evidence-navigation/generation.mjs +22 -0
  384. package/src/evidence-navigation/index.mjs +2 -0
  385. package/src/evidence-navigation/local.mjs +167 -0
  386. package/src/evidence-navigation/schema.generated.mjs +604 -0
  387. package/src/evidence-navigation/types.mjs +12 -0
  388. package/src/export/atelier-export-contract.mjs +35 -8
  389. package/src/graph/enroll.mjs +248 -0
  390. package/src/graph/graph.mjs +17 -1
  391. package/src/graph/knowledge-graph.mjs +58 -8
  392. package/src/harnesses/contracts.mjs +41 -0
  393. package/src/harnesses/exchange.mjs +154 -0
  394. package/src/harnesses/feedback.mjs +16 -0
  395. package/src/harnesses/history.mjs +37 -0
  396. package/src/harnesses/index.mjs +4 -0
  397. package/src/harnesses/store.mjs +29 -0
  398. package/src/ingestion/contracts.mjs +41 -0
  399. package/src/ingestion/evaluation.mjs +119 -0
  400. package/src/ingestion/optional/composition/index.mjs +171 -0
  401. package/src/ingestion/optional/evaluation/index.mjs +219 -0
  402. package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
  403. package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
  404. package/src/ingestion/optional/graphify/graphify_supplier.py +933 -0
  405. package/src/ingestion/optional/graphify/json_bridge.py +163 -0
  406. package/src/ingestion/optional/graphify/query_receiving.py +209 -0
  407. package/src/ingestion/optional/index.mjs +9 -0
  408. package/src/ingestion/optional/jev/bridge.mjs +166 -0
  409. package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
  410. package/src/ingestion/optional/jev/judgments.mjs +174 -0
  411. package/src/ingestion/optional/jev/native-translation.mjs +32 -0
  412. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
  413. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
  414. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
  415. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
  416. package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
  417. package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
  418. package/src/ingestion/optional/vanilla/index.mjs +218 -0
  419. package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
  420. package/src/ingestion/processors.mjs +218 -0
  421. package/src/ingestion/semantic.mjs +203 -0
  422. package/src/ingestion/store.mjs +372 -0
  423. package/src/inquiry/beliefs.mjs +32 -0
  424. package/src/inquiry/contracts.mjs +23 -0
  425. package/src/inquiry/index.mjs +5 -0
  426. package/src/inquiry/ledger.mjs +135 -0
  427. package/src/inquiry/lenses.mjs +15 -0
  428. package/src/inquiry/projection.mjs +77 -0
  429. package/src/inquiry/store.mjs +35 -0
  430. package/src/intake/read-scope.mjs +12 -0
  431. package/src/intake/store.mjs +69 -6
  432. package/src/interaction/index.mjs +91 -0
  433. package/src/judgment/index.mjs +82 -0
  434. package/src/judgment/practice-evaluation.mjs +70 -0
  435. package/src/judgment/practice.mjs +144 -0
  436. package/src/knowledge/context.mjs +217 -0
  437. package/src/knowledge/index.mjs +5 -0
  438. package/src/knowledge/ingestion.mjs +79 -0
  439. package/src/knowledge/intake.mjs +33 -0
  440. package/src/knowledge/ledger.mjs +110 -0
  441. package/src/knowledge/participatory/guidance.mjs +36 -0
  442. package/src/knowledge/participatory/guided-contribution.mjs +44 -0
  443. package/src/knowledge/participatory/index.mjs +2 -0
  444. package/src/knowledge/participatory/installed-workshop.mjs +108 -0
  445. package/src/knowledge/plan.mjs +67 -0
  446. package/src/knowledge/projection.mjs +43 -0
  447. package/src/knowledge/semantic-operation.mjs +567 -0
  448. package/src/knowledge/sessions.mjs +467 -0
  449. package/src/knowledge/workspace.mjs +295 -0
  450. package/src/knowledge-health/assessment.mjs +165 -0
  451. package/src/knowledge-health/capabilities.mjs +350 -0
  452. package/src/knowledge-health/context.mjs +62 -0
  453. package/src/knowledge-health/example.mjs +47 -0
  454. package/src/knowledge-health/index.mjs +10 -0
  455. package/src/knowledge-health/owner-join.mjs +88 -0
  456. package/src/knowledge-health/proof/common.mjs +24 -0
  457. package/src/knowledge-health/proof/installed-check.mjs +100 -0
  458. package/src/knowledge-health/proof/native-readback.mjs +37 -0
  459. package/src/knowledge-health/proof/rehearse.mjs +68 -0
  460. package/src/knowledge-health/proof/validate-participant.mjs +17 -0
  461. package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
  462. package/src/knowledge-health/proof/workshop-check.mjs +36 -0
  463. package/src/knowledge-health/rule-identity.mjs +44 -0
  464. package/src/knowledge-health/workshop-composition.mjs +19 -0
  465. package/src/knowledge-health/workshop-profile.mjs +176 -0
  466. package/src/knowledge-health/workshop.mjs +99 -0
  467. package/src/learning/adapters.mjs +68 -0
  468. package/src/learning/assistance.mjs +127 -0
  469. package/src/learning/contracts.mjs +65 -0
  470. package/src/learning/store.mjs +272 -0
  471. package/src/personal-workspace/index.mjs +391 -0
  472. package/src/personal-workspace/selection.mjs +385 -0
  473. package/src/project/config.mjs +8 -0
  474. package/src/project/durable-state.mjs +3 -1
  475. package/src/project/file-class.mjs +1 -1
  476. package/src/project/private-state.mjs +10 -1
  477. package/src/projection/obsidian/contracts.mjs +224 -18
  478. package/src/projection/obsidian/edits/apply.mjs +63 -19
  479. package/src/projection/obsidian/edits/regions.mjs +137 -42
  480. package/src/projection/obsidian/materialize/index.mjs +6 -3
  481. package/src/projection/obsidian/materialize/path-registry.mjs +443 -57
  482. package/src/projection/obsidian/materialize/prepare-view.mjs +382 -131
  483. package/src/projection/obsidian/materialize/redaction.mjs +327 -0
  484. package/src/projection/obsidian/materialize/settings.mjs +113 -34
  485. package/src/projection/obsidian/personal-workspace.mjs +436 -0
  486. package/src/projection/obsidian/plugin-bridge/bundle.mjs +50 -0
  487. package/src/projection/obsidian/plugin-bridge/channel.mjs +174 -0
  488. package/src/projection/obsidian/plugin-bridge/index.mjs +10 -0
  489. package/src/projection/obsidian/proposals/adapter.mjs +47 -8
  490. package/src/projection/obsidian/proposals/observation.mjs +20 -7
  491. package/src/projection/obsidian/publication/bridge-script.mjs +11 -5
  492. package/src/projection/obsidian/publication/index.mjs +2 -1
  493. package/src/projection/obsidian/publication/publisher.mjs +312 -36
  494. package/src/projection/obsidian/publication/transport.mjs +63 -8
  495. package/src/projection/obsidian/publication/vault-list.mjs +200 -0
  496. package/src/projection/obsidian/recovery/index.mjs +4 -1
  497. package/src/projection/obsidian/recovery/restart.mjs +3 -1
  498. package/src/projection/obsidian/recovery/store.mjs +168 -8
  499. package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
  500. package/src/projection/obsidian/selection-ui/focus.mjs +14 -7
  501. package/src/projection/obsidian/selection-ui/selection-state.mjs +2 -2
  502. package/src/projection/obsidian/selection-ui/selection.mjs +8 -3
  503. package/src/readiness/readiness.mjs +10 -3
  504. package/src/reflection/index.mjs +56 -0
  505. package/src/runtime/obsidian/app-capability.mjs +104 -12
  506. package/src/runtime/obsidian/app-production-seams.mjs +161 -42
  507. package/src/runtime/obsidian/app-registration.mjs +313 -0
  508. package/src/runtime/obsidian/app-restart.mjs +154 -0
  509. package/src/runtime/obsidian/engine.mjs +288 -29
  510. package/src/runtime/obsidian/extension-points.mjs +1 -1
  511. package/src/runtime/obsidian/index.mjs +15 -8
  512. package/src/runtime/obsidian/launch-plan.mjs +91 -0
  513. package/src/runtime/obsidian/lifecycle.mjs +193 -22
  514. package/src/runtime/obsidian/login-item.mjs +332 -0
  515. package/src/runtime/obsidian/machine-settings.mjs +119 -12
  516. package/src/runtime/obsidian/opening.mjs +369 -63
  517. package/src/runtime/obsidian/pending-edits.mjs +10 -0
  518. package/src/runtime/obsidian/pipeline.mjs +71 -3
  519. package/src/runtime/obsidian/plugin-channel.mjs +289 -0
  520. package/src/runtime/obsidian/plugin-choice.mjs +182 -0
  521. package/src/runtime/obsidian/plugin-drift.mjs +124 -0
  522. package/src/runtime/obsidian/plugin-presence.mjs +61 -0
  523. package/src/runtime/obsidian/process-identity.mjs +20 -10
  524. package/src/runtime/obsidian/project-views.mjs +282 -0
  525. package/src/runtime/obsidian/questions.mjs +48 -0
  526. package/src/runtime/obsidian/release-watch.mjs +56 -0
  527. package/src/runtime/obsidian/service-main.mjs +113 -17
  528. package/src/runtime/obsidian/service-manager-production.mjs +61 -0
  529. package/src/runtime/obsidian/service-managers.mjs +275 -0
  530. package/src/runtime/obsidian/service-record.mjs +105 -5
  531. package/src/runtime/obsidian/service-server.mjs +50 -12
  532. package/src/runtime/obsidian/service.mjs +134 -7
  533. package/src/runtime/obsidian/startup-adapters.mjs +59 -21
  534. package/src/runtime/obsidian/state-store.mjs +13 -1
  535. package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
  536. package/src/runtime/obsidian/vault-location.mjs +300 -0
  537. package/src/runtime/obsidian/view-counts.mjs +34 -0
  538. package/src/server/local-sidecar.mjs +193 -7
  539. package/src/server/server.mjs +45 -6
  540. package/src/template-bindings/project.mjs +92 -0
  541. package/src/templates/profile.mjs +225 -0
  542. package/src/trackables/domain.mjs +175 -0
  543. package/src/trackables/store.mjs +114 -0
  544. package/src/ui/knowledge-page.mjs +1388 -0
  545. package/src/upgrade/explanation.mjs +1 -1
  546. package/src/upgrade/template-participant.mjs +171 -0
  547. package/src/upgrade/transaction.mjs +72 -25
  548. package/src/upgrade/upgrade.mjs +2 -1
  549. package/templates/external-project-workspace/pack-lifecycle.json +6 -1
  550. package/templates/knowledge-workspace/.gitattributes +2 -0
  551. package/templates/knowledge-workspace/AGENTS.md +27 -0
  552. package/templates/knowledge-workspace/README.md +44 -0
  553. package/templates/knowledge-workspace/atelier.project.json +22 -0
  554. package/templates/knowledge-workspace/gitignore +6 -0
  555. package/templates/knowledge-workspace/knowledge-plan.json +111 -0
  556. package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
  557. package/templates/knowledge-workspace/records/forecast.md +22 -0
  558. package/templates/knowledge-workspace/records/inspection.md +25 -0
  559. package/templates/knowledge-workspace/records/telescope.md +24 -0
  560. package/templates/knowledge-workspace/repo-access.v1.json +9 -0
@@ -0,0 +1,248 @@
1
+ import { createHash } from 'node:crypto'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { PRIVATE_AUDIENCES, loadBoundaryPolicy } from '../boundary/policy.mjs'
5
+ import { parseProjectOptions } from '../cli/project-options.mjs'
6
+ import { AtelierDiagnosticError, commandProject } from '../project/config.mjs'
7
+ import { VALID_AUDIENCES } from '../projection/policy.mjs'
8
+ import { canonicalGraphOptions } from './graph.mjs'
9
+ import { SOURCE_SIDECAR_SCHEMA, buildKnowledgeGraph, validateSourceSidecar } from './knowledge-graph.mjs'
10
+
11
+ // Document enrollment writes the sidecar the census demands of every
12
+ // non-Markdown document that has none: the smallest classification that lets
13
+ // `atelier graph` pass, never a judgement about the document. It reads the same
14
+ // census as the graph (scope, Git ignore rules and all), writes only where no
15
+ // sidecar exists, and never changes a source.
16
+
17
+ export const DEFAULT_ENROLLMENT_AUDIENCE = 'private'
18
+
19
+ const AUDIENCE_ORDER = ['private', 'sensitive', 'staff', 'operator', 'team', 'public']
20
+ const orderedAudiences = (audiences) => AUDIENCE_ORDER.filter((audience) => audiences.includes(audience))
21
+
22
+ // The audiences a repository's boundary policy lets a node carry, by the same
23
+ // three rules the boundary check applies to every node. Null when the policy
24
+ // does not cover the repository, which the boundary check reports on its own.
25
+ export function enrollableAudiences(repoPolicy) {
26
+ if (!repoPolicy || typeof repoPolicy !== 'object') return null
27
+ const allowed = Array.isArray(repoPolicy.allowedAudiences) ? repoPolicy.allowedAudiences : []
28
+ const forbidden = Array.isArray(repoPolicy.forbiddenAudiences) ? repoPolicy.forbiddenAudiences : []
29
+ return orderedAudiences(
30
+ allowed.filter((audience) => !forbidden.includes(audience) && !(PRIVATE_AUDIENCES.has(audience) && repoPolicy.kind !== 'private_domain')),
31
+ )
32
+ }
33
+
34
+ export function assertEnrollmentAudience(audience) {
35
+ if (typeof audience === 'string' && VALID_AUDIENCES.has(audience)) return audience
36
+ throw new AtelierDiagnosticError('enroll-audience-invalid', `--audience must be one of ${orderedAudiences([...VALID_AUDIENCES]).join(', ')}`, {
37
+ hint: `Omit --audience to enroll documents as ${DEFAULT_ENROLLMENT_AUDIENCE}.`,
38
+ })
39
+ }
40
+
41
+ // Refuses, before anything is written, an audience the boundary policy would
42
+ // reject in any repository that has a document to enroll. A policy in
43
+ // legacy-warning mode only warns about placement, and so does not refuse here.
44
+ export function assertAudienceAllowed({ policy, repoNames, audience }) {
45
+ if (policy?.mode === 'legacy-warning') return
46
+ for (const repoName of repoNames) {
47
+ const repoPolicy = policy?.repos && Object.hasOwn(policy.repos, repoName) ? policy.repos[repoName] : null
48
+ const allowed = enrollableAudiences(repoPolicy)
49
+ if (!allowed || allowed.includes(audience)) continue
50
+ // The message names the allowed audiences itself: adopt prints only the
51
+ // message of a refusal, not its hint.
52
+ const kind = typeof repoPolicy.kind === 'string' ? `${repoPolicy.kind.replaceAll('_', ' ')} ` : ''
53
+ const allows = allowed.length ? `it allows ${allowed.join(', ')}` : 'it allows no audience there'
54
+ throw new AtelierDiagnosticError(
55
+ 'enroll-audience-not-allowed',
56
+ `the boundary policy does not allow audience ${audience} in ${kind}repository ${repoName}; ${allows}`,
57
+ {
58
+ hint: allowed.length
59
+ ? 'Pass --audience with one of the audiences it allows.'
60
+ : 'Review boundary-policy.v1.json before enrolling documents in that repository.',
61
+ },
62
+ )
63
+ }
64
+ }
65
+
66
+ const idSegment = (value) =>
67
+ String(value ?? '')
68
+ .toLowerCase()
69
+ .replace(/[^a-z0-9]+/g, '-')
70
+ .replace(/^-+|-+$/g, '')
71
+
72
+ const pathSegment = (rel) =>
73
+ String(rel)
74
+ .toLowerCase()
75
+ .replace(/[^a-z0-9._/-]+/g, '-')
76
+ .replace(/-+([./])/g, '$1')
77
+ .replace(/([./])-+/g, '$1')
78
+ .replace(/-{2,}/g, '-')
79
+
80
+ const shortDigest = (value) => createHash('sha256').update(value).digest('hex').slice(0, 8)
81
+
82
+ // `<repo>:asset:<path>` keeps the source's folders and file name readable in
83
+ // the id. Two documents whose paths fold to one id (case, spaces) each get a
84
+ // digest of their exact path, as does one whose id is already taken, so the
85
+ // result does not depend on the order documents are found in.
86
+ function assignIds(wanted, taken) {
87
+ const bases = wanted.map((item) => `${idSegment(item.repo) || 'repo'}:asset:${pathSegment(item.path)}`)
88
+ const counts = new Map()
89
+ for (const base of bases) counts.set(base, (counts.get(base) ?? 0) + 1)
90
+ const used = new Set(taken)
91
+ return wanted.map((item, index) => {
92
+ const base = bases[index]
93
+ const id = counts.get(base) > 1 || used.has(base) ? `${base}-${shortDigest(`${item.repo}/${item.path}`)}` : base
94
+ if (used.has(id)) throw new Error('document enrollment could not derive a unique kg.id')
95
+ used.add(id)
96
+ return id
97
+ })
98
+ }
99
+
100
+ function sidecarDocument(node, id, audience) {
101
+ return {
102
+ schema: SOURCE_SIDECAR_SCHEMA,
103
+ asset: path.posix.basename(node.path),
104
+ title: node.title || path.posix.basename(node.path),
105
+ summary: '',
106
+ tags: [],
107
+ kg: {
108
+ id,
109
+ type: node.extension,
110
+ domain: node.domain,
111
+ lifecycle: node.lifecycle,
112
+ status: node.status,
113
+ audience,
114
+ relations: {},
115
+ },
116
+ }
117
+ }
118
+
119
+ // What enrollment would write, and what it leaves alone. `policy` defaults to
120
+ // the project's boundary policy; a missing or unreadable one is not consulted,
121
+ // since the graph itself does not need it.
122
+ export function planDocumentEnrollment(project, { audience = DEFAULT_ENROLLMENT_AUDIENCE, policy = undefined } = {}) {
123
+ assertEnrollmentAudience(audience)
124
+ const { options } = canonicalGraphOptions(project)
125
+ const result = buildKnowledgeGraph(options)
126
+ if (!result.workspaceGraph) {
127
+ throw new AtelierDiagnosticError('enroll-census-unavailable', 'the knowledge graph census could not run, so no document was enrolled', {
128
+ hint: 'Run atelier graph with the same --project path to see why, fix that, then retry.',
129
+ })
130
+ }
131
+ const roots = new Map(options.repoEntries.map((entry) => [entry.name, entry.path]))
132
+ const nodes = result.workspaceGraph.nodes
133
+ // Code-point order, so the report reads the same under every locale.
134
+ const byPath = (a, b) => (`${a.repo}/${a.path}` < `${b.repo}/${b.path}` ? -1 : 1)
135
+ const demanded = nodes.filter((node) => node.extension !== 'md' && !node.hasSidecar).sort(byPath)
136
+
137
+ const wanted = []
138
+ const skipped = []
139
+ for (const node of demanded) {
140
+ const sidecar = `${node.path}.kg.json`
141
+ const target = path.join(roots.get(node.repo), ...sidecar.split('/'))
142
+ const existing = fs.lstatSync(target, { throwIfNoEntry: false })
143
+ // A sidecar that exists but is not visible to the census is Git-ignored,
144
+ // or is a link or folder. Either way it is someone's, and stays as it is.
145
+ if (existing) {
146
+ skipped.push({ repo: node.repo, path: node.path, sidecar, reason: existing.isFile() ? 'sidecar-ignored' : 'sidecar-not-a-file' })
147
+ continue
148
+ }
149
+ wanted.push({ repo: node.repo, path: node.path, sidecar, target, node })
150
+ }
151
+
152
+ if (wanted.length) {
153
+ const loaded = policy === undefined ? loadBoundaryPolicy(project) : { ok: Boolean(policy), policy }
154
+ if (loaded.ok) assertAudienceAllowed({ policy: loaded.policy, repoNames: [...new Set(wanted.map((item) => item.repo))], audience })
155
+ }
156
+
157
+ const enrolling = new Set(wanted.map((item) => item.node))
158
+ const ids = assignIds(wanted, nodes.filter((node) => !enrolling.has(node)).map((node) => node.id))
159
+ const enroll = wanted.map((item, index) => {
160
+ const document = sidecarDocument(item.node, ids[index], audience)
161
+ const errors = validateSourceSidecar(document, document.asset)
162
+ if (errors.length) throw new Error(`document enrollment built an invalid sidecar: ${errors.join('; ')}`)
163
+ return { repo: item.repo, path: item.path, sidecar: item.sidecar, target: item.target, id: ids[index], document }
164
+ })
165
+ return { audience, enroll, skipped }
166
+ }
167
+
168
+ // Writes each planned sidecar with an exclusive create, so a file, link or
169
+ // folder that appeared since the plan is never replaced or followed; it is
170
+ // reported as skipped instead. Sources are never opened.
171
+ export function writeDocumentEnrollment(plan) {
172
+ const written = []
173
+ const skipped = [...plan.skipped]
174
+ for (const item of plan.enroll) {
175
+ try {
176
+ fs.writeFileSync(item.target, `${JSON.stringify(item.document, null, 2)}\n`, { flag: 'wx' })
177
+ written.push(item)
178
+ } catch (error) {
179
+ if (error?.code !== 'EEXIST') throw error
180
+ skipped.push({ repo: item.repo, path: item.path, sidecar: item.sidecar, reason: 'sidecar-exists' })
181
+ }
182
+ }
183
+ return { audience: plan.audience, written, skipped }
184
+ }
185
+
186
+ const SKIP_REASONS = {
187
+ 'sidecar-ignored': 'a sidecar exists but Git ignores it',
188
+ 'sidecar-not-a-file': 'a link or folder has the sidecar name',
189
+ 'sidecar-exists': 'a sidecar appeared while enrolling',
190
+ }
191
+
192
+ export function enrollmentReport({ written = [], skipped = [], audience, dryRun = false }) {
193
+ return {
194
+ ok: true,
195
+ command: 'enroll documents',
196
+ dryRun,
197
+ audience,
198
+ enrolled: written.map((item) => ({ repo: item.repo, path: item.path, sidecar: item.sidecar, id: item.id })),
199
+ skipped: skipped.map((item) => ({ repo: item.repo, path: item.path, sidecar: item.sidecar, reason: item.reason })),
200
+ }
201
+ }
202
+
203
+ function printReport(report) {
204
+ const count = report.enrolled.length
205
+ const noun = count === 1 ? 'document' : 'documents'
206
+ if (count === 0) console.log('No document needs a sidecar.')
207
+ else console.log(`${report.dryRun ? 'Would enroll' : 'Enrolled'} ${count} ${noun} with ${report.audience} sidecars:`)
208
+ for (const item of report.enrolled) console.log(` ${item.repo}/${item.sidecar}`)
209
+ if (report.skipped.length) {
210
+ console.log(`Left ${report.skipped.length} unchanged; the graph still needs a sidecar for each:`)
211
+ for (const item of report.skipped) console.log(` ${item.repo}/${item.path}: ${SKIP_REASONS[item.reason] ?? item.reason}`)
212
+ }
213
+ if (count && !report.dryRun) {
214
+ console.log('Next: review the sidecars (title, audience), then run atelier graph with the same --project path.')
215
+ }
216
+ }
217
+
218
+ const usage = () =>
219
+ new AtelierDiagnosticError('usage', 'enroll has one subcommand, documents', {
220
+ hint: 'Run atelier enroll documents [--audience private] [--dry-run] [--json] [--project ./atelier.project.json].',
221
+ })
222
+
223
+ export function parseEnrollArgs(argv = []) {
224
+ const { remaining } = parseProjectOptions(argv)
225
+ const args = { subcommand: null, audience: DEFAULT_ENROLLMENT_AUDIENCE, dryRun: false, json: false }
226
+ for (let index = 0; index < remaining.length; index += 1) {
227
+ const arg = remaining[index]
228
+ if (arg === '--dry-run') args.dryRun = true
229
+ else if (arg === '--json') args.json = true
230
+ else if (arg === '--audience') args.audience = remaining[++index]
231
+ else if (arg.startsWith('--audience=')) args.audience = arg.slice('--audience='.length)
232
+ else if (!arg.startsWith('-') && args.subcommand === null) args.subcommand = arg
233
+ else throw usage()
234
+ }
235
+ if (args.subcommand !== 'documents') throw usage()
236
+ assertEnrollmentAudience(args.audience)
237
+ return args
238
+ }
239
+
240
+ export function runEnrollCommand(argv = process.argv.slice(2)) {
241
+ const args = parseEnrollArgs(argv)
242
+ const project = commandProject({ argv })
243
+ const plan = planDocumentEnrollment(project, { audience: args.audience })
244
+ const outcome = args.dryRun ? { audience: plan.audience, written: plan.enroll, skipped: plan.skipped } : writeDocumentEnrollment(plan)
245
+ const report = enrollmentReport({ ...outcome, dryRun: args.dryRun })
246
+ if (args.json) console.log(JSON.stringify(report, null, 2))
247
+ else printReport(report)
248
+ }
@@ -19,6 +19,18 @@ export const VALID_AUDIENCES = PROJECTION_AUDIENCES
19
19
  export const VALID_RELATIONS = VALID_RELATION_TYPES
20
20
  export const parseFrontmatterYaml = parseYamlSubset
21
21
 
22
+ // setup.include and setup.exclude scope the census. Both are path patterns
23
+ // relative to the project config's folder, so each repository learns where its
24
+ // root sits relative to that folder.
25
+ function censusScope(project, repoPath) {
26
+ const setup = project.config?.setup
27
+ const include = typeof setup?.include === 'string' && setup.include.trim() ? setup.include : null
28
+ const exclude = typeof setup?.exclude === 'string' && setup.exclude.trim() ? setup.exclude : null
29
+ if (!include && !exclude) return null
30
+ const base = path.relative(project.configDir, repoPath).split(path.sep).join('/')
31
+ return { base, include, exclude }
32
+ }
33
+
22
34
  function canonicalInput(project) {
23
35
  const loadedAccess = loadRepoAccess(project)
24
36
  const managed = []
@@ -31,7 +43,8 @@ function canonicalInput(project) {
31
43
  missing.push(`configured repo is missing or unreadable: ${repo.name ?? '(unnamed)'}`)
32
44
  continue
33
45
  }
34
- managed.push({ name: repo.name, path: repo.path })
46
+ const scope = censusScope(project, repo.path)
47
+ managed.push({ name: repo.name, path: repo.path, ...(scope ? { censusScope: scope } : {}) })
35
48
  accessRepos[repo.name] = {
36
49
  readBoundary:
37
50
  loadedAccess.repos?.[repo.name]?.readBoundary ?? repo.readBoundary ?? loadedAccess.defaultReadBoundary ?? 'team',
@@ -211,6 +224,9 @@ export function runGraphCommand(argv = process.argv.slice(2)) {
211
224
  for (const warning of ignoredSidecarWarnings(project)) console.warn(`warning: ${warning.message}`)
212
225
  if (graph.errors.length) {
213
226
  console.error(graph.errors.join('\n'))
227
+ if (graph.errors.some((error) => error.includes(': non-Markdown source requires sidecar '))) {
228
+ console.error('Next: run atelier enroll documents with the same --project path to write private sidecars for documents that have none, review them, then rerun atelier graph.')
229
+ }
214
230
  process.exit(1)
215
231
  }
216
232
  if (check) {
@@ -4,6 +4,7 @@ import path from 'node:path'
4
4
  import { VALID_AUDIENCES } from '../projection/policy.mjs'
5
5
  import { generatedProjectionBasenames, generatedProjectionDirectoryBasenames } from '../project/file-class.mjs'
6
6
  import { gitIgnoreFilter } from '../project/git-ignore.mjs'
7
+ import { matchesPathPattern, normalizeRelPath } from '../project/path-match.mjs'
7
8
 
8
9
  export const KNOWLEDGE_GRAPH_SCHEMA = 'mnstry.knowledge-graph@v1'
9
10
  export const REPO_ACCESS_SCHEMA = 'mnstry.repo-access@v1'
@@ -339,14 +340,58 @@ export function listRepos(workspaceRoot) {
339
340
  .sort()
340
341
  }
341
342
 
343
+ // The project's census scope: `include` and `exclude` are path patterns in the
344
+ // kit's one dialect (src/project/path-match.mjs), relative to the project
345
+ // config's folder, read from the tracked config so every machine walks the
346
+ // same paths. `base` is the repository root relative to that folder. The
347
+ // returned predicate takes a repository-relative path and says whether it is
348
+ // outside the census; `directory` lets a folder that may still hold included
349
+ // files be walked. A path is excluded when it or a folder above it matches
350
+ // `exclude`; with `include`, a file is in the census only when it or a folder
351
+ // above it matches. Exclude wins. A `<file>.kg.json` sidecar belongs to its
352
+ // source and shares the source's scope. Null when the scope restricts nothing.
353
+ export function censusScopeFilter({ base = '', include = null, exclude = null } = {}) {
354
+ // A trailing slash names a folder, which a pattern without one matches too.
355
+ const pattern = (value) => (typeof value === 'string' && value.trim() ? normalizeRelPath(value.trim()).replace(/\/+$/, '') || null : null)
356
+ const includePattern = pattern(include)
357
+ const excludePattern = pattern(exclude)
358
+ if (!includePattern && !excludePattern) return null
359
+ const prefix = normalizeRelPath(base).replace(/\/+$/, '')
360
+ const lineage = (rel) => {
361
+ const parts = path.posix.normalize(prefix ? `${prefix}/${rel}` : rel).split('/')
362
+ return parts.map((_, index) => parts.slice(0, index + 1).join('/'))
363
+ }
364
+ // A folder may lead to an included file when it lies on the pattern's
365
+ // literal leading path, or below it. A slashless pattern matches at any
366
+ // depth, so every folder may.
367
+ const segments = includePattern?.includes('/') ? includePattern.split('/') : null
368
+ const globAt = segments ? segments.findIndex((part) => part.includes('*')) : -1
369
+ const literalLead = segments ? (globAt === -1 ? segments : segments.slice(0, globAt)) : null
370
+ const mayLeadToInclude = (paths) => {
371
+ if (!literalLead) return true
372
+ const parts = paths.at(-1).split('/')
373
+ const shared = Math.min(parts.length, literalLead.length)
374
+ return parts.slice(0, shared).every((part, index) => part.toLowerCase() === literalLead[index].toLowerCase())
375
+ }
376
+ return (rel, { directory = false } = {}) => {
377
+ const paths = lineage(!directory && rel.endsWith('.kg.json') ? rel.slice(0, -'.kg.json'.length) : rel)
378
+ if (excludePattern && paths.some((candidate) => matchesPathPattern(excludePattern, candidate))) return true
379
+ if (!includePattern || paths.some((candidate) => matchesPathPattern(includePattern, candidate))) return false
380
+ return !(directory && mayLeadToInclude(paths))
381
+ }
382
+ }
383
+
342
384
  // Git-ignored paths are machine-local. Counting them makes committed graph
343
385
  // artifacts differ per machine, which turns every sync tick into a conflict.
386
+ // `isIgnored(rel, { directory })` also carries the project's census scope when
387
+ // the builder composes one in (censusScopeFilter); a plain Git filter ignores
388
+ // the second argument.
344
389
  export function walkDocuments(dir, root, acc = [], isIgnored = gitIgnoreFilter(root)) {
345
390
  for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
346
391
  if (SKIP_DIRS.has(ent.name) || ent.name.startsWith('.')) continue
347
392
  const abs = path.join(dir, ent.name)
348
393
  const rel = relPath(root, abs)
349
- if (isIgnored(rel)) continue
394
+ if (isIgnored(rel, { directory: ent.isDirectory() })) continue
350
395
  if (ent.isDirectory()) {
351
396
  walkDocuments(abs, root, acc, isIgnored)
352
397
  } else if (ent.isFile()) {
@@ -384,7 +429,7 @@ export function ignoredSourceSidecars(repoName, repoRoot, isIgnored = gitIgnoreF
384
429
  const abs = path.join(dir, ent.name)
385
430
  const rel = relPath(repoRoot, abs)
386
431
  if (ent.isDirectory()) {
387
- if (!isIgnored(rel)) visit(abs)
432
+ if (!isIgnored(rel, { directory: true })) visit(abs)
388
433
  continue
389
434
  }
390
435
  if (!ent.isFile() || !ent.name.endsWith('.kg.json') || !isIgnored(rel)) continue
@@ -402,7 +447,7 @@ export function walkSidecars(dir, root, acc = [], isIgnored = gitIgnoreFilter(ro
402
447
  for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
403
448
  if (SKIP_DIRS.has(ent.name) || ent.name.startsWith('.')) continue
404
449
  const abs = path.join(dir, ent.name)
405
- if (isIgnored(relPath(root, abs))) continue
450
+ if (isIgnored(relPath(root, abs), { directory: ent.isDirectory() })) continue
406
451
  if (ent.isDirectory()) {
407
452
  walkSidecars(abs, root, acc, isIgnored)
408
453
  } else if (ent.isFile() && ent.name.endsWith('.kg.json')) {
@@ -898,7 +943,7 @@ const isMarkdownPath = (rel) => rel.toLowerCase().endsWith('.md')
898
943
  // a path is tested, not the path alone.
899
944
  function ignoredAtAnyDepth(isIgnored, rel) {
900
945
  const parts = rel.split('/')
901
- return parts.some((_, index) => isIgnored(parts.slice(0, index + 1).join('/')))
946
+ return parts.some((_, index) => isIgnored(parts.slice(0, index + 1).join('/'), { directory: index < parts.length - 1 }))
902
947
  }
903
948
 
904
949
  // True only for a regular file whose real location is exactly `rel` under the
@@ -979,7 +1024,7 @@ export function resolveWorkspaceLinks({ repos = [], isLinkTargetEligible = () =>
979
1024
  if (ent.name === '.git') continue
980
1025
  const abs = path.join(dir, ent.name)
981
1026
  const rel = relPath(owner.root, abs)
982
- if (isIgnored(rel)) continue
1027
+ if (isIgnored(rel, { directory: ent.isDirectory() })) continue
983
1028
  // A nested enrolled repository owns its own files.
984
1029
  if (ent.isDirectory()) {
985
1030
  if (!roots.has(abs)) visit(abs)
@@ -1362,7 +1407,9 @@ export function buildKnowledgeGraph({
1362
1407
  const resolvedWorkspaceRoot = path.resolve(workspaceRoot)
1363
1408
  const external = new Set(externalRepos)
1364
1409
  const discoveredEntries = repoEntries
1365
- ? repoEntries.map((entry) => ({ name: entry.name, path: path.resolve(entry.path) })).sort((a, b) => String(a.name).localeCompare(String(b.name)))
1410
+ ? repoEntries
1411
+ .map((entry) => ({ name: entry.name, path: path.resolve(entry.path), ...(entry.censusScope ? { censusScope: entry.censusScope } : {}) }))
1412
+ .sort((a, b) => String(a.name).localeCompare(String(b.name)))
1366
1413
  : (repoRoots ? repoRoots.map((repoRoot) => path.resolve(repoRoot)) : listRepos(resolvedWorkspaceRoot))
1367
1414
  .sort()
1368
1415
  .map((repoRoot) => ({ name: path.basename(repoRoot), path: repoRoot }))
@@ -1398,8 +1445,11 @@ export function buildKnowledgeGraph({
1398
1445
  for (const entry of roots) {
1399
1446
  const repoRoot = entry.path
1400
1447
  const repoName = entry.name
1401
- // One batched ignore lookup per repo, shared by every walk below.
1402
- const isIgnored = gitIgnoreFilter(repoRoot)
1448
+ // One batched ignore lookup per repo, shared by every walk below. A path
1449
+ // outside the project's census scope is treated exactly as an ignored one.
1450
+ const gitIgnored = gitIgnoreFilter(repoRoot)
1451
+ const outOfScope = censusScopeFilter(entry.censusScope ?? {})
1452
+ const isIgnored = outOfScope ? (rel, info) => gitIgnored(rel) || outOfScope(rel, info) : gitIgnored
1403
1453
  const files = walkDocuments(repoRoot, repoRoot, [], isIgnored)
1404
1454
  workspaceOrphanSidecars.push(...activeOrphanSidecars(repoName, repoRoot, files, isIgnored))
1405
1455
  workspaceIgnoredSidecars.push(...ignoredSourceSidecars(repoName, repoRoot, isIgnored))
@@ -0,0 +1,41 @@
1
+ import fs from 'node:fs'
2
+ import Ajv from 'ajv/dist/2020.js'
3
+ import addFormats from 'ajv-formats'
4
+ import { objectDigest, digest } from '../capabilities/files.mjs'
5
+ export const HARNESS_LIMITS = Object.freeze({ records: 256, bytes: 8 * 1024 * 1024, dependencyVerifications: 256, dependencyDepth: 12 })
6
+ export const harnessDigest = objectDigest
7
+ export const contentDigest = digest
8
+ export const harnessRef = record => ({ id: record.id, digest: objectDigest(record) })
9
+ export const EMPTY_HARNESS_HEAD = objectDigest([])
10
+ const ajv = new Ajv({ allErrors: true, strict: true }); addFormats(ajv)
11
+ const schemas = new Map(['harness', 'knowledge', 'build'].map(name => [name, JSON.parse(fs.readFileSync(new URL(`../../contracts/atelier-${name}.v1.schema.json`, import.meta.url)))]))
12
+ const validators = new Map()
13
+ export function validateHarnessDocument(value, profile, shape = 'record') {
14
+ const schema = schemas.get(profile), key = `${profile}/${shape}`
15
+ if (!schema || !Object.hasOwn(schema.$defs, shape)) return ['unknown harness profile or shape']
16
+ if (!validators.has(key)) validators.set(key, ajv.compile({ $schema: schema.$schema, $defs: schema.$defs, $ref: `#/$defs/${shape}` }))
17
+ const validate = validators.get(key)
18
+ return validate(value) ? [] : validate.errors.map(e => `${e.instancePath || '/'}: ${e.message}`)
19
+ }
20
+ export function assertHarness(value, profile, shape = 'record') {
21
+ const errors = validateHarnessDocument(value, profile, shape)
22
+ if (errors.length) throw new Error(`invalid ${profile} ${shape}: ${errors.slice(0, 5).join('; ')}`)
23
+ return value
24
+ }
25
+ // A private container may receive a public source. An export that includes that
26
+ // container's metadata must retain the more restrictive combined audience.
27
+ export function restrictiveAudience(...values) {
28
+ if (!values.length || values.some(v => !['public', 'team', 'operator', 'staff', 'private', 'sensitive'].includes(v))) throw new Error('unknown audience')
29
+ const restricted = [...new Set(values.filter(v => v !== 'public'))]
30
+ if (restricted.includes('sensitive')) return 'sensitive'
31
+ if (restricted.includes('private') || restricted.length > 1) return 'private'
32
+ return restricted[0] ?? 'public'
33
+ }
34
+ export function assertAudience(receiver, source) {
35
+ if (restrictiveAudience(receiver, source) !== receiver) throw new Error('audience exceeds receiving scope')
36
+ }
37
+ export function assertHandoff(value) {
38
+ assertHarness(value, 'harness', 'handoff')
39
+ if (contentDigest(value.payload) !== value.payloadDigest) throw new Error('handoff payload digest mismatch')
40
+ return value
41
+ }
@@ -0,0 +1,154 @@
1
+ import { inspectInquiry } from '../inquiry/index.mjs'
2
+ import { inspectKnowledge } from '../knowledge/ledger.mjs'
3
+ import { inspectBuild } from '../build/ledger.mjs'
4
+ import { assertHandoff, contentDigest, harnessDigest, harnessRef, restrictiveAudience, HARNESS_LIMITS } from './contracts.mjs'
5
+ import { canonical } from '../capabilities/files.mjs'
6
+ import { createHash } from 'node:crypto'
7
+
8
+ export function inspectHarness(profile, records) {
9
+ if (profile === 'inquiry') return inspectInquiry(records)
10
+ if (profile === 'knowledge') return inspectKnowledge(records)
11
+ if (profile === 'build') return inspectBuild(records)
12
+ throw new Error('unknown harness profile')
13
+ }
14
+ function context(dependencySnapshots = [], verificationLimit = HARNESS_LIMITS.dependencyVerifications) {
15
+ if (!Array.isArray(dependencySnapshots) || dependencySnapshots.length > 64) throw new Error('dependency snapshot ceiling exceeded')
16
+ if (!Number.isSafeInteger(verificationLimit) || verificationLimit < 1 || verificationLimit > HARNESS_LIMITS.dependencyVerifications) throw new Error('invalid dependency verification limit')
17
+ return { snapshots: dependencySnapshots, inspections: new Map(), verified: new Map(), verificationLimit, verificationCalls: 0, historyReplays: 0, exhausted: false }
18
+ }
19
+ function inspect(profile, records, ctx) {
20
+ if (!ctx.inspections.has(profile)) ctx.inspections.set(profile, new Map())
21
+ const cache = ctx.inspections.get(profile)
22
+ if (!cache.has(records)) { cache.set(records, inspectHarness(profile, records)); ctx.historyReplays++ }
23
+ return cache.get(records)
24
+ }
25
+ const work = ctx => ({ verificationCalls: ctx.verificationCalls, historyReplays: ctx.historyReplays, verificationLimit: ctx.verificationLimit, exhausted: ctx.exhausted })
26
+ const bounded = depth => { if (depth > HARNESS_LIMITS.dependencyDepth) throw new Error('harness dependency depth exceeded') }
27
+ function artifact(profile, records, subjectId, ctx, depth) {
28
+ bounded(depth)
29
+ const state = inspect(profile, records, ctx), subject = records.find(r => r.id === subjectId)
30
+ if (!subject || state.reconsider.some(r => r.id === subjectId)) throw new Error('handoff subject is missing or requires reconsideration')
31
+ let audience, context
32
+ if (profile === 'knowledge') {
33
+ if (subject.kind !== 'contribution' || !state.accepted.includes(subject.id)) throw new Error('knowledge handoff requires an accepted contribution')
34
+ if (knowledgeFreshness(records, ctx, depth, [subject.id]).reconsider.some(r => r.id === subject.id)) throw new Error('knowledge import requires current source snapshots')
35
+ const review = records.findLast(r => r.kind === 'review' && r.data.target.id === subject.id)
36
+ context = [state.domain, review, ...review.data.evaluations.map(pin => records.find(r => r.id === pin.id))]
37
+ audience = restrictiveAudience(subject.data.audience, state.domain.data.audience)
38
+ } else if (profile === 'inquiry') {
39
+ if (subject.kind !== 'decision' || subject.data.disposition !== 'accepted') throw new Error('inquiry handoff requires an accepted decision')
40
+ const assessment = records.find(r => r.id === subject.data.assessment.id)
41
+ context = [records.find(r => r.id === assessment.data.hypothesis.id), assessment]
42
+ audience = state.campaign.data.audience
43
+ } else {
44
+ if (subject.kind !== 'candidate' || state.candidate?.id !== subject.id || !readiness(records, ctx, depth).readyReported) throw new Error('build handoff requires current accepted candidate and fresh dependencies')
45
+ context = [state.establishment, state.decision, ...state.gates]
46
+ audience = state.establishment.data.audience
47
+ }
48
+ return { state, audience, subject, payload: JSON.stringify({ subject, context, assurance: 'caller-reported; acceptance-is-not-truth-or-authority' }) }
49
+ }
50
+ function create({ profile, repository, records, subjectId, target }, ctx, depth) {
51
+ const { state, audience, subject, payload } = artifact(profile, records, subjectId, ctx, depth)
52
+ if (profile !== 'inquiry' && state.establishment.data.repository !== repository) throw new Error('handoff repository differs from establishment')
53
+ const repositoryBinding = profile === 'inquiry' ? 'caller-declared' : 'establishment-record'
54
+ return assertHandoff({ schema: 'atelier-harness-handoff@v1', source: { repository, repositoryBinding, profile, run: records[0].id, historyDigest: state.head, subject: harnessRef(subject) }, target, audience, payload, payloadDigest: contentDigest(payload), assurance: 'caller-reported', authority: 'none' })
55
+ }
56
+ function verify({ handoff, records }, ctx, depth) {
57
+ bounded(depth)
58
+ if (ctx.verificationCalls >= ctx.verificationLimit) { ctx.exhausted = true; throw new Error('harness dependency verification budget exhausted') }
59
+ ctx.verificationCalls++
60
+ assertHandoff(handoff)
61
+ if (!ctx.verified.has(records)) ctx.verified.set(records, new Map())
62
+ const cache = ctx.verified.get(records), key = harnessDigest(handoff), prior = cache.get(key)
63
+ if (prior?.active) throw new Error('harness dependency cycle detected')
64
+ if (prior?.result) return prior.result
65
+ cache.set(key, { active: true })
66
+ try {
67
+ inspect(handoff.source.profile, records, ctx)
68
+ const hash = createHash('sha256').update('[')
69
+ let extendsIssuedHistory = false
70
+ for (let i = 0; i < records.length; i++) {
71
+ if (i) hash.update(',')
72
+ hash.update(canonical(records[i]))
73
+ if (`sha256:${hash.copy().update(']').digest('hex')}` === handoff.source.historyDigest) extendsIssuedHistory = true
74
+ }
75
+ if (!extendsIssuedHistory) throw new Error('handoff source history changed or diverged')
76
+ const current = create({ ...handoff.source, records, subjectId: handoff.source.subject.id, target: handoff.target }, ctx, depth)
77
+ const observedHead = current.source.historyDigest
78
+ current.source.historyDigest = handoff.source.historyDigest
79
+ if (harnessDigest(current) !== harnessDigest(handoff)) throw new Error('handoff is stale or differs from its source history')
80
+ const result = { current: true, historyDigest: observedHead, issuedHistoryDigest: handoff.source.historyDigest, sourceRepositoryBinding: current.source.repositoryBinding, sourceAuthenticity: 'unverified', authority: 'none' }
81
+ cache.set(key, { result })
82
+ return result
83
+ } finally {
84
+ if (cache.get(key)?.active) cache.delete(key)
85
+ }
86
+ }
87
+ function matching(h, snapshots) {
88
+ return snapshots.filter(s => s.repository === h.source.repository && s.profile === h.source.profile && s.records?.[0]?.id === h.source.run)
89
+ }
90
+ function knowledgeFreshness(records, ctx, depth, subjects = null) {
91
+ bounded(depth)
92
+ const state = inspect('knowledge', records, ctx), stale = new Map(state.reconsider.map(r => [r.id, r.reasons]))
93
+ const needed = subjects ? new Set(subjects) : null
94
+ if (needed) for (const record of [...records].reverse()) if (needed.has(record.id) && record.kind === 'contribution') for (const item of record.data.basedOn) needed.add(item.contribution.id)
95
+ for (const record of records) {
96
+ if (record.kind !== 'contribution' || record.data.origin.method !== 'exchange' || (needed && !needed.has(record.id))) continue
97
+ const h = record.data.origin.handoff, snapshots = matching(h, ctx.snapshots)
98
+ let reason
99
+ if (snapshots.length !== 1) reason = 'import-source-missing-or-ambiguous'
100
+ else try { verify({ handoff: h, records: snapshots[0].records }, ctx, depth + 1) } catch { reason = ctx.exhausted ? 'import-source-verification-budget-exhausted' : 'import-source-requires-reconsideration' }
101
+ if (reason) stale.set(record.id, [...(stale.get(record.id) ?? []), reason])
102
+ }
103
+ // References always point backwards, so one history-order pass propagates
104
+ // changes discovered in external inputs through local meaning and activation.
105
+ for (const record of records) {
106
+ const d = record.data
107
+ const refs = record.kind === 'contribution' ? [d.domain, ...d.basedOn.map(e => e.contribution)]
108
+ : record.kind === 'evaluation' ? [d.contribution]
109
+ : record.kind === 'relation' ? [d.domain, d.subject, d.object]
110
+ : record.kind === 'review' ? [d.target, ...d.evaluations]
111
+ : record.kind === 'activation' ? d.reviews : []
112
+ const reasons = refs.flatMap(pin => stale.get(pin.id) ?? [])
113
+ if (reasons.length) stale.set(record.id, [...new Set([...(stale.get(record.id) ?? []), ...reasons])])
114
+ }
115
+ return { historyDigest: state.head, reconsider: [...stale].map(([id, reasons]) => ({ id, reasons })), freshness: 'relative-to-explicit-snapshots', canonicalMutation: false }
116
+ }
117
+ function readiness(records, ctx, depth) {
118
+ bounded(depth)
119
+ const state = inspect('build', records, ctx), blockers = []
120
+ if (!state.candidate) blockers.push('candidate-missing')
121
+ if (!state.reportedAccepted) blockers.push('current-accepted-decision-missing')
122
+ for (const [id, a] of Object.entries(state.attempts)) if (a.candidate === state.candidate?.id && !['completed', 'failed', 'cancelled'].includes(a.state)) blockers.push(`attempt-unsettled:${id}`)
123
+ for (const dependency of state.establishment?.data.dependencies ?? []) {
124
+ const h = dependency.handoff
125
+ const snapshots = matching(h, ctx.snapshots)
126
+ if (snapshots.length !== 1) { blockers.push(`dependency-missing-or-ambiguous:${dependency.id}`); continue }
127
+ try { verify({ handoff: h, records: snapshots[0].records }, ctx, depth + 1) } catch { blockers.push(`${ctx.exhausted ? 'dependency-verification-budget-exhausted' : 'dependency-requires-reconsideration'}:${dependency.id}`) }
128
+ }
129
+ return { candidate: state.candidate, readyReported: blockers.length === 0, blockers, historyDigest: state.head,
130
+ dependencyFreshness: 'relative-to-explicit-snapshots; no-live-observation', authenticatedAcceptance: false, executionAuthorized: false }
131
+ }
132
+ export function createHarnessHandoff(options) { return create(options, context(options.dependencySnapshots, options.verificationLimit), 0) }
133
+ export function verifyHarnessHandoff(options) {
134
+ const ctx = context(options.dependencySnapshots, options.verificationLimit)
135
+ return { ...verify(options, ctx, 0), dependencyWork: work(ctx) }
136
+ }
137
+ export function buildReadiness(records, { dependencySnapshots, verificationLimit } = {}) {
138
+ const ctx = context(dependencySnapshots, verificationLimit)
139
+ return { ...readiness(records, ctx, 0), dependencyWork: work(ctx) }
140
+ }
141
+ export function reconcileKnowledge(records, { dependencySnapshots, verificationLimit } = {}) {
142
+ const ctx = context(dependencySnapshots, verificationLimit)
143
+ return { ...knowledgeFreshness(records, ctx, 0), dependencyWork: work(ctx) }
144
+ }
145
+
146
+ // A read-only projection that a Fabric adapter or another coordinator can read.
147
+ // It imports no private registry, creates no tasks and grants no dispatch power.
148
+ export function buildCoordinationProposal(records, options = {}) {
149
+ const state = inspectBuild(records), readiness = buildReadiness(records, options)
150
+ return { schema: 'atelier-build-coordination-proposal@v1', outcome: state.establishment?.data.purpose ?? null,
151
+ owner: state.establishment?.data.owner ?? null, candidate: state.candidate?.data ?? null,
152
+ dependencies: state.establishment?.data.dependencies ?? [], gates: state.gates.map(r => ({ id: r.data.gate, status: r.data.status, evidence: r.data.evidence, record: harnessRef(r) })),
153
+ ...readiness, reconsider: state.reconsider, acceptedByCoordinator: false }
154
+ }
@@ -0,0 +1,16 @@
1
+ import { assertDocument } from '../capabilities/package.mjs'
2
+ import { harnessRef } from './contracts.mjs'
3
+ import { inspectHarness } from './exchange.mjs'
4
+ export function prepareHarnessFeedback({ profile, records, subjectId, id, outcome, cause, evidenceDigest, at, by }) {
5
+ const state = inspectHarness(profile, records), subject = records.find(r => r.id === subjectId)
6
+ if (!subject) throw new Error('feedback subject missing')
7
+ const atSubject = inspectHarness(profile, records.slice(0, records.indexOf(subject) + 1))
8
+ const binding = (atSubject.domain ?? atSubject.establishment ?? atSubject.campaign)?.data.binding
9
+ if (!binding) throw new Error('harness establishment has no skill binding')
10
+ if (!['skill', 'host', 'tool', 'configuration', 'context', 'provider', 'unknown'].includes(cause)) throw new Error('unknown feedback cause')
11
+ const event = { schema: 'mnstry.atelier-capability-event@v1', id, kind: 'feedback', ...binding, observer: by, outcome,
12
+ cause: ['context', 'provider'].includes(cause) ? 'unknown' : cause, evidenceDigest, at }
13
+ assertDocument(event, 'event')
14
+ return { event, subject: harnessRef(subject), historyDigest: state.head, reportedCause: cause,
15
+ causeMapping: event.cause === cause ? 'exact' : 'unsupported-steward-cause-preserved-in-wrapper', recorded: false, authority: 'none' }
16
+ }