@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
package/docs/obsidian.md CHANGED
@@ -282,18 +282,30 @@ below is the shipped `atelier obsidian` usage text; `atelier obsidian --help`
282
282
  prints it. There are three steps, and none of them is done by hand in
283
283
  Obsidian.
284
284
 
285
- 1. Enable the projection in the project configuration. The member
286
- `ext["mnstry.atelier.obsidian"]` is an `atelier-obsidian-ext-settings/v1`
285
+ 1. Declare a view in the project configuration:
286
+ `atelier obsidian view add everything --all`. It adds a view of every note
287
+ the machine may show to `atelier.project.json`, shows the change as a diff
288
+ and how many notes the view would show, and makes it the default view. You
289
+ commit the file; Atelier commits nothing. See
290
+ [Adding a view](#adding-a-view). The member it writes,
291
+ `ext["mnstry.atelier.obsidian"]`, is an `atelier-obsidian-ext-settings/v1`
287
292
  document: `enabled: true`, `scopes` (each with a `scopeId`, a `mode` of
288
293
  `full`, `scoped` or `focus`, a `selector`, and for an expansion an explicit
289
- `depth` and `maxNodes`), and optionally `defaultScopeId`. A project without
290
- this member is `disabled (not-configured)` and nothing is published.
291
- `atelier obsidian scope list` shows what was declared.
294
+ `depth` and `maxNodes`), and optionally `defaultScopeId`. It can also be
295
+ written by hand. A project without this member is
296
+ `disabled (not-configured)` and nothing is published.
297
+ `atelier obsidian view list` (or `scope list`) shows what was declared.
292
298
  2. Set the private machine settings. They live outside every repository and
293
299
  are never committed:
294
- - `atelier obsidian audience set A,B` names the audiences this machine lets
295
- into a view. No audience is admitted by default, so a view is empty until
296
- one is set; `audience clear` empties it again.
300
+ - `atelier obsidian audience set me` lets "only you" into a view: every
301
+ audience of a note but `sensitive`, and the notes that carry no
302
+ classification, which only a vault that is yours alone shows.
303
+ `audience set A,B` names the audiences instead, and never shows notes
304
+ without a classification; `sensitive` is added by name
305
+ (`audience set me,sensitive`, which is such a list). No audience is
306
+ admitted by default, so a view is empty until one is set;
307
+ `audience clear` empties it again. See
308
+ [Notes without a classification](#notes-without-a-classification).
297
309
  - `atelier obsidian mode set manual` keeps every queued edit waiting for a
298
310
  person. `mode set automatic` is refused until an active automatic policy
299
311
  is installed.
@@ -302,11 +314,14 @@ Obsidian.
302
314
  unchanged; `policy revoke` returns the mode to `manual` and stops every
303
315
  later apply.
304
316
  3. Open a view:
305
- `atelier obsidian open --scope ID --consent-actor ID --adapter=obsidian-cli`.
317
+ `atelier obsidian open --scope ID --adapter=obsidian-cli`.
306
318
  That's it. Reaching the installed app is never a default, so
307
- `--adapter=obsidian-cli` is required, and the first open of a workspace
308
- records who allowed the maintenance service to run (`--consent-actor`;
309
- later opens reconnect to it and need no consent). `open` starts or
319
+ `--adapter=obsidian-cli` is needed the first time; the workspace remembers
320
+ it, and later runs need no flag. The first open of a workspace records who
321
+ allowed the maintenance service to run: for a person at a terminal, the
322
+ account's name; for a program, `--consent-actor ID`, which it must pass.
323
+ Later opens reconnect and need neither. `atelier obsidian settings` shows
324
+ what was remembered and how to change it. `open` starts or
310
325
  reconnects maintenance and asks it for a tick, makes Obsidian (version
311
326
  1.13.7 or later) know the view's vault as one of its vaults, opens it,
312
327
  publishes the view, verifies the vault by reading it back, and waits until
@@ -315,26 +330,227 @@ Obsidian.
315
330
  [What `open` does in each state of Obsidian](#what-open-does-in-each-state-of-obsidian).
316
331
 
317
332
  The maintenance service can also be managed on its own:
318
- `atelier obsidian service start --consent-actor ID --adapter=obsidian-cli`,
319
- `service status` and `service stop`; `service unit --print
320
- --adapter=obsidian-cli` prints a startup unit and installs nothing.
333
+ `atelier obsidian service start [--consent-actor ID] [--adapter=obsidian-cli]`,
334
+ `service status` and `service stop`; `service unit --print` prints the login
335
+ item that `service unit --install` would write, and installs nothing (see
336
+ [Start at login](#start-at-login)). The flags are needed only until the
337
+ workspace remembers them, as for `open`.
338
+
339
+ ### Adding a view
340
+
341
+ ```sh
342
+ atelier obsidian view add ID (--all | --folder PATH [--folder PATH ...] [--repo R] | --tag T) [--expand DEPTH:MAX] [--default] [--allow-empty] [--yes]
343
+ ```
344
+
345
+ - `--all` is a view of every note (`full`). `--folder` takes the notes under a
346
+ folder of a repository, written from the repository's root (`docs/notes`),
347
+ and can be given as often as needed; `--repo R` names the repository when
348
+ the project has more than one. `--tag T` takes the notes with a tag. Each of
349
+ these is a `scoped` view, and `--expand DEPTH:MAX` adds the notes they link
350
+ to, following links up to DEPTH steps and adding at most MAX notes.
351
+ - The first view declared is the default; `--default` makes a later one the
352
+ default.
353
+ - Before writing, the command counts the notes the view would show on this
354
+ machine, as the machine decided who may see (as "only you", with the notes
355
+ without a classification it shows, while nobody decided), and names how
356
+ many of the notes it selects are withheld: those without a classification
357
+ (a `kg` block) that the decision does not show, and those whose audience is
358
+ not admitted. A view that would show no note is refused with
359
+ `view-would-be-empty` and the counts; `--allow-empty` declares it anyway.
360
+ When the machine admits no audience at all (`audience clear`), the refusal
361
+ says so and names `atelier obsidian audience set me`.
362
+ - While nobody decided who may see, the maintenance service publishes no
363
+ note, whatever the counts say: the next step `view add` names is then
364
+ `atelier obsidian audience set me`, before `atelier obsidian open` (`next`
365
+ under `--json`, in order).
366
+ - A person at a terminal sees the change and the counts and is asked
367
+ `Write this change? [Y/n]`; `--yes` answers beforehand, and no answer within
368
+ 10 minutes writes nothing (`unanswered`). For anyone else the command is the
369
+ consent: the change is made, and printed (`diff` under `--json`).
370
+ - The file is rewritten only when it is in the form Atelier writes JSON in
371
+ (two-space indentation, LF or CRLF line ends, with or without a final one),
372
+ so the change is the new view and nothing else. A file in another form is
373
+ refused with `project-config-format-unknown`, and the member to add by hand
374
+ is in the refusal's `detail`. The file is replaced atomically, keeps its
375
+ mode, and only while it holds the bytes the change was made from
376
+ (`project-config-changed` otherwise). A link is not followed
377
+ (`project-file-not-regular`).
378
+ - The workspace pointer is only ever written into an ignored `.atelier-local/`.
379
+ When that folder is not ignored, the same change adds `.atelier-local/` to
380
+ the project's `.gitignore`, and shows it.
381
+ - A name that is taken is refused with `view-exists`. Removing a view is done
382
+ by hand for now.
383
+ - The contract's `type` selector matches a note's file type (`markdown`,
384
+ `html`), not its `kg.type`, so `view add` does not offer it; a view by
385
+ `kg.type` needs a contract change.
386
+
387
+ ### Notes without a classification
388
+
389
+ A note whose front matter carries no `kg` block (or that has no front matter)
390
+ is unclassified: the knowledge graph gives it the `private` audience, and by
391
+ default no view shows it. A vault that is only yours shows it: the audience
392
+ decision `only-you` with `unclassified: shown`, which `audience set me`
393
+ records. A list of audiences never shows one, even a list that names every
394
+ audience "only you" stands for, so a vault for anyone else can never carry
395
+ them.
396
+
397
+ A note whose labels cannot be known is never shown, in any vault: front matter
398
+ Atelier cannot read (a block scalar such as `description: |`, a value wrapped
399
+ onto a second line), front matter that is not plain `key: value` lines at its
400
+ top level (a quoted key such as `"kg":`, a one-line flow or JSON mapping, a
401
+ complex key), or any top-level `kg` key that is not a block. Such a note may
402
+ say `sensitive`, which "only you" leaves out. Only a note with no front
403
+ matter, or with front matter of plain top-level keys at the start of the line,
404
+ none of which is `kg`, counts as one without a classification. A plain key is
405
+ written with ASCII letters, digits, `_` and `-`, starting with a letter or
406
+ `_`: a note whose front matter uses a key with a space (`due date:`), other
407
+ letters (`título:`), a dot or a leading digit, or a quoted key, stays withheld
408
+ even when it holds nothing sensitive. Give it a `kg` block to show it.
409
+
410
+ In an "only you" vault such a note is shown only when its bytes read as a note
411
+ the way the emitter reads every note, so one file that cannot be published
412
+ (front matter that opens and never closes, closes at once, or carries a
413
+ trailing space on its first line; bytes that are not UTF-8) never stops the
414
+ vault: it stays withheld, as does a file that cannot be read. A shown note is
415
+ part of the view in every respect: links to it are links inside the view,
416
+ generated text may name it, the selection operation resolves it, and an edit
417
+ to it is applied to its source like any other.
418
+
419
+ A decision recorded before this release, "only you" with those notes
420
+ withheld, keeps them withheld until `audience set me` is run again. Changing
421
+ the decision rebuilds every view at the next tick (the eligibility revision
422
+ changes).
423
+
424
+ ### What this machine remembers
425
+
426
+ A workspace's machine settings (`atelier-obsidian-machine-settings/v2`, owner
427
+ only, under the private data root, never in a repository) hold what a person
428
+ decided once, so no later run asks again or has to be told again. Each
429
+ decision is null until it is made, and otherwise records what was decided,
430
+ when, by whom when that is known (an actor identifier), and how: answered at
431
+ a terminal (`question`), given on the command line (`command`), taken as the
432
+ defaults (`defaults`), or carried over from an earlier release (`v1`).
433
+
434
+ | Decision | Holds | Made today by |
435
+ | --- | --- | --- |
436
+ | `audience` | `only-you` or `custom`, and whether notes that carry no classification are `shown` or `withheld`. Only `only-you` may show them; a list of audiences always withholds them. The admitted list stays in `audienceAllow`, the engine's audience input | `audience set me` (only you, shown) or `audience set A,B` / `audience clear` (withheld) |
437
+ | `location` | the absolute folder that holds this workspace's vaults | `location set DIR` |
438
+ | `loginItem` | `on` or `off` | `service unit --install` (on); `service unit --remove` and `uninstall` (off) |
439
+ | `adapter` | `obsidian-cli` | `--adapter=obsidian-cli` given to `open`, `service start` or `service unit --install` |
440
+
441
+ Who allowed the maintenance service is not a second copy here: it stays the
442
+ consent the service reads from its own settings. `atelier obsidian settings`
443
+ shows all of it, and how each answer is changed.
444
+
445
+ Releases up to 0.2.0-alpha.12 wrote `atelier-obsidian-machine-settings/v1`.
446
+ Such a file is read as the v2 document it stands for, an audience list set
447
+ with it becoming that person's decision, and stays v1 on disk until the next
448
+ write, which writes v2. A release that knows only v1 refuses a v2 file as
449
+ `invalid-machine-settings`: after an upgrade, a maintenance service that
450
+ still runs the earlier release fails its ticks until it is replaced, which
451
+ `atelier obsidian open` and `atelier obsidian service start` do.
452
+
453
+ ### Start at login
454
+
455
+ Without a login item the vault is complete after a restart, but nothing keeps
456
+ it fresh until the next `open`. A login item starts the maintenance service
457
+ when you log in:
458
+
459
+ ```sh
460
+ atelier obsidian service unit --install [--consent-actor ID] [--adapter=obsidian-cli]
461
+ atelier obsidian service unit --print # what --install writes; writes nothing
462
+ atelier obsidian service unit --remove
463
+ atelier obsidian uninstall
464
+ ```
465
+
466
+ - On macOS it is a launchd agent,
467
+ `~/Library/LaunchAgents/ai.mnstry.atelier.<project>.<workspace-id>.plist`.
468
+ macOS says "Background Items Added" and lists it as "node" under System
469
+ Settings → General → Login Items & Extensions, because there is no app to
470
+ name it after. Switched off there, it stays off: `status` reports
471
+ `installed, switched off in System Settings`, and `open` starts the service
472
+ for that session only.
473
+ - On Linux it is a systemd user unit,
474
+ `~/.config/systemd/user/atelier-obsidian-<workspace-id>.service`
475
+ (`$XDG_CONFIG_HOME` when set), enabled with `systemctl --user`. Disabled
476
+ with `systemctl --user disable`, it stays disabled: `status` reports
477
+ `installed, disabled`, and `open` starts the service for that session
478
+ only. Where no user instance of systemd answers (WSL, a container), the
479
+ answer is `login-item-unavailable` and nothing is left behind.
480
+ - Windows is not offered (`startup-platform-unqualified`), as publication
481
+ itself is refused there.
482
+
483
+ It runs the package installed in the project
484
+ (`<project>/node_modules/@mnstry/atelier/…`, named by that path, so the next
485
+ start runs whatever release the project has installed then), with the Node
486
+ that installed it and the search path you had, in the root folder. It names
487
+ the data folder and the workspace it was installed for, so it finds them at
488
+ login whatever variables your shell set. Run from
489
+ `npx` in a project that has no package installed, it refuses with
490
+ `login-item-needs-installed-package`: install `@mnstry/atelier` in the project
491
+ first. Installing records that you allowed the service to run at login (the
492
+ consent then covers startup): `--consent-actor ID` names who allows it; for a
493
+ person at a terminal, the actor already recorded for this workspace, or else
494
+ the account's name. A program that passes no `--consent-actor` for a
495
+ workspace whose consent covers the service alone is refused
496
+ (`startup-consent-required`). The answer is remembered as the `loginItem`
497
+ decision. `--remove` takes the consent back to the service alone, removes the
498
+ item, and starts the service again for this session only, as a process of its
499
+ own (with the adapter given or remembered; otherwise the next `open` starts
500
+ it). Each workspace has its own login item, port, log and consent, and
501
+ projects may pin different releases; `launchctl list | grep ai.mnstry.atelier`
502
+ lists every one on a Mac.
503
+
504
+ Once a login item is installed, the service is started through it: `service
505
+ start`, `open` and the replacement of an outdated service ask launchd or
506
+ systemd to start it, never start a second one beside it, and accept only the
507
+ service that proves itself with the installed entry. When the unit differs from
508
+ what would be written now (the Node it names was removed, say), the next start
509
+ writes it again and says `login item refreshed`. A refusal of the service at
510
+ login (another service already runs, or the consent does not cover startup)
511
+ ends the process cleanly, so it is not retried every minute, and `status` says
512
+ `the login item did not start the service: <code>` with the next step. A crash
513
+ is restarted after a minute.
514
+
515
+ `uninstall` removes the login item and stops the service. It keeps the vaults,
516
+ the private state, the project file and Obsidian's vault list as they are, and
517
+ prints where each is: every vault under the data root and every folder
518
+ allocated to a view where the workspace decided its vaults live, a view no
519
+ longer declared included. It does not start the service again.
321
520
 
322
521
  After an upgrade of Atelier, a maintenance service started earlier still runs
323
- the earlier release. `open` replaces it: when the service of this workspace
324
- proves itself Atelier's own but runs an earlier release than the installed one
325
- (its record names the release it runs: the package version and a digest of
326
- its modules; an earlier version, the same version with other modules, or no
327
- release named), or refuses a tick that names a view as releases up to
328
- 0.2.0-alpha.11 do,
329
- `open` stops it as `service stop` would and starts the installed release under
330
- the consent already recorded, and says `service: restarted (outdated)`. A
331
- service of a later release is never replaced by an earlier one, so two
332
- installations used on one workspace (a global and a project-local one, say)
333
- do not replace each other's service on every open: `open` answers
334
- `service-unavailable` / `service-other-release`; run `atelier obsidian service
335
- stop`, then open again, or open with the later release. A service in a long
336
- tick is not stopped (`open` answers `busy`), and a listener that does not
337
- prove itself this workspace's service is never touched.
522
+ the earlier release. One the login item started notices it: after each tick
523
+ it compares the package on disk (read through the path the item names, so a
524
+ re-pointed link or a `file:` install counts) with the release it started
525
+ with, and when they differ it exits after that tick with code 75, and its
526
+ service manager starts the new release within a minute. Otherwise `open` and
527
+ `service start` replace it: when the service of this workspace proves itself
528
+ Atelier's own but runs an earlier release than the installed one (its record
529
+ names the release it runs: the package version and a digest of its modules;
530
+ an earlier version, the same version with other modules, or no release
531
+ named), or refuses a tick that names a view as releases up to 0.2.0-alpha.11
532
+ do, they stop it as `service stop` would and start the installed release under
533
+ the consent already recorded (through the login item when there is one), and
534
+ say `service: restarted (outdated)`. The installed release is read from the
535
+ package of the entry that would be started, so with a login item it is the
536
+ project's own package. A service of a later release is never replaced by an
537
+ earlier one, so two installations used on one workspace (a global and a
538
+ project-local one, say) do not replace each other's service on every open:
539
+ `open` answers `service-unavailable` / `service-other-release`, and `service
540
+ start` answers that it runs, with `release: later`; run `atelier obsidian
541
+ service stop`, then open again, or open with the later release. A service that
542
+ does not answer health in time, in a stretch of a tick that does not yield, is
543
+ not stopped (`open` answers `busy`), and a listener that does not prove itself
544
+ this workspace's service is never touched. While a view is being prepared and
545
+ published the service does answer: `open` asks it for a tick that starts after
546
+ the one in flight and waits for that tick, two minutes at most unless
547
+ `atelier obsidian open --wait-ms MS` sets another bound, before it reports the
548
+ view as it is (one that read `current` is reported `updating`, with the reason
549
+ `tick-still-running`, while that tick has not ended); and `service stop` is
550
+ accepted and ends the
551
+ service after the tick in flight or after its shutdown grace, whichever comes
552
+ first. A publication cut short that way is settled by the publisher's restart
553
+ recovery at the next one.
338
554
 
339
555
  The first time Obsidian opens a view's vault it asks "Do you trust the author
340
556
  of this vault?", because every vault Atelier publishes carries Atelier's own
@@ -377,13 +593,79 @@ proposals list` and `proposals show OPERATION` read them, and nothing applies
377
593
  one. A body edit to a note whose source uses CRLF line endings is also a
378
594
  proposal, never an apply, because the app normalizes line endings on save.
379
595
 
380
- The vault itself lives under the private data root, outside every repository:
381
- `~/Library/Application Support/Atelier` on macOS, `$XDG_DATA_HOME/atelier`
382
- (else `~/.local/share/atelier`) on Linux and `%LOCALAPPDATA%\Atelier` on
383
- Windows, under `obsidian/<workspace-id>/`. `--data-root DIR` names another
384
- absolute directory. Private state inside an enrolled repository is refused.
385
- Publication is proven on macOS arm64 only and is refused on Windows; see
386
- [Known limits](#known-limits).
596
+ Atelier's private state lives under the private data root, outside every
597
+ repository: `~/Library/Application Support/Atelier` on macOS,
598
+ `$XDG_DATA_HOME/atelier` (else `~/.local/share/atelier`) on Linux and
599
+ `%LOCALAPPDATA%\Atelier` on Windows, under `obsidian/<workspace-id>/`.
600
+ `--data-root DIR` names another absolute directory. Private state inside an
601
+ enrolled repository is refused. Publication is proven on macOS arm64 only and
602
+ is refused on Windows; see [Known limits](#known-limits).
603
+
604
+ Where the vaults are is decided per workspace. Until a person decides it, a
605
+ view's vault is under the data root too, in `vaults/<view>`, as in every
606
+ earlier release. `atelier obsidian location set DIR` decides a visible folder
607
+ instead (`~/Atelier`, say). Each view that has not been published yet is then
608
+ given its own folder there at its first publication, named
609
+ `<project> (<view>)`: `~/Atelier/harbor-notes (everything)`. That name is also
610
+ the vault's name in Obsidian's vault switcher, so it is readable and unique
611
+ across projects and views. The first name that is free is taken, with a number
612
+ inside the parentheses when needed (`harbor-notes (everything 2)`): free means
613
+ nothing on the disk has it, and no vault Obsidian lists has it in any letter
614
+ case. The folder is created private to this user (mode 0700), and so is `DIR`
615
+ when Atelier creates it; a missing folder on the way to `DIR` is made as any
616
+ other folder is. The allocation is recorded in the workspace's private
617
+ state (`state/allocations/<view>.json`) and is never recomputed: renaming the
618
+ project or deciding another folder later moves no vault, and a vault published
619
+ before under the data root stays there. `location show` says where each
620
+ view's vault is, or will be, or why its record cannot be read.
621
+
622
+ Atelier publishes only into the folder it made. The allocation records the
623
+ folder's real path, device and inode, and the maintenance service checks them
624
+ at every tick, as the publisher does before it writes anything and again
625
+ before each note and before its commit (see Known limits). Another folder
626
+ put where the one made was (moved in, or a link) is not published into
627
+ (`vault-allocation-replaced`), nor is the same folder reached through a link
628
+ put on the way since, into a vault the app lists, say
629
+ (`vault-allocation-moved`). A folder that has gone is made again at the next
630
+ tick, only where its recorded real path leads and only where a location would
631
+ be accepted (not inside a repository, the private state, a vault Atelier
632
+ publishes or one the app lists, and on the data root's volume). A vault
633
+ removed, moved or replaced while it is being published stops that publication
634
+ (`vault-allocation-missing`, `vault-allocation-replaced`, or
635
+ `vault-root-moved` for a vault under the data root). Once the folder is put
636
+ back, or made again, the running service publishes into it again at its next
637
+ tick; no restart is needed. One case waits for the next change at the view's
638
+ sources: a folder made again after a publication that stopped just before its
639
+ commit. That generation is finished by restart recovery, its notes went with
640
+ the folder that went away, and the view reads `vault-note-missing` until then. A
641
+ view that was published and whose vault Atelier cannot find is refused
642
+ (`vault-allocation-lost`) rather than published again into a new, empty vault
643
+ under the data root: its record was lost, or, for a view that never had a
644
+ record (published under the data root), its `vaults/<view>` folder was
645
+ removed. Restore the record from a backup if its vault was allocated
646
+ elsewhere; otherwise make the folder `vaults/<view>` under the data root, and
647
+ the view is published there at the next change at its sources. `status` and
648
+ `open` name that next step for this code, and for `vault-allocation-moved`,
649
+ `vault-allocation-replaced` and `vault-allocation-missing`.
650
+
651
+ `DIR` is refused inside an enrolled repository or the project, inside a vault
652
+ Atelier publishes, inside a folder Obsidian lists as a vault, and on another
653
+ volume than the data root (a note is published by an atomic exchange, which
654
+ cannot cross volumes). A folder a sync client keeps in step (iCloud Drive, a
655
+ cloud storage provider, Dropbox, OneDrive, Google Drive, or a Desktop or
656
+ Documents folder iCloud syncs) is refused unless `--allow-synced-location` is
657
+ given: another machine's Obsidian could hold a vault there unseen. A folder
658
+ macOS protects (Desktop, Documents, Downloads) is accepted with a warning,
659
+ because macOS asks before Obsidian or the maintenance service may read it.
660
+ `~/` is read against the account's home folder, which a shell does not do
661
+ after `--x=`. Each check compares the folder as written and as its real path,
662
+ in any letter case, so a link into a listed or synced vault is refused as
663
+ that vault is. Obsidian's vault list is read through the app when it runs and
664
+ answers, and otherwise from its settings file, never written. When the list
665
+ cannot be read, `location set` says so and accepts the folder, and the
666
+ maintenance service allocates no vault there until it can read the list
667
+ (the view is `stale`, `app-vault-list-unreadable`, and is tried again at
668
+ the next tick).
387
669
 
388
670
  ### What `open` does in each state of Obsidian
389
671
 
@@ -415,28 +697,97 @@ exactly because the folder is listed.
415
697
  reopens every vault it had open, this one included, and once its command
416
698
  line answers (the app itself, not the tool's "unable to find Obsidian")
417
699
  `open` hands it the vault's link through that tool, never through the
418
- operating system. On Linux a plain start is not
700
+ operating system. A vault Obsidian already lists but had closed is not
701
+ reopened by a plain start, so if Obsidian refuses that link just after it
702
+ started, `open` hands it the link once more, again through that tool and
703
+ never through the operating system, before it gives up. On Linux a plain start is not
419
704
  qualified yet, and Obsidian is started with the link (the other vaults'
420
705
  reopen marks are lost there; a known limit).
421
- An Obsidian that never ran on this account has no settings file yet, and
422
- `open` does not create one: start Obsidian once, then open again.
706
+ In the same write, `open` turns Obsidian's command line on (`cli: true`)
707
+ when it is off: publishing into a vault Obsidian holds needs it until
708
+ Atelier's plugin takes over that part, and it is off in a new installation.
709
+ The answer says what was written and where the backup of the file as it
710
+ was is kept.
711
+ - **Obsidian is installed and never started** (no `obsidian.json` yet). When
712
+ the process table says, positively, that no Obsidian runs, `open` creates
713
+ the file, with this vault flagged open and the command line on, and starts
714
+ Obsidian plainly; Obsidian opens the vault. Nothing else is in the file,
715
+ and the settings folder is created private to you when it does not exist
716
+ yet. The file is created exclusively: if Obsidian writes its own first,
717
+ that one is kept and `open` says so. While an Obsidian may run, nothing is
718
+ created (`app-may-be-running`).
423
719
  - **Obsidian runs with no vault open.** Its command line then answers every
424
720
  command with "Vault not found.", so nothing can be asked of it, and its
425
721
  settings file belongs to the running app, so it is only read. A vault the
426
722
  app already lists is opened by path and published through the app as
427
- above. A vault it does not list yet is answered as outcome
428
- `app-version-unsupported` with reason `no-vault-open`: open any vault in
429
- Obsidian, or quit it, and open again. This is the one state of a running
430
- Obsidian that `open` cannot get through alone.
431
- - **Obsidian's command line is turned off** (the default of a new
723
+ above. For a vault it does not list yet, see "Restart, only when you ask"
724
+ below; without that, `open` answers `app-version-unsupported` with reason
725
+ `no-vault-open` and names `--restart-obsidian`.
726
+ - **Obsidian runs with its command line turned off** (the default of a new
432
727
  installation). It then answers every command with "Command line interface
433
- is not enabled", and only a link reaches it. With Obsidian running, `open`
434
- adds and launches nothing. With Obsidian quit, `open` adds the vault to its
435
- settings and starts it, and Obsidian opens the vault. Either way `open` then
436
- answers `app-cli-unavailable` with reason `cli-turned-off` and says where to
437
- turn it on: Settings > General > Advanced > Command line interface.
438
- Publishing into a vault Obsidian holds needs it until Atelier's plugin takes
439
- over that part.
728
+ is not enabled", only a link reaches it, and its settings belong to the
729
+ running app, which keeps the switch in memory and would write it back. See
730
+ "Restart, only when you ask" below; without that, `open` adds and launches
731
+ nothing, and answers `app-cli-unavailable` with reason `cli-turned-off`,
732
+ naming `--restart-obsidian` and where to turn the switch on yourself:
733
+ Settings > General > Advanced > Command line interface.
734
+
735
+ **Restart, only when you ask.** In the two states where Obsidian runs but
736
+ `open` cannot reach it (its command line off, or no vault open and the view's
737
+ vault not in its list), `atelier obsidian open --restart-obsidian` lets `open`
738
+ restart Obsidian once:
739
+
740
+ 1. Before anything is sent, it checks, reading only, that the write the
741
+ restart is for can succeed: the view's vault folder exists, Obsidian's
742
+ settings file can be read and is yours, is no link and has no second
743
+ name, is not a Flatpak or snap build's, is not too large to take the
744
+ vault, and lists no vault at a folder above the view's. An Obsidian that
745
+ has shown only its starter window has written no settings file yet; then
746
+ the folder it goes in must be yours (or be missing inside a folder of
747
+ yours), and the file is created after the quit, as for an Obsidian that
748
+ never started. Otherwise `open` answers that refusal and leaves Obsidian
749
+ running. Right before the first signal, once the process below is proven,
750
+ it keeps a copy of the settings file beside it
751
+ (`obsidian.json.atelier-backup-<time>`), which this run's write does not
752
+ prune, and the answer names it; a copy that cannot be kept sends nothing.
753
+ 2. It sends SIGTERM to Obsidian's main process, and to nothing else. That
754
+ process is found in the process table and must be the only Obsidian main
755
+ process there, one of yours, named by the app bundle's path, with every
756
+ other Obsidian process its child; it is read again just before the signal
757
+ and must be the same process, yours, under the same parent. Anything else
758
+ (two Obsidians, another user's, a helper of another process, a table that
759
+ cannot be read) is answered `app-main-process-unproven`, with the reason in
760
+ `restart.detail`, and nothing is sent.
761
+ 3. Obsidian handles that SIGTERM as a quit: it closes every window, keeping
762
+ the vaults it had open flagged to reopen, and its helper processes end. On
763
+ macOS its main process can then stay, with no window. Electron handles
764
+ only the first SIGTERM as a quit: a second one ends the process at once,
765
+ without its quit handlers. `open` sends that second SIGTERM only when the
766
+ same process has shown no window and no helper at every reading for at
767
+ least 5 seconds (a margin, not a proof that it has nothing left to save). There
768
+ is never a third signal, and never a stronger one: an Obsidian that is not
769
+ gone within 30 seconds is answered `launch-failed` with reason
770
+ `app-did-not-quit`, and nothing is written. The answer says how many
771
+ signals were delivered.
772
+ 4. It adds the vault to Obsidian's settings and turns the command line on, in
773
+ the one write described above.
774
+ 5. It starts Obsidian plainly, and the vaults it had open come back, with this
775
+ one beside them. If the vault could not be added after all, Obsidian is
776
+ still started again, as it was.
777
+
778
+ The answer says that Obsidian was restarted, which process was signalled,
779
+ how, and what was written. The flag is never implied by any other flag or
780
+ answer; every other operation refuses it. The restart is qualified on macOS
781
+ only (on Linux a quit Obsidian is started with a link, which would drop the
782
+ other vaults' reopen flags): elsewhere `open` answers
783
+ `restart-platform-unqualified`.
784
+
785
+ Two limits. `open` asks nothing at a terminal yet: the restart needs the flag
786
+ there too, until the first-run command asks "Obsidian must restart once to add
787
+ this vault … Restart now?" in that state. And if `open` itself is interrupted
788
+ (Ctrl-C, or its process ended) after Obsidian quit and before it was started
789
+ again, Obsidian stays quit: start it yourself, and it reopens its vaults; run
790
+ `open` again to add the view's vault.
440
791
 
441
792
  Only `open` adds a vault to Obsidian: the maintenance service never does, so
442
793
  it never opens a window nobody asked for. A declared view that was never
@@ -464,6 +815,19 @@ the view stays refused until the list names the folder once.
464
815
  While one entry of the folder has a window, `open` reaches only that one and
465
816
  never opens another entry of the same folder beside it.
466
817
 
818
+ A view's first publication into the folder allocated for it is the one
819
+ exception to the rule below. While no list the app keeps names that folder, a
820
+ folder above it or one inside it, the vault is filled even while Obsidian
821
+ runs, by creating files only. When that run completes, `open` adds the vault
822
+ to Obsidian and opens it after the commit. A run that stops partway commits
823
+ nothing and falls back to the coordinated path. That happens when the list
824
+ names the folder meanwhile, a settings file with other bytes is already
825
+ there, or an interrupted run left a replacement or a removal. `open` may then
826
+ add and open the vault before it is complete, and the app publishes the rest.
827
+ Anything that would replace or remove a file waits for the app. See "First publication into a vault no Obsidian lists" in
828
+ [obsidian-contract.md](obsidian-contract.md). The plugin's entry written this
829
+ way counts as offered until the plugin says hello from the vault.
830
+
467
831
  The publisher still writes into a vault only when it can coordinate with every
468
832
  Obsidian that may hold it, or when the process table shows, positively, that
469
833
  none runs. Otherwise it stops, and the view reports `publisher-conflict` with
@@ -500,6 +864,419 @@ command-line interface is turned on). So is a listed vault the command line
500
864
  does not answer for once it is opened, while only the plugin reports the
501
865
  version: `open` says so at once instead of waiting for the app.
502
866
 
867
+ ## Personal workspaces
868
+
869
+ A personal workspace (see [personal-workspace.md](personal-workspace.md)) is
870
+ projected through one route, `bindPersonalWorkspace({ personalHome,
871
+ generationId })` in `src/projection/obsidian/personal-workspace.mjs`. Both
872
+ arguments are explicit; neither is discovered from the environment or the
873
+ working directory. A loader uses `loadBoundProject` with the same arguments,
874
+ or `loadBoundProjectOffThread`, which composes in a worker thread; the engine
875
+ awaits its loader. The command, the service and the login item bind a personal
876
+ home through `--personal-home`, which follows the generation the person
877
+ confirmed there (see "A confirmed selection", below).
878
+
879
+ - **One graph.** The binding is attached to the generation's resolved
880
+ `atelier.project.json` under one symbol key. When a project carries it, the
881
+ pipeline's `buildGraph` builds the canonical graph with the caller's file
882
+ cache and composition's own rule that no private note is a link target, then
883
+ applies eligibility as for any project. The engine, view counts, source apply,
884
+ the proposal adapter and the selection operation all build through it, so they
885
+ read one graph. A plain build of the generation's configuration would differ:
886
+ there, a shared note's link can name a private note.
887
+ - **Only a confirmed graph is used.** A graph is returned only under a validity
888
+ key that `composePersonalWorkspace` confirmed. The key names:
889
+ - the authored manifest and overlay, as regular files of at most 1 MiB;
890
+ - every file of the generation, as regular files of at most 8 MiB (the
891
+ personal-workspace module's own limits);
892
+ - the private home's own state, any `.git` above it, and a `.git` in its
893
+ `generations` folder;
894
+ - each enrolled root's link status and its Git configuration (its remote and
895
+ its helpers), through a `gitdir:` file and its common directory where `.git`
896
+ is a file;
897
+ - the user's Git configuration (`~/.gitconfig` and the XDG one), through a
898
+ link, and any ambient Git variable;
899
+ - a digest of the graph's facts: nodes, edges, embeds, assets, resolved links
900
+ and every finding.
901
+
902
+ The inputs are read before the composition runs. A key is confirmed only when
903
+ the composition then accepted the inputs and composed a graph with exactly
904
+ those facts, so every graph used is one the composition built too. A single
905
+ change that lands while it runs is either seen by it or leaves the key behind;
906
+ a change and its exact undo during one composition is caught at the next full
907
+ reconciliation. Binding confirms the first key. An authored or generation
908
+ file the key reads is opened without following a link and without waiting on
909
+ it. A FIFO, a device or a link refuses at once, with the composition's code
910
+ (`generation-corrupt`, `malformed-input`). A Git configuration file is read
911
+ through a link, as Git reads it, but never waited on.
912
+
913
+ When a build finds its key unconfirmed, a caller that builds once composes in
914
+ its own thread. The engine, and the proposal adapter it runs, compose in a
915
+ worker, off the event loop, then build again. The adapter builds only when it
916
+ looks at a newly observed edit, as for any project. On a tick whose graph is
917
+ pending (`personal-validation-pending`), it records nothing for the edits it
918
+ reached and reports `observed.deferred`. It offers those edits, and every
919
+ earlier refusal, again next tick, and leaves a due proposal operation as it
920
+ is, without spending one of its attempts. While shared sources change before
921
+ every tick, every tick is pending: the edit is deferred again, with one full
922
+ build per tick, until a tick with no change. A worker that does not answer
923
+ within two minutes is let go of and refuses `personal-composition-unavailable`,
924
+ so the tick does not wait. A worker blocked in a system call (a FIFO opened for
925
+ reading) cannot be stopped until that call returns. Until then it keeps the
926
+ process from exiting, and each such expiry leaves one thread behind: a service
927
+ stopped in that state needs a kill. A graph that still differs refuses
928
+ `personal-graph-unconfirmed`.
929
+ - **A copy is refused.** The settings member names the bound generation. A copy
930
+ of a bound project that lost the binding (a structured clone, a JSON round
931
+ trip) refuses `personal-binding-lost`, and is never built as an ordinary
932
+ project.
933
+ - **Validity.** Composition decides whether the generation may be used, at bind
934
+ time and at every build. Its refusals keep their codes (`stale-generation`,
935
+ `generation-missing`, `generation-corrupt`, `generation-relocated`,
936
+ `retained-removed-reference`, `not-private-location`, `root-symlinked`,
937
+ `ambient-git-environment`, `future-schema`, `ambiguous-binding` and the rest).
938
+ `bindPersonalWorkspace` throws them as `PersonalWorkspaceRefusal`.
939
+ `loadBoundProject` and every build refuse with the same code as a maintenance
940
+ refusal, so the engine and every command report it typed, and no view stays
941
+ current. Nothing is published, and no vault, manifest, journal or private home
942
+ byte is written. A refusal the projection adds (`personal-view-id-reserved`,
943
+ `personal-views-exceed-settings-limit`) is a maintenance refusal from the
944
+ start.
945
+ - **Staleness is noticed at the next tick.** The engine observes the person's
946
+ `atelier.personal.json` and `atelier.overlay.json` and the bound generation's
947
+ `generation.json` as configuration. A change to any of them prepares every
948
+ view again on the next tick, unasked, and a generation that no longer holds
949
+ is refused there. Every full reconciliation composes a bound workspace again,
950
+ in a worker, whatever else it prepares. A cause the key does not name is
951
+ therefore refused within the reconciliation interval (five minutes by
952
+ default). Those causes are:
953
+ - the system Git configuration, and files a Git configuration includes
954
+ (`include.path`, `config.worktree`);
955
+ - an enrolled repository's work tree or top level changing;
956
+ - a change of the authored inputs and its exact undo during one composition;
957
+ - a shared note replaced by a link;
958
+ - an untracked note becoming ignored.
959
+
960
+ The views are prepared again only when that composition refuses or confirms a
961
+ different key: an unchanged workspace is not rebuilt, republished or locked.
962
+ After a refusal the engine
963
+ loads the project again at the next tick, so a loader that binds the next
964
+ generation is followed. The generation's record and the settings member both
965
+ name the generation, so a rebound generation invalidates every view even when
966
+ its configuration file is byte-identical.
967
+ - **Views.** The overlay's saved views become scopes in memory: each is a
968
+ scoped view of the repositories it names, and `everything` is a full view of
969
+ the composed graph. A saved view named `everything` is refused. More than 255
970
+ saved views, or a view of more than 256 repositories, is refused at bind
971
+ (`personal-views-exceed-settings-limit`), within the Obsidian settings
972
+ contract. The preferred view, when it names a saved one, is the default. No
973
+ generation file is written. `view add` refuses `views-from-personal-overlay`;
974
+ `audience set`, `audience clear` and `location set` refuse
975
+ `personal-binding-decision-unavailable`. Each refuses before anything is
976
+ resolved or written.
977
+ - **Preferences and coverage.** `preferences` are returned read only. A theme
978
+ is reported and never written: `appearance.json` stays the person's.
979
+ `coverage` is passed through untouched. No Obsidian code reads it: it states
980
+ what is not enforced, and is never permission.
981
+ - **Workspace identity.** The bound project's folder is the private home, so
982
+ the workspace pointer is `<personal home>/.atelier-local/obsidian.json`. A
983
+ generation is a closed, verified inventory, and an extra file there would
984
+ invalidate it. This is provisional until the per-person projection
985
+ configuration exists.
986
+ - **Edits.** A vault edit to a private note is never applied. Source apply
987
+ refuses it with `personal-overlay-proposals-only`. The proposal adapter does
988
+ not observe, record or propose it either: it answers
989
+ `personal-overlay-not-proposed`. Nothing is written into a generation, and
990
+ the view stays
991
+ `held-for-your-edit` until the edit is undone. Turning such an edit into a
992
+ change of the person's overlay is not built.
993
+ - **Reserved settings key.** The settings member
994
+ `ext["mnstry.atelier.personal-workspace"]` is reserved: the binding writes it
995
+ in memory. A project configuration that declares it itself is refused at
996
+ every build (`personal-binding-lost`, naming the key).
997
+
998
+ ### A confirmed selection
999
+
1000
+ `--personal-home DIR`, given instead of `--project` to `atelier obsidian`, to
1001
+ the service entry and in a login item's unit, binds the personal home DIR
1002
+ (`src/runtime/obsidian/personal-selection.mjs`). Without it, every argument,
1003
+ unit and loader is what it was.
1004
+
1005
+ - **What is loaded.** The generation the person last confirmed with
1006
+ `selectPersonalGeneration` (see [personal-workspace.md](personal-workspace.md)),
1007
+ and nothing else. The loader reads the head of the selection history from
1008
+ the history alone (`readPersonalSelectionHead`), loads the generation it
1009
+ names through the route above (in a worker for the service, in the
1010
+ command's thread for a command), then reads the head again. A head that
1011
+ moved meanwhile refuses `personal-selection-changed`. Nothing here confirms
1012
+ a selection or chooses one for the person.
1013
+ - **Nothing confirmed publishes nothing.** No record refuses
1014
+ `nothing-selected`; a confirmed generation whose inputs were edited and not
1015
+ confirmed again refuses `stale-generation`; a history whose hash chain does
1016
+ not hold refuses `selection-history-corrupt`; a history that ends at an
1017
+ earlier record than the running service has read refuses
1018
+ `selection-history-shortened`; a home whose history cannot be read refuses
1019
+ `personal-home-unavailable`, and a home that is gone or not
1020
+ private refuses with the module's own code (`root-missing`,
1021
+ `not-private-location`, `root-symlinked`). Every other refusal of the
1022
+ composition keeps its code. Nothing falls back to another generation or to
1023
+ the project without a binding. The vault keeps what it last published from
1024
+ a confirmed generation. `status`, `open` and `service start` load the
1025
+ selection and refuse with its code; the service's freshness shows every view
1026
+ `stale` with it, and its status document has it as the last tick's reason.
1027
+ `selection-history-shortened` comes only from a process that read the
1028
+ history longer, which is the running service: a command of the command line
1029
+ reads the history in a process of its own, as it then stands, so `status`
1030
+ reports the selection a cut history ends with while the service refuses it.
1031
+ A confirmed selection is reported by `status` (`selection`: the generation
1032
+ and the number of its record).
1033
+ - **The service starts whatever the selection.** A bound project's folder is
1034
+ its private home, so the home alone locates the workspace
1035
+ (`locatePersonalHome`): the service resolves its workspace from that at once
1036
+ and starts, and its engine awaits the loader at each tick. Started by a login
1037
+ item, the service asks the home, not the loader, for its workspace and log.
1038
+ `service status` and `stop`, the ticks `open` asks for, `service unit
1039
+ --remove` and `uninstall` locate the service the same way. When no project
1040
+ loaded (a restart while nothing is confirmed), the engine locates the
1041
+ workspace too, so each view published before says why nothing is published
1042
+ now instead of keeping the previous run's `current`. A service given a loader
1043
+ that answers a promise and no locator is still refused at start with
1044
+ `service-loader-not-synchronous`, and so is one whose locator answers a
1045
+ promise; a located value that names no folder refuses
1046
+ `service-locator-invalid`. The workspace pointer is under the home, so a home
1047
+ that is gone when the service starts leaves it no workspace to find
1048
+ (`service-workspace-not-prepared`).
1049
+ - **Following the history.** A loaded project is pinned to the record it was
1050
+ loaded under, and the engine observes that record and the place of the next
1051
+ one as configuration. A confirmation is therefore loaded at the next tick. A
1052
+ history cut short is seen at the next tick too, and a running service does
1053
+ not follow it back. Selections are only ever appended, so a history that
1054
+ ends at an earlier record than the service has read it ending at was cut by
1055
+ something other than a confirmation, and following it back could publish
1056
+ what nobody confirmed after the records removed: cut after a restore nobody
1057
+ confirmed, it would publish that restore. The service notes the highest
1058
+ record number at every read of the history's end (a load's first read, its
1059
+ read once the generation is loaded, the recheck at a full reconciliation,
1060
+ and a read made when a tick stops a view as `mixed-read` because the tick's
1061
+ settings changed or could not be read, once for views stopped one after
1062
+ another), whether or not the
1063
+ record's generation could be used, and whichever part of the service read
1064
+ it. A history that ends earlier refuses `selection-history-shortened` at a
1065
+ load and at a recheck, and nothing is loaded from it while it is shorter; at
1066
+ the read made when a tick stops a view, the view says `mixed-read` at that
1067
+ tick and the code at the next. A project already loaded is loaded again at
1068
+ the next tick once any part of the service has read the history ending at a
1069
+ later record than the one the project was loaded under, so a confirmation
1070
+ read elsewhere in the service and removed before the engine looked refuses at
1071
+ that tick; otherwise a project already loaded is held to this at its next
1072
+ recheck, at the latest the next full reconciliation, and until then it
1073
+ publishes nothing new under another record, and a change at its sources
1074
+ brings the recheck forward. A record added and removed between two of those
1075
+ reads was never read, and does not count: while a load of its own is under
1076
+ way, the plugin's status read compares the files that decide its views and
1077
+ reads no history, so a record it sees then only as a changed file is not
1078
+ noted. The views say why (`open` and
1079
+ `status --json` name the ways out), and the vault keeps what it last
1080
+ published.
1081
+ - **What ends that refusal.** The removed records put back; a history as long
1082
+ again, since a record in a place already read, with other content, is read
1083
+ as a confirmation (after a cut of one record the person's next confirmation
1084
+ is followed; after a cut of several, one confirmation is not enough); or a
1085
+ start of the service. The number is kept in the process only, so a service
1086
+ started after the cut cannot tell a cut history from one never longer: it
1087
+ reads the history as it then stands and follows what it ends with,
1088
+ publishing it if its generation still holds. Every start counts, the
1089
+ person's own and one at login, after an upgrade or after a failure. Stopping
1090
+ and starting the service is therefore how a person accepts a shorter
1091
+ history, and a start nobody chose accepts it too. Nothing here keeps the
1092
+ number across a start.
1093
+ - **What the history is checked for besides.** At every full reconciliation the
1094
+ history is read again, so a record before the last that no longer links, or
1095
+ a confirmation that landed before the engine first looked at the record,
1096
+ refuses with its code within the reconciliation interval. A confirmation
1097
+ during a tick changes an observed file: the view being prepared or published
1098
+ refuses `mixed-read` at its next wait, before its generation commits (one
1099
+ already committed stays), so do the views after it, and the next tick
1100
+ publishes under the new record. While the last load was refused, every tick
1101
+ loads again: with nothing confirmed only the history is read, and with a
1102
+ confirmed generation that no longer holds, one composition runs in a worker
1103
+ per tick.
1104
+ - **A narrowing.** When the newly confirmed generation drops a source (a
1105
+ repository withdrawn, a view narrowed), each view follows the publisher's
1106
+ rule for notes that leave a view; this binding adds no rule. At the view's
1107
+ next publication a generated note of the dropped source leaves the vault and
1108
+ its bytes are moved to private recovery. A note the person edited, once the
1109
+ engine has queued the edit, holds the whole view at the generation it last
1110
+ published (`held-for-your-edit`, `publication-withheld-for-your-edit`): the
1111
+ note is neither removed nor overwritten. A note edited during the
1112
+ publication itself stays in the vault (`retained-edit`).
1113
+ - **Who may see.** The binding never decides it. It stays this machine's
1114
+ audience setting, none by default, and `audience set` still refuses for a
1115
+ bound project: until an audience is allowed some other way, a bound vault
1116
+ shows no note. Deciding who may see a bound vault is not built.
1117
+ - **Reading the history on the service's event loop.** The head is one
1118
+ directory listing and one bounded read per record. A record that is not a
1119
+ regular file (a FIFO, a device) refuses `selection-history-corrupt` without
1120
+ being waited on. A home that becomes unreadable while the engine holds a
1121
+ project (its permissions changed, not removed) fails the tick when the
1122
+ engine observes its files, as an unreadable configuration file does: nothing
1123
+ is published, and the service reports the failed tick.
1124
+
1125
+ Measured once on 2,156 notes in three repositories, with 50 annotations and five
1126
+ saved views (six views in all). The production contributions were loaded, as
1127
+ the service loads them, so the proposal adapter was registered. The host's load
1128
+ average was near 35, slower than an idle one, so read the times as upper
1129
+ bounds. "Main-thread compositions" counts the Git probe a composition makes on
1130
+ the event loop's thread.
1131
+
1132
+ | Step | Wall time | Longest event-loop block | Main-thread compositions |
1133
+ | --- | --- | --- | --- |
1134
+ | Load, composed in a worker (`loadBoundProjectOffThread`) | 3.8 s | 0.03 s | none |
1135
+ | Load in the calling thread (`loadBoundProject`) | 3.5 s | 3.5 s | one |
1136
+ | One build without a file cache (a caller that builds once) | 1.4 s | 1.3 s | none |
1137
+ | First tick: load, build, and first publication of six vaults | 582 s | 574 s | none |
1138
+ | Tick with every view prepared again | 4.5 s | 4.5 s | none |
1139
+ | Idle tick | 0.25 s | 0.23 s | none |
1140
+ | Tick with a vault edit and a graph change: the adapter defers, the engine confirms in a worker | 12.4 s | 2.8 s | none |
1141
+ | Next tick: the adapter observes that edit (one build) | 2.1 s | 2.0 s | none |
1142
+ | Full-reconciliation tick, the edit still open | 5.0 s | 1.0 s | none |
1143
+
1144
+ **What is met.** No composition runs on the engine's event loop, the proposal
1145
+ adapter's included. The load, a confirmation after a change, and the
1146
+ composition at every full reconciliation run in a worker; the tick does not
1147
+ wait on one that misses its deadline. An unchanged workspace is not prepared
1148
+ again. While an edit stays open and shared sources are quiet, the adapter
1149
+ builds nothing more; under continuous change it builds once per tick until a
1150
+ quiet tick.
1151
+
1152
+ **What a service binding needed.** The first publication blocked the event
1153
+ loop far longer than the service's five-second health probe: about ten minutes
1154
+ here. That is the
1155
+ publication path every project takes, not this route: an ordinary project of
1156
+ 450 notes, measured the same way, blocked as long as a bound one. Preparing six
1157
+ views again blocked 4.5 s, under the probe. The proposal adapter's build of a
1158
+ newly observed edit, without the engine's file cache, blocked 2.0 s, at a
1159
+ two-second target, as for an ordinary project. A service binding needed the
1160
+ first publication to yield to the event loop, which it now does (see "Since
1161
+ that measurement"), and a start that does not wait for its loader. The service
1162
+ resolves its workspace from the project its loader answers when it starts, and refuses a loader that answers a promise or any
1163
+ other thenable, such as `loadBoundProjectOffThread`, with
1164
+ `service-loader-not-synchronous`, before it reads anything from it, listens or
1165
+ records anything; a promise it was handed is let go, and its refusal, if it
1166
+ is refused, does not go unhandled. Started by a login item (`--startup`), the
1167
+ service asks its loader (or, under `--personal-home`, its locator) once for both
1168
+ the lookup of its workspace and its own start. One that answers a promise there
1169
+ is never handed to the workspace resolution: a promise whose `constructor`
1170
+ reads as this realm's `Promise` is let go without calling a `then` of its
1171
+ own, any other thenable (as a rule another realm's promise or a subclass's)
1172
+ is adopted once (its `then` is called once in all), the refusal is recorded
1173
+ in the workspace the unit names, when that exists, and the process ends with
1174
+ 0, so the service manager does not start it again at once. One that throws
1175
+ there, whatever it throws (`undefined` too, a value with no prototype, or one
1176
+ whose `code` cannot be read), or answers a project whose `then` cannot be
1177
+ read, ends the start as a failure of the loader does anywhere else: it is
1178
+ logged, and what carries no code is a crash.
1179
+ A service bound through `--personal-home` is given a locator as well, and starts
1180
+ from it (see "A confirmed selection"). The plugin's status read allows for such
1181
+ a loader without waiting for it (see "The channel" in
1182
+ [obsidian-plugin.md](obsidian-plugin.md)).
1183
+
1184
+ **Since that measurement.** The engine prepares each view cooperatively and
1185
+ the publisher yields between complete units, so the service answers health and
1186
+ status while notes are emitted, staged and published. The first-tick figures
1187
+ in the table are from before that change and have not been taken again on that
1188
+ corpus. Building the canonical graph gives the event loop a turn between the
1189
+ Markdown sources it reads, hashes and parses (the census): a first tick reads
1190
+ every source, and on a heavily loaded host the build of one view of 10,000
1191
+ invented notes held the listener for 8 s in one stretch. The other sources
1192
+ (HTML files and the sidecars of PDF and Word documents) are read without a turn
1193
+ between them, and a project bound to a personal workspace is built by its own
1194
+ route in one stretch. Walking the directories, resolving the links and
1195
+ assembling the graph still run through.
1196
+ So do capturing the source snapshot, the publisher's restart recovery,
1197
+ validating a prepared view and planning its units, the exchange probe, writing
1198
+ and syncing the journal before the candidates move, and the look again at
1199
+ displaced files after the units (`recheckDisplacedFiles`).
1200
+ The read-back after a commit waits between
1201
+ notes: on a loaded host it took 16.8 s for one view of 10,000 invented notes
1202
+ and held the listener for all of it, and now a request is answered between two
1203
+ notes. Nothing is checked at those waits, since the generation is committed. A
1204
+ note edited in the vault while the read-back is under way is read as edited
1205
+ when the walk has not reached it, and is seen by the next tick when it has, as
1206
+ an edit made just after a read-back is. A stop whose grace runs out while a
1207
+ read-back waits ends the process after the commit and before the view is
1208
+ recorded as current, as a crash there does: the next start's first tick reads
1209
+ the committed generation back. Before a view is prepared, a tick looks at
1210
+ every view's notes and then at the sources, and both walks wait between files.
1211
+ They matter after a publication, when the watcher has named the notes it
1212
+ wrote, and at every full reconciliation, when every note and every source is
1213
+ hashed: on one view of 10,000 invented notes on a loaded host such a tick held
1214
+ the listener for 5.5 s when the walks ran through. Nothing is checked at their
1215
+ waits and nothing is written. Within Atelier only the engine commits a
1216
+ manifest or writes the pending edits, under the lock the tick holds, so the
1217
+ pending edits are read, compared and written in one stretch after the walk
1218
+ over the vault, and the bytes an edit keeps are read there. A file changed
1219
+ after its turn in a walk is seen by the next tick, by its stat hint or the
1220
+ watcher (one that keeps its size and times and draws no event, at the latest
1221
+ by the next full reconciliation), as one changed just after a walk that ran
1222
+ through is; when the tick builds the graph, the look again at the sources
1223
+ after the build refuses such a change as `mixed-read`. A source this tick
1224
+ observed, removed before the build read it (or its folder), is refused the
1225
+ same way. So is a selected source removed before a view's preparation, or the
1226
+ look again before its commit, read it: that view is refused, and the tick goes
1227
+ on to its other views. Either way the next tick reads everything again; a path
1228
+ that can never be read still fails the tick. A note gone from a
1229
+ vault, a source that changed, or a configuration file that changed is a change
1230
+ once: a tick that sees it and then fails without a typed reason (a file it
1231
+ cannot read, a disk that is full, a project loader that fails) has recorded it
1232
+ without acting on it. The next tick therefore starts as the first tick of a
1233
+ process does: it loads the project again and looks at every view, so a view
1234
+ whose note is missing reads `vault-note-missing` instead of staying `current`,
1235
+ and a changed source or view is published.
1236
+ The comparison of kept notes with their candidates reads asynchronously,
1237
+ sixteen at a time, and does not hold the listener. Observing pending edits
1238
+ and routing proposals wait between two edits and between two operations, so
1239
+ a burst of edits does not hold the listener for its whole length. One edit,
1240
+ or one operation, is still one stretch. The first edit an observation looks
1241
+ at that the object store does not hold yet builds the canonical graph in its
1242
+ stretch, and the first operation routed builds it again while the lock of
1243
+ its repository is held. An edit whose published note
1244
+ is not retained has its whole view prepared again in its stretch: once for a
1245
+ view and generation in an observation when the preparation succeeds, and for
1246
+ every such edit when it fails. A stop whose grace runs out while proposals
1247
+ are routed can end the process between two operations of a repository with
1248
+ that repository's proposal lock still held, as a crash can. A service in one
1249
+ of those stretches does not answer until it ends, and is `busy` if that
1250
+ outlasts the probe. A request can
1251
+ hold the listener too: the plugin's status read loads the project on the event
1252
+ loop at its first requests after the service starts (until two loads agree),
1253
+ when a configuration file changed, every two and a half minutes, again two to
1254
+ thirty seconds after a load that failed, and at every request while one of
1255
+ those files vouches for nothing or while its loads never agree. For a project
1256
+ named by `--project` none of those loads is taken off the loop. For a bound
1257
+ personal home they compose in a worker, as the engine's loads do, and the read
1258
+ starts each one on the loop by reading the selection history. A load that holds the loop
1259
+ for a second or more is logged (`status-project-loaded`), and so is a load at
1260
+ every request that names the files it started over (see "What the service
1261
+ shows of these loads" in [obsidian-plugin.md](obsidian-plugin.md)).
1262
+
1263
+ What the waits cost. The waits of observation and routing are taken at
1264
+ every boundary between two edits or two operations and before each
1265
+ repository, whatever time was spent; while routing describes its operations
1266
+ a wait comes after about 8 ms of work or 32 units. Nothing is checked around
1267
+ any of them. In preparation and publication a wait
1268
+ comes after about 8 ms of work or 32 units,
1269
+ whichever is first, and around each one the engine checks that the tick's
1270
+ settings still hold: it reads and hashes every configuration file it observes
1271
+ (the project configuration, the repository access file, local overlays and,
1272
+ for a bound personal workspace, its inputs), reads and validates the machine
1273
+ settings, and asks the eligibility revision. Measured on six configuration
1274
+ files, that check took about 0.2 ms, twice per wait; a first publication of
1275
+ 200 invented notes waited 463 times, so roughly 0.2 s of a 64 s tick, on a
1276
+ host with a load average above 200 (indicative only). The selected sources
1277
+ cost one stat call each after the preparation and one before the commit; a
1278
+ note reused from the preparation cache is still not read.
1279
+
503
1280
  ## Known limits
504
1281
 
505
1282
  These are the limits known at this release. None is hidden behind a skipped
@@ -554,6 +1331,35 @@ test reported as a pass.
554
1331
  own launch `open` waits, bounded, while the app is still opening the vault;
555
1332
  an app whose vault window is still loading answers a command with `Error:
556
1333
  Command "…" not found`, which is read as not up yet, never as a version.
1334
+ - Vault location: consent to a folder a sync client keeps in step
1335
+ (`--allow-synced-location`) is given when `location set` decides the folder,
1336
+ and is not recorded, so the maintenance service does not check for sync
1337
+ clients again. The decision keeps the folder as it was written, so at a
1338
+ view's first allocation a folder on the way that has since become a link
1339
+ into a synced folder is followed, and its vault is allocated there without
1340
+ being asked; a folder that becomes synced without any link (iCloud's
1341
+ "Desktop & Documents Folders" turned on, say) is not refused either, at the
1342
+ first allocation or when a vault is made again. Every other check runs
1343
+ again at both, and a vault made again, or published into, through a link on
1344
+ the way is refused (`vault-allocation-moved`).
1345
+ - Replacing a vault during a publication: the allocated folder is checked
1346
+ before the vault lock is taken and again once its folder is made, after the
1347
+ app is asked about the vault, before each note and before the generation is
1348
+ committed. A program of the same account that puts a link in its place
1349
+ between one check and what follows it can still have that go through the
1350
+ link: one note's writes (the folders on its way and the note), or the vault
1351
+ lock's folder (made there, or made private if it was there already) and, in
1352
+ a folder that already holds one (another Atelier vault), a lock ticket and
1353
+ its release flag. For a note replaced or removed while the app holds the
1354
+ vault, the app is asked about that note between the check and the write, so
1355
+ that window lasts a round trip to the app. The next check refuses the
1356
+ publication, and it is never reported current.
1357
+ - Vault identity: an allocated folder is known by its real path, device and
1358
+ inode. On Linux a folder made at the same path after the allocated one was
1359
+ removed can be given the same inode number (ext4 reuses a freed one), and is
1360
+ then taken for the one made; macOS (APFS) did not reuse one in 200 tries.
1361
+ Such a folder is published into only at the recorded real path, and the
1362
+ plugin's bearer only when this account owns it.
557
1363
  - Obsidian's settings file: `open` adds a view's vault to the app's own list,
558
1364
  `obsidian.json` in its user-data directory: `~/Library/Application
559
1365
  Support/obsidian` on macOS and `$XDG_CONFIG_HOME/obsidian` (else
@@ -646,6 +1452,17 @@ test reported as a pass.
646
1452
  in Obsidian's settings or runs
647
1453
  `atelier obsidian plugin on --scope ID`. Plugin files are never removed from
648
1454
  a vault. See [obsidian-plugin.md](obsidian-plugin.md).
1455
+ - Login items: the tests run the launchd and systemd managers' own code
1456
+ against stand-ins that answer as launchctl and systemctl do, and never
1457
+ against a real session. What `launchctl print-disabled` says for an item
1458
+ switched off in System Settings, and the exit statuses read from
1459
+ `launchctl` and `systemctl`, are the documented ones and are not yet
1460
+ confirmed on a host. A unit whose program cannot be loaded at all (the
1461
+ project's package was removed) is restarted by its manager once a minute,
1462
+ and each attempt adds a few lines to `state/service/login-item.log`, which
1463
+ nothing bounds: `service unit --remove` or `uninstall` ends it. An upgrade
1464
+ in the moment between a service's start and the first reading of its release
1465
+ is not noticed until the next change.
649
1466
  - Acceptance: a schema-valid receipt closes no gate, the package proof closes
650
1467
  no gate, and no adopter acceptance is recorded in this repository.
651
1468
 
@@ -665,3 +1482,8 @@ The test suite proves each oracle can fail:
665
1482
  - `createMaintenanceEngineForOracleTests` with an
666
1483
  `ENGINE_PRIMITIVES.isRetryDue` that never holds: a refused view waits for
667
1484
  the full reconciliation, and the retry oracle fails.
1485
+ - `createMaintenanceEngineForOracleTests` with
1486
+ `ENGINE_PRIMITIVES.rechecksEveryViewAfterFailure` false: a note removed, a
1487
+ source changed or a view added in a tick that then fails without a typed
1488
+ reason is lost, the view stays `current` with the note gone or the old
1489
+ content, the new view is never published, and the oracle fails.