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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (531) hide show
  1. package/CHANGELOG.md +894 -0
  2. package/README.md +159 -6
  3. package/contracts/atelier-adoption-policy.v2.schema.json +55 -0
  4. package/contracts/atelier-architecture.v1.schema.json +258 -0
  5. package/contracts/atelier-boundary-delegations.v1.schema.json +161 -0
  6. package/contracts/atelier-boundary-owner-keys.v1.schema.json +98 -0
  7. package/contracts/atelier-build.v1.schema.json +1076 -0
  8. package/contracts/atelier-capability.v1.schema.json +1252 -0
  9. package/contracts/atelier-coordination.v1.schema.json +1050 -0
  10. package/contracts/atelier-evidence-navigation.v1.schema.json +603 -0
  11. package/contracts/atelier-harness.v1.schema.json +234 -0
  12. package/contracts/atelier-ingestion.v1.schema.json +806 -0
  13. package/contracts/atelier-inquiry.v1.schema.json +1772 -0
  14. package/contracts/atelier-instruction-adoption.v1.schema.json +490 -0
  15. package/contracts/atelier-interaction.v1.schema.json +553 -0
  16. package/contracts/atelier-judgment.v1.schema.json +690 -0
  17. package/contracts/atelier-knowledge.v1.schema.json +1512 -0
  18. package/contracts/atelier-learning.v1.schema.json +1095 -0
  19. package/contracts/atelier-migration.v3.schema.json +64 -0
  20. package/contracts/atelier-personal-workspace-manifest.v1.schema.json +87 -0
  21. package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
  22. package/contracts/atelier-template-adoption.v1.schema.json +223 -0
  23. package/contracts/atelier-template-profile.v1.schema.json +1168 -0
  24. package/contracts/atelier-trackable.v1.schema.json +781 -0
  25. package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
  26. package/docs/architecture.md +89 -0
  27. package/docs/assurance-controls.md +2 -2
  28. package/docs/blocks/capability-stewardship.md +17 -0
  29. package/docs/blocks/claims.md +16 -3
  30. package/docs/blocks/inquiry-harnesses.md +19 -0
  31. package/docs/blocks/learning-harnesses.md +17 -0
  32. package/docs/capability-stewardship.md +267 -0
  33. package/docs/coordination.md +51 -0
  34. package/docs/decision-practices.md +111 -0
  35. package/docs/decisions.md +29 -0
  36. package/docs/evidence-navigation.md +237 -0
  37. package/docs/exact-upgrades.md +3 -2
  38. package/docs/ingestion.md +181 -0
  39. package/docs/inquiry-harnesses.md +210 -0
  40. package/docs/install.md +80 -3
  41. package/docs/intake-and-guides.md +5 -0
  42. package/docs/integration-contract-decisions.md +24 -2
  43. package/docs/interaction-and-reflection.md +72 -0
  44. package/docs/knowledge-graph.md +20 -0
  45. package/docs/knowledge-health/api.md +30 -0
  46. package/docs/knowledge-health/assessment/api.md +96 -0
  47. package/docs/knowledge-health/assessment/capabilities.md +40 -0
  48. package/docs/knowledge-health/assessment/evaluation.md +27 -0
  49. package/docs/knowledge-health/assessment/facilitator.md +30 -0
  50. package/docs/knowledge-health/assessment/quickstart.md +61 -0
  51. package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
  52. package/docs/knowledge-health/devday/facilitator.md +16 -0
  53. package/docs/knowledge-health/devday/participant-response.md +22 -0
  54. package/docs/knowledge-health/devday/public-default.md +107 -0
  55. package/docs/knowledge-health/devday/quickstart.md +18 -0
  56. package/docs/knowledge-health/devday/support.md +22 -0
  57. package/docs/knowledge-health/exercise.md +17 -0
  58. package/docs/knowledge-health/facilitator.md +20 -0
  59. package/docs/knowledge-health/quickstart.md +94 -0
  60. package/docs/knowledge-health/readme.md +15 -0
  61. package/docs/knowledge-health/support.md +34 -0
  62. package/docs/knowledge-setup.md +248 -0
  63. package/docs/knowledge-workspace.md +264 -0
  64. package/docs/learning-harnesses.md +237 -0
  65. package/docs/learning.md +215 -0
  66. package/docs/local-services.md +223 -19
  67. package/docs/obsidian-contract.md +246 -15
  68. package/docs/obsidian-plugin.md +252 -7
  69. package/docs/obsidian.md +874 -52
  70. package/docs/optional-ingestion.md +83 -0
  71. package/docs/personal-workspace.md +266 -0
  72. package/docs/practical-judgment.md +92 -0
  73. package/docs/progressive-ingestion-acceptance.md +89 -0
  74. package/docs/progressive-ingestion.md +235 -0
  75. package/docs/project-command-inventory.md +1 -0
  76. package/docs/release-engineering.md +50 -0
  77. package/docs/repo-boundary-guard.md +120 -3
  78. package/docs/research-starter-quickstart.md +66 -0
  79. package/docs/research-starter.md +114 -0
  80. package/docs/semantic-ingestion.md +198 -0
  81. package/docs/semantic-operation.md +318 -0
  82. package/docs/skill-steward.md +6 -0
  83. package/docs/template-profile.md +164 -0
  84. package/docs/template-upgrades.md +114 -0
  85. package/docs/trackables.md +111 -0
  86. package/docs/upgrade.md +219 -2
  87. package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
  88. package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
  89. package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
  90. package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
  91. package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
  92. package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
  93. package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
  94. package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
  95. package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
  96. package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
  97. package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
  98. package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
  99. package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
  100. package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
  101. package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
  102. package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
  103. package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
  104. package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
  105. package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
  106. package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
  107. package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
  108. package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
  109. package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
  110. package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
  111. package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
  112. package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
  113. package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
  114. package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
  115. package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
  116. package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
  117. package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
  118. package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
  119. package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
  120. package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
  121. package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
  122. package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
  123. package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
  124. package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
  125. package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
  126. package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
  127. package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
  128. package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
  129. package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
  130. package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
  131. package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
  132. package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
  133. package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
  134. package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
  135. package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
  136. package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
  137. package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
  138. package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
  139. package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
  140. package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
  141. package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
  142. package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
  143. package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
  144. package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
  145. package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
  146. package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
  147. package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
  148. package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
  149. package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
  150. package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
  151. package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
  152. package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
  153. package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
  154. package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
  155. package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
  156. package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
  157. package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
  158. package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
  159. package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
  160. package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
  161. package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
  162. package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
  163. package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
  164. package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
  165. package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
  166. package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
  167. package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
  168. package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
  169. package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
  170. package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
  171. package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
  172. package/fixtures/atelier-interaction/act/valid/document.json +24 -0
  173. package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
  174. package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
  175. package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
  176. package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
  177. package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
  178. package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
  179. package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
  180. package/fixtures/atelier-judgment/case/valid/document.json +75 -0
  181. package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
  182. package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
  183. package/fixtures/atelier-judgment/native-decision.md +3 -0
  184. package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
  185. package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
  186. package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
  187. package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
  188. package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
  189. package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
  190. package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
  191. package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
  192. package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
  193. package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
  194. package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
  195. package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
  196. package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
  197. package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
  198. package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
  199. package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
  200. package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
  201. package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
  202. package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
  203. package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
  204. package/fixtures/atelier-learning/invalid/authority.json +14 -0
  205. package/fixtures/atelier-learning/valid/capture.json +13 -0
  206. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
  207. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
  208. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
  209. package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
  210. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
  211. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
  212. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
  213. package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
  214. package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
  215. package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
  216. package/fixtures/atelier-trackable/request/valid/document.json +22 -0
  217. package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
  218. package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
  219. package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
  220. package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
  221. package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
  222. package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
  223. package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
  224. package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
  225. package/fixtures/capability-packages/adoption.example.json +70 -0
  226. package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
  227. package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
  228. package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
  229. package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
  230. package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
  231. package/fixtures/capability-packages/research/capability-package.json +88 -0
  232. package/fixtures/capability-packages/research/capability-release.json +134 -0
  233. package/fixtures/capability-packages/research/evaluation.md +15 -0
  234. package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
  235. package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
  236. package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
  237. package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
  238. package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
  239. package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
  240. package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
  241. package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
  242. package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
  243. package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
  244. package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
  245. package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
  246. package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
  247. package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
  248. package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
  249. package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
  250. package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
  251. package/fixtures/evidence-navigation/valid/claim.json +36 -0
  252. package/fixtures/evidence-navigation/valid/host.json +16 -0
  253. package/fixtures/evidence-navigation/valid/profile.json +16 -0
  254. package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
  255. package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
  256. package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
  257. package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
  258. package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
  259. package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
  260. package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
  261. package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
  262. package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
  263. package/fixtures/harnesses/learning-cycle.json +780 -0
  264. package/fixtures/inquiry/workshop.json +675 -0
  265. package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
  266. package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
  267. package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
  268. package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
  269. package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
  270. package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
  271. package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
  272. package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
  273. package/fixtures/knowledge-health/calibration-suite.json +26 -0
  274. package/fixtures/knowledge-health/calibration-trial.json +37 -0
  275. package/fixtures/knowledge-health/corrected/checklist.md +18 -0
  276. package/fixtures/knowledge-health/devday/participant-record.json +19 -0
  277. package/fixtures/knowledge-health/expected-summary.json +27 -0
  278. package/fixtures/knowledge-health/workshop/README.md +20 -0
  279. package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
  280. package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
  281. package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
  282. package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
  283. package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
  284. package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
  285. package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
  286. package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
  287. package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
  288. package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
  289. package/fixtures/personal-workspace/overlay.json +24 -0
  290. package/fixtures/templates/local-library.v1.json +76 -0
  291. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
  292. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
  293. package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
  294. package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
  295. package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
  296. package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
  297. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
  298. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
  299. package/package.json +60 -5
  300. package/plugins/obsidian/main.js +57 -8
  301. package/plugins/obsidian/manifest.json +1 -1
  302. package/skills/claude/atelier-build-harness/SKILL.md +59 -0
  303. package/skills/claude/atelier-coordination/SKILL.md +32 -0
  304. package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
  305. package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
  306. package/skills/claude/atelier-interaction/SKILL.md +34 -0
  307. package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
  308. package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
  309. package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
  310. package/skills/claude/atelier-research-harness/SKILL.md +43 -0
  311. package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
  312. package/skills/claude/atelier-trackables/SKILL.md +40 -0
  313. package/skills/codex/atelier-build-harness/SKILL.md +59 -0
  314. package/skills/codex/atelier-coordination/SKILL.md +32 -0
  315. package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
  316. package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
  317. package/skills/codex/atelier-interaction/SKILL.md +34 -0
  318. package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
  319. package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
  320. package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
  321. package/skills/codex/atelier-research-harness/SKILL.md +43 -0
  322. package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
  323. package/skills/codex/atelier-trackables/SKILL.md +40 -0
  324. package/src/architecture/catalog.mjs +355 -0
  325. package/src/architecture/index.mjs +93 -0
  326. package/src/boundary/content-rules.mjs +536 -69
  327. package/src/boundary/delegation-command.mjs +158 -0
  328. package/src/boundary/delegation.mjs +396 -0
  329. package/src/boundary/policy.mjs +185 -64
  330. package/src/build/git.mjs +18 -0
  331. package/src/build/index.mjs +3 -0
  332. package/src/build/ledger.mjs +88 -0
  333. package/src/capabilities/adoption.mjs +381 -0
  334. package/src/capabilities/evidence.mjs +140 -0
  335. package/src/capabilities/files.mjs +124 -0
  336. package/src/capabilities/index.mjs +4 -0
  337. package/src/capabilities/instructions.mjs +250 -0
  338. package/src/capabilities/package.mjs +136 -0
  339. package/src/cli/command-failure.mjs +36 -0
  340. package/src/cli/execute-command.mjs +16 -2
  341. package/src/cli/run.mjs +66 -9
  342. package/src/coauthor/store.mjs +43 -2
  343. package/src/collaboration/proposals.mjs +189 -0
  344. package/src/commands/architecture.mjs +27 -0
  345. package/src/commands/boundary.mjs +3 -0
  346. package/src/commands/capability.mjs +73 -0
  347. package/src/commands/enroll.mjs +3 -0
  348. package/src/commands/harness.mjs +79 -0
  349. package/src/commands/ingest.mjs +39 -0
  350. package/src/commands/init.mjs +4 -1
  351. package/src/commands/inquiry.mjs +66 -0
  352. package/src/commands/knowledge-semantic.mjs +128 -0
  353. package/src/commands/knowledge.mjs +119 -0
  354. package/src/commands/learn.mjs +61 -0
  355. package/src/commands/obsidian.mjs +772 -65
  356. package/src/commands/practice.mjs +24 -0
  357. package/src/commands/setup.mjs +22 -2
  358. package/src/commands/trackable.mjs +19 -0
  359. package/src/contracts/corpus.mjs +30 -0
  360. package/src/coordination/index.mjs +110 -0
  361. package/src/evidence-navigation/contracts.mjs +60 -0
  362. package/src/evidence-navigation/evaluate.mjs +69 -0
  363. package/src/evidence-navigation/generation.mjs +22 -0
  364. package/src/evidence-navigation/index.mjs +2 -0
  365. package/src/evidence-navigation/local.mjs +167 -0
  366. package/src/evidence-navigation/schema.generated.mjs +604 -0
  367. package/src/evidence-navigation/types.mjs +12 -0
  368. package/src/export/atelier-export-contract.mjs +35 -8
  369. package/src/graph/enroll.mjs +248 -0
  370. package/src/graph/graph.mjs +30 -6
  371. package/src/graph/knowledge-graph.mjs +77 -9
  372. package/src/harnesses/contracts.mjs +41 -0
  373. package/src/harnesses/exchange.mjs +154 -0
  374. package/src/harnesses/feedback.mjs +16 -0
  375. package/src/harnesses/history.mjs +37 -0
  376. package/src/harnesses/index.mjs +4 -0
  377. package/src/harnesses/store.mjs +29 -0
  378. package/src/ingestion/contracts.mjs +41 -0
  379. package/src/ingestion/evaluation.mjs +119 -0
  380. package/src/ingestion/optional/composition/index.mjs +171 -0
  381. package/src/ingestion/optional/evaluation/index.mjs +219 -0
  382. package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
  383. package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
  384. package/src/ingestion/optional/graphify/graphify_supplier.py +938 -0
  385. package/src/ingestion/optional/graphify/json_bridge.py +163 -0
  386. package/src/ingestion/optional/graphify/native-closure-pins.json +275 -0
  387. package/src/ingestion/optional/graphify/native-pins.json +306 -0
  388. package/src/ingestion/optional/graphify/query_receiving.py +209 -0
  389. package/src/ingestion/optional/index.mjs +9 -0
  390. package/src/ingestion/optional/jev/bridge.mjs +166 -0
  391. package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
  392. package/src/ingestion/optional/jev/judgments.mjs +174 -0
  393. package/src/ingestion/optional/jev/native-translation.mjs +32 -0
  394. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
  395. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
  396. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
  397. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
  398. package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
  399. package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
  400. package/src/ingestion/optional/vanilla/index.mjs +218 -0
  401. package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
  402. package/src/ingestion/processors.mjs +218 -0
  403. package/src/ingestion/semantic.mjs +203 -0
  404. package/src/ingestion/store.mjs +372 -0
  405. package/src/inquiry/beliefs.mjs +32 -0
  406. package/src/inquiry/contracts.mjs +23 -0
  407. package/src/inquiry/index.mjs +5 -0
  408. package/src/inquiry/ledger.mjs +135 -0
  409. package/src/inquiry/lenses.mjs +15 -0
  410. package/src/inquiry/projection.mjs +77 -0
  411. package/src/inquiry/store.mjs +35 -0
  412. package/src/intake/read-scope.mjs +12 -0
  413. package/src/intake/store.mjs +69 -6
  414. package/src/interaction/index.mjs +91 -0
  415. package/src/judgment/index.mjs +82 -0
  416. package/src/judgment/practice-evaluation.mjs +70 -0
  417. package/src/judgment/practice.mjs +144 -0
  418. package/src/knowledge/context.mjs +217 -0
  419. package/src/knowledge/index.mjs +5 -0
  420. package/src/knowledge/ingestion.mjs +79 -0
  421. package/src/knowledge/intake.mjs +33 -0
  422. package/src/knowledge/ledger.mjs +110 -0
  423. package/src/knowledge/participatory/guidance.mjs +36 -0
  424. package/src/knowledge/participatory/guided-contribution.mjs +44 -0
  425. package/src/knowledge/participatory/index.mjs +2 -0
  426. package/src/knowledge/participatory/installed-workshop.mjs +108 -0
  427. package/src/knowledge/plan.mjs +67 -0
  428. package/src/knowledge/projection.mjs +43 -0
  429. package/src/knowledge/semantic-operation.mjs +567 -0
  430. package/src/knowledge/sessions.mjs +467 -0
  431. package/src/knowledge/workspace.mjs +295 -0
  432. package/src/knowledge-health/assessment.mjs +165 -0
  433. package/src/knowledge-health/capabilities.mjs +350 -0
  434. package/src/knowledge-health/context.mjs +62 -0
  435. package/src/knowledge-health/example.mjs +47 -0
  436. package/src/knowledge-health/index.mjs +10 -0
  437. package/src/knowledge-health/owner-join.mjs +88 -0
  438. package/src/knowledge-health/proof/common.mjs +24 -0
  439. package/src/knowledge-health/proof/installed-check.mjs +100 -0
  440. package/src/knowledge-health/proof/native-readback.mjs +37 -0
  441. package/src/knowledge-health/proof/rehearse.mjs +68 -0
  442. package/src/knowledge-health/proof/validate-participant.mjs +17 -0
  443. package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
  444. package/src/knowledge-health/proof/workshop-check.mjs +36 -0
  445. package/src/knowledge-health/rule-identity.mjs +49 -0
  446. package/src/knowledge-health/workshop-composition.mjs +19 -0
  447. package/src/knowledge-health/workshop-profile.mjs +176 -0
  448. package/src/knowledge-health/workshop.mjs +99 -0
  449. package/src/learning/adapters.mjs +68 -0
  450. package/src/learning/assistance.mjs +127 -0
  451. package/src/learning/contracts.mjs +65 -0
  452. package/src/learning/store.mjs +272 -0
  453. package/src/personal-workspace/index.mjs +391 -0
  454. package/src/personal-workspace/selection.mjs +401 -0
  455. package/src/project/config.mjs +8 -0
  456. package/src/project/durable-state.mjs +3 -1
  457. package/src/project/file-class.mjs +1 -1
  458. package/src/project/path-match.mjs +2 -1
  459. package/src/projection/obsidian/edits/apply.mjs +55 -16
  460. package/src/projection/obsidian/materialize/prepare-view.mjs +87 -5
  461. package/src/projection/obsidian/personal-workspace.mjs +519 -0
  462. package/src/projection/obsidian/proposals/adapter.mjs +79 -17
  463. package/src/projection/obsidian/proposals/observation.mjs +50 -4
  464. package/src/projection/obsidian/publication/publisher.mjs +214 -18
  465. package/src/projection/obsidian/publication/vault-list.mjs +4 -3
  466. package/src/projection/obsidian/recovery/index.mjs +4 -1
  467. package/src/projection/obsidian/recovery/store.mjs +160 -7
  468. package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
  469. package/src/projection/project.mjs +3 -2
  470. package/src/readiness/readiness.mjs +10 -3
  471. package/src/reflection/index.mjs +56 -0
  472. package/src/runtime/obsidian/app-production-seams.mjs +48 -16
  473. package/src/runtime/obsidian/app-registration.mjs +175 -28
  474. package/src/runtime/obsidian/app-restart.mjs +154 -0
  475. package/src/runtime/obsidian/engine.mjs +437 -56
  476. package/src/runtime/obsidian/extension-points.mjs +1 -1
  477. package/src/runtime/obsidian/index.mjs +10 -7
  478. package/src/runtime/obsidian/launch-plan.mjs +7 -4
  479. package/src/runtime/obsidian/lifecycle.mjs +108 -23
  480. package/src/runtime/obsidian/login-item.mjs +335 -0
  481. package/src/runtime/obsidian/machine-settings.mjs +119 -12
  482. package/src/runtime/obsidian/observation.mjs +36 -4
  483. package/src/runtime/obsidian/opening.mjs +173 -38
  484. package/src/runtime/obsidian/personal-selection.mjs +125 -0
  485. package/src/runtime/obsidian/pipeline.mjs +115 -6
  486. package/src/runtime/obsidian/plugin-channel.mjs +11 -3
  487. package/src/runtime/obsidian/plugin-choice.mjs +16 -4
  488. package/src/runtime/obsidian/plugin-drift.mjs +59 -11
  489. package/src/runtime/obsidian/plugin-presence.mjs +14 -1
  490. package/src/runtime/obsidian/private-lock.mjs +39 -4
  491. package/src/runtime/obsidian/process-identity.mjs +20 -10
  492. package/src/runtime/obsidian/project-views.mjs +282 -0
  493. package/src/runtime/obsidian/questions.mjs +48 -0
  494. package/src/runtime/obsidian/release-watch.mjs +56 -0
  495. package/src/runtime/obsidian/service-main.mjs +148 -19
  496. package/src/runtime/obsidian/service-manager-production.mjs +61 -0
  497. package/src/runtime/obsidian/service-managers.mjs +275 -0
  498. package/src/runtime/obsidian/service-record.mjs +87 -23
  499. package/src/runtime/obsidian/service.mjs +102 -9
  500. package/src/runtime/obsidian/startup-adapters.mjs +59 -21
  501. package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
  502. package/src/runtime/obsidian/vault-location.mjs +300 -0
  503. package/src/runtime/obsidian/view-counts.mjs +34 -0
  504. package/src/runtime/obsidian/view-permission.mjs +334 -0
  505. package/src/server/local-sidecar.mjs +169 -2
  506. package/src/server/server.mjs +28 -2
  507. package/src/template-bindings/project.mjs +92 -0
  508. package/src/templates/profile.mjs +225 -0
  509. package/src/trackables/domain.mjs +175 -0
  510. package/src/trackables/store.mjs +114 -0
  511. package/src/ui/control-system.mjs +201 -0
  512. package/src/ui/html-primitives.mjs +3 -0
  513. package/src/ui/knowledge-page.mjs +1388 -0
  514. package/src/upgrade/closure-diagnostics.mjs +135 -0
  515. package/src/upgrade/closure.mjs +649 -0
  516. package/src/upgrade/explanation.mjs +1 -1
  517. package/src/upgrade/template-participant.mjs +171 -0
  518. package/src/upgrade/transaction.mjs +72 -25
  519. package/src/upgrade/upgrade.mjs +20 -7
  520. package/templates/external-project-workspace/pack-lifecycle.json +7 -1
  521. package/templates/knowledge-workspace/.gitattributes +2 -0
  522. package/templates/knowledge-workspace/AGENTS.md +27 -0
  523. package/templates/knowledge-workspace/README.md +44 -0
  524. package/templates/knowledge-workspace/atelier.project.json +22 -0
  525. package/templates/knowledge-workspace/gitignore +6 -0
  526. package/templates/knowledge-workspace/knowledge-plan.json +111 -0
  527. package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
  528. package/templates/knowledge-workspace/records/forecast.md +22 -0
  529. package/templates/knowledge-workspace/records/inspection.md +25 -0
  530. package/templates/knowledge-workspace/records/telescope.md +24 -0
  531. package/templates/knowledge-workspace/repo-access.v1.json +9 -0
@@ -0,0 +1,519 @@
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, readPersonalSelectionHead } from '@mnstry/atelier/personal-workspace'
6
+ import { ensureLocalState, resolveProjectConfig, validateProjectConfigDoc } from '@mnstry/atelier/project'
7
+ import { buildCanonicalGraph } from '../../graph/graph.mjs'
8
+ import { ObsidianMaintenanceRefusal, 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
+ // The selection record a binding was loaded under, when its loader followed the person's confirmed selection instead of
64
+ // naming a generation itself (runtime/obsidian/personal-selection.mjs): { sequence, head, observe, recheck }.
65
+ const SELECTED = new WeakMap()
66
+ export const EVERYTHING_SCOPE_ID = 'everything'
67
+ const EXT_SCHEMA = 'atelier-obsidian-ext-settings/v1'
68
+ // The settings member of a bound project names its generation here. It survives any copy of the project (a structured
69
+ // clone, a JSON round trip) that drops the symbol-keyed binding, so such a copy is refused instead of being built as an
70
+ // ordinary project; and a rebound generation changes the member, which invalidates every view. An ordinary project's
71
+ // settings may not use this key.
72
+ export const PERSONAL_MEMBER_KEY = 'mnstry.atelier.personal-workspace'
73
+ // The Obsidian settings contract's limits: at most 256 scopes, one of them `everything`, and at most 256 members in a
74
+ // selector's union.
75
+ const MAX_SAVED_VIEWS = 255
76
+ const MAX_VIEW_REPOSITORIES = 256
77
+ // A build that found its validity key unconfirmed, for a caller that confirms it off the event loop and builds again.
78
+ export const PERSONAL_VALIDATION_PENDING = 'personal-validation-pending'
79
+ const WORKER_KIND = 'atelier-obsidian-personal-composition'
80
+
81
+ const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex')
82
+ const deepFreeze = (value) => { if (value && typeof value === 'object') { Object.values(value).forEach(deepFreeze); Object.freeze(value) } return value }
83
+ const sortKeys = (value) => (Array.isArray(value) ? value.map(sortKeys)
84
+ : value && typeof value === 'object' ? Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortKeys(value[key])])) : value)
85
+ const digestOf = (value) => sha256(JSON.stringify(sortKeys(value)))
86
+ const generationRoot = ({ personalHome, generationId }) => path.join(personalHome, 'generations', generationId)
87
+ const linkTargetRule = (overlayRepoId) => (node) => node.repo !== overlayRepoId
88
+
89
+ // The facts of a graph every caller reads: nodes, edges, embeds, assets, resolved links and every finding.
90
+ const graphFactsDigest = (graph) => digestOf([graph.nodes, graph.edges, graph.embeds ?? [], graph.assets ?? [], graph.links ?? [], graph.linkDiagnostics ?? [], graph.diagnostics ?? []])
91
+
92
+ // The personal-workspace module's read limits: an authored input, and a generation file.
93
+ const AUTHORED_LIMIT = 1024 * 1024
94
+ const GENERATION_LIMIT = 8 * 1024 * 1024
95
+ const NONBLOCK = fs.constants.O_NONBLOCK ?? 0
96
+
97
+ // One regular file, opened without following a link and without waiting on it (a FIFO or a device never blocks the
98
+ // caller), and read up to `limit`. Anything else refuses `code`, as the composition would.
99
+ function regularFileBytes(file, limit, code, { follow = false } = {}) {
100
+ let fd
101
+ try { fd = fs.openSync(file, fs.constants.O_RDONLY | (follow ? 0 : fs.constants.O_NOFOLLOW) | NONBLOCK) } catch (error) {
102
+ if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return null
103
+ throw new PersonalWorkspaceRefusal(code)
104
+ }
105
+ try {
106
+ const stat = fs.fstatSync(fd)
107
+ if (!stat.isFile() || stat.size > limit) throw new PersonalWorkspaceRefusal(code)
108
+ return fs.readFileSync(fd)
109
+ } finally { fs.closeSync(fd) }
110
+ }
111
+ const regularFileDigest = (file, limit, code) => { const bytes = regularFileBytes(file, limit, code); return bytes === null ? 'missing' : sha256(bytes) }
112
+ // A Git configuration file, which Git itself reads through a link (a managed dotfile): followed, but still only a
113
+ // regular file, bounded, and never waited on.
114
+ const configDigest = (file) => { try { const bytes = regularFileBytes(file, AUTHORED_LIMIT, 'malformed-input', { follow: true }); return bytes === null ? 'missing' : sha256(bytes) } catch { return 'not-regular' } }
115
+ // The configuration files of an enrolled repository: `.git/config` (through `.git` when it is a link to a directory),
116
+ // or, where `.git` is a file (a separate Git directory, a linked worktree, a submodule), the configuration of the
117
+ // directory it names and of its common directory. A root that cannot be looked at (a file, a link loop, no permission)
118
+ // is recorded as such, so the composition answers with its own code.
119
+ function repositoryConfigDigests(repoRoot) {
120
+ const dotGit = path.join(repoRoot, '.git')
121
+ let kind
122
+ try { kind = fs.lstatSync(dotGit, { throwIfNoEntry: false }) } catch (error) { return [`unreadable:${error.code ?? 'error'}`] }
123
+ if (kind === undefined) return ['missing']
124
+ if (kind.isDirectory()) return [configDigest(path.join(dotGit, 'config'))]
125
+ if (kind.isSymbolicLink()) {
126
+ try { if (fs.statSync(dotGit).isDirectory()) return ['linked', fs.realpathSync(dotGit), configDigest(path.join(dotGit, 'config'))] } catch (error) { return [`unreadable:${error.code ?? 'error'}`] }
127
+ }
128
+ let pointer
129
+ try { pointer = regularFileBytes(dotGit, AUTHORED_LIMIT, 'malformed-input', { follow: true }) } catch { return ['not-regular'] }
130
+ const named = /^gitdir:\s*(.+?)\s*$/m.exec(pointer?.toString('utf8') ?? '')
131
+ if (named === null) return ['unnamed']
132
+ const gitDir = path.resolve(repoRoot, named[1])
133
+ let commonDir = null
134
+ 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' }
135
+ return [gitDir, configDigest(path.join(gitDir, 'config')), commonDir, commonDir === null || commonDir === 'not-regular' ? null : configDigest(path.join(commonDir, 'config'))]
136
+ }
137
+
138
+ // Everything outside the graph that a composition's answer depends on and a stat or a bounded read can see: the authored
139
+ // inputs (regular files only), every file of the generation (an extra, missing or irregular one too), the private home's
140
+ // own state and any `.git` above it, each enrolled root's link status and Git configuration (its remote and its helpers),
141
+ // the user's Git configuration, and any ambient Git variable. A file that is not regular refuses here with the code the
142
+ // composition gives it, before anything waits on it.
143
+ function inputsDigest({ personalHome, generationId }) {
144
+ 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'}` } }
145
+ const authoredBytes = ['atelier.personal.json', 'atelier.overlay.json'].map((name) => regularFileBytes(path.join(personalHome, name), AUTHORED_LIMIT, 'malformed-input'))
146
+ const authored = ['atelier.personal.json', 'atelier.overlay.json'].map((name, index) => [stat(path.join(personalHome, name)), authoredBytes[index] === null ? 'missing' : sha256(authoredBytes[index])])
147
+ const generation = []
148
+ const walk = (directory, prefix) => {
149
+ let entries
150
+ try { entries = fs.readdirSync(directory, { withFileTypes: true }) } catch (error) { generation.push([prefix, `unreadable:${error.code ?? 'error'}`]); return }
151
+ for (const entry of entries.sort((left, right) => (left.name < right.name ? -1 : left.name > right.name ? 1 : 0))) {
152
+ const relative = prefix === '' ? entry.name : `${prefix}/${entry.name}`
153
+ const absolute = path.join(directory, entry.name)
154
+ if (entry.isDirectory()) { generation.push([relative, stat(absolute)]); walk(absolute, relative) } else if (entry.isFile()) generation.push([relative, regularFileDigest(absolute, GENERATION_LIMIT, 'generation-corrupt')])
155
+ else throw new PersonalWorkspaceRefusal('generation-corrupt')
156
+ }
157
+ }
158
+ const root = generationRoot({ personalHome, generationId })
159
+ walk(root, '')
160
+ let realHome
161
+ try { realHome = fs.realpathSync(personalHome) === personalHome } catch { realHome = false }
162
+ const ancestorsWithGit = []
163
+ for (let ancestor = personalHome; ; ancestor = path.dirname(ancestor)) {
164
+ if (fs.lstatSync(path.join(ancestor, '.git'), { throwIfNoEntry: false }) !== undefined) ancestorsWithGit.push(ancestor)
165
+ if (ancestor === path.dirname(ancestor)) break
166
+ }
167
+ let repos = []
168
+ // The bytes read above, never the file again.
169
+ try { repos = JSON.parse(authoredBytes[0]?.toString('utf8') ?? '{}').repos.filter((repo) => repo.enrolled).map((repo) => repo.root) } catch { repos = [] }
170
+ const enrolled = repos.map((repoRoot) => {
171
+ let real
172
+ try { real = fs.realpathSync(repoRoot) === repoRoot } catch { real = false }
173
+ return [repoRoot, stat(repoRoot), real, repositoryConfigDigests(repoRoot)]
174
+ })
175
+ const home = process.env.HOME
176
+ const userGit = typeof home === 'string' && path.isAbsolute(home)
177
+ ? [path.join(home, '.gitconfig'), path.join(process.env.XDG_CONFIG_HOME || path.join(home, '.config'), 'git', 'config')].map(configDigest)
178
+ : ['no-home']
179
+ return digestOf({
180
+ authored,
181
+ generation,
182
+ roots: [stat(personalHome), realHome, stat(path.join(personalHome, 'generations')), stat(path.join(personalHome, 'generations', '.git')), stat(root), ancestorsWithGit],
183
+ enrolled,
184
+ userGit,
185
+ ambientGit: Object.keys(process.env).filter((name) => /^GIT_/i.test(name)).sort(),
186
+ })
187
+ }
188
+
189
+ // One generation file, read without following a link, and checked against the
190
+ // digest the generation records for it (and compose verified a moment ago).
191
+ function generationFile(generation, final, relative) {
192
+ const listed = generation.files.find((file) => file.path === relative)
193
+ if (!listed) throw new PersonalWorkspaceRefusal('generation-corrupt')
194
+ let bytes
195
+ try {
196
+ const fd = fs.openSync(path.join(final, ...relative.split('/')), fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW)
197
+ try { bytes = fs.readFileSync(fd) } finally { fs.closeSync(fd) }
198
+ } catch { throw new PersonalWorkspaceRefusal('generation-corrupt') }
199
+ if (sha256(bytes) !== listed.sha256) throw new PersonalWorkspaceRefusal('generation-corrupt')
200
+ return bytes
201
+ }
202
+
203
+ function selectorFor(repoIds) {
204
+ const repos = [...new Set(repoIds)]
205
+ if (repos.length === 0) return { ids: [] }
206
+ if (repos.length === 1) return { repo: repos[0] }
207
+ return { union: repos.map((repo) => ({ repo })) }
208
+ }
209
+
210
+ // The Obsidian settings member of a personal binding: one scoped view per saved
211
+ // selection of repositories, in the overlay's order, and one full view of the
212
+ // whole composed graph. A preferred view that names a saved one is the default.
213
+ // Refused, typed, where the overlay allows more than a projection can declare.
214
+ export function personalWorkspaceScopes(overlay, generationId) {
215
+ if (overlay.views.some((view) => view.id === EVERYTHING_SCOPE_ID)) {
216
+ refuse('personal-view-id-reserved', `a saved view may not be named "${EVERYTHING_SCOPE_ID}"; that view is the whole personal workspace`)
217
+ }
218
+ 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' })
219
+ if (overlay.views.some((view) => new Set(view.repoIds).size > MAX_VIEW_REPOSITORIES)) {
220
+ 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' })
221
+ }
222
+ const scopes = [
223
+ ...overlay.views.map((view) => ({ scopeId: view.id, mode: 'scoped', selector: selectorFor(view.repoIds) })),
224
+ { scopeId: EVERYTHING_SCOPE_ID, mode: 'full', selector: { all: true } },
225
+ ]
226
+ const preferred = overlay.preferences?.defaultView
227
+ const defaultScopeId = typeof preferred === 'string' && overlay.views.some((view) => view.id === preferred) ? preferred : EVERYTHING_SCOPE_ID
228
+ return { schema: EXT_SCHEMA, enabled: true, defaultScopeId, scopes, ext: { [PERSONAL_MEMBER_KEY]: { generationId } } }
229
+ }
230
+
231
+ // The bound project, from a composition the caller just made: `composed` carries the generation's record, the
232
+ // preferences and the coverage, and the validity key that composition confirmed.
233
+ function assemble({ personalHome, generationId }, composed, route) {
234
+ const final = generationRoot({ personalHome, generationId })
235
+ const projectBytes = generationFile(composed.generation, final, 'atelier.project.json')
236
+ const inputs = JSON.parse(generationFile(composed.generation, final, 'inputs.json').toString('utf8'))
237
+ const configPath = path.join(final, 'atelier.project.json')
238
+ const resolved = resolveProjectConfig({ argv: [`--project-config=${configPath}`], cwd: final, env: { PATH: process.env.PATH }, writeLocalState: false })
239
+ // The same pinned resolution compose made; anything else means the generation changed between the two reads.
240
+ if (JSON.stringify(resolved.config) !== JSON.stringify(JSON.parse(projectBytes.toString('utf8')))) throw new PersonalWorkspaceRefusal('generation-corrupt')
241
+ if (validateProjectConfigDoc(resolved.config).length > 0) throw new PersonalWorkspaceRefusal('generation-corrupt')
242
+ if (resolved.localOverlay.paths.length > 0 || resolved.repos.some((repo) => repo.pathSource !== 'tracked-config')) throw new PersonalWorkspaceRefusal('ambient-overlay-present')
243
+ const overlayRepoId = `personal-${inputs.manifest.workspaceId}`
244
+ const overlayRepo = resolved.repos.find((repo) => repo.name === overlayRepoId)
245
+ if (!overlayRepo || overlayRepo.path !== path.join(final, 'overlay')) throw new PersonalWorkspaceRefusal('generation-corrupt')
246
+
247
+ const binding = Object.freeze({ personalHome, generationId, overlayRepoId })
248
+ ROUTES.set(binding, route)
249
+ CONFIRMED.set(binding, composed.key)
250
+ const member = deepFreeze(personalWorkspaceScopes(inputs.overlay, generationId))
251
+ const project = {
252
+ ...resolved,
253
+ config: { ...resolved.config, ext: { ...(resolved.config.ext ?? {}), [OBSIDIAN_EXT_KEY]: member } },
254
+ configDir: personalHome,
255
+ [BINDING]: binding,
256
+ }
257
+ project.localState = ensureLocalState(project, { write: false, env: { PATH: process.env.PATH } })
258
+ return Object.freeze({ project, binding, preferences: deepFreeze(structuredClone(composed.preferences ?? {})), coverage: composed.coverage })
259
+ }
260
+
261
+ // A composition in this thread, as the summary a worker sends back.
262
+ // The inputs are digested BEFORE composing: a single change that lands while the composition runs then either is seen by
263
+ // it, or leaves the confirmed key behind, so the next build composes again. A change and its exact undo within one
264
+ // composition (A, then B, then A again) is not seen this way: it is caught at the next full reconciliation.
265
+ function composeHere(compose, binding) {
266
+ const inputs = inputsDigest(binding)
267
+ const composed = compose({ personalHome: binding.personalHome, generationId: binding.generationId })
268
+ return { generation: composed.generation, preferences: composed.preferences, coverage: composed.coverage, key: { inputs, facts: graphFactsDigest(composed.graph) } }
269
+ }
270
+
271
+ // A composition in a worker thread, so the event loop of the caller stays free while it runs. Resolves to the summary, or
272
+ // to `{ code }` for a refusal; a worker that cannot run answers `personal-composition-unavailable`.
273
+ // A worker that does not answer within the deadline is terminated and let go of, and the composition refuses
274
+ // `personal-composition-unavailable`: the tick does not wait on it. A worker blocked in a system call (a FIFO opened
275
+ // for reading, say) cannot be stopped until that call returns: until then it keeps the process from exiting, and each
276
+ // such expiry leaves one thread behind. A service stopped in that state needs a kill.
277
+ export const WORKER_DEADLINE_MS = 120 * 1000
278
+ function composeInWorker({ personalHome, generationId }, { deadlineMs = WORKER_DEADLINE_MS, stallMs = 0 } = {}) {
279
+ return new Promise((resolve) => {
280
+ let settled = false
281
+ let worker = null
282
+ let timer = null
283
+ const settle = (value) => {
284
+ if (settled) return
285
+ settled = true
286
+ clearTimeout(timer)
287
+ if (value.code === 'personal-composition-unavailable' && worker !== null) { worker.unref(); worker.terminate().catch(() => {}) }
288
+ resolve(value)
289
+ }
290
+ try { worker = new Worker(new URL(import.meta.url), { workerData: { kind: WORKER_KIND, personalHome, generationId, stallMs } }) } catch { settle({ code: 'personal-composition-unavailable' }); return }
291
+ timer = setTimeout(() => settle({ code: 'personal-composition-unavailable' }), deadlineMs)
292
+ timer.unref?.()
293
+ worker.once('message', settle)
294
+ worker.once('error', () => settle({ code: 'personal-composition-unavailable' }))
295
+ worker.once('exit', () => settle({ code: 'personal-composition-unavailable' }))
296
+ })
297
+ }
298
+
299
+ // Test seam only: `compose` replaces the module's composePersonalWorkspace for the binding and every validation of
300
+ // it, `build` the canonical build that serves its graph, and `worker` the worker's deadline (and a stall, so a worker that
301
+ // never answers can be shown). Production code uses bindPersonalWorkspace.
302
+ export function createPersonalWorkspaceBinderForOracleTests({ compose = composePersonalWorkspace, build = null, worker = {} } = {}) {
303
+ const route = { compose, offThread: compose === composePersonalWorkspace, build, worker }
304
+ const bind = ({ personalHome, generationId } = {}) => {
305
+ // The validity authority first: a refusal propagates as the module's own typed refusal, and nothing is read or
306
+ // written after it.
307
+ const composed = composeHere(compose, { personalHome, generationId })
308
+ return assemble({ personalHome, generationId }, composed, route)
309
+ }
310
+ // The same binding, with the composition off the event loop.
311
+ bind.offThread = async ({ personalHome, generationId } = {}) => {
312
+ const composed = route.offThread ? await composeInWorker({ personalHome, generationId }, route.worker) : composeHere(compose, { personalHome, generationId })
313
+ if (typeof composed.code === 'string') {
314
+ if (composed.code === 'personal-composition-unavailable') refuse(composed.code, 'the personal workspace could not be composed off the event loop; nothing is bound')
315
+ throw new PersonalWorkspaceRefusal(composed.code)
316
+ }
317
+ return assemble({ personalHome, generationId }, composed, route)
318
+ }
319
+ return bind
320
+ }
321
+
322
+ export const bindPersonalWorkspace = createPersonalWorkspaceBinderForOracleTests()
323
+
324
+ // A refusal of the personal-workspace module becomes the maintenance refusal with the same code, as at every build, so
325
+ // the engine and every command report it typed and no view stays current.
326
+ const asMaintenanceRefusal = (error, message) => {
327
+ if (error instanceof PersonalWorkspaceRefusal) refuse(error.code, message, { source: 'personal-workspace' })
328
+ throw error
329
+ }
330
+
331
+ // The project of a binding, for a loader that may block (a command run once).
332
+ export function loadBoundProject({ personalHome, generationId } = {}, { bind = bindPersonalWorkspace } = {}) {
333
+ try { return bind({ personalHome, generationId }).project } catch (error) { return asMaintenanceRefusal(error, 'the personal workspace refused this generation; nothing is loaded from it') }
334
+ }
335
+
336
+ // The project of a binding, for a loader on a service's event loop: the composition runs in a worker. The engine
337
+ // awaits its loader, so this is the loader a service uses.
338
+ export async function loadBoundProjectOffThread({ personalHome, generationId } = {}, { bind = bindPersonalWorkspace } = {}) {
339
+ try { return (await bind.offThread({ personalHome, generationId })).project } catch (error) { return asMaintenanceRefusal(error, 'the personal workspace refused this generation; nothing is loaded from it') }
340
+ }
341
+
342
+ // The binding a project carries, or null for every other project.
343
+ export function personalWorkspaceBindingOf(project) {
344
+ return project !== null && typeof project === 'object' && Object.hasOwn(project, BINDING) ? project[BINDING] : null
345
+ }
346
+
347
+ // The graph of a bound project, or null when the project carries no binding. `fileCache` and `observedDigest` are the
348
+ // caller's, as for any build. With `defer`, a key the composition has not confirmed is not confirmed here: the build
349
+ // refuses `personal-validation-pending`, and the caller confirms it off the event loop (validatePersonalWorkspace) and
350
+ // builds again.
351
+ export function personalWorkspaceGraph(project, { fileCache = null, observedDigest = null, defer = false } = {}) {
352
+ const binding = personalWorkspaceBindingOf(project)
353
+ if (binding === null) {
354
+ if (project?.config?.ext?.[OBSIDIAN_EXT_KEY]?.ext?.[PERSONAL_MEMBER_KEY] !== undefined) {
355
+ 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`)
356
+ }
357
+ return null
358
+ }
359
+ const route = ROUTES.get(binding)
360
+ if (route === undefined) refuse('personal-binding-unrecognized', 'the project carries a personal-workspace binding this release did not make; nothing is prepared from it')
361
+ 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')
362
+ 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' })
363
+ const confirmed = CONFIRMED.get(binding)
364
+ // Inputs that changed are composed before anything is built from them.
365
+ let inputs
366
+ try { inputs = inputsDigest(binding) } catch (error) { CONFIRMED.delete(binding); asMaintenanceRefusal(error, 'the personal workspace refused this generation; nothing is prepared from it') }
367
+ if (confirmed?.inputs !== inputs) {
368
+ if (defer) pending()
369
+ confirmHere(route, binding)
370
+ }
371
+ const build = route.build ?? ((target, options) => buildCanonicalGraph(target, { ...options, isLinkTargetEligible: linkTargetRule(binding.overlayRepoId) }))
372
+ const graph = build(project, { fileCache, observedDigest })
373
+ if (CONFIRMED.get(binding)?.facts === graphFactsDigest(graph)) return graph
374
+ if (defer) pending()
375
+ confirmHere(route, binding)
376
+ if (CONFIRMED.get(binding)?.facts === graphFactsDigest(graph)) return graph
377
+ // Composed just now, and still another graph: the sources moved between the two reads, or the build is not the
378
+ // composition's. Nothing is prepared from it.
379
+ 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' })
380
+ }
381
+
382
+ function confirmHere(route, binding) {
383
+ 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') }
384
+ }
385
+
386
+ // Asks the composition again, off the event loop when it can: `{ ok: true, changed }` when it accepts the generation
387
+ // (`changed` when its key differs from the one last confirmed), `{ ok: false, code }` when it refuses. Null for a project
388
+ // without a binding. The engine asks this before a build that found its key unconfirmed, and at every full
389
+ // reconciliation of a bound project.
390
+ export async function validatePersonalWorkspace(project) {
391
+ const binding = personalWorkspaceBindingOf(project)
392
+ if (binding === null) return null
393
+ const route = ROUTES.get(binding)
394
+ if (route === undefined) return { ok: false, code: 'personal-binding-unrecognized' }
395
+ // A project pinned to a confirmed selection holds only while the history still ends, intact, with that selection's
396
+ // record: what observation of the record cannot see (a record before it broken, a confirmation that landed before
397
+ // the engine first looked) is seen here.
398
+ if (SELECTED.get(binding)?.recheck) {
399
+ try { assertPersonalSelection(project) } catch (error) { if (!(error instanceof ObsidianMaintenanceRefusal)) throw error; CONFIRMED.delete(binding); return { ok: false, code: error.code } }
400
+ }
401
+ let composed
402
+ if (route.offThread) composed = await composeInWorker(binding, route.worker)
403
+ else {
404
+ try { composed = composeHere(route.compose, binding) } catch (error) { if (!(error instanceof PersonalWorkspaceRefusal)) throw error; composed = { code: error.code } }
405
+ }
406
+ if (typeof composed.code === 'string') { CONFIRMED.delete(binding); return { ok: false, code: composed.code } }
407
+ const previous = CONFIRMED.get(binding)
408
+ CONFIRMED.set(binding, composed.key)
409
+ return { ok: true, changed: previous?.inputs !== composed.key.inputs || previous?.facts !== composed.key.facts }
410
+ }
411
+
412
+ const selectionRecord = ({ personalHome }, sequence) => path.join(personalHome, 'selections', `${String(sequence).padStart(6, '0')}.json`)
413
+
414
+ // The highest record number this process has read each home's selection history ending at, at any read of it: a
415
+ // loader's first read, its read-again, the recheck of a pinned project, and the read the engine's guard asks for when
416
+ // it stops a view (notePersonalSelectionNow). Kept in the main thread: no composition worker reads the history's end,
417
+ // and one that did would have a map of its own and escape the rule.
418
+ const READ_UP_TO = new Map()
419
+
420
+ // Notes where a home's selection history ends, as just read, and answers that head. Selections are only ever appended,
421
+ // so a history that ends at an earlier record than this process has read it ending at was cut short by something other
422
+ // than a confirmation. It refuses `selection-history-shortened` at every read, the read-again and the recheck of a
423
+ // project already loaded included, while it is shorter. Only the number is kept, and only in this process: a record in
424
+ // a place already read, with other content, is read as a confirmation, and a process started after the cut reads the
425
+ // history as it then stands. `refusesShortened: false` is a test seam only, the rule's mutation control.
426
+ export function notePersonalSelectionHead(personalHome, head, { refusesShortened = true } = {}) {
427
+ const before = READ_UP_TO.get(personalHome) ?? 0
428
+ if (head.sequence > before) READ_UP_TO.set(personalHome, head.sequence)
429
+ else if (refusesShortened && head.sequence < before) refuse('selection-history-shortened', 'the selection history of the personal workspace ends at an earlier record than this process has read it ending at; nothing is used from it while it is shorter', { source: 'personal-workspace' })
430
+ return head
431
+ }
432
+
433
+ // Pins a bound project to the selection record it was loaded under, and answers the project. From then on the engine
434
+ // observes that record and the place of the next one as configuration (personalWorkspaceInputs): a confirmation
435
+ // appended to the history, or a history cut short, is a change. And every time the composition is asked again
436
+ // (validatePersonalWorkspace) the history is read again and must still end with that record. `observe: false`,
437
+ // `recheck: false` and `refusesShortened: false` are test seams only, the mutation controls of each.
438
+ export function pinPersonalSelection(project, { sequence, head }, { observe = true, recheck = true, refusesShortened = true } = {}) {
439
+ SELECTED.set(personalWorkspaceBindingOf(project), Object.freeze({ sequence, head, observe, recheck, refusesShortened }))
440
+ return project
441
+ }
442
+
443
+ // The selection a project is pinned to, as { personalHome, generationId, sequence, head }; null for any other project.
444
+ export function personalSelectionOf(project) {
445
+ const binding = personalWorkspaceBindingOf(project)
446
+ const pinned = binding === null ? undefined : SELECTED.get(binding)
447
+ return pinned === undefined ? null : { personalHome: binding.personalHome, generationId: binding.generationId, sequence: pinned.sequence, head: pinned.head }
448
+ }
449
+
450
+ // Refuses `personal-selection-changed` unless the history still ends with the record this project is pinned to; a
451
+ // history that cannot be read refuses with the module's own code. What is read is noted first
452
+ // (notePersonalSelectionHead): a later record seen here counts from then on, and a history shorter than this process
453
+ // has read refuses `selection-history-shortened`, also when it ends with the pinned record. Nothing for a project that
454
+ // is not pinned.
455
+ export function assertPersonalSelection(project) {
456
+ const pinned = personalSelectionOf(project)
457
+ if (pinned === null) return
458
+ let now
459
+ try { now = readPersonalSelectionHead({ personalHome: pinned.personalHome }) } catch (error) { asMaintenanceRefusal(error, 'the selection history of the personal workspace could not be read; nothing is used from it') }
460
+ notePersonalSelectionHead(pinned.personalHome, now, SELECTED.get(personalWorkspaceBindingOf(project)))
461
+ if (now.sequence !== pinned.sequence || now.head !== pinned.head) refuse('personal-selection-changed', 'the confirmed selection of the personal workspace is no longer the one this project was loaded under; nothing is used from it', { source: 'personal-workspace' })
462
+ }
463
+
464
+ // Whether this process has read a pinned project's selection history ending at a later record than the one the
465
+ // project is pinned to: a confirmation since, or a history that was longer and is cut short now. Either way the project
466
+ // is not the one to publish under any more, and is loaded again: the load follows the confirmation, or refuses the
467
+ // shortened history. In memory, with no read. False for a project that is not pinned.
468
+ export function personalSelectionBehind(project) {
469
+ const pinned = personalSelectionOf(project)
470
+ return pinned !== null && (READ_UP_TO.get(pinned.personalHome) ?? 0) > pinned.sequence
471
+ }
472
+
473
+ // Reads where the history of a pinned project's home ends now, for the note alone: what the read refuses is not this
474
+ // caller's to raise. For the engine's guard, which sees a selection record's file change in the middle of a tick and
475
+ // stops the view as `mixed-read` without reading the history: the record that stopped it is read while it is there, so
476
+ // it counts when it is removed before the next tick. Nothing for a project that is not pinned.
477
+ export function notePersonalSelectionNow(project) {
478
+ try { assertPersonalSelection(project) } catch (error) { if (!(error instanceof ObsidianMaintenanceRefusal)) throw error }
479
+ }
480
+
481
+ // The authored and generation files whose change means the binding must be asked again: the manifest, the overlay and
482
+ // the bound generation's record; and, for a project pinned to a selection, that selection's record and the place of the
483
+ // next one. Observed as configuration by the engine. Empty for any other project.
484
+ export function personalWorkspaceInputs(project) {
485
+ const binding = personalWorkspaceBindingOf(project)
486
+ if (binding === null) return []
487
+ const pinned = SELECTED.get(binding)
488
+ return [
489
+ path.join(binding.personalHome, 'atelier.personal.json'), path.join(binding.personalHome, 'atelier.overlay.json'), path.join(generationRoot(binding), 'generation.json'),
490
+ ...(pinned?.observe ? [selectionRecord(binding, pinned.sequence), selectionRecord(binding, pinned.sequence + 1)] : []),
491
+ ]
492
+ }
493
+
494
+ // The repository of a bound project's private notes, or null. A vault edit to one of them is never applied into a
495
+ // module-owned generation, and is not proposed yet: it stays held in the vault.
496
+ export function personalOverlayRepoOf(project) {
497
+ return personalWorkspaceBindingOf(project)?.overlayRepoId ?? null
498
+ }
499
+
500
+ // Settings that a personal binding takes from the overlay or does not have yet. A saved view is authored in the
501
+ // overlay, so `view add` would write a module-owned generation file; who may see and where vaults live wait for a
502
+ // stable per-person projection configuration. Refused before anything is resolved or written.
503
+ export function refuseOnPersonalBinding(project, operation) {
504
+ if (personalWorkspaceBindingOf(project) === null) return
505
+ if (operation === 'view add') refuse('views-from-personal-overlay', 'the views of a personal workspace come from its overlay; nothing was written', { operation })
506
+ refuse('personal-binding-decision-unavailable', `${operation} is not available for a personal workspace yet; nothing was written`, { operation })
507
+ }
508
+
509
+ // The worker side of composeInWorker: one composition, its summary, and exit.
510
+ if (!isMainThread && workerData?.kind === WORKER_KIND) {
511
+ const binding = { personalHome: workerData.personalHome, generationId: workerData.generationId }
512
+ // A worker that never answers, for the deadline test only: blocked as a system call would block it.
513
+ if (workerData.stallMs > 0) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, workerData.stallMs)
514
+ try {
515
+ parentPort.postMessage(composeHere(composePersonalWorkspace, binding))
516
+ } catch (error) {
517
+ parentPort.postMessage({ code: error instanceof PersonalWorkspaceRefusal ? error.code : 'personal-composition-unavailable' })
518
+ }
519
+ }