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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (560) hide show
  1. package/CHANGELOG.md +1005 -0
  2. package/README.md +149 -9
  3. package/contracts/atelier-adoption-policy.v2.schema.json +55 -0
  4. package/contracts/atelier-architecture.v1.schema.json +258 -0
  5. package/contracts/atelier-boundary-delegations.v1.schema.json +161 -0
  6. package/contracts/atelier-boundary-owner-keys.v1.schema.json +98 -0
  7. package/contracts/atelier-build.v1.schema.json +1076 -0
  8. package/contracts/atelier-capability.v1.schema.json +1252 -0
  9. package/contracts/atelier-coordination.v1.schema.json +1050 -0
  10. package/contracts/atelier-evidence-navigation.v1.schema.json +603 -0
  11. package/contracts/atelier-harness.v1.schema.json +234 -0
  12. package/contracts/atelier-ingestion.v1.schema.json +806 -0
  13. package/contracts/atelier-inquiry.v1.schema.json +1772 -0
  14. package/contracts/atelier-instruction-adoption.v1.schema.json +490 -0
  15. package/contracts/atelier-interaction.v1.schema.json +553 -0
  16. package/contracts/atelier-judgment.v1.schema.json +690 -0
  17. package/contracts/atelier-knowledge.v1.schema.json +1512 -0
  18. package/contracts/atelier-learning.v1.schema.json +1095 -0
  19. package/contracts/atelier-migration.v3.schema.json +64 -0
  20. package/contracts/atelier-obsidian-generation-manifest.v2.schema.json +372 -0
  21. package/contracts/atelier-personal-workspace-manifest.v1.schema.json +87 -0
  22. package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
  23. package/contracts/atelier-template-adoption.v1.schema.json +223 -0
  24. package/contracts/atelier-template-profile.v1.schema.json +1168 -0
  25. package/contracts/atelier-trackable.v1.schema.json +781 -0
  26. package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
  27. package/contracts/compat-baseline.json +1 -1
  28. package/docs/architecture.md +89 -0
  29. package/docs/assurance-controls.md +4 -3
  30. package/docs/blocks/capability-stewardship.md +17 -0
  31. package/docs/blocks/claims.md +16 -3
  32. package/docs/blocks/inquiry-harnesses.md +19 -0
  33. package/docs/blocks/learning-harnesses.md +17 -0
  34. package/docs/capability-stewardship.md +269 -0
  35. package/docs/coordination.md +51 -0
  36. package/docs/decision-practices.md +111 -0
  37. package/docs/decisions.md +29 -0
  38. package/docs/evidence-navigation.md +237 -0
  39. package/docs/exact-upgrades.md +3 -2
  40. package/docs/ingestion.md +181 -0
  41. package/docs/inquiry-harnesses.md +211 -0
  42. package/docs/install.md +80 -3
  43. package/docs/intake-and-guides.md +5 -0
  44. package/docs/integration-contract-decisions.md +24 -2
  45. package/docs/interaction-and-reflection.md +72 -0
  46. package/docs/knowledge-graph.md +20 -0
  47. package/docs/knowledge-health/api.md +30 -0
  48. package/docs/knowledge-health/assessment/api.md +96 -0
  49. package/docs/knowledge-health/assessment/capabilities.md +40 -0
  50. package/docs/knowledge-health/assessment/evaluation.md +27 -0
  51. package/docs/knowledge-health/assessment/facilitator.md +30 -0
  52. package/docs/knowledge-health/assessment/quickstart.md +61 -0
  53. package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
  54. package/docs/knowledge-health/devday/facilitator.md +16 -0
  55. package/docs/knowledge-health/devday/participant-response.md +22 -0
  56. package/docs/knowledge-health/devday/public-default.md +107 -0
  57. package/docs/knowledge-health/devday/quickstart.md +18 -0
  58. package/docs/knowledge-health/devday/support.md +22 -0
  59. package/docs/knowledge-health/exercise.md +17 -0
  60. package/docs/knowledge-health/facilitator.md +20 -0
  61. package/docs/knowledge-health/quickstart.md +94 -0
  62. package/docs/knowledge-health/readme.md +15 -0
  63. package/docs/knowledge-health/support.md +34 -0
  64. package/docs/knowledge-setup.md +248 -0
  65. package/docs/knowledge-workspace.md +264 -0
  66. package/docs/learning-harnesses.md +238 -0
  67. package/docs/learning.md +215 -0
  68. package/docs/local-services.md +242 -26
  69. package/docs/obsidian-contract.md +878 -36
  70. package/docs/obsidian-plugin.md +619 -0
  71. package/docs/obsidian.md +809 -64
  72. package/docs/optional-ingestion.md +83 -0
  73. package/docs/personal-workspace.md +257 -0
  74. package/docs/practical-judgment.md +92 -0
  75. package/docs/progressive-ingestion-acceptance.md +89 -0
  76. package/docs/progressive-ingestion.md +235 -0
  77. package/docs/project-command-inventory.md +1 -0
  78. package/docs/release-engineering.md +56 -1
  79. package/docs/repo-boundary-guard.md +94 -0
  80. package/docs/research-starter-quickstart.md +66 -0
  81. package/docs/research-starter.md +114 -0
  82. package/docs/semantic-ingestion.md +198 -0
  83. package/docs/semantic-operation.md +317 -0
  84. package/docs/skill-steward.md +6 -0
  85. package/docs/template-profile.md +164 -0
  86. package/docs/template-upgrades.md +114 -0
  87. package/docs/trackables.md +111 -0
  88. package/docs/upgrade.md +68 -3
  89. package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
  90. package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
  91. package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
  92. package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
  93. package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
  94. package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
  95. package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
  96. package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
  97. package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
  98. package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
  99. package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
  100. package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
  101. package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
  102. package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
  103. package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
  104. package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
  105. package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
  106. package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
  107. package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
  108. package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
  109. package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
  110. package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
  111. package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
  112. package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
  113. package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
  114. package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
  115. package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
  116. package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
  117. package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
  118. package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
  119. package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
  120. package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
  121. package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
  122. package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
  123. package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
  124. package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
  125. package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
  126. package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
  127. package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
  128. package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
  129. package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
  130. package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
  131. package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
  132. package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
  133. package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
  134. package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
  135. package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
  136. package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
  137. package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
  138. package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
  139. package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
  140. package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
  141. package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
  142. package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
  143. package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
  144. package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
  145. package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
  146. package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
  147. package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
  148. package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
  149. package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
  150. package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
  151. package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
  152. package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
  153. package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
  154. package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
  155. package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
  156. package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
  157. package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
  158. package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
  159. package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
  160. package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
  161. package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
  162. package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
  163. package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
  164. package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
  165. package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
  166. package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
  167. package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
  168. package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
  169. package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
  170. package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
  171. package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
  172. package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
  173. package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
  174. package/fixtures/atelier-interaction/act/valid/document.json +24 -0
  175. package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
  176. package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
  177. package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
  178. package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
  179. package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
  180. package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
  181. package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
  182. package/fixtures/atelier-judgment/case/valid/document.json +75 -0
  183. package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
  184. package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
  185. package/fixtures/atelier-judgment/native-decision.md +3 -0
  186. package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
  187. package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
  188. package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
  189. package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
  190. package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
  191. package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
  192. package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
  193. package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
  194. package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
  195. package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
  196. package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
  197. package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
  198. package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
  199. package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
  200. package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
  201. package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
  202. package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
  203. package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
  204. package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
  205. package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
  206. package/fixtures/atelier-learning/invalid/authority.json +14 -0
  207. package/fixtures/atelier-learning/valid/capture.json +13 -0
  208. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
  209. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
  210. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
  211. package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
  212. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
  213. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
  214. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
  215. package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
  216. package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
  217. package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
  218. package/fixtures/atelier-trackable/request/valid/document.json +22 -0
  219. package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
  220. package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
  221. package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
  222. package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
  223. package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
  224. package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
  225. package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
  226. package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
  227. package/fixtures/capability-packages/adoption.example.json +70 -0
  228. package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
  229. package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
  230. package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
  231. package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
  232. package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
  233. package/fixtures/capability-packages/research/capability-package.json +88 -0
  234. package/fixtures/capability-packages/research/capability-release.json +134 -0
  235. package/fixtures/capability-packages/research/evaluation.md +15 -0
  236. package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
  237. package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
  238. package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
  239. package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
  240. package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
  241. package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
  242. package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
  243. package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
  244. package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
  245. package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
  246. package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
  247. package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
  248. package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
  249. package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
  250. package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
  251. package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
  252. package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
  253. package/fixtures/evidence-navigation/valid/claim.json +36 -0
  254. package/fixtures/evidence-navigation/valid/host.json +16 -0
  255. package/fixtures/evidence-navigation/valid/profile.json +16 -0
  256. package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
  257. package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
  258. package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
  259. package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
  260. package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
  261. package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
  262. package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
  263. package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
  264. package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
  265. package/fixtures/harnesses/learning-cycle.json +780 -0
  266. package/fixtures/inquiry/workshop.json +675 -0
  267. package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
  268. package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
  269. package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
  270. package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
  271. package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
  272. package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
  273. package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
  274. package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
  275. package/fixtures/knowledge-health/calibration-suite.json +26 -0
  276. package/fixtures/knowledge-health/calibration-trial.json +37 -0
  277. package/fixtures/knowledge-health/corrected/checklist.md +18 -0
  278. package/fixtures/knowledge-health/devday/participant-record.json +19 -0
  279. package/fixtures/knowledge-health/expected-summary.json +27 -0
  280. package/fixtures/knowledge-health/workshop/README.md +20 -0
  281. package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
  282. package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
  283. package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
  284. package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
  285. package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
  286. package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
  287. package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
  288. package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
  289. package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
  290. package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
  291. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/case-only-collision.v2.json +160 -0
  292. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/flat-note-path.v2.json +160 -0
  293. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/hidden-folder.v2.json +160 -0
  294. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/identity-in-the-body.v2.json +160 -0
  295. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/link-breaking-name.v2.json +160 -0
  296. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-identity-region.v2.json +156 -0
  297. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-layout-version.v2.json +159 -0
  298. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/note-named-like-a-folder.v2.json +160 -0
  299. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/unknown-region-kind.v2.json +160 -0
  300. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/v1-contract-version.v2.json +161 -0
  301. package/fixtures/obsidian/contracts/generation-manifest-v2/valid/three-notes.v2.json +160 -0
  302. package/fixtures/obsidian/edits/workspace.json +1 -1
  303. package/fixtures/obsidian/materialization/expected-full.json +21 -21
  304. package/fixtures/obsidian/materialization/expected-full.layout-1.json +99 -0
  305. package/fixtures/obsidian/materialization/expected-scoped.json +7 -7
  306. package/fixtures/obsidian/materialization/expected-scoped.layout-1.json +39 -0
  307. package/fixtures/personal-workspace/overlay.json +24 -0
  308. package/fixtures/templates/local-library.v1.json +75 -0
  309. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
  310. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
  311. package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
  312. package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
  313. package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
  314. package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
  315. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
  316. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
  317. package/package.json +64 -6
  318. package/plugins/obsidian/main.js +444 -0
  319. package/plugins/obsidian/manifest.json +9 -0
  320. package/plugins/obsidian/styles.css +22 -0
  321. package/skills/claude/atelier-build-harness/SKILL.md +59 -0
  322. package/skills/claude/atelier-coordination/SKILL.md +32 -0
  323. package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
  324. package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
  325. package/skills/claude/atelier-interaction/SKILL.md +34 -0
  326. package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
  327. package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
  328. package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
  329. package/skills/claude/atelier-research-harness/SKILL.md +43 -0
  330. package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
  331. package/skills/claude/atelier-trackables/SKILL.md +40 -0
  332. package/skills/codex/atelier-build-harness/SKILL.md +59 -0
  333. package/skills/codex/atelier-coordination/SKILL.md +32 -0
  334. package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
  335. package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
  336. package/skills/codex/atelier-interaction/SKILL.md +34 -0
  337. package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
  338. package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
  339. package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
  340. package/skills/codex/atelier-research-harness/SKILL.md +43 -0
  341. package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
  342. package/skills/codex/atelier-trackables/SKILL.md +40 -0
  343. package/src/architecture/catalog.mjs +355 -0
  344. package/src/architecture/index.mjs +93 -0
  345. package/src/boundary/content-rules.mjs +4 -2
  346. package/src/boundary/delegation-command.mjs +158 -0
  347. package/src/boundary/delegation.mjs +396 -0
  348. package/src/boundary/policy.mjs +52 -10
  349. package/src/build/git.mjs +18 -0
  350. package/src/build/index.mjs +3 -0
  351. package/src/build/ledger.mjs +88 -0
  352. package/src/capabilities/adoption.mjs +381 -0
  353. package/src/capabilities/evidence.mjs +140 -0
  354. package/src/capabilities/files.mjs +124 -0
  355. package/src/capabilities/index.mjs +4 -0
  356. package/src/capabilities/instructions.mjs +250 -0
  357. package/src/capabilities/package.mjs +136 -0
  358. package/src/cli/command-failure.mjs +36 -0
  359. package/src/cli/execute-command.mjs +16 -2
  360. package/src/cli/run.mjs +65 -9
  361. package/src/coauthor/store.mjs +43 -2
  362. package/src/collaboration/proposals.mjs +189 -0
  363. package/src/commands/architecture.mjs +27 -0
  364. package/src/commands/boundary.mjs +3 -0
  365. package/src/commands/capability.mjs +73 -0
  366. package/src/commands/enroll.mjs +3 -0
  367. package/src/commands/harness.mjs +79 -0
  368. package/src/commands/ingest.mjs +39 -0
  369. package/src/commands/init.mjs +4 -1
  370. package/src/commands/inquiry.mjs +66 -0
  371. package/src/commands/knowledge-semantic.mjs +128 -0
  372. package/src/commands/knowledge.mjs +119 -0
  373. package/src/commands/learn.mjs +61 -0
  374. package/src/commands/obsidian.mjs +832 -62
  375. package/src/commands/practice.mjs +24 -0
  376. package/src/commands/setup.mjs +22 -2
  377. package/src/commands/trackable.mjs +19 -0
  378. package/src/contracts/corpus.mjs +36 -0
  379. package/src/coordination/index.mjs +110 -0
  380. package/src/egress/forbidden-egress.mjs +2 -0
  381. package/src/evidence-navigation/contracts.mjs +60 -0
  382. package/src/evidence-navigation/evaluate.mjs +69 -0
  383. package/src/evidence-navigation/generation.mjs +22 -0
  384. package/src/evidence-navigation/index.mjs +2 -0
  385. package/src/evidence-navigation/local.mjs +167 -0
  386. package/src/evidence-navigation/schema.generated.mjs +604 -0
  387. package/src/evidence-navigation/types.mjs +12 -0
  388. package/src/export/atelier-export-contract.mjs +35 -8
  389. package/src/graph/enroll.mjs +248 -0
  390. package/src/graph/graph.mjs +17 -1
  391. package/src/graph/knowledge-graph.mjs +58 -8
  392. package/src/harnesses/contracts.mjs +41 -0
  393. package/src/harnesses/exchange.mjs +154 -0
  394. package/src/harnesses/feedback.mjs +16 -0
  395. package/src/harnesses/history.mjs +37 -0
  396. package/src/harnesses/index.mjs +4 -0
  397. package/src/harnesses/store.mjs +29 -0
  398. package/src/ingestion/contracts.mjs +41 -0
  399. package/src/ingestion/evaluation.mjs +119 -0
  400. package/src/ingestion/optional/composition/index.mjs +171 -0
  401. package/src/ingestion/optional/evaluation/index.mjs +219 -0
  402. package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
  403. package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
  404. package/src/ingestion/optional/graphify/graphify_supplier.py +933 -0
  405. package/src/ingestion/optional/graphify/json_bridge.py +163 -0
  406. package/src/ingestion/optional/graphify/query_receiving.py +209 -0
  407. package/src/ingestion/optional/index.mjs +9 -0
  408. package/src/ingestion/optional/jev/bridge.mjs +166 -0
  409. package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
  410. package/src/ingestion/optional/jev/judgments.mjs +174 -0
  411. package/src/ingestion/optional/jev/native-translation.mjs +32 -0
  412. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
  413. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
  414. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
  415. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
  416. package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
  417. package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
  418. package/src/ingestion/optional/vanilla/index.mjs +218 -0
  419. package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
  420. package/src/ingestion/processors.mjs +218 -0
  421. package/src/ingestion/semantic.mjs +203 -0
  422. package/src/ingestion/store.mjs +372 -0
  423. package/src/inquiry/beliefs.mjs +32 -0
  424. package/src/inquiry/contracts.mjs +23 -0
  425. package/src/inquiry/index.mjs +5 -0
  426. package/src/inquiry/ledger.mjs +135 -0
  427. package/src/inquiry/lenses.mjs +15 -0
  428. package/src/inquiry/projection.mjs +77 -0
  429. package/src/inquiry/store.mjs +35 -0
  430. package/src/intake/read-scope.mjs +12 -0
  431. package/src/intake/store.mjs +69 -6
  432. package/src/interaction/index.mjs +91 -0
  433. package/src/judgment/index.mjs +82 -0
  434. package/src/judgment/practice-evaluation.mjs +70 -0
  435. package/src/judgment/practice.mjs +144 -0
  436. package/src/knowledge/context.mjs +217 -0
  437. package/src/knowledge/index.mjs +5 -0
  438. package/src/knowledge/ingestion.mjs +79 -0
  439. package/src/knowledge/intake.mjs +33 -0
  440. package/src/knowledge/ledger.mjs +110 -0
  441. package/src/knowledge/participatory/guidance.mjs +36 -0
  442. package/src/knowledge/participatory/guided-contribution.mjs +44 -0
  443. package/src/knowledge/participatory/index.mjs +2 -0
  444. package/src/knowledge/participatory/installed-workshop.mjs +108 -0
  445. package/src/knowledge/plan.mjs +67 -0
  446. package/src/knowledge/projection.mjs +43 -0
  447. package/src/knowledge/semantic-operation.mjs +567 -0
  448. package/src/knowledge/sessions.mjs +467 -0
  449. package/src/knowledge/workspace.mjs +295 -0
  450. package/src/knowledge-health/assessment.mjs +165 -0
  451. package/src/knowledge-health/capabilities.mjs +350 -0
  452. package/src/knowledge-health/context.mjs +62 -0
  453. package/src/knowledge-health/example.mjs +47 -0
  454. package/src/knowledge-health/index.mjs +10 -0
  455. package/src/knowledge-health/owner-join.mjs +88 -0
  456. package/src/knowledge-health/proof/common.mjs +24 -0
  457. package/src/knowledge-health/proof/installed-check.mjs +100 -0
  458. package/src/knowledge-health/proof/native-readback.mjs +37 -0
  459. package/src/knowledge-health/proof/rehearse.mjs +68 -0
  460. package/src/knowledge-health/proof/validate-participant.mjs +17 -0
  461. package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
  462. package/src/knowledge-health/proof/workshop-check.mjs +36 -0
  463. package/src/knowledge-health/rule-identity.mjs +44 -0
  464. package/src/knowledge-health/workshop-composition.mjs +19 -0
  465. package/src/knowledge-health/workshop-profile.mjs +176 -0
  466. package/src/knowledge-health/workshop.mjs +99 -0
  467. package/src/learning/adapters.mjs +68 -0
  468. package/src/learning/assistance.mjs +127 -0
  469. package/src/learning/contracts.mjs +65 -0
  470. package/src/learning/store.mjs +272 -0
  471. package/src/personal-workspace/index.mjs +391 -0
  472. package/src/personal-workspace/selection.mjs +385 -0
  473. package/src/project/config.mjs +8 -0
  474. package/src/project/durable-state.mjs +3 -1
  475. package/src/project/file-class.mjs +1 -1
  476. package/src/project/private-state.mjs +10 -1
  477. package/src/projection/obsidian/contracts.mjs +224 -18
  478. package/src/projection/obsidian/edits/apply.mjs +63 -19
  479. package/src/projection/obsidian/edits/regions.mjs +137 -42
  480. package/src/projection/obsidian/materialize/index.mjs +6 -3
  481. package/src/projection/obsidian/materialize/path-registry.mjs +443 -57
  482. package/src/projection/obsidian/materialize/prepare-view.mjs +382 -131
  483. package/src/projection/obsidian/materialize/redaction.mjs +327 -0
  484. package/src/projection/obsidian/materialize/settings.mjs +113 -34
  485. package/src/projection/obsidian/personal-workspace.mjs +436 -0
  486. package/src/projection/obsidian/plugin-bridge/bundle.mjs +50 -0
  487. package/src/projection/obsidian/plugin-bridge/channel.mjs +174 -0
  488. package/src/projection/obsidian/plugin-bridge/index.mjs +10 -0
  489. package/src/projection/obsidian/proposals/adapter.mjs +47 -8
  490. package/src/projection/obsidian/proposals/observation.mjs +20 -7
  491. package/src/projection/obsidian/publication/bridge-script.mjs +11 -5
  492. package/src/projection/obsidian/publication/index.mjs +2 -1
  493. package/src/projection/obsidian/publication/publisher.mjs +312 -36
  494. package/src/projection/obsidian/publication/transport.mjs +63 -8
  495. package/src/projection/obsidian/publication/vault-list.mjs +200 -0
  496. package/src/projection/obsidian/recovery/index.mjs +4 -1
  497. package/src/projection/obsidian/recovery/restart.mjs +3 -1
  498. package/src/projection/obsidian/recovery/store.mjs +168 -8
  499. package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
  500. package/src/projection/obsidian/selection-ui/focus.mjs +14 -7
  501. package/src/projection/obsidian/selection-ui/selection-state.mjs +2 -2
  502. package/src/projection/obsidian/selection-ui/selection.mjs +8 -3
  503. package/src/readiness/readiness.mjs +10 -3
  504. package/src/reflection/index.mjs +56 -0
  505. package/src/runtime/obsidian/app-capability.mjs +104 -12
  506. package/src/runtime/obsidian/app-production-seams.mjs +161 -42
  507. package/src/runtime/obsidian/app-registration.mjs +313 -0
  508. package/src/runtime/obsidian/app-restart.mjs +154 -0
  509. package/src/runtime/obsidian/engine.mjs +288 -29
  510. package/src/runtime/obsidian/extension-points.mjs +1 -1
  511. package/src/runtime/obsidian/index.mjs +15 -8
  512. package/src/runtime/obsidian/launch-plan.mjs +91 -0
  513. package/src/runtime/obsidian/lifecycle.mjs +193 -22
  514. package/src/runtime/obsidian/login-item.mjs +332 -0
  515. package/src/runtime/obsidian/machine-settings.mjs +119 -12
  516. package/src/runtime/obsidian/opening.mjs +369 -63
  517. package/src/runtime/obsidian/pending-edits.mjs +10 -0
  518. package/src/runtime/obsidian/pipeline.mjs +71 -3
  519. package/src/runtime/obsidian/plugin-channel.mjs +289 -0
  520. package/src/runtime/obsidian/plugin-choice.mjs +182 -0
  521. package/src/runtime/obsidian/plugin-drift.mjs +124 -0
  522. package/src/runtime/obsidian/plugin-presence.mjs +61 -0
  523. package/src/runtime/obsidian/process-identity.mjs +20 -10
  524. package/src/runtime/obsidian/project-views.mjs +282 -0
  525. package/src/runtime/obsidian/questions.mjs +48 -0
  526. package/src/runtime/obsidian/release-watch.mjs +56 -0
  527. package/src/runtime/obsidian/service-main.mjs +113 -17
  528. package/src/runtime/obsidian/service-manager-production.mjs +61 -0
  529. package/src/runtime/obsidian/service-managers.mjs +275 -0
  530. package/src/runtime/obsidian/service-record.mjs +105 -5
  531. package/src/runtime/obsidian/service-server.mjs +50 -12
  532. package/src/runtime/obsidian/service.mjs +134 -7
  533. package/src/runtime/obsidian/startup-adapters.mjs +59 -21
  534. package/src/runtime/obsidian/state-store.mjs +13 -1
  535. package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
  536. package/src/runtime/obsidian/vault-location.mjs +300 -0
  537. package/src/runtime/obsidian/view-counts.mjs +34 -0
  538. package/src/server/local-sidecar.mjs +193 -7
  539. package/src/server/server.mjs +45 -6
  540. package/src/template-bindings/project.mjs +92 -0
  541. package/src/templates/profile.mjs +225 -0
  542. package/src/trackables/domain.mjs +175 -0
  543. package/src/trackables/store.mjs +114 -0
  544. package/src/ui/knowledge-page.mjs +1388 -0
  545. package/src/upgrade/explanation.mjs +1 -1
  546. package/src/upgrade/template-participant.mjs +171 -0
  547. package/src/upgrade/transaction.mjs +72 -25
  548. package/src/upgrade/upgrade.mjs +2 -1
  549. package/templates/external-project-workspace/pack-lifecycle.json +6 -1
  550. package/templates/knowledge-workspace/.gitattributes +2 -0
  551. package/templates/knowledge-workspace/AGENTS.md +27 -0
  552. package/templates/knowledge-workspace/README.md +44 -0
  553. package/templates/knowledge-workspace/atelier.project.json +22 -0
  554. package/templates/knowledge-workspace/gitignore +6 -0
  555. package/templates/knowledge-workspace/knowledge-plan.json +111 -0
  556. package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
  557. package/templates/knowledge-workspace/records/forecast.md +22 -0
  558. package/templates/knowledge-workspace/records/inspection.md +25 -0
  559. package/templates/knowledge-workspace/records/telescope.md +24 -0
  560. package/templates/knowledge-workspace/repo-access.v1.json +9 -0
@@ -0,0 +1,91 @@
1
+ // How `open` shows a vault in Obsidian, as steps a launcher carries out. Pure.
2
+ //
3
+ // Two facts of the app (1.13.7, read from its main process and checked on an
4
+ // isolated instance) decide the steps:
5
+ //
6
+ // - An app started with an `obsidian://` URL opens only that URL's vault and
7
+ // deletes the `open` flag of every other vault without a window, so the
8
+ // person's other vaults do not come back the next time Obsidian starts.
9
+ // Started plainly, it reopens every vault its list marks open (the view's
10
+ // vault included, when it was added to the list marked open) and keeps
11
+ // the flags.
12
+ // - `open?path=` picks the listed vault whose folder is the longest string
13
+ // prefix of the path, with no separator check; `open?vault=<id>` names one
14
+ // vault exactly.
15
+ //
16
+ // So a vault is named by its id where that id reaches it first (a vaultRoute
17
+ // of `how: 'id'`), and otherwise by its own path, which the app then matches
18
+ // exactly (a listed folder is the longest prefix of its own path); an app that is not running is
19
+ // started plainly and handed the URL only once it answers: a URL reaching a
20
+ // running app opens a window and changes no flag. On Linux a plain start is not
21
+ // qualified, and the URL starts the app, with the flag loss as a known limit.
22
+ //
23
+ // Steps:
24
+ // { step: 'plain-start' } start the app with no URL
25
+ // { step: 'wait-for-app' } wait, bounded, until the app itself answers its command-line tool
26
+ // { step: 'url', uri } hand the URL to the running app
27
+ // { step: 'url-start', uri } start the app with the URL (Linux only)
28
+ //
29
+ // { ok: true, steps } or { ok: false, reason }.
30
+
31
+ const VAULT_ID = /^[A-Za-z0-9_-]{1,64}$/
32
+
33
+ export function launchPlan({ platform = process.platform, appRunning, vaultId = null, vaultPath = null } = {}) {
34
+ let uri
35
+ if (typeof vaultId === 'string' && VAULT_ID.test(vaultId)) uri = `obsidian://open?vault=${encodeURIComponent(vaultId)}`
36
+ else if (typeof vaultPath === 'string' && vaultPath.startsWith('/') && !vaultPath.includes('\u0000')) uri = `obsidian://open?path=${encodeURIComponent(vaultPath)}`
37
+ else return { ok: false, reason: 'vault-id-unknown' }
38
+ if (platform !== 'darwin' && platform !== 'linux') return { ok: false, reason: 'launcher-platform-unqualified' }
39
+ if (appRunning === true) return { ok: true, steps: [{ step: 'url', uri }] }
40
+ if (platform === 'darwin') return { ok: true, steps: [{ step: 'plain-start' }, { step: 'wait-for-app' }, { step: 'url', uri }] }
41
+ return { ok: true, steps: [{ step: 'url-start', uri }] }
42
+ }
43
+
44
+ // Pure. Whether the command-line tool's reply to a URL says the app took it ("Processed URI …").
45
+ export const urlProcessed = (stdout) => typeof stdout === 'string' && /^processed uri\b/im.test(stdout)
46
+
47
+ // Carries out a launchPlan with injected actions, so the order and the
48
+ // fallbacks are the same for the production launcher and the test harness:
49
+ //
50
+ // start() -> Promise<boolean> start the app with no URL
51
+ // answered() -> Promise<boolean> one poll: the app itself answered (appAnswered); a tool that cannot find the
52
+ // app yet is not an answer
53
+ // handLink(uri) -> Promise<boolean> the app's tool took the URL (urlProcessed)
54
+ // osOpen(uri) -> Promise<boolean> the operating system accepted the URL
55
+ //
56
+ // A URL is never handed to the operating system for an app this launch
57
+ // started: that would start or reach it while it starts, which is what drops
58
+ // the other vaults' reopen flags. A vault added flagged open is opened anyway by
59
+ // a plainly started app, and `open` waits for it; one already listed but closed
60
+ // is handed the link once more instead (opening.mjs). `startedEarlier` says
61
+ // that an earlier launch of the same `open` started the app (a link handed to
62
+ // it once more): the link then goes through the app's tool only, as well.
63
+ export async function runLaunchPlan(plan, { start, answered, handLink, osOpen, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), waitMs = 30_000, pollMs = 500, now = () => Date.now(), startedEarlier = false }) {
64
+ if (!plan?.ok) return { launched: false, reason: plan?.reason ?? 'launcher-refused' }
65
+ let started = startedEarlier === true
66
+ let reason = 'url-accepted'
67
+ for (const { step, uri } of plan.steps) {
68
+ if (step === 'plain-start') {
69
+ if (!(await start())) return { launched: false, reason: 'os-open-failed' }
70
+ started = true
71
+ } else if (step === 'wait-for-app') {
72
+ const deadline = now() + waitMs
73
+ let up = false
74
+ while (!up) {
75
+ up = await answered()
76
+ if (up) break
77
+ if (now() >= deadline) return { launched: true, reason: 'app-started-not-answering' }
78
+ await sleep(pollMs)
79
+ }
80
+ } else if (step === 'url') {
81
+ if (await handLink(uri)) continue
82
+ if (started) return { launched: true, reason: 'app-started-link-not-taken' }
83
+ if (!(await osOpen(uri))) return { launched: false, reason: 'os-open-failed' }
84
+ reason = 'os-open-accepted'
85
+ } else if (step === 'url-start') {
86
+ if (!(await osOpen(uri))) return { launched: false, reason: 'os-open-failed' }
87
+ reason = 'os-open-accepted'
88
+ }
89
+ }
90
+ return { launched: true, reason: started && reason === 'url-accepted' ? 'plain-start-then-url' : reason }
91
+ }
@@ -2,15 +2,17 @@ import childProcess from 'node:child_process'
2
2
  import { randomBytes as cryptoRandomBytes } from 'node:crypto'
3
3
  import fs from 'node:fs'
4
4
  import net from 'node:net'
5
+ import path from 'node:path'
6
+ import { compareAppVersions } from './app-capability.mjs'
5
7
  import { isoTime } from './documents.mjs'
6
- import { refuse } from './errors.mjs'
8
+ import { ObsidianMaintenanceRefusal, refuse } from './errors.mjs'
7
9
  import { ensureWorkspaceIdentity } from './machine-settings.mjs'
8
10
  import { acquirePrivateGenerationLock, createAbandonmentProof, isProcessAlive } from './private-lock.mjs'
9
11
  import { processRunsRecordedExecutable } from './process-identity.mjs'
10
12
  import { DEFAULT_PROBE_TIMEOUT_MS, LOOPBACK_HOSTS, probeHealth, requestLoopback } from './service-client.mjs'
11
13
  import { SERVICE_ENTRY_PATH } from './service-entry-path.mjs'
12
14
  import {
13
- CONSENT_COVERAGES, SERVICE_SETTINGS_SCHEMA, executableIdentity, openServiceLog, publicRecord, readServiceRecord, readServiceSettings, removeServiceRecord,
15
+ CONSENT_COVERAGES, SERVICE_SETTINGS_SCHEMA, executableIdentity, openServiceLog, publicRecord, readLastStartup, readServiceRecord, readServiceSettings, releaseOfEntry, removeServiceRecord,
14
16
  serviceNameFor, servicePaths, writeServiceSettings,
15
17
  } from './service-record.mjs'
16
18
  import { resolveServiceWorkspace } from './service.mjs'
@@ -103,8 +105,18 @@ function freeLoopbackPort(host) {
103
105
  })
104
106
  }
105
107
 
106
- async function resolveSettings({ workspaceRoot, workspaceId }, { host, port, consent, now }) {
108
+ // The service settings of this workspace with the address and consent given, written when that changes anything: the
109
+ // first start needs a consent naming its actor, and a consent for another actor or coverage replaces the recorded one.
110
+ // Installing and removing a login item raise and lower the coverage through it (login-item.mjs).
111
+ export async function ensureServiceSettings(workspace, { host, port, consent, now }) {
112
+ return resolveSettings(workspace, { host, port, consent, now })
113
+ }
114
+
115
+ async function resolveSettings({ workspaceRoot, workspaceId }, { host, port, consent: given, now }) {
107
116
  const current = readServiceSettings({ workspaceRoot, workspaceId })
117
+ // A consent derived from the account of a person at a terminal (`derived: true`) stands only for a workspace that has
118
+ // none. Read here, under the start lock, so one recorded meanwhile by another run is never replaced or narrowed.
119
+ const consent = given?.derived === true && current !== null ? undefined : given
108
120
  if (host !== undefined && !LOOPBACK_HOSTS.includes(host)) refuse('service-address-not-loopback', 'the service host must be the literal 127.0.0.1 or ::1')
109
121
  if (port !== undefined && (!Number.isInteger(port) || port < 1024 || port > 65535)) refuse('service-address-not-loopback', 'the service port must be between 1024 and 65535')
110
122
  if (consent !== undefined && (typeof consent?.actor !== 'string' || !CONSENT_COVERAGES.includes(consent.coverage ?? 'service'))) refuse('startup-consent-required', 'a consent names its actor and what it covers')
@@ -119,11 +131,55 @@ async function resolveSettings({ workspaceRoot, workspaceId }, { host, port, con
119
131
  return changed ? writeServiceSettings({ workspaceRoot, workspaceId, settings: next }) : current
120
132
  }
121
133
 
134
+ // Asks the service manager of the installed login item to start the service, and waits, bounded, for this
135
+ // workspace's service to prove itself (healthy, or busy in its first tick) with a record naming the digest of the
136
+ // entry the item runs. The runtime identifier is the service's own there, so that digest is what makes it the one
137
+ // asked for. A service that refused under the item exited cleanly and recorded why (`last-startup.json`); that code is
138
+ // the answer. { result } or, when the manager did not start it (the item is not loaded: switched off in System
139
+ // Settings, or no user service manager), { fallback: code }.
140
+ async function startThroughLoginItem({ workspace, loginItem, entryPath, deadline, probeTimeoutMs, alive, rules }) {
141
+ const { workspaceRoot, workspaceId } = workspace
142
+ const lastStartup = () => { try { return JSON.stringify(readLastStartup(workspace)) } catch { return null } }
143
+ const before = lastStartup()
144
+ let asked
145
+ try { asked = await loginItem.start() } catch { asked = { ok: false, code: 'login-item-start-failed' } }
146
+ const item = { via: 'login-item', ...(asked?.refreshed === true ? { refreshed: true } : {}) }
147
+ if (asked?.ok !== true) return { fallback: typeof asked?.code === 'string' ? asked.code : 'login-item-start-failed', refreshed: asked?.refreshed === true }
148
+ // A unit written again on the way names the entry of now. One whose entry cannot be read cannot prove anything.
149
+ let expected
150
+ try { expected = executableIdentity(typeof asked.entryPath === 'string' ? asked.entryPath : entryPath).digest } catch { return { result: { state: 'start-failed', started: false, alreadyRunning: false, workspaceId, reason: 'login-item-entry-missing', observed: null, logPath: servicePaths(workspaceRoot).loginItemLog, loginItem: item } } }
151
+ let last = null
152
+ const proves = (status) => status?.record?.executable?.digest === expected
153
+ while (Date.now() < deadline) {
154
+ last = await evaluate(workspace, { probeTimeoutMs, alive, rules }).catch((error) => ({ state: 'refused', reason: error.code ?? 'untyped-error' }))
155
+ if (last.state === 'healthy' && proves(last)) return { result: { ...shown(last), started: true, alreadyRunning: false, loginItem: item } }
156
+ const now = lastStartup()
157
+ if (now !== before && now !== null) {
158
+ const startup = JSON.parse(now)
159
+ if (startup?.outcome === 'refused') return { result: { state: 'start-failed', started: false, alreadyRunning: false, workspaceId, reason: startup.code, observed: last.state ?? null, logPath: servicePaths(workspaceRoot).log, loginItem: item } }
160
+ }
161
+ await sleep(50)
162
+ }
163
+ // As for a child started here: a service that proved itself and went straight into a long first tick runs, and is
164
+ // answered as busy only once the wait for it to be healthy is over.
165
+ if (last?.state === 'busy' && proves(last)) return { result: { ...shown(last), started: true, alreadyRunning: false, busy: true, loginItem: item } }
166
+ return { result: { state: 'start-failed', started: false, alreadyRunning: false, workspaceId, reason: 'health-never-proved-ownership', observed: last?.state ?? null, logPath: servicePaths(workspaceRoot).loginItemLog, loginItem: item } }
167
+ }
168
+
169
+ // `loginItem`, for a workspace with an installed login item: { start() }, which asks its service manager to start the
170
+ // service (login-item.mjs). The service is then started through it, never as a child beside it, so two starts never
171
+ // compete; `entryPath` is the entry the item runs. When the manager does not start it, the service is started as a
172
+ // child, and the answer says why (`loginItem: { via: 'child', reason }`).
173
+ //
174
+ // `replaceOutdated`: a proven runtime of this workspace that runs an earlier release than the entry this start would run
175
+ // (runtimeRelease) is stopped as `stopService` stops it and started again in its place, under the consent already
176
+ // recorded (`replaced: 'outdated'`); one of a later release is never replaced (`release: 'later'`). Without it, any
177
+ // proven runtime is answered as already running.
122
178
  export async function startService(options = {}, rules = LIFECYCLE_PRIMITIVES) {
123
179
  const {
124
180
  loadProject, dataRoot, host, port, consent, detached = false, entryPath = SERVICE_ENTRY_PATH, entryArgs = [], intervalMs,
125
181
  startTimeoutMs = DEFAULT_START_TIMEOUT_MS, probeTimeoutMs, clock = () => new Date(), env = process.env, platform = process.platform,
126
- spawn = childProcess.spawn, execPath = process.execPath, alive = isProcessAlive, randomBytes = cryptoRandomBytes,
182
+ spawn = childProcess.spawn, execPath = process.execPath, alive = isProcessAlive, randomBytes = cryptoRandomBytes, loginItem = null, replaceOutdated = false,
127
183
  } = options
128
184
  const { project, workspace } = context({ loadProject, dataRoot, env, platform, create: true })
129
185
  const { workspaceRoot, workspaceId } = workspace
@@ -144,11 +200,26 @@ export async function startService(options = {}, rules = LIFECYCLE_PRIMITIVES) {
144
200
 
145
201
  let child = null
146
202
  try {
147
- const before = await evaluate(workspace, { probeTimeoutMs, alive, rules })
148
- if (rules.isOurs(before)) return { ...shown(before), started: false, alreadyRunning: true }
203
+ let before = await evaluate(workspace, { probeTimeoutMs, alive, rules })
204
+ // A runtime of an earlier release, still running after an upgrade, is replaced when asked to; it is stopped through its
205
+ // own listener, as its owner stops it, and nothing else is ever stopped here.
206
+ let replaced = null
207
+ if (rules.isOurs(before)) {
208
+ const standing = replaceOutdated ? runtimeRelease(before, entryPath) : null
209
+ if (standing === 'later') return { ...shown(before), started: false, alreadyRunning: true, release: 'later' }
210
+ if (standing !== 'outdated') return { ...shown(before), started: false, alreadyRunning: true }
211
+ const stopped = await stopService({ loadProject, dataRoot, env, platform, probeTimeoutMs, alive }, rules)
212
+ if (!stopped.stopped) return { ...shown(before), started: false, alreadyRunning: true, release: 'outdated', reason: stopped.reason ?? 'the-runtime-was-not-stopped' }
213
+ replaced = 'outdated'
214
+ before = await evaluate(workspace, { probeTimeoutMs, alive, rules })
215
+ }
149
216
  // Running, and in a long tick: nothing is started beside it and nothing replaces it.
150
217
  if (before.state === 'busy') return { ...shown(before), started: false, alreadyRunning: true, busy: true }
151
- if (rules.refusesOccupied(before)) refuse('service-port-occupied', 'something that is not this service answers on the loopback port; it is never taken over', { reason: before.reason, address: before.address })
218
+ // With a login item, a listener that has not written its record yet may be the service its manager is starting
219
+ // right now (it listens before it records itself): the start below asks the manager, which starts nothing beside
220
+ // a running job, and accepts only a runtime that proves itself. Anything else stays occupied and is never adopted.
221
+ const starting = loginItem !== null && before.state === 'occupied' && before.reason === 'a-listener-without-a-record'
222
+ if (rules.refusesOccupied(before) && !starting) refuse('service-port-occupied', 'something that is not this service answers on the loopback port; it is never taken over', { reason: before.reason, address: before.address })
152
223
  // `stale-record` and `pid-not-ours`: nothing listens, so the recorded runtime is not serving. Its PID is never signalled;
153
224
  // the new service replaces the record once it listens. Nothing under recovery or staging is touched on the way.
154
225
  const settings = await resolveSettings(workspace, { host, port, consent, now: isoTime(clock) })
@@ -157,13 +228,23 @@ export async function startService(options = {}, rules = LIFECYCLE_PRIMITIVES) {
157
228
  if (moved.kind !== 'refused') refuse('service-port-occupied', 'something already answers on the selected loopback port; it is never taken over', { address: { host: settings.host, port: settings.port } })
158
229
  }
159
230
 
231
+ // An installed login item starts it. Only when its manager does not is a child started, and the answer says why.
232
+ let fallback = null
233
+ if (loginItem !== null) {
234
+ const managed = await startThroughLoginItem({ workspace, loginItem, entryPath, deadline, probeTimeoutMs, alive, rules })
235
+ if (managed.result) return replaced === null ? managed.result : { ...managed.result, replaced }
236
+ fallback = { via: 'child', reason: managed.fallback, ...(managed.refreshed ? { refreshed: true } : {}) }
237
+ }
238
+ const withFallback = (result) => ({ ...result, ...(fallback === null ? {} : { loginItem: fallback }), ...(replaced === null ? {} : { replaced }) })
239
+
160
240
  const runtimeId = `rt-${randomBytes(16).toString('hex')}`
161
241
  const executable = executableIdentity(entryPath)
162
242
  const log = openServiceLog(workspaceRoot)
163
243
  try {
164
244
  const args = [executable.path, `--project=${project.configPath}`, ...(dataRoot === undefined ? [] : [`--data-root=${dataRoot}`]), `--runtime-id=${runtimeId}`, ...(intervalMs === undefined ? [] : [`--interval-ms=${intervalMs}`]), ...entryArgs]
165
- // No shell. Detached only when the service is meant to outlive the command that starts it.
166
- child = spawn(execPath, args, { detached, shell: false, windowsHide: true, stdio: ['ignore', log.descriptor, log.descriptor], env })
245
+ // No shell. Detached only when the service is meant to outlive the command that starts it. It runs in the root
246
+ // directory, never in the one the command was started in, which may be a vault or a folder somebody removes.
247
+ child = spawn(execPath, args, { detached, shell: false, windowsHide: true, stdio: ['ignore', log.descriptor, log.descriptor], env, cwd: path.parse(executable.path).root })
167
248
  } finally {
168
249
  fs.closeSync(log.descriptor)
169
250
  }
@@ -177,19 +258,19 @@ export async function startService(options = {}, rules = LIFECYCLE_PRIMITIVES) {
177
258
  last = await evaluate(workspace, { probeTimeoutMs, alive, rules }).catch((error) => ({ state: 'refused', reason: error.code ?? 'untyped-error' }))
178
259
  if (last.state === 'healthy' && last.record.runtimeId === runtimeId && last.record.pid === child.pid) {
179
260
  if (detached) child.unref()
180
- return { ...shown(last), started: true, alreadyRunning: false, ...(detached ? {} : { child }) }
261
+ return withFallback({ ...shown(last), started: true, alreadyRunning: false, ...(detached ? {} : { child }) })
181
262
  }
182
263
  await sleep(50)
183
264
  }
184
265
  // The child created here wrote the record and went straight into a long first tick: it runs, and is not stopped.
185
266
  if (exited === null && last?.state === 'busy' && last.record?.runtimeId === runtimeId && last.record?.pid === child.pid) {
186
267
  if (detached) child.unref()
187
- return { ...shown(last), started: true, alreadyRunning: false, busy: true, ...(detached ? {} : { child }) }
268
+ return withFallback({ ...shown(last), started: true, alreadyRunning: false, busy: true, ...(detached ? {} : { child }) })
188
269
  }
189
270
  // Ownership was never proven: only the child created here is stopped, by its handle.
190
271
  if (exited === null) { child.kill(); const until = Date.now() + 5000; while (exited === null && Date.now() < until) await sleep(25) }
191
272
  removeServiceRecord({ workspaceRoot, workspaceId, runtimeId, pid: child.pid })
192
- return { state: 'start-failed', started: false, alreadyRunning: false, workspaceId, reason: exited?.error ?? (exited?.code === null || exited === null ? 'health-never-proved-ownership' : `service-exited-${exited.code}`), observed: last?.state ?? null, logPath: log.file }
273
+ return withFallback({ state: 'start-failed', started: false, alreadyRunning: false, workspaceId, reason: exited?.error ?? (exited?.code === null || exited === null ? 'health-never-proved-ownership' : `service-exited-${exited.code}`), observed: last?.state ?? null, logPath: log.file })
193
274
  } finally {
194
275
  lock.release()
195
276
  }
@@ -226,26 +307,116 @@ export async function stopService(options = {}, rules = LIFECYCLE_PRIMITIVES) {
226
307
 
227
308
  // One authenticated request to the proven runtime, and to nothing else: anything but a healthy status is answered
228
309
  // with that status and no request is made.
229
- async function askProvenRuntime(options, rules, { method, operation, timeoutMs }) {
310
+ async function askProvenRuntime(options, rules, { method, operation, timeoutMs, body = {} }) {
230
311
  const { loadProject, dataRoot, probeTimeoutMs, env = process.env, platform = process.platform, alive = isProcessAlive } = options
231
312
  const { workspace } = context({ loadProject, dataRoot, env, platform })
232
313
  if (!workspace?.workspaceRoot) return { requested: false, state: 'stopped', reason: 'workspace-not-prepared' }
233
314
  const status = await evaluate(workspace, { probeTimeoutMs, alive, rules })
234
315
  if (status.state !== 'healthy') return { requested: false, state: status.state, reason: status.reason }
235
316
  const { record } = status
236
- const answer = await requestLoopback({ host: record.host, port: record.port, method, path: operation, bearer: record.ext.bearer, payload: method === 'POST' ? { runtimeId: record.runtimeId } : null, timeoutMs })
237
- return { requested: true, state: 'healthy', answer }
317
+ const answer = await requestLoopback({ host: record.host, port: record.port, method, path: operation, bearer: record.ext.bearer, payload: method === 'POST' ? { runtimeId: record.runtimeId, ...body } : null, timeoutMs })
318
+ return { requested: true, state: 'healthy', answer, runtimeId: record.runtimeId }
319
+ }
320
+
321
+ // Pure. How the release a runtime recorded at its start (`executable.ext.release`, see releaseIdentity: the package
322
+ // version and a digest of every runtime module) stands to the installed one, `installed` being { entry, release }, the
323
+ // digest of the entry this command would start and the release that entry's package holds now (releaseOfEntry):
324
+ // current the same version, entry module and modules;
325
+ // outdated an earlier version; the same version with another entry module or other modules; or no release named,
326
+ // as releases before the one that began recording it record none;
327
+ // later a later version, or another version that cannot be ordered. The installed release never replaces it, so two
328
+ // installations used on one workspace do not replace each other's runtime.
329
+ // Versions are ordered as semantic versions, a prerelease below its release (0.2.0-alpha.11 < 0.2.0-alpha.12 < 0.2.0).
330
+ export function releaseStanding(executable, installed) {
331
+ const recorded = executable?.ext?.release
332
+ if (recorded === undefined || recorded === null) return 'outdated'
333
+ // The same version string is compared by content, whether or not it can be ordered: a fork's "dev" on both sides
334
+ // is this release when its modules match, and outdated otherwise, never later.
335
+ const order = recorded.version === installed.release.version ? 0 : compareAppVersions(recorded.version, installed.release.version)
336
+ if (order === null || order > 0) return 'later'
337
+ if (order < 0) return 'outdated'
338
+ return executable.digest === installed.entry && recorded.digest === installed.release.digest ? 'current' : 'outdated'
339
+ }
340
+
341
+ // How the release the proven runtime runs stands to the installed one (releaseStanding), `entryPath` being the entry
342
+ // this command would start: its digest and the release are both read from that entry, so with a login item the
343
+ // installed release is the project's own package, read through the path the item names, whichever package this command
344
+ // runs from. Null when the runtime is not proven, or the installed side cannot be read.
345
+ export function runtimeRelease(status, entryPath) {
346
+ if (status?.state !== 'healthy' || typeof status.record?.executable?.digest !== 'string' || typeof entryPath !== 'string') return null
347
+ let installed
348
+ try { installed = { entry: executableIdentity(entryPath).digest, release: releaseOfEntry(entryPath) } } catch { return null }
349
+ return releaseStanding(status.record.executable, installed)
350
+ }
351
+
352
+ // Stops the proven runtime of this workspace through its own listener and starts `entryPath` in its place, detached,
353
+ // under the consent already recorded. Only a runtime that proves itself ours is ever stopped (stopService).
354
+ // { ok: true } or { ok: false, state, reason }.
355
+ async function restartProvenRuntime(options, rules) {
356
+ const { consent: _given, ...rest } = options
357
+ const stopped = await stopService(rest, rules)
358
+ if (!stopped.stopped) return { ok: false, state: stopped.state, reason: stopped.reason ?? 'the-runtime-was-not-stopped' }
359
+ let started
360
+ try { started = await startService({ ...rest, detached: true }, rules) } catch (error) {
361
+ if (!(error instanceof ObsidianMaintenanceRefusal)) throw error
362
+ return { ok: false, state: 'stopped', reason: error.code }
363
+ }
364
+ if (started.state === 'healthy') return { ok: true }
365
+ return { ok: false, state: started.state, reason: started.state === 'busy' ? 'restarted-service-in-its-first-tick' : started.reason ?? started.state }
238
366
  }
239
367
 
240
368
  // Asks the proven runtime for one tick now and returns what that tick reported. A tick that outlasts the wait is
241
- // `pending`, not an error.
369
+ // `pending`, not an error. With `scopeId`, that view is prepared and published once more on the tick.
370
+ //
371
+ // `service`, the start options of the entry this command would start ({ entryPath, entryArgs, ... }), lets an
372
+ // outdated runtime be replaced: one that runs an earlier release (runtimeRelease) is restarted before it is asked,
373
+ // and one that refuses a tick naming a view (a runtime of 0.2.0-alpha.11 or earlier, which knows no view in a tick)
374
+ // is restarted and asked again, each time under the consent already recorded (`restarted: 'outdated'`). A listener
375
+ // of now refuses a tick only when it names another runtime: when a concurrent command replaced the runtime between
376
+ // the check and the request, the one that took its place is asked, and nothing is restarted. A runtime of a later
377
+ // release is never replaced by this one, nor asked: `service-other-release`. Without `service`, an outdated runtime
378
+ // is only reported (`service-outdated`). `runtimeId` names the runtime that answered, or the one left running.
242
379
  export async function requestServiceTick(options = {}, rules = LIFECYCLE_PRIMITIVES) {
243
- const asked = await askProvenRuntime(options, rules, { method: 'POST', operation: '/tick', timeoutMs: options.tickTimeoutMs ?? 60 * 1000 })
244
- if (!asked.requested) return asked
245
- const { answer } = asked
246
- if (answer.kind === 'timeout') return { requested: true, state: 'healthy', pending: true, tick: null, reason: 'tick-still-running' }
247
- if (answer.kind !== 'response' || answer.statusCode !== 200 || answer.body === null) return { requested: true, state: 'healthy', pending: false, tick: null, reason: 'tick-was-not-answered' }
248
- return { requested: true, state: 'healthy', pending: false, tick: answer.body, reason: 'tick-ran' }
380
+ const { service = null, scopeId } = options
381
+ const ask = () => askProvenRuntime(options, rules, { method: 'POST', operation: '/tick', timeoutMs: options.tickTimeoutMs ?? 60 * 1000, ...(scopeId === undefined ? {} : { body: { scopeId } }) })
382
+ let restarted = null
383
+ const restart = async () => {
384
+ const renewed = await restartProvenRuntime({ ...options, ...service }, rules)
385
+ if (renewed.ok || renewed.reason === 'restarted-service-in-its-first-tick') restarted = 'outdated'
386
+ return renewed.ok ? null : { requested: false, state: renewed.state, reason: renewed.reason, ...(restarted === null ? {} : { restarted }) }
387
+ }
388
+ const later = (status) => ({ requested: false, state: 'healthy', runtimeId: status.record.runtimeId, reason: 'service-other-release' })
389
+ const replaces = typeof service?.entryPath === 'string'
390
+ if (replaces) {
391
+ const status = await serviceStatus(options, rules)
392
+ const standing = runtimeRelease(status, service.entryPath)
393
+ if (standing === 'later') return later(status)
394
+ if (standing === 'outdated') {
395
+ const failed = await restart()
396
+ if (failed) return failed
397
+ }
398
+ }
399
+ let asked = await ask()
400
+ const refusesView = () => asked.requested && scopeId !== undefined && asked.answer.kind === 'response' && asked.answer.statusCode === 409
401
+ if (refusesView() && replaces && restarted === null) {
402
+ // The record is read again first: a runtime that took this one's place meanwhile is asked, not replaced.
403
+ const now = await serviceStatus(options, rules)
404
+ if (now.state === 'healthy' && now.record?.runtimeId !== asked.runtimeId) asked = await ask()
405
+ if (refusesView()) {
406
+ if (runtimeRelease(now, service.entryPath) === 'later') return later(now)
407
+ const failed = await restart()
408
+ if (failed) return failed
409
+ asked = await ask()
410
+ }
411
+ }
412
+ const shown = restarted === null ? {} : { restarted }
413
+ if (!asked.requested) return { ...asked, ...shown }
414
+ const { answer, runtimeId } = asked
415
+ const base = { requested: true, state: 'healthy', runtimeId, ...shown }
416
+ if (refusesView()) return { ...base, pending: false, tick: null, reason: 'service-outdated' }
417
+ if (answer.kind === 'timeout') return { ...base, pending: true, tick: null, reason: 'tick-still-running' }
418
+ if (answer.kind !== 'response' || answer.statusCode !== 200 || answer.body === null) return { ...base, pending: false, tick: null, reason: 'tick-was-not-answered' }
419
+ return { ...base, pending: false, tick: answer.body, reason: 'tick-ran' }
249
420
  }
250
421
 
251
422
  // The status document of the proven runtime: loop, last tick, last error code, freshness summary and, where the