@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,436 @@
1
+ import { createHash } from 'node:crypto'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads'
5
+ import { PersonalWorkspaceRefusal, composePersonalWorkspace } from '@mnstry/atelier/personal-workspace'
6
+ import { ensureLocalState, resolveProjectConfig, validateProjectConfigDoc } from '@mnstry/atelier/project'
7
+ import { buildCanonicalGraph } from '../../graph/graph.mjs'
8
+ import { refuse } from '../../runtime/obsidian/errors.mjs'
9
+ import { OBSIDIAN_EXT_KEY } from './contracts.mjs'
10
+
11
+ // The one personal-workspace route of the Obsidian projection.
12
+ //
13
+ // A person's private home holds the authored manifest and overlay, and the
14
+ // generations the personal-workspace module materialized from them. A binding
15
+ // names one home and one generation, both explicitly: nothing here discovers
16
+ // either from the environment or the working directory. The module's
17
+ // `composePersonalWorkspace` is the only authority on whether that generation
18
+ // may be used.
19
+ //
20
+ // One graph. Every caller (the engine, view counts, source apply, the proposal
21
+ // adapter and the selection operation) builds through the pipeline's
22
+ // `buildGraph`, which builds a bound project here: the canonical graph of the
23
+ // generation's configuration, with the composition's own rule that no private
24
+ // note is a link target, and the caller's file cache. A plain build would
25
+ // differ wherever a shared note's link names a private note.
26
+ //
27
+ // Validity. A graph is returned only under a validity key the composition has
28
+ // confirmed: the key names the inputs a stat or a bounded read can see (see
29
+ // inputsDigest) and a digest of the built graph's facts. The inputs are read
30
+ // before composing, so a key is confirmed only when the composition accepted
31
+ // those inputs and composed a graph with exactly those facts, and a returned
32
+ // graph is always one the composition built too (a change and its exact undo
33
+ // during one composition is caught at the next full reconciliation). A key not yet confirmed is
34
+ // confirmed by composing: synchronously for a caller that builds once, and off
35
+ // the event loop, in a worker with a deadline, for the engine and the proposal
36
+ // adapter it runs (`validatePersonalWorkspace`). What no key names is asked of
37
+ // the composition at every full reconciliation.
38
+ //
39
+ // What a binding returns:
40
+ // project the generation's atelier.project.json, resolved, with the
41
+ // Obsidian settings member built in memory from the overlay's
42
+ // saved views (each a scope) plus an `everything` scope, and the
43
+ // frozen binding under one symbol key. No generation file is
44
+ // ever written.
45
+ // binding { personalHome, generationId, overlayRepoId }, frozen.
46
+ // preferences the overlay's preferences, read only. A theme is reported,
47
+ // never written: the vault's appearance is the person's.
48
+ // coverage passed through as the module returned it. Nothing here reads
49
+ // it: it describes what is not enforced, and is never permission.
50
+ //
51
+ // The project's own folder is the private home rather than the generation:
52
+ // the projection keeps its workspace pointer in `<folder>/.atelier-local/`,
53
+ // and a generation is a closed, verified inventory that any extra file
54
+ // invalidates. A home is outside every Git worktree, so its local state is
55
+ // ignored by construction.
56
+
57
+ const BINDING = Symbol('atelier.obsidian.personal-workspace-binding')
58
+ // What each binding was made with: the composition that admitted it, the build that serves its graph, and whether the
59
+ // composition can run in a worker (only the module's own can).
60
+ const ROUTES = new WeakMap()
61
+ // The last validity key the composition confirmed for each binding: { inputs, facts }.
62
+ const CONFIRMED = new WeakMap()
63
+ export const EVERYTHING_SCOPE_ID = 'everything'
64
+ const EXT_SCHEMA = 'atelier-obsidian-ext-settings/v1'
65
+ // The settings member of a bound project names its generation here. It survives any copy of the project (a structured
66
+ // clone, a JSON round trip) that drops the symbol-keyed binding, so such a copy is refused instead of being built as an
67
+ // ordinary project; and a rebound generation changes the member, which invalidates every view. An ordinary project's
68
+ // settings may not use this key.
69
+ export const PERSONAL_MEMBER_KEY = 'mnstry.atelier.personal-workspace'
70
+ // The Obsidian settings contract's limits: at most 256 scopes, one of them `everything`, and at most 256 members in a
71
+ // selector's union.
72
+ const MAX_SAVED_VIEWS = 255
73
+ const MAX_VIEW_REPOSITORIES = 256
74
+ // A build that found its validity key unconfirmed, for a caller that confirms it off the event loop and builds again.
75
+ export const PERSONAL_VALIDATION_PENDING = 'personal-validation-pending'
76
+ const WORKER_KIND = 'atelier-obsidian-personal-composition'
77
+
78
+ const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex')
79
+ const deepFreeze = (value) => { if (value && typeof value === 'object') { Object.values(value).forEach(deepFreeze); Object.freeze(value) } return value }
80
+ const sortKeys = (value) => (Array.isArray(value) ? value.map(sortKeys)
81
+ : value && typeof value === 'object' ? Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortKeys(value[key])])) : value)
82
+ const digestOf = (value) => sha256(JSON.stringify(sortKeys(value)))
83
+ const generationRoot = ({ personalHome, generationId }) => path.join(personalHome, 'generations', generationId)
84
+ const linkTargetRule = (overlayRepoId) => (node) => node.repo !== overlayRepoId
85
+
86
+ // The facts of a graph every caller reads: nodes, edges, embeds, assets, resolved links and every finding.
87
+ const graphFactsDigest = (graph) => digestOf([graph.nodes, graph.edges, graph.embeds ?? [], graph.assets ?? [], graph.links ?? [], graph.linkDiagnostics ?? [], graph.diagnostics ?? []])
88
+
89
+ // The personal-workspace module's read limits: an authored input, and a generation file.
90
+ const AUTHORED_LIMIT = 1024 * 1024
91
+ const GENERATION_LIMIT = 8 * 1024 * 1024
92
+ const NONBLOCK = fs.constants.O_NONBLOCK ?? 0
93
+
94
+ // One regular file, opened without following a link and without waiting on it (a FIFO or a device never blocks the
95
+ // caller), and read up to `limit`. Anything else refuses `code`, as the composition would.
96
+ function regularFileBytes(file, limit, code, { follow = false } = {}) {
97
+ let fd
98
+ try { fd = fs.openSync(file, fs.constants.O_RDONLY | (follow ? 0 : fs.constants.O_NOFOLLOW) | NONBLOCK) } catch (error) {
99
+ if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return null
100
+ throw new PersonalWorkspaceRefusal(code)
101
+ }
102
+ try {
103
+ const stat = fs.fstatSync(fd)
104
+ if (!stat.isFile() || stat.size > limit) throw new PersonalWorkspaceRefusal(code)
105
+ return fs.readFileSync(fd)
106
+ } finally { fs.closeSync(fd) }
107
+ }
108
+ const regularFileDigest = (file, limit, code) => { const bytes = regularFileBytes(file, limit, code); return bytes === null ? 'missing' : sha256(bytes) }
109
+ // A Git configuration file, which Git itself reads through a link (a managed dotfile): followed, but still only a
110
+ // regular file, bounded, and never waited on.
111
+ const configDigest = (file) => { try { const bytes = regularFileBytes(file, AUTHORED_LIMIT, 'malformed-input', { follow: true }); return bytes === null ? 'missing' : sha256(bytes) } catch { return 'not-regular' } }
112
+ // The configuration files of an enrolled repository: `.git/config` (through `.git` when it is a link to a directory),
113
+ // or, where `.git` is a file (a separate Git directory, a linked worktree, a submodule), the configuration of the
114
+ // directory it names and of its common directory. A root that cannot be looked at (a file, a link loop, no permission)
115
+ // is recorded as such, so the composition answers with its own code.
116
+ function repositoryConfigDigests(repoRoot) {
117
+ const dotGit = path.join(repoRoot, '.git')
118
+ let kind
119
+ try { kind = fs.lstatSync(dotGit, { throwIfNoEntry: false }) } catch (error) { return [`unreadable:${error.code ?? 'error'}`] }
120
+ if (kind === undefined) return ['missing']
121
+ if (kind.isDirectory()) return [configDigest(path.join(dotGit, 'config'))]
122
+ if (kind.isSymbolicLink()) {
123
+ try { if (fs.statSync(dotGit).isDirectory()) return ['linked', fs.realpathSync(dotGit), configDigest(path.join(dotGit, 'config'))] } catch (error) { return [`unreadable:${error.code ?? 'error'}`] }
124
+ }
125
+ let pointer
126
+ try { pointer = regularFileBytes(dotGit, AUTHORED_LIMIT, 'malformed-input', { follow: true }) } catch { return ['not-regular'] }
127
+ const named = /^gitdir:\s*(.+?)\s*$/m.exec(pointer?.toString('utf8') ?? '')
128
+ if (named === null) return ['unnamed']
129
+ const gitDir = path.resolve(repoRoot, named[1])
130
+ let commonDir = null
131
+ try { const common = regularFileBytes(path.join(gitDir, 'commondir'), AUTHORED_LIMIT, 'malformed-input', { follow: true }); if (common !== null) commonDir = path.resolve(gitDir, common.toString('utf8').trim()) } catch { commonDir = 'not-regular' }
132
+ return [gitDir, configDigest(path.join(gitDir, 'config')), commonDir, commonDir === null || commonDir === 'not-regular' ? null : configDigest(path.join(commonDir, 'config'))]
133
+ }
134
+
135
+ // Everything outside the graph that a composition's answer depends on and a stat or a bounded read can see: the authored
136
+ // inputs (regular files only), every file of the generation (an extra, missing or irregular one too), the private home's
137
+ // own state and any `.git` above it, each enrolled root's link status and Git configuration (its remote and its helpers),
138
+ // the user's Git configuration, and any ambient Git variable. A file that is not regular refuses here with the code the
139
+ // composition gives it, before anything waits on it.
140
+ function inputsDigest({ personalHome, generationId }) {
141
+ const stat = (file) => { try { const s = fs.lstatSync(file); return [s.mode, s.uid, s.ino, s.isSymbolicLink()] } catch (error) { return `missing:${error.code ?? 'error'}` } }
142
+ const authoredBytes = ['atelier.personal.json', 'atelier.overlay.json'].map((name) => regularFileBytes(path.join(personalHome, name), AUTHORED_LIMIT, 'malformed-input'))
143
+ const authored = ['atelier.personal.json', 'atelier.overlay.json'].map((name, index) => [stat(path.join(personalHome, name)), authoredBytes[index] === null ? 'missing' : sha256(authoredBytes[index])])
144
+ const generation = []
145
+ const walk = (directory, prefix) => {
146
+ let entries
147
+ try { entries = fs.readdirSync(directory, { withFileTypes: true }) } catch (error) { generation.push([prefix, `unreadable:${error.code ?? 'error'}`]); return }
148
+ for (const entry of entries.sort((left, right) => (left.name < right.name ? -1 : left.name > right.name ? 1 : 0))) {
149
+ const relative = prefix === '' ? entry.name : `${prefix}/${entry.name}`
150
+ const absolute = path.join(directory, entry.name)
151
+ if (entry.isDirectory()) { generation.push([relative, stat(absolute)]); walk(absolute, relative) } else if (entry.isFile()) generation.push([relative, regularFileDigest(absolute, GENERATION_LIMIT, 'generation-corrupt')])
152
+ else throw new PersonalWorkspaceRefusal('generation-corrupt')
153
+ }
154
+ }
155
+ const root = generationRoot({ personalHome, generationId })
156
+ walk(root, '')
157
+ let realHome
158
+ try { realHome = fs.realpathSync(personalHome) === personalHome } catch { realHome = false }
159
+ const ancestorsWithGit = []
160
+ for (let ancestor = personalHome; ; ancestor = path.dirname(ancestor)) {
161
+ if (fs.lstatSync(path.join(ancestor, '.git'), { throwIfNoEntry: false }) !== undefined) ancestorsWithGit.push(ancestor)
162
+ if (ancestor === path.dirname(ancestor)) break
163
+ }
164
+ let repos = []
165
+ // The bytes read above, never the file again.
166
+ try { repos = JSON.parse(authoredBytes[0]?.toString('utf8') ?? '{}').repos.filter((repo) => repo.enrolled).map((repo) => repo.root) } catch { repos = [] }
167
+ const enrolled = repos.map((repoRoot) => {
168
+ let real
169
+ try { real = fs.realpathSync(repoRoot) === repoRoot } catch { real = false }
170
+ return [repoRoot, stat(repoRoot), real, repositoryConfigDigests(repoRoot)]
171
+ })
172
+ const home = process.env.HOME
173
+ const userGit = typeof home === 'string' && path.isAbsolute(home)
174
+ ? [path.join(home, '.gitconfig'), path.join(process.env.XDG_CONFIG_HOME || path.join(home, '.config'), 'git', 'config')].map(configDigest)
175
+ : ['no-home']
176
+ return digestOf({
177
+ authored,
178
+ generation,
179
+ roots: [stat(personalHome), realHome, stat(path.join(personalHome, 'generations')), stat(path.join(personalHome, 'generations', '.git')), stat(root), ancestorsWithGit],
180
+ enrolled,
181
+ userGit,
182
+ ambientGit: Object.keys(process.env).filter((name) => /^GIT_/i.test(name)).sort(),
183
+ })
184
+ }
185
+
186
+ // One generation file, read without following a link, and checked against the
187
+ // digest the generation records for it (and compose verified a moment ago).
188
+ function generationFile(generation, final, relative) {
189
+ const listed = generation.files.find((file) => file.path === relative)
190
+ if (!listed) throw new PersonalWorkspaceRefusal('generation-corrupt')
191
+ let bytes
192
+ try {
193
+ const fd = fs.openSync(path.join(final, ...relative.split('/')), fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW)
194
+ try { bytes = fs.readFileSync(fd) } finally { fs.closeSync(fd) }
195
+ } catch { throw new PersonalWorkspaceRefusal('generation-corrupt') }
196
+ if (sha256(bytes) !== listed.sha256) throw new PersonalWorkspaceRefusal('generation-corrupt')
197
+ return bytes
198
+ }
199
+
200
+ function selectorFor(repoIds) {
201
+ const repos = [...new Set(repoIds)]
202
+ if (repos.length === 0) return { ids: [] }
203
+ if (repos.length === 1) return { repo: repos[0] }
204
+ return { union: repos.map((repo) => ({ repo })) }
205
+ }
206
+
207
+ // The Obsidian settings member of a personal binding: one scoped view per saved
208
+ // selection of repositories, in the overlay's order, and one full view of the
209
+ // whole composed graph. A preferred view that names a saved one is the default.
210
+ // Refused, typed, where the overlay allows more than a projection can declare.
211
+ export function personalWorkspaceScopes(overlay, generationId) {
212
+ if (overlay.views.some((view) => view.id === EVERYTHING_SCOPE_ID)) {
213
+ refuse('personal-view-id-reserved', `a saved view may not be named "${EVERYTHING_SCOPE_ID}"; that view is the whole personal workspace`)
214
+ }
215
+ if (overlay.views.length > MAX_SAVED_VIEWS) refuse('personal-views-exceed-settings-limit', `a personal workspace can show at most ${MAX_SAVED_VIEWS} saved views; nothing is bound`, { limit: MAX_SAVED_VIEWS, kind: 'views' })
216
+ if (overlay.views.some((view) => new Set(view.repoIds).size > MAX_VIEW_REPOSITORIES)) {
217
+ refuse('personal-views-exceed-settings-limit', `a saved view can name at most ${MAX_VIEW_REPOSITORIES} repositories; nothing is bound`, { limit: MAX_VIEW_REPOSITORIES, kind: 'repositories' })
218
+ }
219
+ const scopes = [
220
+ ...overlay.views.map((view) => ({ scopeId: view.id, mode: 'scoped', selector: selectorFor(view.repoIds) })),
221
+ { scopeId: EVERYTHING_SCOPE_ID, mode: 'full', selector: { all: true } },
222
+ ]
223
+ const preferred = overlay.preferences?.defaultView
224
+ const defaultScopeId = typeof preferred === 'string' && overlay.views.some((view) => view.id === preferred) ? preferred : EVERYTHING_SCOPE_ID
225
+ return { schema: EXT_SCHEMA, enabled: true, defaultScopeId, scopes, ext: { [PERSONAL_MEMBER_KEY]: { generationId } } }
226
+ }
227
+
228
+ // The bound project, from a composition the caller just made: `composed` carries the generation's record, the
229
+ // preferences and the coverage, and the validity key that composition confirmed.
230
+ function assemble({ personalHome, generationId }, composed, route) {
231
+ const final = generationRoot({ personalHome, generationId })
232
+ const projectBytes = generationFile(composed.generation, final, 'atelier.project.json')
233
+ const inputs = JSON.parse(generationFile(composed.generation, final, 'inputs.json').toString('utf8'))
234
+ const configPath = path.join(final, 'atelier.project.json')
235
+ const resolved = resolveProjectConfig({ argv: [`--project-config=${configPath}`], cwd: final, env: { PATH: process.env.PATH }, writeLocalState: false })
236
+ // The same pinned resolution compose made; anything else means the generation changed between the two reads.
237
+ if (JSON.stringify(resolved.config) !== JSON.stringify(JSON.parse(projectBytes.toString('utf8')))) throw new PersonalWorkspaceRefusal('generation-corrupt')
238
+ if (validateProjectConfigDoc(resolved.config).length > 0) throw new PersonalWorkspaceRefusal('generation-corrupt')
239
+ if (resolved.localOverlay.paths.length > 0 || resolved.repos.some((repo) => repo.pathSource !== 'tracked-config')) throw new PersonalWorkspaceRefusal('ambient-overlay-present')
240
+ const overlayRepoId = `personal-${inputs.manifest.workspaceId}`
241
+ const overlayRepo = resolved.repos.find((repo) => repo.name === overlayRepoId)
242
+ if (!overlayRepo || overlayRepo.path !== path.join(final, 'overlay')) throw new PersonalWorkspaceRefusal('generation-corrupt')
243
+
244
+ const binding = Object.freeze({ personalHome, generationId, overlayRepoId })
245
+ ROUTES.set(binding, route)
246
+ CONFIRMED.set(binding, composed.key)
247
+ const member = deepFreeze(personalWorkspaceScopes(inputs.overlay, generationId))
248
+ const project = {
249
+ ...resolved,
250
+ config: { ...resolved.config, ext: { ...(resolved.config.ext ?? {}), [OBSIDIAN_EXT_KEY]: member } },
251
+ configDir: personalHome,
252
+ [BINDING]: binding,
253
+ }
254
+ project.localState = ensureLocalState(project, { write: false, env: { PATH: process.env.PATH } })
255
+ return Object.freeze({ project, binding, preferences: deepFreeze(structuredClone(composed.preferences ?? {})), coverage: composed.coverage })
256
+ }
257
+
258
+ // A composition in this thread, as the summary a worker sends back.
259
+ // The inputs are digested BEFORE composing: a single change that lands while the composition runs then either is seen by
260
+ // it, or leaves the confirmed key behind, so the next build composes again. A change and its exact undo within one
261
+ // composition (A, then B, then A again) is not seen this way: it is caught at the next full reconciliation.
262
+ function composeHere(compose, binding) {
263
+ const inputs = inputsDigest(binding)
264
+ const composed = compose({ personalHome: binding.personalHome, generationId: binding.generationId })
265
+ return { generation: composed.generation, preferences: composed.preferences, coverage: composed.coverage, key: { inputs, facts: graphFactsDigest(composed.graph) } }
266
+ }
267
+
268
+ // A composition in a worker thread, so the event loop of the caller stays free while it runs. Resolves to the summary, or
269
+ // to `{ code }` for a refusal; a worker that cannot run answers `personal-composition-unavailable`.
270
+ // A worker that does not answer within the deadline is terminated and let go of, and the composition refuses
271
+ // `personal-composition-unavailable`: the tick does not wait on it. A worker blocked in a system call (a FIFO opened
272
+ // for reading, say) cannot be stopped until that call returns: until then it keeps the process from exiting, and each
273
+ // such expiry leaves one thread behind. A service stopped in that state needs a kill.
274
+ export const WORKER_DEADLINE_MS = 120 * 1000
275
+ function composeInWorker({ personalHome, generationId }, { deadlineMs = WORKER_DEADLINE_MS, stallMs = 0 } = {}) {
276
+ return new Promise((resolve) => {
277
+ let settled = false
278
+ let worker = null
279
+ let timer = null
280
+ const settle = (value) => {
281
+ if (settled) return
282
+ settled = true
283
+ clearTimeout(timer)
284
+ if (value.code === 'personal-composition-unavailable' && worker !== null) { worker.unref(); worker.terminate().catch(() => {}) }
285
+ resolve(value)
286
+ }
287
+ try { worker = new Worker(new URL(import.meta.url), { workerData: { kind: WORKER_KIND, personalHome, generationId, stallMs } }) } catch { settle({ code: 'personal-composition-unavailable' }); return }
288
+ timer = setTimeout(() => settle({ code: 'personal-composition-unavailable' }), deadlineMs)
289
+ timer.unref?.()
290
+ worker.once('message', settle)
291
+ worker.once('error', () => settle({ code: 'personal-composition-unavailable' }))
292
+ worker.once('exit', () => settle({ code: 'personal-composition-unavailable' }))
293
+ })
294
+ }
295
+
296
+ // Test seam only: `compose` replaces the module's composePersonalWorkspace for the binding and every validation of
297
+ // it, `build` the canonical build that serves its graph, and `worker` the worker's deadline (and a stall, so a worker that
298
+ // never answers can be shown). Production code uses bindPersonalWorkspace.
299
+ export function createPersonalWorkspaceBinderForOracleTests({ compose = composePersonalWorkspace, build = null, worker = {} } = {}) {
300
+ const route = { compose, offThread: compose === composePersonalWorkspace, build, worker }
301
+ const bind = ({ personalHome, generationId } = {}) => {
302
+ // The validity authority first: a refusal propagates as the module's own typed refusal, and nothing is read or
303
+ // written after it.
304
+ const composed = composeHere(compose, { personalHome, generationId })
305
+ return assemble({ personalHome, generationId }, composed, route)
306
+ }
307
+ // The same binding, with the composition off the event loop.
308
+ bind.offThread = async ({ personalHome, generationId } = {}) => {
309
+ const composed = route.offThread ? await composeInWorker({ personalHome, generationId }, route.worker) : composeHere(compose, { personalHome, generationId })
310
+ if (typeof composed.code === 'string') {
311
+ if (composed.code === 'personal-composition-unavailable') refuse(composed.code, 'the personal workspace could not be composed off the event loop; nothing is bound')
312
+ throw new PersonalWorkspaceRefusal(composed.code)
313
+ }
314
+ return assemble({ personalHome, generationId }, composed, route)
315
+ }
316
+ return bind
317
+ }
318
+
319
+ export const bindPersonalWorkspace = createPersonalWorkspaceBinderForOracleTests()
320
+
321
+ // A refusal of the personal-workspace module becomes the maintenance refusal with the same code, as at every build, so
322
+ // the engine and every command report it typed and no view stays current.
323
+ const asMaintenanceRefusal = (error, message) => {
324
+ if (error instanceof PersonalWorkspaceRefusal) refuse(error.code, message, { source: 'personal-workspace' })
325
+ throw error
326
+ }
327
+
328
+ // The project of a binding, for a loader that may block (a command run once).
329
+ export function loadBoundProject({ personalHome, generationId } = {}, { bind = bindPersonalWorkspace } = {}) {
330
+ try { return bind({ personalHome, generationId }).project } catch (error) { return asMaintenanceRefusal(error, 'the personal workspace refused this generation; nothing is loaded from it') }
331
+ }
332
+
333
+ // The project of a binding, for a loader on a service's event loop: the composition runs in a worker. The engine
334
+ // awaits its loader, so this is the loader a service uses.
335
+ export async function loadBoundProjectOffThread({ personalHome, generationId } = {}, { bind = bindPersonalWorkspace } = {}) {
336
+ try { return (await bind.offThread({ personalHome, generationId })).project } catch (error) { return asMaintenanceRefusal(error, 'the personal workspace refused this generation; nothing is loaded from it') }
337
+ }
338
+
339
+ // The binding a project carries, or null for every other project.
340
+ export function personalWorkspaceBindingOf(project) {
341
+ return project !== null && typeof project === 'object' && Object.hasOwn(project, BINDING) ? project[BINDING] : null
342
+ }
343
+
344
+ // The graph of a bound project, or null when the project carries no binding. `fileCache` and `observedDigest` are the
345
+ // caller's, as for any build. With `defer`, a key the composition has not confirmed is not confirmed here: the build
346
+ // refuses `personal-validation-pending`, and the caller confirms it off the event loop (validatePersonalWorkspace) and
347
+ // builds again.
348
+ export function personalWorkspaceGraph(project, { fileCache = null, observedDigest = null, defer = false } = {}) {
349
+ const binding = personalWorkspaceBindingOf(project)
350
+ if (binding === null) {
351
+ if (project?.config?.ext?.[OBSIDIAN_EXT_KEY]?.ext?.[PERSONAL_MEMBER_KEY] !== undefined) {
352
+ refuse('personal-binding-lost', `the project's Obsidian settings use the reserved key ext["${PERSONAL_MEMBER_KEY}"], which only a personal-workspace binding sets, and the project carries no binding; nothing is prepared from it`)
353
+ }
354
+ return null
355
+ }
356
+ const route = ROUTES.get(binding)
357
+ if (route === undefined) refuse('personal-binding-unrecognized', 'the project carries a personal-workspace binding this release did not make; nothing is prepared from it')
358
+ if (project.configPath !== path.join(generationRoot(binding), 'atelier.project.json')) refuse('personal-binding-mismatch', 'the project is not the configuration of the generation it is bound to; nothing is prepared from it')
359
+ const pending = () => refuse(PERSONAL_VALIDATION_PENDING, 'the personal workspace has changed since it was last composed; it is composed again before a graph is used', { source: 'personal-workspace-pending' })
360
+ const confirmed = CONFIRMED.get(binding)
361
+ // Inputs that changed are composed before anything is built from them.
362
+ let inputs
363
+ try { inputs = inputsDigest(binding) } catch (error) { CONFIRMED.delete(binding); asMaintenanceRefusal(error, 'the personal workspace refused this generation; nothing is prepared from it') }
364
+ if (confirmed?.inputs !== inputs) {
365
+ if (defer) pending()
366
+ confirmHere(route, binding)
367
+ }
368
+ const build = route.build ?? ((target, options) => buildCanonicalGraph(target, { ...options, isLinkTargetEligible: linkTargetRule(binding.overlayRepoId) }))
369
+ const graph = build(project, { fileCache, observedDigest })
370
+ if (CONFIRMED.get(binding)?.facts === graphFactsDigest(graph)) return graph
371
+ if (defer) pending()
372
+ confirmHere(route, binding)
373
+ if (CONFIRMED.get(binding)?.facts === graphFactsDigest(graph)) return graph
374
+ // Composed just now, and still another graph: the sources moved between the two reads, or the build is not the
375
+ // composition's. Nothing is prepared from it.
376
+ return refuse('personal-graph-unconfirmed', 'the graph built for the personal workspace is not the one its composition built; nothing is prepared from it', { source: 'personal-workspace' })
377
+ }
378
+
379
+ function confirmHere(route, binding) {
380
+ try { CONFIRMED.set(binding, composeHere(route.compose, binding).key) } catch (error) { CONFIRMED.delete(binding); asMaintenanceRefusal(error, 'the personal workspace refused this generation; nothing is prepared from it') }
381
+ }
382
+
383
+ // Asks the composition again, off the event loop when it can: `{ ok: true, changed }` when it accepts the generation
384
+ // (`changed` when its key differs from the one last confirmed), `{ ok: false, code }` when it refuses. Null for a project
385
+ // without a binding. The engine asks this before a build that found its key unconfirmed, and at every full
386
+ // reconciliation of a bound project.
387
+ export async function validatePersonalWorkspace(project) {
388
+ const binding = personalWorkspaceBindingOf(project)
389
+ if (binding === null) return null
390
+ const route = ROUTES.get(binding)
391
+ if (route === undefined) return { ok: false, code: 'personal-binding-unrecognized' }
392
+ let composed
393
+ if (route.offThread) composed = await composeInWorker(binding, route.worker)
394
+ else {
395
+ try { composed = composeHere(route.compose, binding) } catch (error) { if (!(error instanceof PersonalWorkspaceRefusal)) throw error; composed = { code: error.code } }
396
+ }
397
+ if (typeof composed.code === 'string') { CONFIRMED.delete(binding); return { ok: false, code: composed.code } }
398
+ const previous = CONFIRMED.get(binding)
399
+ CONFIRMED.set(binding, composed.key)
400
+ return { ok: true, changed: previous?.inputs !== composed.key.inputs || previous?.facts !== composed.key.facts }
401
+ }
402
+
403
+ // The authored and generation files whose change means the binding must be asked again: the manifest, the overlay and
404
+ // the bound generation's record. Observed as configuration by the engine. Empty for any other project.
405
+ export function personalWorkspaceInputs(project) {
406
+ const binding = personalWorkspaceBindingOf(project)
407
+ if (binding === null) return []
408
+ return [path.join(binding.personalHome, 'atelier.personal.json'), path.join(binding.personalHome, 'atelier.overlay.json'), path.join(generationRoot(binding), 'generation.json')]
409
+ }
410
+
411
+ // The repository of a bound project's private notes, or null. A vault edit to one of them is never applied into a
412
+ // module-owned generation, and is not proposed yet: it stays held in the vault.
413
+ export function personalOverlayRepoOf(project) {
414
+ return personalWorkspaceBindingOf(project)?.overlayRepoId ?? null
415
+ }
416
+
417
+ // Settings that a personal binding takes from the overlay or does not have yet. A saved view is authored in the
418
+ // overlay, so `view add` would write a module-owned generation file; who may see and where vaults live wait for a
419
+ // stable per-person projection configuration. Refused before anything is resolved or written.
420
+ export function refuseOnPersonalBinding(project, operation) {
421
+ if (personalWorkspaceBindingOf(project) === null) return
422
+ if (operation === 'view add') refuse('views-from-personal-overlay', 'the views of a personal workspace come from its overlay; nothing was written', { operation })
423
+ refuse('personal-binding-decision-unavailable', `${operation} is not available for a personal workspace yet; nothing was written`, { operation })
424
+ }
425
+
426
+ // The worker side of composeInWorker: one composition, its summary, and exit.
427
+ if (!isMainThread && workerData?.kind === WORKER_KIND) {
428
+ const binding = { personalHome: workerData.personalHome, generationId: workerData.generationId }
429
+ // A worker that never answers, for the deadline test only: blocked as a system call would block it.
430
+ if (workerData.stallMs > 0) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, workerData.stallMs)
431
+ try {
432
+ parentPort.postMessage(composeHere(composePersonalWorkspace, binding))
433
+ } catch (error) {
434
+ parentPort.postMessage({ code: error instanceof PersonalWorkspaceRefusal ? error.code : 'personal-composition-unavailable' })
435
+ }
436
+ }
@@ -0,0 +1,50 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { refuse, sha256Digest } from '../materialize/byte-lens.mjs'
5
+ import { PLUGIN_DATA_FILE, PLUGIN_DATA_MODE, PLUGIN_DIRECTORY, PLUGIN_ID, PLUGIN_MINIMUM_APP_VERSION, PLUGIN_SOURCE_FILES, PLUGIN_SOURCE_MODE, pluginDataBytes } from './channel.mjs'
6
+
7
+ // The files of Atelier's plugin as they go into one vault: the three shipped
8
+ // source files, byte for byte, and that vault's data file. Pure apart from
9
+ // reading the shipped source once; nothing here writes anywhere.
10
+
11
+ export const PLUGIN_SOURCE_ROOT = fileURLToPath(new URL('../../../../plugins/obsidian/', import.meta.url))
12
+
13
+ export { PLUGIN_DATA_MODE, PLUGIN_SOURCE_MODE }
14
+
15
+ let cached = null
16
+
17
+ // { version, files: [{ name, bytes }] } of the shipped plugin. The manifest
18
+ // must name this plugin, the protocol's app floor and a desktop-only plugin.
19
+ export function readPluginSource({ root = PLUGIN_SOURCE_ROOT } = {}) {
20
+ if (root === PLUGIN_SOURCE_ROOT && cached) return cached
21
+ const files = PLUGIN_SOURCE_FILES.map((name) => ({ name, bytes: fs.readFileSync(path.join(root, name)) }))
22
+ let manifest
23
+ try { manifest = JSON.parse(files.find((file) => file.name === 'manifest.json').bytes.toString('utf8')) } catch { refuse('plugin-source-invalid', 'the shipped plugin manifest is not JSON') }
24
+ if (manifest?.id !== PLUGIN_ID || manifest.minAppVersion !== PLUGIN_MINIMUM_APP_VERSION || manifest.isDesktopOnly !== true || typeof manifest.version !== 'string' || manifest.version === '') {
25
+ refuse('plugin-source-invalid', 'the shipped plugin manifest does not name this plugin, its app floor and a desktop-only plugin')
26
+ }
27
+ const source = Object.freeze({ version: manifest.version, files: Object.freeze(files) })
28
+ if (root === PLUGIN_SOURCE_ROOT) cached = source
29
+ return source
30
+ }
31
+
32
+ // Prepared files of kind `plugin` for one vault, and the record that pins
33
+ // their digests in the generation manifest. `channel` is where the service of
34
+ // the workspace listens; `bearer` is this vault's. With `onlyIfPresent` every
35
+ // file is kept current where it exists and never created: the person turned
36
+ // the plugin off, and a folder they removed stays removed.
37
+ export function preparePluginFiles({ channel, scopeId, bearer, source = readPluginSource(), onlyIfPresent = false }) {
38
+ const presence = onlyIfPresent ? { onlyIfPresent: true } : {}
39
+ const files = [
40
+ ...source.files.map(({ name, bytes }) => ({ path: `${PLUGIN_DIRECTORY}/${name}`, kind: 'plugin', bytes, digest: sha256Digest(bytes), mode: PLUGIN_SOURCE_MODE, ...presence })),
41
+ (() => {
42
+ const bytes = pluginDataBytes({ host: channel?.host, port: channel?.port, scopeId, bearer })
43
+ return { path: `${PLUGIN_DIRECTORY}/${PLUGIN_DATA_FILE}`, kind: 'plugin', bytes, digest: sha256Digest(bytes), mode: PLUGIN_DATA_MODE, ...presence }
44
+ })(),
45
+ ]
46
+ return {
47
+ files,
48
+ ownership: { id: PLUGIN_ID, version: source.version, directory: PLUGIN_DIRECTORY, files: files.map(({ path, digest, bytes }) => ({ path, digest, byteLength: bytes.length })) },
49
+ }
50
+ }