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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (560) hide show
  1. package/CHANGELOG.md +1005 -0
  2. package/README.md +149 -9
  3. package/contracts/atelier-adoption-policy.v2.schema.json +55 -0
  4. package/contracts/atelier-architecture.v1.schema.json +258 -0
  5. package/contracts/atelier-boundary-delegations.v1.schema.json +161 -0
  6. package/contracts/atelier-boundary-owner-keys.v1.schema.json +98 -0
  7. package/contracts/atelier-build.v1.schema.json +1076 -0
  8. package/contracts/atelier-capability.v1.schema.json +1252 -0
  9. package/contracts/atelier-coordination.v1.schema.json +1050 -0
  10. package/contracts/atelier-evidence-navigation.v1.schema.json +603 -0
  11. package/contracts/atelier-harness.v1.schema.json +234 -0
  12. package/contracts/atelier-ingestion.v1.schema.json +806 -0
  13. package/contracts/atelier-inquiry.v1.schema.json +1772 -0
  14. package/contracts/atelier-instruction-adoption.v1.schema.json +490 -0
  15. package/contracts/atelier-interaction.v1.schema.json +553 -0
  16. package/contracts/atelier-judgment.v1.schema.json +690 -0
  17. package/contracts/atelier-knowledge.v1.schema.json +1512 -0
  18. package/contracts/atelier-learning.v1.schema.json +1095 -0
  19. package/contracts/atelier-migration.v3.schema.json +64 -0
  20. package/contracts/atelier-obsidian-generation-manifest.v2.schema.json +372 -0
  21. package/contracts/atelier-personal-workspace-manifest.v1.schema.json +87 -0
  22. package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
  23. package/contracts/atelier-template-adoption.v1.schema.json +223 -0
  24. package/contracts/atelier-template-profile.v1.schema.json +1168 -0
  25. package/contracts/atelier-trackable.v1.schema.json +781 -0
  26. package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
  27. package/contracts/compat-baseline.json +1 -1
  28. package/docs/architecture.md +89 -0
  29. package/docs/assurance-controls.md +4 -3
  30. package/docs/blocks/capability-stewardship.md +17 -0
  31. package/docs/blocks/claims.md +16 -3
  32. package/docs/blocks/inquiry-harnesses.md +19 -0
  33. package/docs/blocks/learning-harnesses.md +17 -0
  34. package/docs/capability-stewardship.md +269 -0
  35. package/docs/coordination.md +51 -0
  36. package/docs/decision-practices.md +111 -0
  37. package/docs/decisions.md +29 -0
  38. package/docs/evidence-navigation.md +237 -0
  39. package/docs/exact-upgrades.md +3 -2
  40. package/docs/ingestion.md +181 -0
  41. package/docs/inquiry-harnesses.md +211 -0
  42. package/docs/install.md +80 -3
  43. package/docs/intake-and-guides.md +5 -0
  44. package/docs/integration-contract-decisions.md +24 -2
  45. package/docs/interaction-and-reflection.md +72 -0
  46. package/docs/knowledge-graph.md +20 -0
  47. package/docs/knowledge-health/api.md +30 -0
  48. package/docs/knowledge-health/assessment/api.md +96 -0
  49. package/docs/knowledge-health/assessment/capabilities.md +40 -0
  50. package/docs/knowledge-health/assessment/evaluation.md +27 -0
  51. package/docs/knowledge-health/assessment/facilitator.md +30 -0
  52. package/docs/knowledge-health/assessment/quickstart.md +61 -0
  53. package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
  54. package/docs/knowledge-health/devday/facilitator.md +16 -0
  55. package/docs/knowledge-health/devday/participant-response.md +22 -0
  56. package/docs/knowledge-health/devday/public-default.md +107 -0
  57. package/docs/knowledge-health/devday/quickstart.md +18 -0
  58. package/docs/knowledge-health/devday/support.md +22 -0
  59. package/docs/knowledge-health/exercise.md +17 -0
  60. package/docs/knowledge-health/facilitator.md +20 -0
  61. package/docs/knowledge-health/quickstart.md +94 -0
  62. package/docs/knowledge-health/readme.md +15 -0
  63. package/docs/knowledge-health/support.md +34 -0
  64. package/docs/knowledge-setup.md +248 -0
  65. package/docs/knowledge-workspace.md +264 -0
  66. package/docs/learning-harnesses.md +238 -0
  67. package/docs/learning.md +215 -0
  68. package/docs/local-services.md +242 -26
  69. package/docs/obsidian-contract.md +878 -36
  70. package/docs/obsidian-plugin.md +619 -0
  71. package/docs/obsidian.md +809 -64
  72. package/docs/optional-ingestion.md +83 -0
  73. package/docs/personal-workspace.md +257 -0
  74. package/docs/practical-judgment.md +92 -0
  75. package/docs/progressive-ingestion-acceptance.md +89 -0
  76. package/docs/progressive-ingestion.md +235 -0
  77. package/docs/project-command-inventory.md +1 -0
  78. package/docs/release-engineering.md +56 -1
  79. package/docs/repo-boundary-guard.md +94 -0
  80. package/docs/research-starter-quickstart.md +66 -0
  81. package/docs/research-starter.md +114 -0
  82. package/docs/semantic-ingestion.md +198 -0
  83. package/docs/semantic-operation.md +317 -0
  84. package/docs/skill-steward.md +6 -0
  85. package/docs/template-profile.md +164 -0
  86. package/docs/template-upgrades.md +114 -0
  87. package/docs/trackables.md +111 -0
  88. package/docs/upgrade.md +68 -3
  89. package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
  90. package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
  91. package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
  92. package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
  93. package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
  94. package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
  95. package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
  96. package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
  97. package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
  98. package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
  99. package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
  100. package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
  101. package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
  102. package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
  103. package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
  104. package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
  105. package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
  106. package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
  107. package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
  108. package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
  109. package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
  110. package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
  111. package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
  112. package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
  113. package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
  114. package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
  115. package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
  116. package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
  117. package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
  118. package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
  119. package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
  120. package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
  121. package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
  122. package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
  123. package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
  124. package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
  125. package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
  126. package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
  127. package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
  128. package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
  129. package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
  130. package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
  131. package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
  132. package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
  133. package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
  134. package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
  135. package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
  136. package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
  137. package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
  138. package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
  139. package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
  140. package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
  141. package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
  142. package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
  143. package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
  144. package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
  145. package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
  146. package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
  147. package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
  148. package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
  149. package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
  150. package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
  151. package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
  152. package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
  153. package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
  154. package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
  155. package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
  156. package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
  157. package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
  158. package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
  159. package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
  160. package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
  161. package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
  162. package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
  163. package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
  164. package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
  165. package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
  166. package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
  167. package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
  168. package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
  169. package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
  170. package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
  171. package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
  172. package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
  173. package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
  174. package/fixtures/atelier-interaction/act/valid/document.json +24 -0
  175. package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
  176. package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
  177. package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
  178. package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
  179. package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
  180. package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
  181. package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
  182. package/fixtures/atelier-judgment/case/valid/document.json +75 -0
  183. package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
  184. package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
  185. package/fixtures/atelier-judgment/native-decision.md +3 -0
  186. package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
  187. package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
  188. package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
  189. package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
  190. package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
  191. package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
  192. package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
  193. package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
  194. package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
  195. package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
  196. package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
  197. package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
  198. package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
  199. package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
  200. package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
  201. package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
  202. package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
  203. package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
  204. package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
  205. package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
  206. package/fixtures/atelier-learning/invalid/authority.json +14 -0
  207. package/fixtures/atelier-learning/valid/capture.json +13 -0
  208. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
  209. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
  210. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
  211. package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
  212. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
  213. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
  214. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
  215. package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
  216. package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
  217. package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
  218. package/fixtures/atelier-trackable/request/valid/document.json +22 -0
  219. package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
  220. package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
  221. package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
  222. package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
  223. package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
  224. package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
  225. package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
  226. package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
  227. package/fixtures/capability-packages/adoption.example.json +70 -0
  228. package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
  229. package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
  230. package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
  231. package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
  232. package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
  233. package/fixtures/capability-packages/research/capability-package.json +88 -0
  234. package/fixtures/capability-packages/research/capability-release.json +134 -0
  235. package/fixtures/capability-packages/research/evaluation.md +15 -0
  236. package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
  237. package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
  238. package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
  239. package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
  240. package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
  241. package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
  242. package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
  243. package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
  244. package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
  245. package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
  246. package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
  247. package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
  248. package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
  249. package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
  250. package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
  251. package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
  252. package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
  253. package/fixtures/evidence-navigation/valid/claim.json +36 -0
  254. package/fixtures/evidence-navigation/valid/host.json +16 -0
  255. package/fixtures/evidence-navigation/valid/profile.json +16 -0
  256. package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
  257. package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
  258. package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
  259. package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
  260. package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
  261. package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
  262. package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
  263. package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
  264. package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
  265. package/fixtures/harnesses/learning-cycle.json +780 -0
  266. package/fixtures/inquiry/workshop.json +675 -0
  267. package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
  268. package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
  269. package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
  270. package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
  271. package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
  272. package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
  273. package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
  274. package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
  275. package/fixtures/knowledge-health/calibration-suite.json +26 -0
  276. package/fixtures/knowledge-health/calibration-trial.json +37 -0
  277. package/fixtures/knowledge-health/corrected/checklist.md +18 -0
  278. package/fixtures/knowledge-health/devday/participant-record.json +19 -0
  279. package/fixtures/knowledge-health/expected-summary.json +27 -0
  280. package/fixtures/knowledge-health/workshop/README.md +20 -0
  281. package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
  282. package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
  283. package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
  284. package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
  285. package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
  286. package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
  287. package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
  288. package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
  289. package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
  290. package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
  291. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/case-only-collision.v2.json +160 -0
  292. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/flat-note-path.v2.json +160 -0
  293. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/hidden-folder.v2.json +160 -0
  294. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/identity-in-the-body.v2.json +160 -0
  295. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/link-breaking-name.v2.json +160 -0
  296. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-identity-region.v2.json +156 -0
  297. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-layout-version.v2.json +159 -0
  298. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/note-named-like-a-folder.v2.json +160 -0
  299. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/unknown-region-kind.v2.json +160 -0
  300. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/v1-contract-version.v2.json +161 -0
  301. package/fixtures/obsidian/contracts/generation-manifest-v2/valid/three-notes.v2.json +160 -0
  302. package/fixtures/obsidian/edits/workspace.json +1 -1
  303. package/fixtures/obsidian/materialization/expected-full.json +21 -21
  304. package/fixtures/obsidian/materialization/expected-full.layout-1.json +99 -0
  305. package/fixtures/obsidian/materialization/expected-scoped.json +7 -7
  306. package/fixtures/obsidian/materialization/expected-scoped.layout-1.json +39 -0
  307. package/fixtures/personal-workspace/overlay.json +24 -0
  308. package/fixtures/templates/local-library.v1.json +75 -0
  309. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
  310. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
  311. package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
  312. package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
  313. package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
  314. package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
  315. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
  316. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
  317. package/package.json +64 -6
  318. package/plugins/obsidian/main.js +444 -0
  319. package/plugins/obsidian/manifest.json +9 -0
  320. package/plugins/obsidian/styles.css +22 -0
  321. package/skills/claude/atelier-build-harness/SKILL.md +59 -0
  322. package/skills/claude/atelier-coordination/SKILL.md +32 -0
  323. package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
  324. package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
  325. package/skills/claude/atelier-interaction/SKILL.md +34 -0
  326. package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
  327. package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
  328. package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
  329. package/skills/claude/atelier-research-harness/SKILL.md +43 -0
  330. package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
  331. package/skills/claude/atelier-trackables/SKILL.md +40 -0
  332. package/skills/codex/atelier-build-harness/SKILL.md +59 -0
  333. package/skills/codex/atelier-coordination/SKILL.md +32 -0
  334. package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
  335. package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
  336. package/skills/codex/atelier-interaction/SKILL.md +34 -0
  337. package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
  338. package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
  339. package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
  340. package/skills/codex/atelier-research-harness/SKILL.md +43 -0
  341. package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
  342. package/skills/codex/atelier-trackables/SKILL.md +40 -0
  343. package/src/architecture/catalog.mjs +355 -0
  344. package/src/architecture/index.mjs +93 -0
  345. package/src/boundary/content-rules.mjs +4 -2
  346. package/src/boundary/delegation-command.mjs +158 -0
  347. package/src/boundary/delegation.mjs +396 -0
  348. package/src/boundary/policy.mjs +52 -10
  349. package/src/build/git.mjs +18 -0
  350. package/src/build/index.mjs +3 -0
  351. package/src/build/ledger.mjs +88 -0
  352. package/src/capabilities/adoption.mjs +381 -0
  353. package/src/capabilities/evidence.mjs +140 -0
  354. package/src/capabilities/files.mjs +124 -0
  355. package/src/capabilities/index.mjs +4 -0
  356. package/src/capabilities/instructions.mjs +250 -0
  357. package/src/capabilities/package.mjs +136 -0
  358. package/src/cli/command-failure.mjs +36 -0
  359. package/src/cli/execute-command.mjs +16 -2
  360. package/src/cli/run.mjs +65 -9
  361. package/src/coauthor/store.mjs +43 -2
  362. package/src/collaboration/proposals.mjs +189 -0
  363. package/src/commands/architecture.mjs +27 -0
  364. package/src/commands/boundary.mjs +3 -0
  365. package/src/commands/capability.mjs +73 -0
  366. package/src/commands/enroll.mjs +3 -0
  367. package/src/commands/harness.mjs +79 -0
  368. package/src/commands/ingest.mjs +39 -0
  369. package/src/commands/init.mjs +4 -1
  370. package/src/commands/inquiry.mjs +66 -0
  371. package/src/commands/knowledge-semantic.mjs +128 -0
  372. package/src/commands/knowledge.mjs +119 -0
  373. package/src/commands/learn.mjs +61 -0
  374. package/src/commands/obsidian.mjs +832 -62
  375. package/src/commands/practice.mjs +24 -0
  376. package/src/commands/setup.mjs +22 -2
  377. package/src/commands/trackable.mjs +19 -0
  378. package/src/contracts/corpus.mjs +36 -0
  379. package/src/coordination/index.mjs +110 -0
  380. package/src/egress/forbidden-egress.mjs +2 -0
  381. package/src/evidence-navigation/contracts.mjs +60 -0
  382. package/src/evidence-navigation/evaluate.mjs +69 -0
  383. package/src/evidence-navigation/generation.mjs +22 -0
  384. package/src/evidence-navigation/index.mjs +2 -0
  385. package/src/evidence-navigation/local.mjs +167 -0
  386. package/src/evidence-navigation/schema.generated.mjs +604 -0
  387. package/src/evidence-navigation/types.mjs +12 -0
  388. package/src/export/atelier-export-contract.mjs +35 -8
  389. package/src/graph/enroll.mjs +248 -0
  390. package/src/graph/graph.mjs +17 -1
  391. package/src/graph/knowledge-graph.mjs +58 -8
  392. package/src/harnesses/contracts.mjs +41 -0
  393. package/src/harnesses/exchange.mjs +154 -0
  394. package/src/harnesses/feedback.mjs +16 -0
  395. package/src/harnesses/history.mjs +37 -0
  396. package/src/harnesses/index.mjs +4 -0
  397. package/src/harnesses/store.mjs +29 -0
  398. package/src/ingestion/contracts.mjs +41 -0
  399. package/src/ingestion/evaluation.mjs +119 -0
  400. package/src/ingestion/optional/composition/index.mjs +171 -0
  401. package/src/ingestion/optional/evaluation/index.mjs +219 -0
  402. package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
  403. package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
  404. package/src/ingestion/optional/graphify/graphify_supplier.py +933 -0
  405. package/src/ingestion/optional/graphify/json_bridge.py +163 -0
  406. package/src/ingestion/optional/graphify/query_receiving.py +209 -0
  407. package/src/ingestion/optional/index.mjs +9 -0
  408. package/src/ingestion/optional/jev/bridge.mjs +166 -0
  409. package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
  410. package/src/ingestion/optional/jev/judgments.mjs +174 -0
  411. package/src/ingestion/optional/jev/native-translation.mjs +32 -0
  412. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
  413. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
  414. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
  415. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
  416. package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
  417. package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
  418. package/src/ingestion/optional/vanilla/index.mjs +218 -0
  419. package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
  420. package/src/ingestion/processors.mjs +218 -0
  421. package/src/ingestion/semantic.mjs +203 -0
  422. package/src/ingestion/store.mjs +372 -0
  423. package/src/inquiry/beliefs.mjs +32 -0
  424. package/src/inquiry/contracts.mjs +23 -0
  425. package/src/inquiry/index.mjs +5 -0
  426. package/src/inquiry/ledger.mjs +135 -0
  427. package/src/inquiry/lenses.mjs +15 -0
  428. package/src/inquiry/projection.mjs +77 -0
  429. package/src/inquiry/store.mjs +35 -0
  430. package/src/intake/read-scope.mjs +12 -0
  431. package/src/intake/store.mjs +69 -6
  432. package/src/interaction/index.mjs +91 -0
  433. package/src/judgment/index.mjs +82 -0
  434. package/src/judgment/practice-evaluation.mjs +70 -0
  435. package/src/judgment/practice.mjs +144 -0
  436. package/src/knowledge/context.mjs +217 -0
  437. package/src/knowledge/index.mjs +5 -0
  438. package/src/knowledge/ingestion.mjs +79 -0
  439. package/src/knowledge/intake.mjs +33 -0
  440. package/src/knowledge/ledger.mjs +110 -0
  441. package/src/knowledge/participatory/guidance.mjs +36 -0
  442. package/src/knowledge/participatory/guided-contribution.mjs +44 -0
  443. package/src/knowledge/participatory/index.mjs +2 -0
  444. package/src/knowledge/participatory/installed-workshop.mjs +108 -0
  445. package/src/knowledge/plan.mjs +67 -0
  446. package/src/knowledge/projection.mjs +43 -0
  447. package/src/knowledge/semantic-operation.mjs +567 -0
  448. package/src/knowledge/sessions.mjs +467 -0
  449. package/src/knowledge/workspace.mjs +295 -0
  450. package/src/knowledge-health/assessment.mjs +165 -0
  451. package/src/knowledge-health/capabilities.mjs +350 -0
  452. package/src/knowledge-health/context.mjs +62 -0
  453. package/src/knowledge-health/example.mjs +47 -0
  454. package/src/knowledge-health/index.mjs +10 -0
  455. package/src/knowledge-health/owner-join.mjs +88 -0
  456. package/src/knowledge-health/proof/common.mjs +24 -0
  457. package/src/knowledge-health/proof/installed-check.mjs +100 -0
  458. package/src/knowledge-health/proof/native-readback.mjs +37 -0
  459. package/src/knowledge-health/proof/rehearse.mjs +68 -0
  460. package/src/knowledge-health/proof/validate-participant.mjs +17 -0
  461. package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
  462. package/src/knowledge-health/proof/workshop-check.mjs +36 -0
  463. package/src/knowledge-health/rule-identity.mjs +44 -0
  464. package/src/knowledge-health/workshop-composition.mjs +19 -0
  465. package/src/knowledge-health/workshop-profile.mjs +176 -0
  466. package/src/knowledge-health/workshop.mjs +99 -0
  467. package/src/learning/adapters.mjs +68 -0
  468. package/src/learning/assistance.mjs +127 -0
  469. package/src/learning/contracts.mjs +65 -0
  470. package/src/learning/store.mjs +272 -0
  471. package/src/personal-workspace/index.mjs +391 -0
  472. package/src/personal-workspace/selection.mjs +385 -0
  473. package/src/project/config.mjs +8 -0
  474. package/src/project/durable-state.mjs +3 -1
  475. package/src/project/file-class.mjs +1 -1
  476. package/src/project/private-state.mjs +10 -1
  477. package/src/projection/obsidian/contracts.mjs +224 -18
  478. package/src/projection/obsidian/edits/apply.mjs +63 -19
  479. package/src/projection/obsidian/edits/regions.mjs +137 -42
  480. package/src/projection/obsidian/materialize/index.mjs +6 -3
  481. package/src/projection/obsidian/materialize/path-registry.mjs +443 -57
  482. package/src/projection/obsidian/materialize/prepare-view.mjs +382 -131
  483. package/src/projection/obsidian/materialize/redaction.mjs +327 -0
  484. package/src/projection/obsidian/materialize/settings.mjs +113 -34
  485. package/src/projection/obsidian/personal-workspace.mjs +436 -0
  486. package/src/projection/obsidian/plugin-bridge/bundle.mjs +50 -0
  487. package/src/projection/obsidian/plugin-bridge/channel.mjs +174 -0
  488. package/src/projection/obsidian/plugin-bridge/index.mjs +10 -0
  489. package/src/projection/obsidian/proposals/adapter.mjs +47 -8
  490. package/src/projection/obsidian/proposals/observation.mjs +20 -7
  491. package/src/projection/obsidian/publication/bridge-script.mjs +11 -5
  492. package/src/projection/obsidian/publication/index.mjs +2 -1
  493. package/src/projection/obsidian/publication/publisher.mjs +312 -36
  494. package/src/projection/obsidian/publication/transport.mjs +63 -8
  495. package/src/projection/obsidian/publication/vault-list.mjs +200 -0
  496. package/src/projection/obsidian/recovery/index.mjs +4 -1
  497. package/src/projection/obsidian/recovery/restart.mjs +3 -1
  498. package/src/projection/obsidian/recovery/store.mjs +168 -8
  499. package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
  500. package/src/projection/obsidian/selection-ui/focus.mjs +14 -7
  501. package/src/projection/obsidian/selection-ui/selection-state.mjs +2 -2
  502. package/src/projection/obsidian/selection-ui/selection.mjs +8 -3
  503. package/src/readiness/readiness.mjs +10 -3
  504. package/src/reflection/index.mjs +56 -0
  505. package/src/runtime/obsidian/app-capability.mjs +104 -12
  506. package/src/runtime/obsidian/app-production-seams.mjs +161 -42
  507. package/src/runtime/obsidian/app-registration.mjs +313 -0
  508. package/src/runtime/obsidian/app-restart.mjs +154 -0
  509. package/src/runtime/obsidian/engine.mjs +288 -29
  510. package/src/runtime/obsidian/extension-points.mjs +1 -1
  511. package/src/runtime/obsidian/index.mjs +15 -8
  512. package/src/runtime/obsidian/launch-plan.mjs +91 -0
  513. package/src/runtime/obsidian/lifecycle.mjs +193 -22
  514. package/src/runtime/obsidian/login-item.mjs +332 -0
  515. package/src/runtime/obsidian/machine-settings.mjs +119 -12
  516. package/src/runtime/obsidian/opening.mjs +369 -63
  517. package/src/runtime/obsidian/pending-edits.mjs +10 -0
  518. package/src/runtime/obsidian/pipeline.mjs +71 -3
  519. package/src/runtime/obsidian/plugin-channel.mjs +289 -0
  520. package/src/runtime/obsidian/plugin-choice.mjs +182 -0
  521. package/src/runtime/obsidian/plugin-drift.mjs +124 -0
  522. package/src/runtime/obsidian/plugin-presence.mjs +61 -0
  523. package/src/runtime/obsidian/process-identity.mjs +20 -10
  524. package/src/runtime/obsidian/project-views.mjs +282 -0
  525. package/src/runtime/obsidian/questions.mjs +48 -0
  526. package/src/runtime/obsidian/release-watch.mjs +56 -0
  527. package/src/runtime/obsidian/service-main.mjs +113 -17
  528. package/src/runtime/obsidian/service-manager-production.mjs +61 -0
  529. package/src/runtime/obsidian/service-managers.mjs +275 -0
  530. package/src/runtime/obsidian/service-record.mjs +105 -5
  531. package/src/runtime/obsidian/service-server.mjs +50 -12
  532. package/src/runtime/obsidian/service.mjs +134 -7
  533. package/src/runtime/obsidian/startup-adapters.mjs +59 -21
  534. package/src/runtime/obsidian/state-store.mjs +13 -1
  535. package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
  536. package/src/runtime/obsidian/vault-location.mjs +300 -0
  537. package/src/runtime/obsidian/view-counts.mjs +34 -0
  538. package/src/server/local-sidecar.mjs +193 -7
  539. package/src/server/server.mjs +45 -6
  540. package/src/template-bindings/project.mjs +92 -0
  541. package/src/templates/profile.mjs +225 -0
  542. package/src/trackables/domain.mjs +175 -0
  543. package/src/trackables/store.mjs +114 -0
  544. package/src/ui/knowledge-page.mjs +1388 -0
  545. package/src/upgrade/explanation.mjs +1 -1
  546. package/src/upgrade/template-participant.mjs +171 -0
  547. package/src/upgrade/transaction.mjs +72 -25
  548. package/src/upgrade/upgrade.mjs +2 -1
  549. package/templates/external-project-workspace/pack-lifecycle.json +6 -1
  550. package/templates/knowledge-workspace/.gitattributes +2 -0
  551. package/templates/knowledge-workspace/AGENTS.md +27 -0
  552. package/templates/knowledge-workspace/README.md +44 -0
  553. package/templates/knowledge-workspace/atelier.project.json +22 -0
  554. package/templates/knowledge-workspace/gitignore +6 -0
  555. package/templates/knowledge-workspace/knowledge-plan.json +111 -0
  556. package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
  557. package/templates/knowledge-workspace/records/forecast.md +22 -0
  558. package/templates/knowledge-workspace/records/inspection.md +25 -0
  559. package/templates/knowledge-workspace/records/telescope.md +24 -0
  560. package/templates/knowledge-workspace/repo-access.v1.json +9 -0
@@ -0,0 +1,619 @@
1
+ # Atelier's Obsidian plugin
2
+
3
+ Atelier ships its own Obsidian plugin inside every vault it manages. Nobody
4
+ finds or installs it: the maintenance service puts it into the vault with the
5
+ notes, and Obsidian asks once per vault whether to trust the vault's plugins.
6
+ Until the person says yes, and whenever the plugin is absent, turned off or
7
+ running in an app that is too old, everything works as it did before through
8
+ the command-line path described in [obsidian.md](obsidian.md).
9
+
10
+ This is phase 1: the plugin is present, reports status and holds a channel to
11
+ the service. It decides nothing and writes nothing. Phase 2, planned at the
12
+ end of this document, moves the in-app part of publication into it.
13
+
14
+ The source is `plugins/obsidian/` in the package: `manifest.json`, `main.js`
15
+ and `styles.css`, plain JavaScript with no build step. The channel it speaks
16
+ is `src/projection/obsidian/plugin-bridge/channel.mjs`; the service's half is
17
+ `src/runtime/obsidian/plugin-channel.mjs`.
18
+
19
+ ## What it does
20
+
21
+ - A status bar item says how current the view is: `Atelier: current`,
22
+ `Atelier: updating`, `Atelier: held (N)` with the number of notes held for
23
+ an edit you made, `Atelier: stale` or `Atelier: service unreachable`. Before
24
+ the first answer it says `Atelier: connecting`; without channel data, or in
25
+ a copy of the vault rather than the vault Atelier maintains, it says
26
+ `Atelier: not set up`; in an app older than 1.13.7 it says
27
+ `Atelier: needs a newer Obsidian`. Between `not set up` and
28
+ `service unreachable` it moves only when two rounds in a row agree, so a
29
+ listener that answers one way and then the other does not make it flip;
30
+ every other change shows at once.
31
+ - The command "Atelier: show status", or a click on the status bar item, opens
32
+ a window with the view, its state and the reason in words, the committed and
33
+ the prepared generation, when freshness was last checked, the held, retained
34
+ and pending edits, the service address and state, and the plugin and app
35
+ versions.
36
+ - A notice appears when the view moves into a state that needs a person (held,
37
+ stale, service unreachable, app too old) and when it is current again.
38
+ `updating` is not announced: it is what every change looks like.
39
+ - While the vault is open the plugin tells the service so: it shakes hands
40
+ once (see "The channel"), then renews a lease every two seconds, and
41
+ releases it when the plugin unloads (the vault closes, the app quits, the
42
+ plugin is turned off). A lease that is not renewed lapses after six seconds.
43
+ The hello carries the plugin version, the app version (`apiVersion`, the
44
+ version of the app the plugin runs in), an id of this launch of the plugin,
45
+ and a proof of the real path of the vault the app has open. Both sides
46
+ take that path as the file system stores it (on a volume that folds
47
+ letter case, in the case it is stored in; on Windows, Node's own
48
+ resolution), so a data root given in another letter case, or an app that
49
+ has the vault in another, still proves the view's vault. Plugin 1.1.1
50
+ does so; 1.1.0 kept the app's spelling.
51
+
52
+ ## What it never does
53
+
54
+ - It writes no file. No note, no setting, and not its own `data.json`: it
55
+ never calls `saveData` and never uses the vault's write operations.
56
+ - It runs no code it receives. The service answers with JSON documents of
57
+ fixed shape and the plugin reads fields out of them.
58
+ - It reaches nothing but the maintenance service named in its `data.json`, on
59
+ the literal address `127.0.0.1` or `::1`, through Node's `http` module, with
60
+ no name to resolve. It sends no telemetry.
61
+ - It never sends its vault's key, and it sends nothing that names the vault or
62
+ the view until whatever listens at that address has proven that it holds
63
+ the key.
64
+ - It holds no state that matters: no manifest, no edit, no policy, no recovery
65
+ bytes. Removing it loses nothing.
66
+
67
+ The test suite holds the plugin to this: it loads `main.js` the way the app
68
+ does into a stand-in app whose every write throws, records every module the
69
+ plugin requires (`obsidian`, `http`, `fs`, `crypto`) and every request it
70
+ makes with its exact body, and fails a copy of the plugin that points a
71
+ request at a host from its data file (see "Evidence" below).
72
+
73
+ ## How it gets into a vault
74
+
75
+ The publisher's settings unit owns these paths of a vault, and nothing else
76
+ under `.obsidian/`:
77
+
78
+ | Path | Owned | How it is written |
79
+ | --- | --- | --- |
80
+ | `.obsidian/core-plugins.json` | the `publish` and `sync` keys, kept `false` | merged with the file on disk; every other key stays |
81
+ | `.obsidian/community-plugins.json` | the `atelier-projection` entry | appended to the list on disk when missing; every other entry stays, in its order |
82
+ | `.obsidian/plugins/atelier-projection/manifest.json`, `main.js`, `styles.css` | the whole file | the shipped bytes |
83
+ | `.obsidian/plugins/atelier-projection/data.json` | the whole file | this vault's channel: the service address, the view and the view's bearer |
84
+
85
+ The plugin files' digests are pinned in the generation manifest, under
86
+ `ext["mnstry.atelier.obsidian"].settings.pluginOwned`, beside the record of
87
+ the owned settings keys and entries. A prepared view that carries a plugin
88
+ file its manifest does not pin, or pins one it does not carry, is refused
89
+ before anything is written.
90
+
91
+ Plugin files go through the same conditional publication as notes, with two
92
+ differences. Each is compared with what the disk holds when the run begins
93
+ rather than with an earlier generation, so whatever occupies a plugin path (a
94
+ hand edit, an earlier release, something else entirely) is exchanged out into
95
+ recovery with a receipt and is never lost. And a plugin path that a person has
96
+ to repair never holds a view back: a symlinked folder, a directory where a
97
+ file should be, or a file or folder nobody may read (`path-unsafe`), a vault
98
+ root that is not private enough for the bearer (`vault-not-private`), or a
99
+ file that cannot be made or replaced there, in a plugin folder the person can
100
+ read but not write, say (`create-failed`, `exchange-failed`), is reported and
101
+ left alone while every note converges. Once it is repaired,
102
+ or when a person changed or removed a plugin file, the file is written again
103
+ at the maintenance service's next tick, with nothing changed at the view's
104
+ sources: the service compares the plugin files each committed generation pins
105
+ with the disk, has a view whose files differ prepared again, and a generation
106
+ that is already committed is published again, as it is, with its notes kept.
107
+ A drift left for the person is asked about once, and again once it changes.
108
+ What a publication could not change is not asked about again: a drift that
109
+ publishing cannot repair (the data file of a vault root that is a link, say)
110
+ is asked about at most once more after a publication that left it, which
111
+ writes no vault file (the view is prepared again and takes the vault's lock,
112
+ and for that tick shows as `stale`, then `updating`, before it is `current`
113
+ again), and not again after that. A plugin file
114
+ another writer removes or changes after the publication, even while the same
115
+ tick goes on, unless it holds again what it held before the tick under the
116
+ same pin and vault root, and a path
117
+ the person repairs before the tick ends (the vault root made private,
118
+ say), is asked about at the next look and written;
119
+ where the plugin is turned off in the vault, a pinned file that is gone is no
120
+ drift. Publishing a committed generation again needs the app when one runs;
121
+ if that app does not qualify, nothing is written, the view stays `current`
122
+ (a committed generation needs no app, so an app that cannot be qualified
123
+ never makes a current view stale), and the file is written at a later
124
+ attempt, on the retry schedule of an unsettled view, once the app
125
+ qualifies. A drift that publishing would only leave for the person again
126
+ (the data file of a vault root that is a link or not private, a folder where
127
+ a file goes) is no reason to publish again and needs no app: the path is
128
+ left as it is, and the view keeps its plain reason. Nothing in `status` or
129
+ `open` names such a path yet; without its data file the plugin cannot reach
130
+ the service, so it shows as not present (`no-live-lease`). A few paths the
131
+ publisher leaves for the person still count as a drift here, and wait for the
132
+ app as one: a file nobody may read, a parent folder that cannot be looked
133
+ into, and a vault root Atelier keeps whose mode cannot be changed. Until the app qualifies, a
134
+ view waiting for it has the reason
135
+ `verified-by-read-back-plugin-waits-for-app` rather than
136
+ `verified-by-read-back`, and `status` and `open` add `plugin files wait for
137
+ the app` to the plugin line (`files: "waits-for-app"` in `--json`), with a
138
+ next step for the plugin. (The settings files are held to the same rule for a file nobody
139
+ may read.) A plugin file that another program changes while a publication runs
140
+ is a race and is handled like one on a note: that publication does not
141
+ commit, the view is tried again, and the file is planned from the disk anew
142
+ (`plugin-file-changed`); nothing reads a plugin file as an edit of a note.
143
+ The view is then `publisher-conflict`, and its next step in `status` and
144
+ `open` names that file as what stopped the publication (changed by another
145
+ program or by the person; one change is enough), rather than asking the
146
+ person to close another publisher; the same holds for a settings file
147
+ (`settings-changed`). The reason is the first unit that stopped the
148
+ publication, so other causes are not ruled out.
149
+ Upgrades replace the plugin files the same way. The plugin's version in
150
+ `manifest.json` changes whenever its code does, and the test suite holds the
151
+ two together: the app keeps running the `main.js` it loaded until it reloads
152
+ the plugin, so one version must name one code.
153
+
154
+ `community-plugins.json` is a list the person owns, with Atelier's entry in it
155
+ only while the person wants the plugin in that vault (next section). A file
156
+ that is not a list is the person's to repair; it is reported as
157
+ `settings-invalid` and left alone. Atelier never removes plugin files from a
158
+ vault.
159
+
160
+ ### Turning it off, and on again
161
+
162
+ The person decides whether Atelier's plugin runs in a vault, and Atelier
163
+ follows. The decision is the one Obsidian itself records: the
164
+ `atelier-projection` entry of `community-plugins.json`.
165
+
166
+ - The first publication of a vault offers the entry. Once the app has it, a
167
+ list without it is the person's decision: turning the plugin off in
168
+ Settings, Community plugins, removes the entry, and uninstalling it there
169
+ removes the entry and deletes the plugin's folder. The app has the entry
170
+ when it was in place before an app next opened the vault (published while
171
+ no app held the vault), or once the plugin has run there. An entry
172
+ published while an app holds the vault is only `offered`: that app keeps
173
+ the list it read when it opened the vault and may write it back without the
174
+ entry, which is no decision of the person's, so the next publication offers
175
+ the entry again.
176
+ - From then on the vault's plugin is `off`. Atelier leaves
177
+ `community-plugins.json` exactly as the person left it and never adds the
178
+ entry back. It creates no plugin file that is not there, so a folder the
179
+ person deleted stays deleted, and it keeps the files that are still there
180
+ current, so turning the plugin back on gets the current code. The vault
181
+ works through the command-line path, and `status`, `open` and
182
+ `atelier obsidian plugin show` report the plugin as not present with reason
183
+ `turned-off-in-this-vault` and the way back. They read the vault's list, so
184
+ they say so as soon as the person has turned the plugin off; the decision
185
+ is recorded when the view is next prepared (`plugin show` marks a choice
186
+ not recorded yet as `pending`).
187
+ - There are two ways back. Turning "Atelier" on again in Obsidian's settings
188
+ (possible while its folder is there) lists the entry again; the view's next
189
+ publication records the plugin as on and owns the entry again.
190
+ `atelier obsidian plugin on --scope ID` records a request instead and asks
191
+ the running maintenance service for a tick that names the view: that tick
192
+ prepares and publishes the view again, which adds the entry back and makes
193
+ the plugin's files again (the way back after an uninstall), and the command
194
+ says so when it returns (`takesEffect: "published"`). With
195
+ `--adapter=obsidian-cli`, a service of an earlier release still running
196
+ after an upgrade is replaced by the installed one first, under the consent
197
+ already recorded, as `open` does; without it, such a service is only
198
+ reported (`service-outdated`) with that next step. A service of a later
199
+ release is never replaced; it is reported (`service-other-release`) with
200
+ `open`'s next step for it. Either next step is also the document's
201
+ `next`. With no service
202
+ running, the view's next publication does it: the one the next change at
203
+ its sources causes, or the one when the service next starts.
204
+ Obsidian reads `community-plugins.json` when it opens a vault, so a plugin
205
+ brought back this way runs from the next time the vault is opened; one
206
+ turned on in Obsidian's settings runs at once.
207
+ - The list is read before the view is prepared, and the entry is written only
208
+ over exactly the bytes it was read as. A change the person makes in
209
+ between, turning the plugin off while a publication runs for example, is
210
+ never written over: that settings unit stops (`settings-changed`), the
211
+ publication does not commit, and the view is tried again, when its
212
+ preparation reads the list as the person left it.
213
+ - A list that is missing, or that is not a list, decides nothing. A missing
214
+ list is written again with the entry, unless the vault's plugin is `off`.
215
+ Restricted mode leaves the list alone, and needs nothing from Atelier: no
216
+ community plugin runs.
217
+
218
+ The decision is kept owner-only in the workspace's private state, under
219
+ `state/plugin/choices/<view>.json`, as `requested`, `offered`, `on` or `off`,
220
+ with the reason (`entry-confirmed` or `entry-seen-by-the-app` for `on`,
221
+ `entry-removed-by-person` for `off`), so an entry the app never had is never
222
+ taken for the person's decision. A record that cannot be read counts as
223
+ `off`: nothing is added back on a guess.
224
+
225
+ ### The data file and the privacy of the vault
226
+
227
+ `data.json` names the address the service listens on and the key of this one
228
+ vault, its bearer:
229
+
230
+ ```json
231
+ {
232
+ "schema": "atelier-obsidian-plugin-data/v1",
233
+ "channel": { "host": "127.0.0.1", "port": 43123 },
234
+ "scopeId": "scope-example",
235
+ "bearer": "<43 characters>"
236
+ }
237
+ ```
238
+
239
+ The bearer is 32 random bytes, minted by the service the first time the view
240
+ is prepared and kept owner-only in `state/plugin/<view>.json` under the
241
+ workspace's private state. It is the key of the handshake below and never
242
+ crosses the wire. The data file is written with mode `0600`, and only into a
243
+ vault root that is private to this user (owned by this user, no group or other
244
+ permission bits). Atelier creates the vaults it places under its own data root
245
+ with mode `0700` and makes an existing one private before the first bearer
246
+ goes in. Any other vault root is only checked: when it is not private the data
247
+ file is not published (`vault-not-private`), the rest of the vault is, and the
248
+ plugin says `Atelier: not set up`. A vault path under the data root that is a
249
+ link to a folder somewhere else never receives the data file, private or not
250
+ (`vault-not-private`, reason `vault-root-is-a-link`), and its mode is never
251
+ changed; the view is published into it as ever.
252
+
253
+ Deleting `state/plugin/<view>.json` rotates the bearer. A session made with
254
+ the old one ends at its next request, and a handshake with it is refused; the
255
+ next publication of the view (its next change, or the next start of the
256
+ service) mints a new one and replaces the data file, which the plugin reads
257
+ again without a restart.
258
+
259
+ ## The trust prompt
260
+
261
+ A vault that carries community plugins asks, the first time an app profile
262
+ opens it:
263
+
264
+ > Do you trust the author of this vault?
265
+ >
266
+ > You're opening this vault for the first time, and it comes with some plugins.
267
+
268
+ with two buttons, "Browse vault in Restricted Mode" and "Trust author and
269
+ enable plugins". Obsidian keeps the answer for each vault in the app's own
270
+ storage for that app profile (not in the vault), so it is asked once per vault
271
+ and per profile.
272
+
273
+ - Trust: Obsidian enables the plugins the vault lists, Atelier's among them.
274
+ The status bar item appears and the service sees the vault open.
275
+ - Restricted mode: no community plugin runs. Obsidian asks again the next time
276
+ the vault is opened, until restricted mode is switched on for good in
277
+ Settings, Community plugins. Nothing of Atelier depends on the answer:
278
+ maintenance, `open` and `status` work through the command-line path, and
279
+ `status` reports the plugin as not present.
280
+
281
+ Only the person answers this prompt. Atelier does not answer it, write the
282
+ app's storage, or change the app profile. The isolated real-app test answers
283
+ it in a disposable app profile only; see "Evidence".
284
+
285
+ ## The channel
286
+
287
+ The service's loopback listener (see [local-services.md](local-services.md))
288
+ answers five plugin commands besides its own four operations. Every one is a
289
+ `POST` of a JSON object of at most 1 KiB with exactly the fields of its
290
+ command, and none carries a credential in a header: the plugin proves it holds
291
+ the vault's key without sending it, and the service proves it first
292
+ (protocol `atelier-obsidian-plugin-channel/v2`).
293
+
294
+ 1. The plugin sends a fresh 32-byte nonce, the time it made it, and a hint at
295
+ its key: an HMAC of both under the key. The hint names no vault, and two
296
+ hints of one vault do not look alike. The service computes the hint for
297
+ each vault's key it holds, in constant time for every one of them, and
298
+ answers only for a key it holds, only within thirty seconds of the time the
299
+ challenge names, and each nonce only once: its own nonce, a handshake id,
300
+ and a proof, an HMAC under the key over the view, its own exact address,
301
+ both nonces and the handshake id. At most four handshakes wait for their
302
+ hello per view, so a view's challenges never hold up another view's. A
303
+ challenge recorded by a program that took the port while the service was
304
+ down is therefore worth one answer, and only for thirty seconds, and the
305
+ answer leads nowhere without the key.
306
+ 2. The plugin checks that proof, in constant time, against the address in
307
+ its data file. A listener that cannot make it (a program that took the
308
+ port while the service was down, or one relaying a service that listens
309
+ elsewhere) is told nothing more: the plugin shows the service as
310
+ unreachable (`listener-not-proven`) and tries again at its next round.
311
+ 3. Only then does the plugin say hello: its own proof under the key, over the
312
+ same values and everything the hello carries (plugin version, app version,
313
+ the id of this launch, and a proof of the vault path under the session key,
314
+ so the path itself is never sent). A handshake is used once, whatever the
315
+ hello proves, and lapses after five seconds.
316
+ 4. Both derive a key for this session from the same values. Every later
317
+ command carries the session, a counter that only goes up, and a MAC under
318
+ the session key over the command, the session and the counter; every
319
+ answer the service gives is the exact text of its document with a MAC over
320
+ it, the command and the request's counter. The plugin believes no answer
321
+ that does not verify. Any answer to a command that is not sealed (an
322
+ error, which nobody vouches for, a timeout, or an answer that does not
323
+ verify) ends the session in the plugin, which shakes hands again: at once
324
+ when a renewal is refused as an unknown session or as not authenticated,
325
+ at its next round otherwise. A listener that cannot seal therefore
326
+ receives one command of a session at most, besides its release. A session
327
+ ends when its lease lapses, when it is released, after fifteen minutes (the
328
+ plugin shakes hands again), or at its next request after its vault's key
329
+ was rotated.
330
+
331
+ | Command | Fields | Answer |
332
+ | --- | --- | --- |
333
+ | `POST /plugin/challenge` | `protocol`, `keyHint`, `clientNonce`, `issuedAt` | `protocol`, `handshakeId`, `serverNonce`, `serverProof` |
334
+ | `POST /plugin/hello` | `handshakeId`, `pluginVersion`, `appVersion`, `instanceId`, `vaultProof`, `clientProof` | sealed: a session identity, the lease time and the renewal interval |
335
+ | `POST /plugin/lease` | `sessionId`, `counter`, `mac` | sealed: the renewed lease |
336
+ | `POST /plugin/release` | `sessionId`, `counter`, `mac` | sealed: whether the session was released |
337
+ | `POST /plugin/status` | `sessionId`, `counter`, `mac` | sealed: the view's state, reason, verification, committed and prepared generation, held notes (counted, not named), retained edits, open pending edits, and the service's own state |
338
+
339
+ Refusals, before anything else is looked at: `Host` other than the listener's
340
+ literal loopback authority, a cross-site `Origin` or `Sec-Fetch-Site`, a path
341
+ other than one of the nine exactly (a query included), or another method (the
342
+ listener's rules for every request). Then:
343
+
344
+ | Case | Status | Code |
345
+ | --- | --- | --- |
346
+ | a credential in the `Authorization` header, the service's runtime bearer included | 400 | `plugin-command-takes-no-bearer` |
347
+ | a body over 1 KiB | 413 | `payload-too-large` |
348
+ | a body that is not a JSON object | 400 | `payload-not-json-object` |
349
+ | a field too many or missing, or of the wrong shape (a challenge that names the view, say) | 400 | `request-malformed` |
350
+ | a challenge in another protocol | 409 | `protocol-unsupported` |
351
+ | a challenge whose hint matches no key the service holds | 401 | `plugin-key-unknown` |
352
+ | a challenge whose time is more than thirty seconds from the service's clock | 401 | `challenge-stale` |
353
+ | a challenge whose nonce the service has already answered for that view | 401 | `challenge-replayed` |
354
+ | four handshakes of that view already waiting for their hello | 429 | `too-many-handshakes` |
355
+ | a hello for a handshake that is unknown, used, lapsed, or was made at another address | 401 | `handshake-unknown` |
356
+ | a hello whose proof does not verify under the key | 401 | `plugin-not-authenticated` |
357
+ | a hello that proves a vault path other than this view's | 409 | `wrong-vault` |
358
+ | a ninth live session for one view | 429 | `too-many-sessions` |
359
+ | a command for a session the service does not know, or whose key was rotated | 409 | `session-unknown` |
360
+ | a command whose MAC does not verify | 401 | `request-not-authenticated` |
361
+ | a command with a counter already seen | 401 | `request-replayed` |
362
+
363
+ The service keeps handshakes and sessions in memory. A service that started
364
+ again knows neither; the plugin's next lease is refused with
365
+ `session-unknown`, and it shakes hands again.
366
+
367
+ What the person sees of a refused challenge: a 401 is a key the service does
368
+ not know (`Atelier: not set up`, reason "key not known to the service"; the
369
+ data file is older than a rotated key, say), unless the answer says the
370
+ challenge was `challenge-stale` or `challenge-replayed`. The key is not in
371
+ question then: the plugin shows the service as unreachable with that reason,
372
+ and its next round tries again with a fresh challenge. None of these answers
373
+ is authenticated, so they only ever decide what is shown.
374
+
375
+ ## Security argument
376
+
377
+ - Reach. The listener binds a literal loopback address and refuses any `Host`
378
+ but its own authority, so nothing off the machine and no name that resolves
379
+ to loopback reaches it. A web page, including a note rendered in the app,
380
+ sends an `Origin` or `Sec-Fetch-Site` that is refused, and it cannot compute
381
+ a proof. The plugin's own requests come from Node's `http` module and carry
382
+ neither header.
383
+ - Authenticity, both ways. The service answers a challenge only for a key it
384
+ holds and proves it over its own exact address; the plugin proves the same
385
+ key over the same values; every later request and answer is sealed with the
386
+ session's key. Comparisons are constant time. A proof or a MAC cannot stand
387
+ in for another (each has its own label), a challenge is answered once and
388
+ only while fresh, a handshake is used once, and a counter is accepted once,
389
+ so nothing recorded can be replayed for any use.
390
+ - Secrecy. The key lives in two owner-only places, the private state of the
391
+ workspace and the vault's `data.json` (`0600`, in a vault root that is
392
+ `0700`), and never crosses the wire. Another user of the machine cannot read
393
+ it, and a program of any user that takes the service's port while it is
394
+ down learns a nonce, a hint it cannot link to a vault, and of a session
395
+ that ended with the service one sealed renewal or status request at most
396
+ (and its release, when the plugin unloads meanwhile): no key, no view, no
397
+ path, no session and no app version it can use (the squatter tests in
398
+ "Evidence"). Any process that runs as this user can read the data file,
399
+ including other community plugins in the same app: the key separates vaults
400
+ and processes of one person, it does not authenticate a person. That is the
401
+ same boundary the service's runtime bearer has. A vault copied or
402
+ synchronized to another machine carries its key there, where it reaches
403
+ nothing: the service answers on this machine's loopback address only.
404
+ - Blast radius. Whoever holds a vault's key can make that view look open, or
405
+ closed, take its eight sessions for as long as it keeps renewing them, and
406
+ claim an app version for it. A claimed version only admits the command-line
407
+ path for that view: publication still needs the app to answer for the vault
408
+ through its own channel, and the in-app step still refuses on an app that
409
+ lacks the saved-content field it relies on (`unsupported-app`). Nothing a
410
+ key holder sends writes a file, starts a publication or changes an edit, and
411
+ the key reaches nothing of the service itself: not its status, tick or stop.
412
+ - Cost. A request without a key costs the service one look at the bearer
413
+ directory (the bearers are kept in memory and read again only when that
414
+ directory changed) and one HMAC per vault; at most four handshakes wait per
415
+ view, and recorded challenges can keep them filled for at most about
416
+ thirty seconds after a restart, since a challenge is answered only within
417
+ thirty seconds of the time it names.
418
+ - Egress. `plugins/` is in the egress scan, which fails a request whose target
419
+ is not a literal loopback address, and the release audit requires the three
420
+ plugin files in the package and scans them in the tarball.
421
+
422
+ ## When the plugin is not there
423
+
424
+ If the plugin is not loaded (the person chose restricted mode or has not
425
+ answered the prompt yet, the app is older than 1.13.7, or the person turned it
426
+ off in the vault), nothing changes: the service coordinates through the
427
+ command-line path exactly as before, `open` asks the command-line tool for the
428
+ app version, and `status` reports the plugin as not present with the reason
429
+ (`no-live-lease`, `turned-off-in-this-vault` or `service-not-running`).
430
+
431
+ ## The app version a plugin reports
432
+
433
+ The plugin runs inside the app whose version it reports, so its version is
434
+ the app's. The command-line tool, though, reaches the app a publication
435
+ coordinates with, which may be another app holding the same vault, so its own
436
+ answer decides wherever it gives one:
437
+
438
+ - The service's adapter factory asks the probe as it would without a plugin,
439
+ through the same remembered answer. Where the tool answers with a version,
440
+ that version qualifies or refuses the app, whatever the plugin reports.
441
+ Where it gives none (no vault open yet, as it says while a vault is still
442
+ loading, or no answer in time), the version the plugin reports stands in,
443
+ reason `plugin-reported`, while one launch of the plugin alone holds the
444
+ view. Whether the app is installed where Atelier looks and has its
445
+ command-line tool is the probe's answer either way. The adapter still
446
+ coordinates through the command-line tool, which must answer for exactly
447
+ this vault before anything is published.
448
+ - `open` does the same: the plugin's version stands in only where the tool
449
+ gives none. It asks the service about the plugin at most once a second
450
+ while it waits for the app. A version only the plugin reported is not the
451
+ command line answering, so `open` then asks the command line nothing to
452
+ find the vault: it reads the app's settings file, as with no vault open,
453
+ and opens by path the vault the app lists. Where the settings file does
454
+ not show it (a sandboxed build, say), or where it lists it and, once the
455
+ vault is opened, the command line still does not answer for it while only
456
+ the plugin reports the version, `open` answers
457
+ `app-cli-unavailable` / `vault-open-cli-silent`: the plugin shows the vault
458
+ open, so the command line is what did not answer, and waiting for the app
459
+ would change nothing. After a launch that answer is given at the second
460
+ such round in a row, not the first: one tool call that timed out while the
461
+ app was busy opening its window looks the same once, and `open` then goes
462
+ on waiting for the vault. A wait that ends right after one such round gives
463
+ the same answer.
464
+ - Whether an app runs is the process table's answer, which says no only when
465
+ it finds no Obsidian process at all. A lease outlives its app by up to the
466
+ lease time (six seconds after a crash), and any process that holds the
467
+ vault's key can hold one, so while the process table shows no app, a
468
+ plugin's report counts for nothing, in the adapter factory and in `open`.
469
+ The probe's own answer stands (`app-not-running-version-not-needed`): the
470
+ publication goes to the files, and an adapter qualified on that answer
471
+ never coordinates with an app found running later (`app-version-unchecked`),
472
+ so an app started since, perhaps updated as it restarted, is asked for its
473
+ own version at the next qualification.
474
+ - With two launches holding one view (two app profiles with the same vault
475
+ open, both running the plugin), neither plugin's version decides.
476
+ - An app below the floor refuses as before (`app-version-unsupported`,
477
+ `below-minimum-version`), whoever reported the version.
478
+
479
+ What remains: where the tool gives no version but still coordinates (its
480
+ `version` call timed out, say), and a second app holds the same vault without
481
+ the plugin, the plugin's version may belong to the other app. The in-app step
482
+ still refuses an app without the saved-content field it relies on, for a
483
+ note open in the editor.
484
+
485
+ `atelier obsidian status` and `open` report the plugin for each view:
486
+ `{ present, reason, appVersion, pluginVersion, sessions }`, and a line such as
487
+ `plugin present (Obsidian 1.13.7)`.
488
+
489
+ ## Evidence
490
+
491
+ - `test/obsidian-plugin.test.mjs` runs the shipped `main.js` in a stand-in app
492
+ against a real listener on an ephemeral loopback port: parity of the
493
+ plugin's constants and of every proof and MAC with the channel contract (a
494
+ plugin that computes any one of them otherwise gets nowhere); the status
495
+ bar, the status window and the notices through every state; lease renewal,
496
+ release and lapse; a service restart; a republished data file; a rotated
497
+ key; missing or unusable channel data; the app floor; a program squatting
498
+ the service's address, which learns no key, no view and no path and, with
499
+ everything it received replayed against the restarted service, opens no
500
+ session and sets no app version; an answer relayed from a service at
501
+ another address; answers that are not sealed, and a listener that answers
502
+ every command with an error, which gets one renewal of a session and
503
+ nothing more of it; answers that alternate between the two failures, which
504
+ do not make the status bar flip; an unload while a hello is
505
+ under way; the plugin's own timer (a renewal every two seconds, rounds that
506
+ never overlap, nothing after an unload); the refusal table above with
507
+ mutation controls that must fail
508
+ it; bearer minting, rotation and the bearer cache; publication of the
509
+ plugin files, entries and data file, with a person's settings kept, the
510
+ person's choice followed (an entry the app had and the person removed is
511
+ not added back; one published while an app held the vault is only offered
512
+ until the plugin runs there; `plugin on` publishing the view at once while
513
+ the service runs),
514
+ displaced bytes kept in recovery, the privacy rule of the vault root, a
515
+ linked vault root, unreadable files, a drifted plugin file written again
516
+ (at the service's next tick, with nothing changed at the sources, and a
517
+ drift left for the person asked about once),
518
+ and an upgrade; the service publishing the plugin and the plugin it
519
+ published holding the view; qualification from a plugin report, with one
520
+ launch or two; and `status` and `open` reporting it. A spawn guard refuses
521
+ any child of the suite that could reach an Obsidian with the developer's
522
+ own `HOME`.
523
+ - The same file has three real-app tests, skipped unless
524
+ `ATELIER_OBSIDIAN_PLUGIN=1` is set on a desktop host with Obsidian
525
+ installed. Each publishes a synthetic vault through the maintenance service
526
+ and opens it in a disposable Obsidian instance (private `HOME`, private
527
+ profile, mock keychain, a copy of the pinned app archive when
528
+ `ATELIER_OBSIDIAN_ASAR` names one). The first checks that the trust prompt
529
+ is shown, answers it the way a person does, by pressing "Trust author and
530
+ enable plugins" in that window through the command-line tool's `eval`, and
531
+ asserts that the service sees the lease with the app's version, that the
532
+ status bar item says `Atelier: current`, that a publication made while the
533
+ plugin holds the view qualifies the app from the command-line tool's own
534
+ version (reason `meets-minimum-version`), that
535
+ a rotated key reaches the running plugin, and that quitting the app ends the
536
+ presence. The second declines the prompt and publishes through the
537
+ command-line path as before. The third uninstalls the plugin in the app and
538
+ follows it until `atelier obsidian plugin on` brings it back at once (the
539
+ running service publishes the view on a tick that names it), offered while
540
+ that app holds the vault and confirmed when the plugin runs after a
541
+ restart. They never touch another app profile, and they end each disposable
542
+ instance by its profile path.
543
+
544
+ The disposable window takes focus when it opens, so input meant for another
545
+ window can answer its prompt before the test looks. The first test then
546
+ fails and says so; the third, whose subject comes after trust, accepts a
547
+ vault trusted that way. The desktop procedures
548
+ (`scripts/obsidian/desktop-receipts.mjs`) decline the prompt as the second
549
+ test does. When the command-line tool loses the reply to that press, a
550
+ prompt that is gone proves nothing by itself: the app's own restricted mode
551
+ says how it was answered, and a procedure refuses to run with community
552
+ plugins on (`community-plugins-on`), ending the instance it started.
553
+
554
+ Pressing the button in the prompt was chosen over the alternatives because
555
+ it exercises the prompt a person sees and needs no knowledge of the app's
556
+ storage. Writing the profile's local storage would mean editing a LevelDB
557
+ database behind the app's back, and calling `app.plugins.setEnable(true)`
558
+ would skip the prompt altogether.
559
+
560
+ ## Phase 2 plan
561
+
562
+ Phase 2 moves the in-app critical section of publication into the plugin, and
563
+ closes gates G20 and G21 of the initiative. Nothing below is implemented.
564
+
565
+ 1. Commands. The channel gains the publication protocol's three operations,
566
+ `inspect`, `publish` (`replace` and `remove`) and `collect`, with the
567
+ payloads the command-line path validates today (`validatePayload`), and a
568
+ protocol variant, `obsidian-plugin-critical-section/v1`, recorded in every
569
+ journal it writes. The service stays the only client that starts work; the
570
+ plugin fetches work with a bounded long poll (`POST /plugin/work`) and
571
+ answers with `POST /plugin/result`, both sealed under the session's key
572
+ like every command of phase 1, so the plugin never listens on a port and
573
+ takes work only from a service that proved it holds the vault's key.
574
+ At most one operation per vault is in flight, every operation has an
575
+ identity, a lost answer is recovered with `collect`, and a publish is never
576
+ sent twice.
577
+ 2. The critical section. The plugin runs the same synchronous body the
578
+ command-line path sends through `eval` (`criticalSection` in
579
+ `bridge-script.mjs`), shipped in `main.js` rather than sent: refuse on a
580
+ dirty or differing editor in any window, refuse on changed bytes on disk,
581
+ exchange atomically, update open editors in one transaction of minimal
582
+ hunks and record `lastSavedData`, record the outcome, reply at once. The
583
+ late-writer re-check and the manifest commit stay in the publisher.
584
+ 3. Path selection. With a live lease from a plugin whose protocol matches, the
585
+ publisher uses the plugin channel; otherwise the command-line path, and
586
+ with no app running, the app-free path, exactly as today. A refusal is
587
+ never relaxed by switching paths.
588
+ 4. Live edit preservation. The plugin forwards the vault's `modify` events and
589
+ the editor's dirty state for Atelier's notes to the service as they happen,
590
+ so an edit is preserved in recovery before any maintenance tick looks for
591
+ it. The service still decides what an edit is.
592
+ 5. G20. The complete G00 interleaving suite (sixteen interleavings, racing
593
+ cases repeated) and the G04 production-publisher suite run in plugin mode
594
+ on a real isolated app, unchanged, with zero lost bytes. A case that passes
595
+ on the command-line path and fails in plugin mode fails the gate; the suite
596
+ is not narrowed.
597
+ 6. G21. Apply and Discard in the status window for held edits, forwarded to
598
+ the service's existing apply operation, which decides; then the procedure
599
+ AP-08: enable, disable, uninstall and an app below the floor on one vault
600
+ with pending edits and recovery bytes, comparing manifests, path
601
+ allocations, pending edits and recovery digests with the command-line path
602
+ after each step.
603
+
604
+ Open questions for phase 2: when, if ever, plugin files are removed from a
605
+ vault; and how an upgraded service answers an older `main.js` that the app
606
+ keeps running until it reloads the plugin (the hello carries the plugin
607
+ version and the protocol for that).
608
+
609
+ ### Deviations from the track plan
610
+
611
+ The obsidian-plugin track planned an optional enhanced mode. The owner's
612
+ decision of 2026-09-24 made the plugin the primary in-app half, shipped in
613
+ every managed vault, with the command-line path as the fallback. Phase 1 here
614
+ therefore builds the plugin, its delivery and a presence and status channel
615
+ before the publication commands the plan's first phase describes; the parity
616
+ held today is between the plugin's constants and the channel contract, and the
617
+ refusal parity with the command-line path belongs to phase 2, when the plugin
618
+ carries publication commands. Fixtures are generated in temporary directories
619
+ rather than kept under `fixtures/obsidian/plugin/`.