@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
@@ -10,16 +10,19 @@ here enables a feature. The publisher under
10
10
  ## Registered shapes
11
11
 
12
12
  Eleven closed v1 schemas, `contracts/atelier-obsidian-<shape>.v1.schema.json`,
13
+ and one v2 schema, `contracts/atelier-obsidian-generation-manifest.v2.schema.json`,
13
14
  are registered in `src/contracts/corpus.mjs` with valid and invalid fixtures
14
- under `fixtures/obsidian/contracts/<shape>/`. `src/projection/obsidian/contracts.mjs`
15
- validates them and adds the refusals a schema cannot express.
15
+ under `fixtures/obsidian/contracts/<shape>/` (the v2 manifest under
16
+ `generation-manifest-v2/`). `src/projection/obsidian/contracts.mjs` validates
17
+ them and adds the refusals a schema cannot express; a generation manifest is
18
+ validated against the version its `schema` names.
16
19
 
17
20
  | Shape | Portable | Purpose |
18
21
  | --- | --- | --- |
19
22
  | `corpus-profile` | yes | Workspace and repository identities, enrollment, audience |
20
23
  | `scope` | yes | Mode and selector set-AST; an empty selection is valid and empty |
21
24
  | `source-snapshot` | yes | Raw byte digests, graph pin, dirty state, single consistent read |
22
- | `generation-manifest` | yes | Path map, byte regions, link inversion map, completeness |
25
+ | `generation-manifest` | yes | Path map, byte regions, link inversion map, completeness. v1 is vault layout 1; v2 is layout 2 (see "Vault layout") |
23
26
  | `publication-journal` | no | Conditional operations, partial transition, restart; protocol ID required |
24
27
  | `service-state` | no | Literal loopback, port, runtime ID, PID, executable, consent |
25
28
  | `edit-operation` | yes | Object identity, origin generation, base digest, idempotency key |
@@ -32,6 +35,11 @@ Portable shapes refuse absolute paths. `atelier-project-config.v1` is
32
35
  unchanged: extension settings are validated by their own schema, and an
33
36
  unknown extension key refuses in the adapter, not in the project validator.
34
37
 
38
+ The `ext-settings` key `ext["mnstry.atelier.personal-workspace"]` is reserved.
39
+ Only a personal-workspace binding sets it, in memory, to name its generation.
40
+ A project whose settings declare it without a binding is refused at every
41
+ build with `personal-binding-lost`.
42
+
35
43
  ## Selection
36
44
 
37
45
  `selectScope({ canonicalSnapshot, profile, selector, expansion })` is the only
@@ -44,17 +52,65 @@ proceeds in canonical-identity order and reports truncation. Relation types are
44
52
  `contradicts`, `belongs_to` and the derived `links_to`. Literal oracles and
45
53
  refusals live in `fixtures/obsidian/contracts/oracles/scope-cases.json`.
46
54
 
55
+ ### Which notes are eligible
56
+
57
+ The eligibility a view is built with is decided by the machine settings of
58
+ the workspace (`eligibilityFor` in `src/runtime/obsidian/pipeline.mjs`), and
59
+ the engine, source apply, the proposal adapter and the selection operation
60
+ all ask it, so they agree on what a view holds. By default
61
+ (`DEFAULT_ELIGIBILITY`, revision
62
+ `classified-documents/v2+assets-embedded-by-eligible-documents/v1`) a node is
63
+ eligible only when the census classified it. When the audience decision is
64
+ `only-you` with `unclassified: shown`, and only then, an unclassified note is
65
+ eligible too, provided its bytes pass the byte lens the emitter reads notes
66
+ with, and it has no front matter or front matter whose every top-level line
67
+ starts at column 0 with a plain `key:` (an ASCII letter or `_`, then ASCII
68
+ letters, digits, `_` and `-`), none of them `kg`, with indented lines only as
69
+ the values of such keys, and in which the graph's own reading finds no `kg`
70
+ key either. A quoted key, a flow or JSON mapping, a complex key, a top-level
71
+ list, an indented root mapping or front matter the graph could not read keeps
72
+ it withheld, since it may carry a `kg.audience` such as `sensitive`
73
+ (`onlyYouEligibility`, revision
74
+ `classified-documents/v2+unclassified-notes-read-as-notes-for-only-you/v4+assets-embedded-by-eligible-documents/v1`).
75
+ An unclassified note carries the `private` audience, which "only you" admits.
76
+ An admitted note is an ordinary member of the view for the redaction guard:
77
+ its identity may appear in generated bytes of that view, and stays forbidden
78
+ in every view it is not part of. Assets follow the documents that embed them.
79
+
47
80
  ### What redaction covers
48
81
 
49
- Emission is byte-faithful: a visible author's bytes are emitted unchanged. A
50
- visible author's own references to withheld or out-of-selection documents
51
- (canonical identities in front matter, repository-relative paths, link text)
52
- therefore appear in the vault exactly as written. Only generated bytes are
53
- redacted: generated regions, note paths, manifest entries and link inversions
54
- never carry a withheld node's identity, title, summary, tags or path. A scoped
55
- vault is not a confidentiality boundary against what visible authors wrote. A
56
- redaction boundary over authored bytes cannot coexist with byte-faithful
57
- emission; it would be a separate mode with its own evidence.
82
+ The guarantee: Atelier never generates a reference to a note outside the
83
+ view. No link, embed or relation row the emitter writes targets one, no
84
+ identity block names one, and no folder name or collision qualifier is
85
+ derived from one; manifest entries and link inversions carry nothing about
86
+ one either.
87
+
88
+ Text that comes from an author is carried as authored. Emission is
89
+ byte-faithful: a visible author's bytes are emitted unchanged, so a visible
90
+ author's own references to withheld or out-of-selection documents (canonical
91
+ identities in front matter, repository-relative paths, link text) appear in
92
+ the vault exactly as written. Generated regions repeat author text as well:
93
+ the titles of in-view notes in relation rows, and a wrapped file's title,
94
+ summary and tags in its note. That text is carried as its authors wrote it
95
+ too, escaped for Markdown or, in file names and link aliases, with the
96
+ characters those cannot hold turned into spaces. A scoped vault is not a
97
+ confidentiality boundary against what visible authors wrote (owner decision,
98
+ 2026-09-22). A redaction boundary over authored bytes cannot coexist with
99
+ byte-faithful emission; it would be a separate mode with its own evidence.
100
+ The deny-list below is defence in depth over the author text that generated
101
+ regions repeat, not the guarantee, and it follows the audience: a note outside
102
+ the selection that the view's audience may see is not withheld from the
103
+ vault's readers, so text naming it is reported, not refused (see "The
104
+ redaction guard").
105
+
106
+ A layout 2 vault shows, in its folder names, the repository identity and the
107
+ source directory chain of the notes in the view, and every note names its
108
+ repository and source path in its `atelier-repo` and `atelier-source`
109
+ properties; layout 1 showed neither. A folder exists only because a note of
110
+ the view, a file the view copies for an embed, or a file held for an open edit
111
+ is in it, though it may hold only some of that source directory's files. This
112
+ surface is acceptable for every vault, scoped ones included (owner decision,
113
+ 2026-09-24).
58
114
 
59
115
  Each note may carry the generated line `Relationships leading outside this
60
116
  view: N`. N counts that note's relationships to notes that are visible but not
@@ -62,15 +118,416 @@ selected, so it reveals that count, and two views of one workspace can be
62
118
  compared. It never counts a withheld node: edges to a withheld endpoint are
63
119
  dropped before the count is taken.
64
120
 
65
- ## Note paths
66
-
67
- `notes/<readable title>--<identity suffix>.md`. The suffix derives from the
68
- stable repository identity plus node identity, starts at 12 hexadecimal
69
- characters and lengthens on collision. Titles are Unicode-normalized, platform
70
- reserved names are avoided, and case-folding collisions are detected. Duplicate
71
- titles are allowed; duplicate canonical identities refuse; a title-only link
72
- that matches more than one visible note refuses. Paths are allocated once per
73
- workspace and reused by every view.
121
+ A collision qualifier is the qualified note's own source file stem, or a
122
+ short id of its own identity (see "Collisions"); a qualified folder carries a
123
+ short id of its own source folder or repository. A qualifier appears only
124
+ because something this vault shows took the name first: another note of the
125
+ view, an asset the view copies, or a file held for an open edit, which stays
126
+ in the vault while its edit is open, also after its note has left the view.
127
+ The one exception is fixed and names no note: a repository whose folder would
128
+ be `notes` or `attachments` spelled another way (see "Folders"). It stays while the
129
+ qualified note is in the view, also after the other has left, because a path
130
+ never moves while its note is in the view. A note of another view, a withheld
131
+ note, or one that was never in this view never causes a qualifier by itself:
132
+ each view allocates among its own notes and the files its vault holds.
133
+
134
+ ### The redaction guard
135
+
136
+ Before anything is returned, `prepareView` checks the whole result, every
137
+ note included, whether it was emitted on this call or reused from the
138
+ preparation cache. Any failure refuses the view with `redaction-failure`,
139
+ whose detail names the rule (`allow-list`, `identity-block` or `deny-list`)
140
+ and, where there is one, the note or file of this view it concerns: a path
141
+ allocated to this view (`notePath` or `filePath`), never the value that was
142
+ found and never a path in question. A deny-list refusal names the note whose
143
+ generated region holds the text, which for an incoming relation row is the
144
+ note the row is in, not the note whose title it repeats. An allow-list
145
+ refusal names the note by its allocated path, or the rule alone when the path
146
+ in question is the only one at hand (an attachment that is not the view's, a
147
+ manifest entry of no note of the view). The engine records the same in the
148
+ view's freshness entry, and `atelier obsidian status` shows it. Three rules,
149
+ in the vault layout the view is prepared in:
150
+
151
+ 1. Allow-list. Every path the emitter wrote must be a path allocated to this
152
+ view: every note and attachment path of the manifest, every rewritten link
153
+ or embed target (the bytes an inversion emitted must be such a path, as it
154
+ is or percent-encoded the way the emitter encodes it; the words an
155
+ author's wikilink keeps after `|` are the author's), every attachment an
156
+ embed names, and every `[[…]]` or `![[…]]` target in a generated region,
157
+ without its `#` fragment or `|` alias (relation rows, a wrapper's link and
158
+ embed of its file). The identity block of each note must name exactly that
159
+ note's own repository, identity and source path, and nothing else may
160
+ appear in it.
161
+ 2. Deny-list, defence in depth, following the audience. The free text of the
162
+ generated regions (the titles in relation rows, a wrapper's title, summary
163
+ and tags), the link targets excluded, is read as a reader sees it, with the
164
+ backslashes the emitter adds to escape Markdown removed, and compared in
165
+ NFC, against the identifiers of every census node and embedded asset
166
+ outside this view:
167
+ - Of a node or asset the view's audience may not see (withheld), an
168
+ unambiguous identifier refuses the view: an identity qualified by any
169
+ repository of the census (`<repository>:…`, whichever repository holds
170
+ the note), a repository-qualified source path (`<repository>/<path>`), a
171
+ repository-relative path that holds a folder (a `/`), or a vault path
172
+ recorded for it (see below). An identity that is a bare
173
+ word, and a file name at a repository's root (`README.md`, `index.md`),
174
+ are ambiguous and only reported: in-view text names such words and files
175
+ all the time.
176
+ - Of a node or asset the audience may see but this view does not hold (not
177
+ selected, or an asset the view does not copy), every identifier, a vault
178
+ path of it in another view included, is only reported: every view of a
179
+ workspace has one audience today, so such a note is not withheld from
180
+ anyone who reads this vault (decided 2026-09-24 under the owner decision
181
+ of 2026-09-22). So is a vault path of a note no longer in the census.
182
+ The rule follows the audience: a view with an audience of its own would
183
+ refuse for whatever that audience may not see, with no change to this
184
+ rule.
185
+ - A value that also identifies a note of this view names nothing outside it
186
+ and is neither.
187
+
188
+ What is reported is recorded as a diagnostic (see "Notes that were laid out
189
+ anyway"). A match is a whole token when no letter or digit is beside it,
190
+ and no one of `. _ : / -` joined to a letter or digit (a full stop that
191
+ ends a sentence is beside a whole token; `x.north-desk:a` is one longer
192
+ token); it is compared exactly, case included. The identity block, the one
193
+ place a note names its own repository-relative path, is held to rule 1.
194
+ 3. Coverage. Rules 1 and 2 run over every note of the view on every
195
+ preparation; a reused note is judged exactly as an emitted one.
196
+
197
+ What an author wrote inside the authored body or front matter of an in-view
198
+ note (a withheld node's canonical identity in a relation list, a link to its
199
+ path) is authored bytes: it is emitted as written and never triggers the
200
+ guard. When the same words reach generated prose unchanged, as an incoming
201
+ relation row repeats an in-view title or a wrapper repeats its summary, rule 2
202
+ refuses the view. Where they reach generated text only in a form made safe for
203
+ a name, the outgoing relation row's alias and every file name, `:` and `/`
204
+ have become spaces and runs of whitespace one space; that form is the
205
+ author's text carried as authored, and rule 2 does not match it: there is no
206
+ fuzzy matching (the residual of the 2026-09-22 decision above).
207
+
208
+ A refusing deny-list is itself a signal. Someone who can edit the text of an
209
+ in-view note and watch the view go stale learns that the words they wrote name
210
+ something the audience may not see; the refusal never says which note or
211
+ value. That existence oracle is open only to a person who can already edit
212
+ in-view text, and is accepted with the rule.
213
+
214
+ The vault paths the deny-list knows come from two places only: the sections of
215
+ the persistent registry, which hold each view's last allocation, this view's
216
+ included, and this view's prior generation. So after the registry is lost, a
217
+ vault path another view holds is not denied until that view is prepared again
218
+ and writes its section; within one tick, what a view denies of another view
219
+ depends on the order the views are prepared in; and a view still held in
220
+ layout 1 records its layout 2 allocation in its section, not the layout 1
221
+ paths its vault holds, which other views therefore do not deny. The
222
+ identities and source paths of the same notes are always denied.
223
+
224
+ Identities are what the graph records. A sidecar's identity is checked for its
225
+ form, but a Markdown note's `kg.id` may be any non-empty string, a bare word
226
+ included; Atelier's own documents use repository-qualified identities
227
+ (`<repository>:<name>`). An identity qualified by any repository of the census
228
+ is unambiguous, whichever repository holds its note. A bare word could be any
229
+ word of a title, and a root file name such as `README.md` any repository's, so
230
+ both are reported, never refused. The matcher is one Aho–Corasick automaton
231
+ over every value, with the token boundary tested at each hit: building it
232
+ takes time and memory linear in the total length of the values, on every
233
+ preparation (some tenths of a second and about 100 MB at 120,000 values), and
234
+ it then reads a text once, however many values there are.
235
+
236
+ In layout 1 the guard additionally refuses any `--<hex>` identity suffix of a
237
+ census identity outside the view, anywhere in generated text or emitted paths,
238
+ as the earlier release did. `test/obsidian-materialization.test.mjs` carries a
239
+ mutation control per rule: with that rule removed, a view that breaks it is
240
+ accepted.
241
+
242
+ ## Vault layout
243
+
244
+ A prepared view lays its files out in layout 2: the file name is the title a
245
+ person reads, the folders are the repository's own folders, and the identity a
246
+ machine needs is written into the note, not into its name. Obsidian shows a
247
+ file name as the note's title in the file explorer, tabs, the graph, search and
248
+ link completion, so the file name is the human title. The generation manifest
249
+ records the layout (`layoutVersion: 2`, in
250
+ `atelier-obsidian-generation-manifest/v2`) and keeps the path of every note and
251
+ attachment beside its identity, as before. Layout 1
252
+ (`notes/<readable title>--<identity suffix>.md`, flat, with attachments under
253
+ `attachments/`) is what releases up to 0.2.0-alpha.11 wrote; see "Upgrade from
254
+ layout 1".
255
+
256
+ ### Folders
257
+
258
+ A note lives at `<repository folder>/<source directory>/<file name>`.
259
+ `<source directory>` is the directory of its source relative to its repository
260
+ root, segment by segment, and `<repository folder>` is the repository's
261
+ identity (`repoId`); each segment is made safe as a name (below). Nothing is
262
+ stripped or shared across repositories: a vault of several repositories has
263
+ one top-level folder per repository. No part of a path depends on a property
264
+ of the whole census, such as a prefix every path shares, so adding or removing
265
+ a file never moves another.
266
+
267
+ Two folders whose names differ only in case or Unicode normalization are one
268
+ folder on a case-insensitive file system (APFS, NTFS), with one spelling. The
269
+ first allocation of a folder in a view fixes its spelling, and a later note
270
+ whose folder differs from it only that way takes the existing spelling: every
271
+ path of a vault spells each folder one way, so the app reports each note under
272
+ exactly the path the manifest records, which is how the publisher finds the
273
+ note's open editors. Two repositories of a view whose folder names would
274
+ collide that way are told apart: the one allocated later gets ` (<id>)`, the
275
+ first 6 hexadecimal characters of the SHA-256 of its identity. A source folder
276
+ whose mirrored folder would take the name of a file already allocated (a
277
+ source directory `docs/Guide.md/` after a note `docs/Guide.md`, or `a/Data/`
278
+ after a wrapped file `a/data`) is qualified the same way, with the id of the
279
+ source folder: `docs/Guide.md (1a2b3c)/`. So is a repository folder whose name
280
+ a file held for an edit already spells another way in its folder (a layout 1
281
+ file under `notes/` and a repository named `Notes`).
282
+
283
+ The folders `notes` and `attachments`, where layout 1 kept every note and file,
284
+ count as taken in every vault: an upgrade retires their files but leaves the
285
+ folders, and on a case-insensitive file system a repository folder named
286
+ `Notes` would otherwise be merged into `notes`, spelled as the disk has it and
287
+ not as the manifest does. A repository whose folder differs from either only
288
+ in case or normalization gets ` (<id>)`; a repository named exactly `notes` or
289
+ `attachments` uses the folder as it is.
290
+
291
+ ### File names
292
+
293
+ The name of a note is:
294
+
295
+ - for a Markdown source, its canonical title, the one the graph read: the
296
+ front-matter `title`, else the first H1, with the author's casing and
297
+ spacing. A title that ends in the source's own extension
298
+ (`07-tide-survey-brief.md`) loses that extension. When the graph's title
299
+ is exactly the title-cased file name, the graph's fallback, the title came
300
+ from the file name or equals it, and the file stem is used as written
301
+ (`getting-started`, not `Getting Started`). The rule reads the node record
302
+ only, so every allocation, and a selection resolved without the sources,
303
+ gives the same name. The record does not say where its title came from, so
304
+ an author heading that is exactly the title-cased file name (`# Getting
305
+ Started` in `getting-started.md`) names the note by its stem as well.
306
+ - for any other source, a wrapped file, the file name itself: the note is
307
+ `<file name>.md` (`depth-chart.pdf.md`) and the file keeps its own name
308
+ (`depth-chart.pdf`) in the same folder. The pairing is visible, a search
309
+ for the file finds both, and the file's own title (from its sidecar or
310
+ `<title>`) is the first heading of the note.
311
+
312
+ Every name and folder segment is made safe for macOS, Linux, Windows and
313
+ Obsidian links:
314
+
315
+ 1. It is NFC-normalized.
316
+ 2. Control characters (C0, C1, U+2028 and U+2029) and
317
+ `/ \ : * ? " < > | # ^ [ ]` become a space; bidirectional embedding,
318
+ override and isolate controls and U+FEFF are removed.
319
+ 3. Runs of spaces become one space, and leading and trailing spaces and dots
320
+ are removed: a leading dot hides a file from the app, and Windows allows no
321
+ trailing dot or space.
322
+ 4. It is cut to 150 bytes of UTF-8 (a folder segment to 120, a wrapped file's
323
+ stem so that its extension still fits), on a character boundary; a
324
+ combining character, zero-width joiner or variation selector left at the end by
325
+ the cut is removed.
326
+ 5. A Windows device name (`CON`, `PRN`, `AUX`, `NUL`, `COM0` to `COM9`,
327
+ `LPT0` to `LPT9`, `COM¹` to `COM³`, `LPT¹` to `LPT³`, `CONIN$`, `CONOUT$`),
328
+ compared case-insensitively on the part before the first dot, gets a `_`
329
+ in front.
330
+ 6. A name left empty is the file stem, made safe the same way, else
331
+ `Untitled`; a folder segment left empty is `_`.
332
+
333
+ A path longer than the file system allows (255 bytes a name, and
334
+ `maxFullPathBytes`, 1024 by default, with the view's own vault root) is
335
+ shortened by cutting the title part further, never below 16 bytes. A folder is
336
+ kept only where a name of 16 bytes and its extensions still fits in it,
337
+ whatever the title of the note at hand, so a folder's spelling depends on its
338
+ source directory alone. A folder chain that leaves less room than that keeps
339
+ as many leading folders as fit, and the last folder kept, cut, carries
340
+ ` (<id>)`, the first 6 hexadecimal characters of the SHA-256 of the
341
+ repository and the whole source directory: two deep directories under one
342
+ readable prefix stay two folders. When not even `<repository folder>/(<id>)`
343
+ leaves that room, the note sits directly in its repository's folder. Only a
344
+ note whose name does not fit even there, qualified if it collides, refuses
345
+ the view, with `path-too-long`: its repository's folder under the view's vault
346
+ root leaves no room for it.
347
+
348
+ ### Collisions
349
+
350
+ Names are compared the way a case-insensitive, normalization-insensitive file
351
+ system compares them (`collisionKey`: NFKC, case-folded), across every file of
352
+ the vault: notes, wrapped files and embedded assets. When two would share a
353
+ name in one folder, the one allocated later is qualified:
354
+
355
+ 1. `<name> (<source file stem>).md`, the stem made safe and cut to 60 bytes;
356
+ 2. when that is taken too, `<name> (<stem>) (<id>).md`, where `<id>` is the
357
+ first 6 hexadecimal characters of the SHA-256 of the repository identity
358
+ and the node identity, lengthened two at a time, up to 16, until it is
359
+ free.
360
+
361
+ A wrapped file's name is already its file name, so it is qualified by the
362
+ identity alone, before its extension: `depth-chart (1a2b3c).pdf` and
363
+ `depth-chart (1a2b3c).pdf.md`. An embedded asset is qualified the same way.
364
+ A name that collides with nothing carries no qualifier and no hash.
365
+
366
+ Each view allocates among its own notes only, in canonical identity order
367
+ (repository, then node), then its embedded assets, and an allocation that its
368
+ prior generation published is never changed while the note is in the view: a
369
+ newcomer that collides takes the qualifier. The same notes and prior
370
+ generation give the same paths in whatever order the census lists them.
371
+
372
+ ### Stability
373
+
374
+ A view's paths are seeded from what its prior generation published: a path,
375
+ once published, stays while its note is in the view. A retitled source keeps
376
+ its path: the note shows the new title in its heading and every relation row
377
+ that names it shows it too. Renaming on retitle arrives with the in-vault
378
+ plugin, which can use Obsidian's own rename so that the app's bookmarks, open
379
+ tabs, graph layout and links follow; a rename made by a program outside the
380
+ app would break all of them.
381
+
382
+ A note that leaves the view (deleted, withheld, no longer selected, or renamed
383
+ into a new identity, since a source without an identity of its own is
384
+ identified by its path) releases its path there, unless its file is held for
385
+ an edit: then the file stays in the vault and its name stays taken. A renamed
386
+ source therefore takes its name back instead of keeping a qualifier its old
387
+ name forced. A note that comes back is allocated again like a newcomer: it
388
+ gets its earlier path only when its title is unchanged and the names around it
389
+ are as they were, nothing having taken its name meanwhile and no note it
390
+ collided with having left. A published path that no longer fits the view's
391
+ vault root (a root that grew) is allocated again as well. One identity may
392
+ have different paths in different views: each view allocates among its own
393
+ notes, with its own vault root.
394
+
395
+ The persistent path registry keeps each view's last allocation in a section of
396
+ its own (`views.<scopeId>`); a selection resolved without the view's
397
+ generation reads it. The engine passes the views it maintains
398
+ (`viewScopeIds`), and the registry drops the section of any other view.
399
+ Preparation seeds from the prior generation itself, so a lost registry costs
400
+ no view its paths and is written again on the next preparation.
401
+
402
+ ### Notes that were laid out anyway
403
+
404
+ What is worth a person's look, but no reason to refuse a view, is recorded as a
405
+ diagnostic that names the note and never a value: in the generation manifest
406
+ (`ext["mnstry.atelier.obsidian"].diagnostics`, layout 2), in the view's
407
+ freshness entry (the first 100), and in `atelier obsidian status`. The manifest
408
+ holds those of the preparation that first committed its generation: a
409
+ preparation that yields the same generation (the same files) does not commit
410
+ it again. The freshness entry, and so `status`, holds those of the latest
411
+ preparation. Each is
412
+ `{ code, rule?, repoId, nodeId, notePath }` (an embedded asset has `assetPath`
413
+ and `filePath` instead of `nodeId` and `notePath`):
414
+
415
+ - `folder-shortened`: the folder chain was cut to fit (see "File names");
416
+ - `folder-qualified`: a folder met a file of the same name (see "Folders");
417
+ - `author-identity-properties`: the author's own front matter holds one of the
418
+ identity keys, which Properties shows instead of the generated block (see
419
+ "Identity in the note");
420
+ - `bare-identity-in-generated-text` (rule `deny-list`): generated text of the
421
+ note holds, as a whole token, the bare-word identity or the root file name
422
+ (`README.md`) of a note the audience may not see (see "The redaction
423
+ guard");
424
+ - `unselected-identity-in-generated-text` (rule `deny-list`): generated text of
425
+ the note holds an identifier of a note or asset the audience may see but the
426
+ view does not hold.
427
+
428
+ A view refused by the redaction guard records `{ code: "redaction-failure",
429
+ rule, notePath | filePath }` in its freshness entry the same way.
430
+
431
+ ### Identity in the note
432
+
433
+ Every note names its identity in three generated properties that Obsidian
434
+ shows (Properties, Bases, search) and any YAML reader or agent can read
435
+ without the manifest:
436
+
437
+ ```yaml
438
+ atelier-id: "north-desk:harbor-plan"
439
+ atelier-repo: "north-desk"
440
+ atelier-source: "plans/harbor-plan.md"
441
+ ```
442
+
443
+ `atelier-id` is the canonical node identity, `atelier-repo` the repository
444
+ identity and `atelier-source` the repository-relative source path, each a
445
+ JSON string. They are the last lines of the note's front matter, directly
446
+ before its closing delimiter, when the source's front matter is a plain block
447
+ mapping at column 0 that does not already use one of the three keys. A source
448
+ without front matter gets a generated front matter holding only them (after a
449
+ byte order prefix, which the app does not read). Any other front matter (a flow
450
+ mapping, an indented or sequence root, a document marker, a line that is not a
451
+ key) could change meaning with a line added, and one that already uses one of
452
+ the three keys would hold it twice, so for those sources the lines go into a
453
+ generated `%%` comment block at the very end of the note instead, the last
454
+ generated region. Keys are not otherwise compared: a front matter that repeats
455
+ a key of its own still takes the lines. The line ending is the source's. When
456
+ the author's own front matter holds one of the three keys, it is what
457
+ Properties shows, not the generated block; the note is reported as
458
+ `author-identity-properties` (see "Notes that were laid out anyway").
459
+
460
+ The source digest is not written into the note. Once a person's edit is
461
+ applied, the source has a new digest, and the hold on the edited note lifts
462
+ only when the note prepared from the new source is byte for byte the note the
463
+ person edited; a digest in the note would keep it held forever. The digest of
464
+ every generation is in the manifest
465
+ (`notes[].ext["mnstry.atelier.obsidian"].source.rawDigest`).
466
+
467
+ The block is generated. The manifest records its bytes as `regions.identity`,
468
+ inverting a note removes them, and the edit lens never attributes them to the
469
+ author. An edit to them is a front-matter edit, which is never applied to a
470
+ source: it becomes a semantic proposal (see "Proposal adapter for structural
471
+ edits"). In the end-of-note placement it is a generated-region edit and
472
+ refuses.
473
+
474
+ ### Links
475
+
476
+ A rewritten link or embed, a relation row and a wrapper's link to its file
477
+ name the target by its full vault path:
478
+ `[[north-desk/plans/Harbor plan.md|the plan]]`,
479
+ `[tide table](south-desk/tables/Tide%20table.md#spring)`,
480
+ `![[east-desk/pages/img/gauge.png|200]]`. A wikilink carries `.md`, so a note
481
+ named after a file never resolves to the file. Obsidian's resolver
482
+ (`MetadataCache.getLinkpathDest`, read in the 1.13.7 application bundle) looks
483
+ a path that contains a folder up as an exact vault path before it tries any
484
+ suffix or file-name match, so the target is exactly the allocated file,
485
+ whatever folder the linking note is in. A wikilink rewrite keeps the words the
486
+ author chose by appending `|<what the author wrote>`, as before.
487
+
488
+ ### Upgrade from layout 1
489
+
490
+ A view whose prior generation is in layout 1 is laid out again once, in one
491
+ generation:
492
+
493
+ 1. `prepareView` allocates layout 2 paths for every note and asset. The
494
+ layout 1 entries of the persistent registry are dropped, and the registry
495
+ records `layout: 2`. The generation is an
496
+ `atelier-obsidian-generation-manifest/v2`, and `changes.removed` names
497
+ every layout 1 path.
498
+ 2. The publisher retires each layout 1 path with its ordinary remove unit. A
499
+ note or file that still holds its published bytes is moved to the recovery
500
+ area with a receipt; a note somebody edited is never removed: it stays
501
+ where it is and is surfaced as a retained edit. Nothing is deleted.
502
+ 3. While a note of the view has an open edit, the view is not laid out again.
503
+ The engine passes as `heldNotePaths` the files of open edits, which stay in
504
+ the vault and keep their names taken in layout 2, and as
505
+ `layoutHeldNotePaths` those and the files whose edit closed on this tick.
506
+ `prepareView` prepares a view whose prior generation is in layout 1 and
507
+ holds one of the latter in layout 1, exactly as the earlier release did,
508
+ a new note included
509
+ (`notes/<title>--<suffix>.md`). A held note therefore stays under its
510
+ layout 1 path and is held, applied and withdrawn by the rules it was held
511
+ under. The engine lays a settled layout 1 view out again at the first tick
512
+ where no note holds it.
513
+ 4. An edit made on a layout 1 note can be applied after the upgrade: apply
514
+ and the proposal adapter prepare the generation the edit was made on in
515
+ that generation's own layout (`layout`) to recover the note as it was
516
+ published.
517
+
518
+ The folders `notes/` and `attachments/` are left behind empty: the publisher
519
+ moves files, never folders, and a person may delete them. While a view waits
520
+ for a held note, a selection resolved from the registry names layout 2 paths
521
+ that the vault does not hold yet.
522
+
523
+ An edit that closes on a tick still holds the layout on that tick. In
524
+ automatic mode a policy applies an edit on the very tick the edit is observed:
525
+ the view is prepared in layout 1 once more, from the new source, so the note
526
+ the person edited becomes the published note, and the next tick lays the view
527
+ out again and retires that file to recovery like any other. It is never left
528
+ in the vault unobserved. In manual mode the hold lifts when the observed note
529
+ equals the published one, one tick in layout 1 follows the same way, and the
530
+ view is then laid out again.
74
531
 
75
532
  ## Graph behaviour changes since the shared link resolver
76
533
 
@@ -124,9 +581,11 @@ In a view, an asset is copied only when the embedding note is in the vault
124
581
  set, the asset record carries `eligible: true` (`withEligibility` takes a
125
582
  second predicate; without it every asset is withheld) and the profile enrols
126
583
  its repository under the same audience rule as a node without an audience of
127
- its own. It is copied once, byte for byte, to
128
- `attachments/<readable stem>--<identity suffix>.<ext>`, read through the pinned
129
- snapshot (`source-not-in-snapshot`, `mixed-read`). The embed target is
584
+ its own. It is copied once, byte for byte, to its own path mirrored in the
585
+ vault (`<repository folder>/<source directory>/<file name>`, allocated like a
586
+ note; see "Vault layout"; in layout 1,
587
+ `attachments/<readable stem>--<identity suffix>.<ext>`), read through the
588
+ pinned snapshot (`source-not-in-snapshot`, `mixed-read`). The embed target is
130
589
  rewritten through the link inversion machinery: a Markdown embed to the
131
590
  percent-encoded vault-root path, a wikilink embed to the plain path with its
132
591
  size or fragment left as authored and no alias added. The manifest lists the
@@ -185,13 +644,18 @@ seen by the graph, the snapshot or the view until observation hashes the file
185
644
  again. The result reports `fileCensus: { reused, derived, read }`.
186
645
 
187
646
  Preparation stage: `prepareView` takes `cache` (`createPreparationCache`).
188
- Each note's bytes and manifest entry are a function of its pinned source
189
- digest, node record, allocated path, generated rows, outside-selection count
190
- and rewritten occurrences with their emitted targets; those inputs form a
191
- dependency key, and a note whose key equals the cached one reuses the cached
192
- bytes, manifest entry, attachment record and inversions. Everything else
193
- (asset copies, links, settings, collision checks, the manifest and the
194
- redaction guard over the whole result) runs as before. The result reports
647
+ Each note's bytes and manifest entry are a function of the vault layout and
648
+ emitter version, its pinned source digest, node record (repository, identity
649
+ and source path, which also make its identity block), allocated path (which
650
+ also names a wrapped file), generated rows (which carry the paths and titles
651
+ of their targets), outside-selection count and rewritten occurrences with
652
+ their emitted targets; those inputs form a dependency key, and a note whose
653
+ key equals the cached one reuses the cached bytes, manifest entry, attachment
654
+ record and inversions. Where the identity block goes and with which line
655
+ ending is decided from the source bytes, which the pinned digest covers.
656
+ Everything else (asset copies, links, settings, collision checks, the
657
+ manifest and the redaction guard over the whole result) runs as before. The
658
+ result reports
195
659
  `preparation: { emitted, reused }`, and a preparation that emits exactly the
196
660
  notes whose output changed is what the tests pin.
197
661
 
@@ -243,7 +707,7 @@ This is a layout inside the private store; the protocol is unchanged.
243
707
  | --- | --- |
244
708
  | Any candidate while it is written and fsynced (generated bytes only) | `staging/<journalId>/NNNNNN.candidate`, or `NNNNNN.late.candidate` for one that could not be known when the run began |
245
709
  | Candidate of a file that is created (exclusive link, no exchange) | stays at that staging path until it is linked |
246
- | Candidate of a file that is replaced (note, attachment, policy settings) | moved, complete, to `recovery/<journalId>/<unit>/exchange.candidate`, or `exchange.late.candidate` |
710
+ | Candidate of a file that is replaced (note, attachment, policy settings, Atelier's plugin files) | moved, complete, to `recovery/<journalId>/<unit>/exchange.candidate`, or `exchange.late.candidate` |
247
711
  | Displaced bytes | the vault path, then that same candidate path, then `recovery/<journalId>/<unit>/displaced.bin` |
248
712
 
249
713
  The journal names every exchange candidate, by path and SHA-256, before the
@@ -264,8 +728,10 @@ share nothing but the vault, so the vault lock lives under the vault's real
264
728
  path in `.atelier-publication/`, a dot-directory that no note path can name
265
729
  and that the app does not show. The second publisher refuses with
266
730
  `publication-in-progress` and writes nothing. It is the only thing the
267
- publisher writes in a vault besides notes, attachments and the policy
268
- settings file.
731
+ publisher writes in a vault besides notes, attachments, the two policy
732
+ settings files (`core-plugins.json`, `community-plugins.json`) and Atelier's
733
+ own plugin files under `.obsidian/plugins/atelier-projection/` (see
734
+ [obsidian-plugin.md](obsidian-plugin.md)).
269
735
 
270
736
  Release and recovery follow the view lock exactly. A finished publication
271
737
  writes a release marker. A publisher that dies leaves a ticket naming its
@@ -304,6 +770,77 @@ Before 0.2.0-alpha.10 no production reading was ever absent, because the probe
304
770
  found Atelier's own service; from that release this path, and this window, are
305
771
  live.
306
772
 
773
+ ### First publication into a vault no Obsidian lists
774
+
775
+ A running or unknowable app that cannot be coordinated with still refuses the
776
+ whole publication, with one exception, the `direct-unheld` path. It is chosen
777
+ at path selection only when all of these hold:
778
+
779
+ 1. The view has no committed generation.
780
+ 2. Its vault is the folder allocated for it (`vaultOrigin: 'allocated'`), and
781
+ that folder's device and inode are the ones its record names.
782
+ 3. The app's own settings file (`obsidian.json`) was read, and none of its
783
+ entries is the vault folder, a folder above it or a folder inside it. Both
784
+ sides are compared as written and by their real paths, in any letter case.
785
+ A file that cannot be read is read again, up to three times 50 ms apart,
786
+ because the app rewrites it with a plain write. A missing or unreadable
787
+ file, an entry whose folder is not an absolute path, an entry with a `..`
788
+ segment (the folder it names through a link cannot be placed by its text),
789
+ or a Flatpak or snap build of the app anywhere on the account (its list is
790
+ elsewhere) gives no evidence.
791
+ 4. The platform is macOS or Linux.
792
+
793
+ Any other case gives the refusal it gave before. The maintenance service reads
794
+ the evidence from the file and never asks the app.
795
+
796
+ On this path the publisher writes only creates: a file appears through the
797
+ exclusive link where nothing is, or not at all. A unit that would replace or
798
+ remove a file, or write a settings file over one that is there, refuses as
799
+ `editor-uncoordinated` (blocking) and writes nothing. A settings file that
800
+ already holds exactly what the policy asks is left as it is, which is what an
801
+ interrupted run of this path leaves. A file already at a note path is kept as
802
+ `create-conflict`. The evidence is read again immediately before the first
803
+ unit and whenever two seconds have passed. Once a reading gives none, every
804
+ remaining unit refuses, a kept one included, and nothing is committed. The
805
+ view lock and the vault lock are taken as on every path, and the journal
806
+ records `mode: 'direct-unheld'`. Restart recovery and the late-writer check
807
+ treat it as a `direct` journal.
808
+
809
+ Why this loses nothing a person wrote, whoever holds the vault: no editor can
810
+ hold an unsaved buffer of a file that does not exist; the link either makes
811
+ the whole file appear or fails because something is there, which is kept; and
812
+ an editor that later saves over a created file wins, and is read as an edit.
813
+ The list evidence is for the experience, and it holds only for a run that
814
+ completes: that run is committed before `open` first tells the app about the
815
+ vault. A run that stops partway commits nothing. That happens when the list
816
+ names the folder meanwhile, a settings file with other bytes is already there,
817
+ or an interrupted run left a replacement or a removal. The view then reports
818
+ `editor-uncoordinated` and falls back to the coordinated path: `open` may add
819
+ the vault to the app and open it before it is complete, and what is left,
820
+ every replacement and removal included, is published through the app with its
821
+ usual base and buffer checks. The person may see an incomplete vault then;
822
+ nothing they write is at risk. Lists this account's app does not keep are
823
+ covered by the argument above alone: another user-data folder, another
824
+ machine through a synced folder.
825
+
826
+ A maintenance service whose app does not qualify (its command line off, no
827
+ vault open, a version below the floor) no longer keeps such a view from its
828
+ first publication: the publisher is reached with an adapter that coordinates
829
+ with nothing, and takes this path when every condition holds. When the path
830
+ does not apply, or the run stops partway at a unit refused as
831
+ `editor-uncoordinated`, the view reports the app's refusal, as before. `open`
832
+ then answers that refusal without adding the vault to the app, unless the
833
+ person asks it to restart Obsidian (`--restart-obsidian`): the vault is then
834
+ added and opened, and may be incomplete until the app has published the rest.
835
+ One case opens a vault without that flag: with the app running and no vault
836
+ open, a vault the app already lists is opened by its path, as before this
837
+ path existed, and may be incomplete. A run that stops only on a file in its
838
+ way reports that (`create-conflict`). When it stops for more than one reason,
839
+ one is reported: while the app does not qualify, the app's refusal wins over a
840
+ file in the way, and while it qualifies, the first blocking unit's reason is
841
+ reported. A reason not reported comes back at a later run, and nothing is
842
+ written meanwhile.
843
+
307
844
  ### Proven boundary
308
845
 
309
846
  macOS (Darwin 25, arm64) with Obsidian 1.13.7 (installer 1.12.7), CLI enabled,
@@ -322,6 +859,13 @@ A fifth run aborted because the harness could not open a note after the
322
859
  second-window case; no bytes were involved. The receipts are maintainer-held
323
860
  and are not part of this repository.
324
861
 
862
+ Every vault Atelier publishes now carries Atelier's own plugin
863
+ ([obsidian-plugin.md](obsidian-plugin.md)), so a trusted vault runs one
864
+ community plugin. In phase 1 it registers no editor extension, no vault event
865
+ handler and nothing that writes; it only talks to the maintenance service over
866
+ loopback. The protocol cases above have not been re-run with it loaded; that
867
+ run is gate G20, before the plugin carries any part of publication.
868
+
325
869
  ### Receiving obligations for the production publisher
326
870
 
327
871
  The publisher may not claim the protocol outside the proven boundary until
@@ -345,6 +889,297 @@ each open item is discharged with its own evidence.
345
889
  | Link-then-rename | Rejected. It leaves a window in which a concurrent replacement is destroyed. |
346
890
  | App-driven save after replacement | Rejected. Observed losing an outside writer's bytes; kept as a negative control. |
347
891
 
892
+ ## Obsidian's vault list
893
+
894
+ Obsidian opens a vault from a link only when the folder is in its own vault
895
+ list. `atelier obsidian open` puts the view's vault there.
896
+ Outside its own storage (the data root, which holds the vaults and their
897
+ policy-owned `.obsidian/core-plugins.json`), the settings file that holds the
898
+ list is the only file of another application that Atelier writes, and in it
899
+ only two things: the view's vault entry and the command-line switch `cli`. To
900
+ write it, Atelier writes nothing else outside the app's user-data directory,
901
+ and no vault note. Every write is shown in `open`'s answer
902
+ (`obsidianSettings`: the file, whether it was created, whether the vault was
903
+ added, whether the switch was turned on, and the backup).
904
+
905
+ **The file.** `obsidian.json` in the app's user-data directory:
906
+ `$HOME/Library/Application Support/obsidian/` on macOS and
907
+ `$XDG_CONFIG_HOME/obsidian/` (else `$HOME/.config/obsidian/`) on Linux; no
908
+ location is known on other platforms. Its shape is
909
+ `{ "vaults": { "<16 hex>": { "path", "ts", "open"? } }, …other settings }`.
910
+ The app reads it when it starts and rewrites all of it, with
911
+ `JSON.stringify`, whenever its list changes; `open` is true while a window of
912
+ that vault is open and stays true for the vaults open when the app quit.
913
+ Modules: `src/projection/obsidian/publication/vault-list.mjs` (read only) and
914
+ `src/runtime/obsidian/app-registration.mjs` (the write).
915
+
916
+ **While Obsidian runs and answers its command line**, the file is the running
917
+ app's and is only read. `open` asks the app instead, through `obsidian-cli
918
+ eval` with constant scripts: `vault-list` (the app's own map) and `vault-open`
919
+ with the folder and `false`, which adds an existing folder to the list (the app
920
+ writes its own file) and opens it in a window; `true`, which would create a
921
+ folder, is never sent. The folder travels as a base64-encoded JSON payload, as
922
+ in the publication bridge, so a path never becomes code. The addition is
923
+ verified by reading the app's list again and finding the vault root's entry
924
+ (asked again, a bounded number of times, while the window the app just opened
925
+ is still loading and answers that a command does not exist yet); an addition
926
+ the app did not answer (a call that timed out) is looked up the same way, and
927
+ is `addition-not-answered` when the list does not show it. `open` then waits,
928
+ bounded, until the app answers for exactly that vault. Like Obsidian's own
929
+ "open folder as vault", `vault-open` also adds the folder to the operating
930
+ system's recently used documents (Recent Items on macOS).
931
+
932
+ An entry is the vault root's when its folder, as written, is the root as
933
+ given or its real path; a listed folder's own real path is read only when its
934
+ last component is the root's (the root reached through a linked parent), so
935
+ a vault on a mount that does not answer is never waited on, and a link to the
936
+ root under another name is not recognised.
937
+
938
+ **While Obsidian runs with no vault open**, its command line answers nothing
939
+ and the file is still the running app's: it is read, never written. A vault
940
+ it already lists is opened by path; one it does not is refused as
941
+ `no-vault-open`, unless the person asked for a restart (below).
942
+
943
+ **While Obsidian runs with its command line off**, it answers every command
944
+ but a link with "Command line interface is not enabled", read as
945
+ `app-cli-unavailable` / `cli-turned-off`. The file is the running app's, which
946
+ holds the switch in memory and writes it back with its list, so it is not
947
+ written; the switch is turned on only while no Obsidian runs, or through the
948
+ restart below.
949
+
950
+ **Never inside another vault.** In every state, a vault inside a folder the
951
+ list has as a vault already (compared as written, against the vault root and
952
+ its real path) is not added, through the
953
+ app or in the file: that vault would show its notes too, and a call run in
954
+ its folder would reach that vault. `open` answers `launch-failed` with reason
955
+ `vault-inside-another-vault`.
956
+
957
+ **One folder open in several windows.** The list can name one folder more
958
+ than once (under another letter case, or through a link), and the app opens
959
+ one window per entry. When more than one of the entries that name the view's
960
+ folder is open, each window holds the vault and a call reaches one of them
961
+ only (`vaultRoute` answers `duplicated`): no publication call is made (the
962
+ publisher refuses `vault-open-in-several-windows`, a `publisher-conflict`),
963
+ and `open` answers `publisher-conflict` with that reason, names the entries
964
+ (`duplicates`) and launches nothing. The entries are read from the list's
965
+ `open` marks, and the app keeps the last window it closed marked open, so this
966
+ can hold with a single window showing; the next step is to remove the extra
967
+ entries from the list. While one entry has a window, a call
968
+ reaches only that entry, and `open` finds and launches it, so a closed entry of
969
+ the same folder is never opened beside it.
970
+
971
+ **A Flatpak or snap build** keeps its list inside its sandbox
972
+ (`~/.var/app/md.obsidian.Obsidian/config/obsidian/`,
973
+ `~/snap/obsidian/current/.config/obsidian/`) and never reads the file above.
974
+ On Linux the build in use is the one whose `obsidian.json` (native, Flatpak or
975
+ snap) was written last, since every build rewrites its own when a vault window
976
+ opens or closes; only when none was ever written, a Flatpak or snap
977
+ installation or leftover decides (`~/.var/app/md.obsidian.Obsidian`,
978
+ `~/.local/share/flatpak/app/md.obsidian.Obsidian`,
979
+ `/var/lib/flatpak/app/md.obsidian.Obsidian`, `~/snap/obsidian`,
980
+ `/snap/obsidian`, `/var/lib/snapd/snap/obsidian`). For a Flatpak or snap
981
+ build the file is neither read nor written, `open` answers
982
+ `obsidian-sandboxed`, and the vault is added through the running app only.
983
+
984
+ **While no Obsidian runs**, the vault is added to the file itself, and only
985
+ then:
986
+
987
+ 1. The process table is read and must say, positively, `absent`. `running` or
988
+ `unknown` refuses (`app-may-be-running`).
989
+ 2. The user-data directory and the file must exist, be this user's own, and be
990
+ a real directory and a regular file (neither a link), and the file must be
991
+ a JSON object whose `vaults`, when present, is an object, of at most 4 MiB.
992
+ A file with a second name (a hard link) is refused as
993
+ `obsidian-settings-unsafe`: the replacement would leave that name with the
994
+ old list. A missing file means Obsidian never started on this account; it
995
+ is created as described under "Obsidian never started" below. The other
996
+ refusals are `obsidian-settings-unsafe`, `obsidian-settings-not-owned`,
997
+ `obsidian-settings-unreadable` and `obsidian-settings-not-object`.
998
+ 3. A vault whose real path an entry already has is left as it is. When `cli`
999
+ is already `true` too, nothing is written. A vault inside a folder an
1000
+ entry has is refused (`vault-inside-another-vault`).
1001
+ 4. The new document is the file's object with one entry added under a fresh
1002
+ random 16-hex id that no entry has: `{ path: <the vault root's real path>,
1003
+ ts: <now, ms>, open: true }`, unless the vault is listed already, and with
1004
+ `cli` set to `true` when it is not (the command-line switch: in its place
1005
+ when the file has the key, otherwise last, as the app adds it). Every
1006
+ other key and every other entry is kept in its place, as values. A
1007
+ document that would be larger than 4 MiB is refused
1008
+ (`obsidian-settings-too-large`).
1009
+ 5. A temporary file in the same directory is written with the file's mode and
1010
+ fsynced; the bytes as they were are written, fsynced, to
1011
+ `obsidian.json.atelier-backup-<UTC time>` beside it.
1012
+ 6. Immediately before the rename, the process table must still say `absent`
1013
+ and the file must still hold the bytes that were read; otherwise both new
1014
+ files are removed and the write is refused (`app-may-be-running`,
1015
+ `obsidian-settings-changed`).
1016
+ 7. The temporary file is renamed over `obsidian.json` and the directory is
1017
+ fsynced. Of the backups beside it, the first (the list as it was before
1018
+ Atelier wrote it) and this one are kept, and any between them removed. The
1019
+ process table is read once more: an app that appeared just then may have
1020
+ read the list before the rename, so the addition is reported unconfirmed
1021
+ and `open` answers `launch-failed` with reason
1022
+ `app-started-during-registration` instead of launching; the next `open`
1023
+ adds the vault through the app if it missed it. A file that cannot be read
1024
+ back to find the new entry is reported unconfirmed as well, with reason
1025
+ `registration-not-read-back`.
1026
+
1027
+ Every refusal writes nothing and leaves nothing behind: a temporary file or
1028
+ backup created on the way is removed again whichever later step fails.
1029
+
1030
+ **Obsidian never started.** When the file does not exist and the process
1031
+ table says, positively, `absent` (read before, and again immediately before
1032
+ the file appears), the file is created, and only then:
1033
+
1034
+ 1. The user-data directory must be this user's own real directory. When it
1035
+ does not exist, it is created with mode 0700, only inside an existing
1036
+ parent directory that is this user's own and no link; a missing parent is
1037
+ `obsidian-settings-missing`, and nothing is created above the directory.
1038
+ 2. The content is exactly `{ "vaults": { "<16 hex>": { "path": <the vault
1039
+ root's real path>, "ts": <now, ms>, "open": true } }, "cli": true }`, mode
1040
+ 0600. The app reads the file at start-up (a missing or unreadable one reads
1041
+ as no settings) and opens every vault flagged open, so a plain start opens
1042
+ this one, with its command line on.
1043
+ 3. The bytes are written and fsynced to a temporary file in the directory,
1044
+ which is then hard-linked to `obsidian.json` and removed: the file appears
1045
+ complete or not at all, and the link fails on any file there, one the app
1046
+ wrote meanwhile included, which is kept (`obsidian-settings-changed`). The
1047
+ directory is fsynced. There is nothing to back up.
1048
+ 4. An app that appears before the link refuses (`app-may-be-running`); one
1049
+ that appears right after it leaves the addition unconfirmed, as in step 7
1050
+ above. A refusal removes the temporary file and a directory created on the
1051
+ way.
1052
+
1053
+ `open` answers `registration.how: 'created-settings'` for it.
1054
+
1055
+ **Restart, only when the person asks** (`open --restart-obsidian`; never
1056
+ implied, and refused by every other operation). Only in the two states above
1057
+ where the app runs and cannot be reached: its command line off, or no vault
1058
+ open and the view's vault not in its list. Module:
1059
+ `src/runtime/obsidian/app-restart.mjs`.
1060
+
1061
+ 0. Read-only, before anything is sent (`settingsWriteOutlook`): the view's
1062
+ vault folder exists, the settings file reads as in step 2 above (no
1063
+ Flatpak or snap build), has one name, would stay within 4 MiB with the
1064
+ entry, and lists no vault at a folder above the vault's. A file that does
1065
+ not exist (the app has shown only its starter window, which writes none)
1066
+ is the create case, checked as "Obsidian never started" checks before it
1067
+ writes (step 1 there); it is created after the quit. Otherwise `open`
1068
+ answers that refusal and the app keeps running. Once the main process is
1069
+ proven (step 2 below), right before the first signal, a copy of an existing
1070
+ file is kept beside it under the backup name (`backupObsidianSettings`,
1071
+ fsynced, the file's mode; `restart.settingsCopy`); a copy that cannot be
1072
+ kept sends nothing. The write in the same run does not prune that copy
1073
+ (`keepBackups`); a later write prunes it as step 7 says.
1074
+ 1. The app's main process is proven from the process table
1075
+ (`ps -ww -A -o pid=,ppid=,uid=,comm=`; a uid is signed, since macOS shows
1076
+ `nobody` as -2): every Obsidian process (the names the process probe
1077
+ counts) runs as this user, exactly one names the app bundle's main
1078
+ executable by an absolute path (`…/Contents/MacOS/Obsidian`), and every
1079
+ other one is its child. `comm` is the process's argv[0], not a verified
1080
+ image: a process of the same user could take that name, which proves
1081
+ nothing while the real app runs too (two main processes). Otherwise nothing
1082
+ is sent (`app-main-process-unproven`, with `restart.detail`).
1083
+ 2. That process is read again (`ps -o ppid=,uid=,lstart=,comm=`): it must be
1084
+ this user's, under the parent the table showed, with the same name, and
1085
+ read the same twice in a row; then it, and only it, gets SIGTERM, which
1086
+ Electron handles as a quit. The app closes every window, keeping the
1087
+ vaults that were open flagged open, and its helpers end (1.13.7, checked on
1088
+ an isolated instance).
1089
+ 3. The process probe must say `absent` within 30 seconds. On macOS the main
1090
+ process can stay after the first SIGTERM, with no window (seen on
1091
+ an isolated 1.13.7, with vaults open and with only the starter window).
1092
+ Electron handles only the first SIGTERM as a quit; a second one ends the
1093
+ process at once, without its quit handlers. It is sent once, only when the
1094
+ table shows that same process alone (no helper) and it reads the same, at
1095
+ two readings at least 5 seconds apart. There is never a third signal, and
1096
+ never another kind; `restart.signals` counts the signals delivered.
1097
+ Otherwise `open` answers `launch-failed` / `app-did-not-quit`, writes
1098
+ nothing and starts nothing.
1099
+ 4. The file is written as above, with the vault and the switch.
1100
+ 5. The app is started plainly (`open -b md.obsidian`) by the launch steps
1101
+ below, and reopens its vaults and this one. An answer given after the quit
1102
+ and before the launch (the file could not take the vault, say) starts the
1103
+ app plainly first, as it was; a start that fails is said, with the next
1104
+ step to start it by hand.
1105
+
1106
+ The answer carries `restart: { asked, state, quit, signalled, pid, signals,
1107
+ settingsCopy, startedAgain }`, and `reason` and `detail` when it did not quit.
1108
+ An `open` interrupted between the quit and the start leaves the app quit (a
1109
+ known limit). At a terminal the restart still needs the flag; the question the
1110
+ design asks in that state comes with the first-run command. Qualified on macOS only; elsewhere the answer is
1111
+ `restart-platform-unqualified`, since a quit app there is started with a
1112
+ link, which would drop the other vaults' reopen flags.
1113
+
1114
+ `open` then shows the vault by the steps of `launchPlan`
1115
+ (`src/runtime/obsidian/launch-plan.mjs`, carried out by `runLaunchPlan`),
1116
+ naming it `obsidian://open?vault=<id>` when the id reaches it first (a path is
1117
+ matched against the list by string prefix, with no separator check), and by
1118
+ its own listed path otherwise (another vault's folder named like the id, an
1119
+ unusual id): a running app is handed the link through its command-line tool,
1120
+ which takes a link even with its command line turned off, or through the
1121
+ operating system when the tool did not take it; on macOS a quit app is started
1122
+ plainly (`open -b md.obsidian`), which reopens every vault the list flags open
1123
+ (the entry written above is flagged open), and handed the link through its
1124
+ tool once the app itself answers the tool (`appAnswered`: a version or one of
1125
+ the app's own lines; the tool's "unable to find Obsidian" while the app starts
1126
+ does not count). A link is never handed to the operating system for an app
1127
+ this launch started, since that would reach it while it starts. A link the
1128
+ started app did not take is left to it when the list flags the vault open,
1129
+ since the plain start reopens it; for a listed vault that is closed, which a
1130
+ plain start does not reopen, the link is handed once more, through the tool
1131
+ only (this launch started the app), one wait round later, before `open`
1132
+ answers `launch-failed` /
1133
+ `app-did-not-answer-for-this-vault`. An app started with a link would delete the
1134
+ reopen flag of every other vault without a window (1.13.7, checked on an
1135
+ isolated instance). On Linux a quit app is started with the link, a known
1136
+ limit. With its command line turned off the app answers every other command
1137
+ with "Command line interface is not enabled"; that is read as
1138
+ `app-cli-unavailable` / `cli-turned-off`, never as a version.
1139
+
1140
+ **Which window answers.** The command line (1.13.7) answers a call whose first
1141
+ argument is `vault=<value>` in the window of the first listed vault whose id is
1142
+ the value, or whose folder's name is the value in any letter case; any other
1143
+ call in the window of the first listed vault whose folder is the tool's working
1144
+ directory or contains it, the folders compared as written; and otherwise in the
1145
+ vault window that had focus last. It opens a vault that takes a call when it is
1146
+ closed. The first match in list order wins, not the deepest folder, so a vault
1147
+ listed before the view's vault at a folder above it would take every call run
1148
+ in the view's folder. Calls about no vault (the version, the vault list, the
1149
+ addition) run in a directory that is no vault and name none. A call about the
1150
+ view's vault goes where the list says only that vault takes it
1151
+ (`vaultRoute` in `vault-list.mjs`): from a directory that is no vault with
1152
+ `vault=<id>` first when that id names this vault first, which does not depend
1153
+ on how a working directory is spelled; otherwise in its folder when the first
1154
+ listed vault that is or contains that folder is this vault; when neither
1155
+ holds, no call is made. The vault root is taken in the spelling the file
1156
+ system stores (`realpathSync.native` on macOS and Linux), which is how the
1157
+ operating system reports the tool's working directory: on a volume that folds
1158
+ letter case, a data root given in another case neither hides a vault listed
1159
+ above it nor routes a call there. `open`'s check
1160
+ that the app answers for the vault is routed from the list it verified the
1161
+ vault in. Publication calls are routed from the file, and only while it lists
1162
+ the vault open; otherwise they run in a directory that is no vault and name
1163
+ none, so maintenance never reopens a vault window that was closed and never
1164
+ reaches another vault. The bridge still checks that the app answered for
1165
+ exactly this vault, and `open` that the app answers for it, both folders taken
1166
+ as the file system stores them: an app that holds the vault under another
1167
+ letter case or through a link holds this vault, and a store written under
1168
+ another spelling keeps its vault.
1169
+
1170
+ **Retries.** The maintenance service is told what the app looks like from
1171
+ the process table and the file alone (whether an Obsidian process runs, and
1172
+ which vaults the file shows open; the file is read, never written, by the
1173
+ service): nothing runs in the app to find out. The app is asked for its
1174
+ version only when a view is about to be published, and without blocking the
1175
+ service. A tick requested over the service's listener for one view (`open`
1176
+ names its own) prepares and publishes that view once more, with the app's
1177
+ qualification asked again; the engine takes such requests only for views the
1178
+ project declared at its last tick, and at most 64 before its first. A view
1179
+ whose last publication did not settle is also tried again as soon as that
1180
+ picture changes, and otherwise after a delay that starts at 30 seconds and
1181
+ doubles per attempt, up to the full reconciliation interval.
1182
+
348
1183
  Source application back into canonical files carries the same conditional-write
349
1184
  obligation against other source writers; its protocol follows.
350
1185
 
@@ -465,6 +1300,7 @@ today) it refuses `exchange-unavailable`. Both write nothing.
465
1300
  | `interrupted-before-exchange`, `apply-interrupted-needs-person` | what restart recovery decided for an interrupted apply |
466
1301
  | `recovery-state-unreadable` | a candidate, backup or source path of an interrupted apply may not be looked at or read; nothing is settled from a read this process was denied, that record is reported with its intent still open, and every other one is still settled |
467
1302
  | `apply-outcome-unknown` | the source was exchanged and the settlement from digests could not be carried out; the source may have been changed, the intent stays open and `apply recover` decides |
1303
+ | `personal-overlay-proposals-only` | the note is a private note of a personal workspace, whose file belongs to a module-owned generation; nothing is written there. The proposal adapter's observation answers the same edit with `personal-overlay-not-proposed`, and records nothing |
468
1304
 
469
1305
  A path that another program removes or replaces between two steps, before the
470
1306
  intent is recorded, answers one of these refusals (`source-missing`,
@@ -556,7 +1392,11 @@ list`, `show`, `run` and `recover` answer such a record as a typed refusal.
556
1392
  fragment of one left in the body, and text typed at the very end of the body
557
1393
  together with a removed region all refuse `generated-region-edited`. Some of
558
1394
  those refusals a person could have been spared; a generated byte in a source
559
- is never accepted in exchange.
1395
+ is never accepted in exchange. The identity block in the front matter is
1396
+ generated as well: it is removed before the source bytes are made, and a
1397
+ note whose front matter changed, the identity lines included, is a
1398
+ front-matter edit that becomes a proposal. A byte order prefix the editor
1399
+ dropped is taken from the source, as before.
560
1400
  - After an apply the next tick prepares the view again. When the prepared note
561
1401
  is byte for byte the note the person edited (read from the vault at the
562
1402
  moment of that decision, never taken from the engine's observation index; a
@@ -683,7 +1523,9 @@ line, 16 MiB, 10,000 events, and an unreadable line refuses every append.
683
1523
  | `ledger-corrupt` | refused, for that repository only; other repositories progress in the same tick |
684
1524
  | `store-unavailable` | the store cannot be read or is locked now; waits like a full one |
685
1525
 
686
- A waiting operation is tried at most 8 times, 60 seconds after the first attempt
1526
+ For a personal workspace whose graph the engine has not confirmed yet, a due
1527
+ operation is left as it is (`personal-validation-pending`), and that tick is not
1528
+ counted as an attempt. A waiting operation is tried at most 8 times, 60 seconds after the first attempt
687
1529
  and twice as long after each, up to an hour. At most 8 operations per repository
688
1530
  and 64 unexamined edits are looked at per tick. A repository holds at most 4096
689
1531
  operations, 256 of them open, 64 records of 16 KiB each; a full queue refuses