@mnstry/atelier 0.2.0-alpha.12 → 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 (516) hide show
  1. package/CHANGELOG.md +661 -0
  2. package/README.md +142 -6
  3. package/contracts/atelier-adoption-policy.v2.schema.json +55 -0
  4. package/contracts/atelier-architecture.v1.schema.json +258 -0
  5. package/contracts/atelier-boundary-delegations.v1.schema.json +161 -0
  6. package/contracts/atelier-boundary-owner-keys.v1.schema.json +98 -0
  7. package/contracts/atelier-build.v1.schema.json +1076 -0
  8. package/contracts/atelier-capability.v1.schema.json +1252 -0
  9. package/contracts/atelier-coordination.v1.schema.json +1050 -0
  10. package/contracts/atelier-evidence-navigation.v1.schema.json +603 -0
  11. package/contracts/atelier-harness.v1.schema.json +234 -0
  12. package/contracts/atelier-ingestion.v1.schema.json +806 -0
  13. package/contracts/atelier-inquiry.v1.schema.json +1772 -0
  14. package/contracts/atelier-instruction-adoption.v1.schema.json +490 -0
  15. package/contracts/atelier-interaction.v1.schema.json +553 -0
  16. package/contracts/atelier-judgment.v1.schema.json +690 -0
  17. package/contracts/atelier-knowledge.v1.schema.json +1512 -0
  18. package/contracts/atelier-learning.v1.schema.json +1095 -0
  19. package/contracts/atelier-migration.v3.schema.json +64 -0
  20. package/contracts/atelier-personal-workspace-manifest.v1.schema.json +87 -0
  21. package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
  22. package/contracts/atelier-template-adoption.v1.schema.json +223 -0
  23. package/contracts/atelier-template-profile.v1.schema.json +1168 -0
  24. package/contracts/atelier-trackable.v1.schema.json +781 -0
  25. package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
  26. package/docs/architecture.md +89 -0
  27. package/docs/assurance-controls.md +2 -2
  28. package/docs/blocks/capability-stewardship.md +17 -0
  29. package/docs/blocks/claims.md +16 -3
  30. package/docs/blocks/inquiry-harnesses.md +19 -0
  31. package/docs/blocks/learning-harnesses.md +17 -0
  32. package/docs/capability-stewardship.md +269 -0
  33. package/docs/coordination.md +51 -0
  34. package/docs/decision-practices.md +111 -0
  35. package/docs/decisions.md +29 -0
  36. package/docs/evidence-navigation.md +237 -0
  37. package/docs/exact-upgrades.md +3 -2
  38. package/docs/ingestion.md +181 -0
  39. package/docs/inquiry-harnesses.md +211 -0
  40. package/docs/install.md +80 -3
  41. package/docs/intake-and-guides.md +5 -0
  42. package/docs/integration-contract-decisions.md +24 -2
  43. package/docs/interaction-and-reflection.md +72 -0
  44. package/docs/knowledge-graph.md +20 -0
  45. package/docs/knowledge-health/api.md +30 -0
  46. package/docs/knowledge-health/assessment/api.md +96 -0
  47. package/docs/knowledge-health/assessment/capabilities.md +40 -0
  48. package/docs/knowledge-health/assessment/evaluation.md +27 -0
  49. package/docs/knowledge-health/assessment/facilitator.md +30 -0
  50. package/docs/knowledge-health/assessment/quickstart.md +61 -0
  51. package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
  52. package/docs/knowledge-health/devday/facilitator.md +16 -0
  53. package/docs/knowledge-health/devday/participant-response.md +22 -0
  54. package/docs/knowledge-health/devday/public-default.md +107 -0
  55. package/docs/knowledge-health/devday/quickstart.md +18 -0
  56. package/docs/knowledge-health/devday/support.md +22 -0
  57. package/docs/knowledge-health/exercise.md +17 -0
  58. package/docs/knowledge-health/facilitator.md +20 -0
  59. package/docs/knowledge-health/quickstart.md +94 -0
  60. package/docs/knowledge-health/readme.md +15 -0
  61. package/docs/knowledge-health/support.md +34 -0
  62. package/docs/knowledge-setup.md +248 -0
  63. package/docs/knowledge-workspace.md +264 -0
  64. package/docs/learning-harnesses.md +238 -0
  65. package/docs/learning.md +215 -0
  66. package/docs/local-services.md +181 -16
  67. package/docs/obsidian-contract.md +223 -15
  68. package/docs/obsidian-plugin.md +38 -4
  69. package/docs/obsidian.md +613 -51
  70. package/docs/optional-ingestion.md +83 -0
  71. package/docs/personal-workspace.md +257 -0
  72. package/docs/practical-judgment.md +92 -0
  73. package/docs/progressive-ingestion-acceptance.md +89 -0
  74. package/docs/progressive-ingestion.md +235 -0
  75. package/docs/project-command-inventory.md +1 -0
  76. package/docs/release-engineering.md +49 -0
  77. package/docs/repo-boundary-guard.md +94 -0
  78. package/docs/research-starter-quickstart.md +66 -0
  79. package/docs/research-starter.md +114 -0
  80. package/docs/semantic-ingestion.md +198 -0
  81. package/docs/semantic-operation.md +317 -0
  82. package/docs/skill-steward.md +6 -0
  83. package/docs/template-profile.md +164 -0
  84. package/docs/template-upgrades.md +114 -0
  85. package/docs/trackables.md +111 -0
  86. package/docs/upgrade.md +47 -2
  87. package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
  88. package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
  89. package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
  90. package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
  91. package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
  92. package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
  93. package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
  94. package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
  95. package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
  96. package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
  97. package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
  98. package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
  99. package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
  100. package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
  101. package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
  102. package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
  103. package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
  104. package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
  105. package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
  106. package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
  107. package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
  108. package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
  109. package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
  110. package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
  111. package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
  112. package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
  113. package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
  114. package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
  115. package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
  116. package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
  117. package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
  118. package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
  119. package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
  120. package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
  121. package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
  122. package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
  123. package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
  124. package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
  125. package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
  126. package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
  127. package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
  128. package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
  129. package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
  130. package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
  131. package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
  132. package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
  133. package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
  134. package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
  135. package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
  136. package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
  137. package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
  138. package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
  139. package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
  140. package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
  141. package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
  142. package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
  143. package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
  144. package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
  145. package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
  146. package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
  147. package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
  148. package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
  149. package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
  150. package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
  151. package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
  152. package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
  153. package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
  154. package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
  155. package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
  156. package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
  157. package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
  158. package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
  159. package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
  160. package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
  161. package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
  162. package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
  163. package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
  164. package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
  165. package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
  166. package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
  167. package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
  168. package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
  169. package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
  170. package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
  171. package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
  172. package/fixtures/atelier-interaction/act/valid/document.json +24 -0
  173. package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
  174. package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
  175. package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
  176. package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
  177. package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
  178. package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
  179. package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
  180. package/fixtures/atelier-judgment/case/valid/document.json +75 -0
  181. package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
  182. package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
  183. package/fixtures/atelier-judgment/native-decision.md +3 -0
  184. package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
  185. package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
  186. package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
  187. package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
  188. package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
  189. package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
  190. package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
  191. package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
  192. package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
  193. package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
  194. package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
  195. package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
  196. package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
  197. package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
  198. package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
  199. package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
  200. package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
  201. package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
  202. package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
  203. package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
  204. package/fixtures/atelier-learning/invalid/authority.json +14 -0
  205. package/fixtures/atelier-learning/valid/capture.json +13 -0
  206. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
  207. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
  208. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
  209. package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
  210. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
  211. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
  212. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
  213. package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
  214. package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
  215. package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
  216. package/fixtures/atelier-trackable/request/valid/document.json +22 -0
  217. package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
  218. package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
  219. package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
  220. package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
  221. package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
  222. package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
  223. package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
  224. package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
  225. package/fixtures/capability-packages/adoption.example.json +70 -0
  226. package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
  227. package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
  228. package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
  229. package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
  230. package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
  231. package/fixtures/capability-packages/research/capability-package.json +88 -0
  232. package/fixtures/capability-packages/research/capability-release.json +134 -0
  233. package/fixtures/capability-packages/research/evaluation.md +15 -0
  234. package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
  235. package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
  236. package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
  237. package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
  238. package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
  239. package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
  240. package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
  241. package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
  242. package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
  243. package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
  244. package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
  245. package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
  246. package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
  247. package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
  248. package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
  249. package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
  250. package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
  251. package/fixtures/evidence-navigation/valid/claim.json +36 -0
  252. package/fixtures/evidence-navigation/valid/host.json +16 -0
  253. package/fixtures/evidence-navigation/valid/profile.json +16 -0
  254. package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
  255. package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
  256. package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
  257. package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
  258. package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
  259. package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
  260. package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
  261. package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
  262. package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
  263. package/fixtures/harnesses/learning-cycle.json +780 -0
  264. package/fixtures/inquiry/workshop.json +675 -0
  265. package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
  266. package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
  267. package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
  268. package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
  269. package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
  270. package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
  271. package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
  272. package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
  273. package/fixtures/knowledge-health/calibration-suite.json +26 -0
  274. package/fixtures/knowledge-health/calibration-trial.json +37 -0
  275. package/fixtures/knowledge-health/corrected/checklist.md +18 -0
  276. package/fixtures/knowledge-health/devday/participant-record.json +19 -0
  277. package/fixtures/knowledge-health/expected-summary.json +27 -0
  278. package/fixtures/knowledge-health/workshop/README.md +20 -0
  279. package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
  280. package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
  281. package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
  282. package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
  283. package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
  284. package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
  285. package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
  286. package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
  287. package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
  288. package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
  289. package/fixtures/personal-workspace/overlay.json +24 -0
  290. package/fixtures/templates/local-library.v1.json +75 -0
  291. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
  292. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
  293. package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
  294. package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
  295. package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
  296. package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
  297. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
  298. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
  299. package/package.json +59 -5
  300. package/skills/claude/atelier-build-harness/SKILL.md +59 -0
  301. package/skills/claude/atelier-coordination/SKILL.md +32 -0
  302. package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
  303. package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
  304. package/skills/claude/atelier-interaction/SKILL.md +34 -0
  305. package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
  306. package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
  307. package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
  308. package/skills/claude/atelier-research-harness/SKILL.md +43 -0
  309. package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
  310. package/skills/claude/atelier-trackables/SKILL.md +40 -0
  311. package/skills/codex/atelier-build-harness/SKILL.md +59 -0
  312. package/skills/codex/atelier-coordination/SKILL.md +32 -0
  313. package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
  314. package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
  315. package/skills/codex/atelier-interaction/SKILL.md +34 -0
  316. package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
  317. package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
  318. package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
  319. package/skills/codex/atelier-research-harness/SKILL.md +43 -0
  320. package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
  321. package/skills/codex/atelier-trackables/SKILL.md +40 -0
  322. package/src/architecture/catalog.mjs +355 -0
  323. package/src/architecture/index.mjs +93 -0
  324. package/src/boundary/content-rules.mjs +4 -2
  325. package/src/boundary/delegation-command.mjs +158 -0
  326. package/src/boundary/delegation.mjs +396 -0
  327. package/src/boundary/policy.mjs +52 -10
  328. package/src/build/git.mjs +18 -0
  329. package/src/build/index.mjs +3 -0
  330. package/src/build/ledger.mjs +88 -0
  331. package/src/capabilities/adoption.mjs +381 -0
  332. package/src/capabilities/evidence.mjs +140 -0
  333. package/src/capabilities/files.mjs +124 -0
  334. package/src/capabilities/index.mjs +4 -0
  335. package/src/capabilities/instructions.mjs +250 -0
  336. package/src/capabilities/package.mjs +136 -0
  337. package/src/cli/command-failure.mjs +36 -0
  338. package/src/cli/execute-command.mjs +16 -2
  339. package/src/cli/run.mjs +65 -9
  340. package/src/coauthor/store.mjs +43 -2
  341. package/src/collaboration/proposals.mjs +189 -0
  342. package/src/commands/architecture.mjs +27 -0
  343. package/src/commands/boundary.mjs +3 -0
  344. package/src/commands/capability.mjs +73 -0
  345. package/src/commands/enroll.mjs +3 -0
  346. package/src/commands/harness.mjs +79 -0
  347. package/src/commands/ingest.mjs +39 -0
  348. package/src/commands/init.mjs +4 -1
  349. package/src/commands/inquiry.mjs +66 -0
  350. package/src/commands/knowledge-semantic.mjs +128 -0
  351. package/src/commands/knowledge.mjs +119 -0
  352. package/src/commands/learn.mjs +61 -0
  353. package/src/commands/obsidian.mjs +754 -63
  354. package/src/commands/practice.mjs +24 -0
  355. package/src/commands/setup.mjs +22 -2
  356. package/src/commands/trackable.mjs +19 -0
  357. package/src/contracts/corpus.mjs +30 -0
  358. package/src/coordination/index.mjs +110 -0
  359. package/src/evidence-navigation/contracts.mjs +60 -0
  360. package/src/evidence-navigation/evaluate.mjs +69 -0
  361. package/src/evidence-navigation/generation.mjs +22 -0
  362. package/src/evidence-navigation/index.mjs +2 -0
  363. package/src/evidence-navigation/local.mjs +167 -0
  364. package/src/evidence-navigation/schema.generated.mjs +604 -0
  365. package/src/evidence-navigation/types.mjs +12 -0
  366. package/src/export/atelier-export-contract.mjs +35 -8
  367. package/src/graph/enroll.mjs +248 -0
  368. package/src/graph/graph.mjs +17 -1
  369. package/src/graph/knowledge-graph.mjs +58 -8
  370. package/src/harnesses/contracts.mjs +41 -0
  371. package/src/harnesses/exchange.mjs +154 -0
  372. package/src/harnesses/feedback.mjs +16 -0
  373. package/src/harnesses/history.mjs +37 -0
  374. package/src/harnesses/index.mjs +4 -0
  375. package/src/harnesses/store.mjs +29 -0
  376. package/src/ingestion/contracts.mjs +41 -0
  377. package/src/ingestion/evaluation.mjs +119 -0
  378. package/src/ingestion/optional/composition/index.mjs +171 -0
  379. package/src/ingestion/optional/evaluation/index.mjs +219 -0
  380. package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
  381. package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
  382. package/src/ingestion/optional/graphify/graphify_supplier.py +933 -0
  383. package/src/ingestion/optional/graphify/json_bridge.py +163 -0
  384. package/src/ingestion/optional/graphify/query_receiving.py +209 -0
  385. package/src/ingestion/optional/index.mjs +9 -0
  386. package/src/ingestion/optional/jev/bridge.mjs +166 -0
  387. package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
  388. package/src/ingestion/optional/jev/judgments.mjs +174 -0
  389. package/src/ingestion/optional/jev/native-translation.mjs +32 -0
  390. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
  391. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
  392. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
  393. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
  394. package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
  395. package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
  396. package/src/ingestion/optional/vanilla/index.mjs +218 -0
  397. package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
  398. package/src/ingestion/processors.mjs +218 -0
  399. package/src/ingestion/semantic.mjs +203 -0
  400. package/src/ingestion/store.mjs +372 -0
  401. package/src/inquiry/beliefs.mjs +32 -0
  402. package/src/inquiry/contracts.mjs +23 -0
  403. package/src/inquiry/index.mjs +5 -0
  404. package/src/inquiry/ledger.mjs +135 -0
  405. package/src/inquiry/lenses.mjs +15 -0
  406. package/src/inquiry/projection.mjs +77 -0
  407. package/src/inquiry/store.mjs +35 -0
  408. package/src/intake/read-scope.mjs +12 -0
  409. package/src/intake/store.mjs +69 -6
  410. package/src/interaction/index.mjs +91 -0
  411. package/src/judgment/index.mjs +82 -0
  412. package/src/judgment/practice-evaluation.mjs +70 -0
  413. package/src/judgment/practice.mjs +144 -0
  414. package/src/knowledge/context.mjs +217 -0
  415. package/src/knowledge/index.mjs +5 -0
  416. package/src/knowledge/ingestion.mjs +79 -0
  417. package/src/knowledge/intake.mjs +33 -0
  418. package/src/knowledge/ledger.mjs +110 -0
  419. package/src/knowledge/participatory/guidance.mjs +36 -0
  420. package/src/knowledge/participatory/guided-contribution.mjs +44 -0
  421. package/src/knowledge/participatory/index.mjs +2 -0
  422. package/src/knowledge/participatory/installed-workshop.mjs +108 -0
  423. package/src/knowledge/plan.mjs +67 -0
  424. package/src/knowledge/projection.mjs +43 -0
  425. package/src/knowledge/semantic-operation.mjs +567 -0
  426. package/src/knowledge/sessions.mjs +467 -0
  427. package/src/knowledge/workspace.mjs +295 -0
  428. package/src/knowledge-health/assessment.mjs +165 -0
  429. package/src/knowledge-health/capabilities.mjs +350 -0
  430. package/src/knowledge-health/context.mjs +62 -0
  431. package/src/knowledge-health/example.mjs +47 -0
  432. package/src/knowledge-health/index.mjs +10 -0
  433. package/src/knowledge-health/owner-join.mjs +88 -0
  434. package/src/knowledge-health/proof/common.mjs +24 -0
  435. package/src/knowledge-health/proof/installed-check.mjs +100 -0
  436. package/src/knowledge-health/proof/native-readback.mjs +37 -0
  437. package/src/knowledge-health/proof/rehearse.mjs +68 -0
  438. package/src/knowledge-health/proof/validate-participant.mjs +17 -0
  439. package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
  440. package/src/knowledge-health/proof/workshop-check.mjs +36 -0
  441. package/src/knowledge-health/rule-identity.mjs +44 -0
  442. package/src/knowledge-health/workshop-composition.mjs +19 -0
  443. package/src/knowledge-health/workshop-profile.mjs +176 -0
  444. package/src/knowledge-health/workshop.mjs +99 -0
  445. package/src/learning/adapters.mjs +68 -0
  446. package/src/learning/assistance.mjs +127 -0
  447. package/src/learning/contracts.mjs +65 -0
  448. package/src/learning/store.mjs +272 -0
  449. package/src/personal-workspace/index.mjs +391 -0
  450. package/src/personal-workspace/selection.mjs +385 -0
  451. package/src/project/config.mjs +8 -0
  452. package/src/project/durable-state.mjs +3 -1
  453. package/src/project/file-class.mjs +1 -1
  454. package/src/projection/obsidian/edits/apply.mjs +55 -16
  455. package/src/projection/obsidian/personal-workspace.mjs +436 -0
  456. package/src/projection/obsidian/proposals/adapter.mjs +47 -8
  457. package/src/projection/obsidian/proposals/observation.mjs +10 -2
  458. package/src/projection/obsidian/publication/publisher.mjs +142 -13
  459. package/src/projection/obsidian/publication/vault-list.mjs +4 -3
  460. package/src/projection/obsidian/recovery/index.mjs +4 -1
  461. package/src/projection/obsidian/recovery/store.mjs +160 -7
  462. package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
  463. package/src/readiness/readiness.mjs +10 -3
  464. package/src/reflection/index.mjs +56 -0
  465. package/src/runtime/obsidian/app-production-seams.mjs +48 -16
  466. package/src/runtime/obsidian/app-registration.mjs +175 -28
  467. package/src/runtime/obsidian/app-restart.mjs +154 -0
  468. package/src/runtime/obsidian/engine.mjs +163 -20
  469. package/src/runtime/obsidian/extension-points.mjs +1 -1
  470. package/src/runtime/obsidian/index.mjs +10 -7
  471. package/src/runtime/obsidian/launch-plan.mjs +7 -4
  472. package/src/runtime/obsidian/lifecycle.mjs +92 -12
  473. package/src/runtime/obsidian/login-item.mjs +332 -0
  474. package/src/runtime/obsidian/machine-settings.mjs +119 -12
  475. package/src/runtime/obsidian/opening.mjs +171 -37
  476. package/src/runtime/obsidian/pipeline.mjs +71 -3
  477. package/src/runtime/obsidian/plugin-channel.mjs +11 -3
  478. package/src/runtime/obsidian/plugin-choice.mjs +16 -4
  479. package/src/runtime/obsidian/plugin-drift.mjs +59 -11
  480. package/src/runtime/obsidian/plugin-presence.mjs +14 -1
  481. package/src/runtime/obsidian/process-identity.mjs +20 -10
  482. package/src/runtime/obsidian/project-views.mjs +282 -0
  483. package/src/runtime/obsidian/questions.mjs +48 -0
  484. package/src/runtime/obsidian/release-watch.mjs +56 -0
  485. package/src/runtime/obsidian/service-main.mjs +99 -16
  486. package/src/runtime/obsidian/service-manager-production.mjs +61 -0
  487. package/src/runtime/obsidian/service-managers.mjs +275 -0
  488. package/src/runtime/obsidian/service-record.mjs +87 -23
  489. package/src/runtime/obsidian/service.mjs +36 -3
  490. package/src/runtime/obsidian/startup-adapters.mjs +59 -21
  491. package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
  492. package/src/runtime/obsidian/vault-location.mjs +300 -0
  493. package/src/runtime/obsidian/view-counts.mjs +34 -0
  494. package/src/server/local-sidecar.mjs +169 -2
  495. package/src/server/server.mjs +28 -2
  496. package/src/template-bindings/project.mjs +92 -0
  497. package/src/templates/profile.mjs +225 -0
  498. package/src/trackables/domain.mjs +175 -0
  499. package/src/trackables/store.mjs +114 -0
  500. package/src/ui/knowledge-page.mjs +1388 -0
  501. package/src/upgrade/explanation.mjs +1 -1
  502. package/src/upgrade/template-participant.mjs +171 -0
  503. package/src/upgrade/transaction.mjs +72 -25
  504. package/src/upgrade/upgrade.mjs +1 -0
  505. package/templates/external-project-workspace/pack-lifecycle.json +6 -1
  506. package/templates/knowledge-workspace/.gitattributes +2 -0
  507. package/templates/knowledge-workspace/AGENTS.md +27 -0
  508. package/templates/knowledge-workspace/README.md +44 -0
  509. package/templates/knowledge-workspace/atelier.project.json +22 -0
  510. package/templates/knowledge-workspace/gitignore +6 -0
  511. package/templates/knowledge-workspace/knowledge-plan.json +111 -0
  512. package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
  513. package/templates/knowledge-workspace/records/forecast.md +22 -0
  514. package/templates/knowledge-workspace/records/inspection.md +25 -0
  515. package/templates/knowledge-workspace/records/telescope.md +24 -0
  516. package/templates/knowledge-workspace/repo-access.v1.json +9 -0
@@ -1,7 +1,9 @@
1
1
  import { createHash } from 'node:crypto'
2
2
  import path from 'node:path'
3
3
  import { buildCanonicalGraph, createGraphFileCache } from '../../graph/graph.mjs'
4
- import { EMITTER_VERSION, createPreparationCache, prepareView, withEligibility } from '../../projection/obsidian/materialize/index.mjs'
4
+ import { markdownMetadata } from '../../graph/knowledge-graph.mjs'
5
+ import { EMITTER_VERSION, createPreparationCache, prepareView, readMarkdownLens, withEligibility } from '../../projection/obsidian/materialize/index.mjs'
6
+ import { personalWorkspaceGraph } from '../../projection/obsidian/personal-workspace.mjs'
5
7
  import { publishView } from '../../projection/obsidian/publication/publisher.mjs'
6
8
  import { recheckDisplacedFiles } from '../../projection/obsidian/recovery/late-writer.mjs'
7
9
  import { createRecoveryStore, readFileBytes } from '../../projection/obsidian/recovery/store.mjs'
@@ -31,6 +33,68 @@ export const DEFAULT_ELIGIBILITY = Object.freeze({
31
33
  isEligible: (node) => node.classification === 'classified',
32
34
  })
33
35
 
36
+ // A vault that is the person's own also shows the notes that carry no classification, when they decided so: the
37
+ // audience decision `only-you` with `unclassified: 'shown'`, which no other audience decision may carry. Those notes
38
+ // are the person's own files, in a vault only they see. One is admitted only when its bytes read as a note (the byte
39
+ // lens the emitter reads it with), so a file the emitter would refuse never stops the vault: it stays withheld, as does
40
+ // one that cannot be read. A note whose labels are unknown is never admitted: front matter Atelier could not read
41
+ // (`malformed-frontmatter`: a block scalar, a wrapped value), or any top-level `kg` key that is not a
42
+ // block, may carry an audience such as `sensitive` that "only you" leaves out. Only a note with no front matter, or
43
+ // front matter of plain top-level keys at column 0 none of which is `kg` (plainTopLevelKeys) and in which the graph
44
+ // reads no `kg` either, is the person's plain note. Assets follow the documents that
45
+ // embed them, as always.
46
+ export function onlyYouEligibility({ project }) {
47
+ const roots = new Map((project.repos ?? []).filter((repo) => !repo.external && typeof repo.path === 'string').map((repo) => [repo.name, repo.path]))
48
+ const readsAsNote = (node) => {
49
+ const root = roots.get(node.repo)
50
+ if (root === undefined || node.extension !== 'md' || typeof node.path !== 'string') return false
51
+ let bytes
52
+ let lens
53
+ try { bytes = readFileBytes(path.join(root, ...node.path.split('/'))); lens = readMarkdownLens(bytes) } catch { return false }
54
+ // Read now, not as the graph saw it: a file that gained front matter since is judged by what it holds now.
55
+ if (lens.frontmatter === null) return node.classificationReason === 'absent-frontmatter'
56
+ if (node.classificationReason !== 'missing-kg-block') return false
57
+ const text = bytes.toString('utf8')
58
+ const block = text.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)
59
+ if (block === null || !plainTopLevelKeys(block[1])) return false
60
+ // A second layer: the graph's own reading of the front matter has no kg key either.
61
+ try { return !('kg' in markdownMetadata(text)) } catch { return false }
62
+ }
63
+ return Object.freeze({
64
+ revision: () => 'classified-documents/v2+unclassified-notes-read-as-notes-for-only-you/v4+assets-embedded-by-eligible-documents/v1',
65
+ isEligible: (node) => node.classification === 'classified' || (node.classification === 'unclassified' && readsAsNote(node)),
66
+ })
67
+ }
68
+
69
+ // Whether front matter is plain `key: value` lines at its top level, none of them `kg`: every line that is not blank,
70
+ // a comment, or indented (a nested value or a continuation) starts with a bare key. A quoted key, a flow or JSON
71
+ // mapping, a complex key (`? `), a top-level list or anything else unusual is not plain, so a label it may hold (a
72
+ // `kg.audience: sensitive` Atelier did not read) keeps the note withheld. An allow-list, never a pattern to find.
73
+ const PLAIN_KEY = /^([A-Za-z_][A-Za-z0-9_-]*)[ \t]*:(?:[ \t]|$)/
74
+ function plainTopLevelKeys(frontmatter) {
75
+ // Line breaks YAML 1.1 readers honour and this reading does not (NEL, LS, PS) could hide a kg from it.
76
+ if (/[\u0085\u2028\u2029]/.test(frontmatter)) return false
77
+ let rooted = false
78
+ for (const line of frontmatter.split(/\r?\n/)) {
79
+ const trimmed = line.trim()
80
+ if (trimmed === '' || trimmed.startsWith('#')) continue
81
+ // An indented line is a nested value or a continuation only below a key at column 0; YAML also lets a whole root
82
+ // mapping be indented, and then its keys (kg among them) are indented too.
83
+ if (/^[ \t]/.test(line)) { if (!rooted) return false; continue }
84
+ const key = PLAIN_KEY.exec(line)
85
+ if (key === null || key[1] === 'kg') return false
86
+ rooted = true
87
+ }
88
+ return true
89
+ }
90
+
91
+ // The eligibility a workspace's machine settings decide: the one above only for "only you" with unclassified notes
92
+ // shown, and the default, which withholds them, for every other decision and for none.
93
+ export function eligibilityFor({ machine, project }) {
94
+ const audience = machine?.decisions?.audience
95
+ return audience?.choice === 'only-you' && audience.unclassified === 'shown' ? onlyYouEligibility({ project }) : DEFAULT_ELIGIBILITY
96
+ }
97
+
34
98
  export function assetEligibilityFor({ graph, eligibility }) {
35
99
  const eligibleSources = new Set(graph.nodes.filter((node) => eligibility.isEligible(node) === true).map((node) => node.id))
36
100
  const embeds = Array.isArray(graph.embeds) ? graph.embeds : []
@@ -59,9 +123,13 @@ export function profileFor({ project, workspaceId, audienceAllow }) {
59
123
  // `index` is the engine's observation index. With both, a source whose observed digest is the cached one is not
60
124
  // opened: observation already decided by digest that it did not change, under the stat-hint bound observation
61
125
  // documents, and a full reconciliation hashes it again. Without an index every source is read and hashed here.
62
- export function buildGraph({ project, eligibility, cache = null, index = null }) {
126
+ // A project bound to a personal workspace (personal-workspace.mjs) is built by that route, for every caller, with the
127
+ // same caches: the composition's graph, served only under a validity key the composition confirmed. With
128
+ // `deferPersonalValidation` (the engine), an unconfirmed key refuses `personal-validation-pending` instead of composing
129
+ // on this thread.
130
+ export function buildGraph({ project, eligibility, cache = null, index = null, deferPersonalValidation = false }) {
63
131
  const observedDigest = cache && index ? (repoId, relative) => index.get(sourceKey(repoId, relative))?.digest ?? null : null
64
- const canonical = buildCanonicalGraph(project, { fileCache: cache, observedDigest })
132
+ const canonical = personalWorkspaceGraph(project, { fileCache: cache, observedDigest, defer: deferPersonalValidation }) ?? buildCanonicalGraph(project, { fileCache: cache, observedDigest })
65
133
  if (!canonical.ok) refuse('canonical-graph-invalid', 'the canonical graph has errors; no view is prepared from it', { errorCount: canonical.errors.length })
66
134
  return withEligibility(canonical, eligibility.isEligible, assetEligibilityFor({ graph: canonical, eligibility }))
67
135
  }
@@ -2,6 +2,7 @@ import { createHash, randomBytes as cryptoRandomBytes, timingSafeEqual } from 'n
2
2
  import fs from 'node:fs'
3
3
  import path from 'node:path'
4
4
  import { atomicReplacePrivateText, ensureContainedPrivateDirectory, readRegularTextNoFollow, realPathAsStored } from '../../project/private-state.mjs'
5
+ import { assertAllocatedFolder, vaultRootFor } from '../../projection/obsidian/recovery/store.mjs'
5
6
  import {
6
7
  PLUGIN_BEARER, PLUGIN_CHALLENGE_WINDOW_MS, PLUGIN_CHANNEL_PROTOCOL, PLUGIN_HANDSHAKE_TTL_MS, PLUGIN_LEASE_TTL_MS, PLUGIN_MAX_PENDING_HANDSHAKES_PER_SCOPE, PLUGIN_MAX_SESSION_AGE_MS, PLUGIN_MAX_SESSIONS_PER_SCOPE,
7
8
  PLUGIN_RENEW_INTERVAL_MS, PLUGIN_STATUS_SCHEMA, pluginClientProof, pluginKeyHint, pluginRequestMac, pluginResponseMac, pluginServerProof, pluginSessionKey, pluginVaultProof,
@@ -158,9 +159,16 @@ export function createPluginChannelForOracleTests({
158
159
  onSessionOpened = () => {},
159
160
  }, primitives = PLUGIN_CHANNEL_PRIMITIVES) {
160
161
  const rules = { ...PLUGIN_CHANNEL_PRIMITIVES, ...primitives }
161
- // As the file system stores the path, as the store registers the vault in the app (realPathAsStored): a data root given
162
- // in another letter case names the same vault.
163
- const vaultRootOf = (scopeId) => { try { return realPathAsStored(path.join(workspaceRoot, 'vaults', segment(scopeId))) } catch { return null } }
162
+ // The view's vault wherever it is (vaultRootFor: allocated for the view, else under the data root), and an allocated one
163
+ // only while it is the folder recorded; as the file system stores the path, as the store registers the vault in the app
164
+ // (realPathAsStored): a data root given in another letter case names the same vault.
165
+ const vaultRootOf = (scopeId) => {
166
+ try {
167
+ const found = vaultRootFor({ workspaceRoot, workspaceId, scopeId })
168
+ if (found.allocation !== null) assertAllocatedFolder(found.allocation)
169
+ return realPathAsStored(found.path)
170
+ } catch { return null }
171
+ }
164
172
  const keyDigestOf = (bearer) => createHash('sha256').update(bearer, 'utf8').digest()
165
173
  const handshakes = new Map()
166
174
  const pruneHandshakes = () => { const at = now(); for (const [handshakeId, handshake] of handshakes) if (handshake.expiresAt <= at) handshakes.delete(handshakeId) }
@@ -4,6 +4,7 @@ import { atomicReplacePrivateText, ensureContainedPrivateDirectory, openRegularF
4
4
  import { PLUGIN_ID } from '../../projection/obsidian/plugin-bridge/channel.mjs'
5
5
  import { COMMUNITY_PLUGINS_PATH } from '../../projection/obsidian/materialize/settings.mjs'
6
6
  import { sha256Digest } from '../../projection/obsidian/materialize/byte-lens.mjs'
7
+ import { vaultRootFor } from '../../projection/obsidian/recovery/store.mjs'
7
8
  import { canonicalJson, isPlainObject, isoTime } from './documents.mjs'
8
9
 
9
10
  // Whether the person wants Atelier's plugin in one vault, as the vault itself
@@ -42,8 +43,14 @@ const MAX_SETTINGS_BYTES = 256 * 1024
42
43
  const segment = (identifier) => identifier.replaceAll(':', '_')
43
44
 
44
45
  export const pluginChoiceDirectory = (workspaceRoot) => path.join(workspaceRoot, 'state', 'plugin', 'choices')
45
- // Where the recovery store places a view's vault.
46
- export const viewVaultRoot = (workspaceRoot, scopeId) => path.join(workspaceRoot, 'vaults', segment(scopeId))
46
+ // Where a view's vault is, as the recovery store finds it (vaultRootFor): the folder allocated to the view where the
47
+ // workspace decided its vaults live, else its folder under the data root. A record that cannot be read, or was lost,
48
+ // refuses, typed. Pass `workspaceId`; every caller here does. The two-argument form of earlier releases still works:
49
+ // it takes the name of the workspace's private-state folder as the id, which a record of another workspace (or a
50
+ // folder reached through a link) does not match, and such a record refuses, typed, rather than answering another vault.
51
+ export function viewVaultRoot(workspaceRoot, scopeId, workspaceId = path.basename(String(workspaceRoot))) {
52
+ return vaultRootFor({ workspaceRoot, workspaceId, scopeId }).path
53
+ }
47
54
  const choiceFile = (workspaceRoot, scopeId) => path.join(pluginChoiceDirectory(workspaceRoot), `${segment(scopeId)}.json`)
48
55
 
49
56
  function validChoice(document, { workspaceId, scopeId }) {
@@ -142,7 +149,10 @@ export function decidePluginChoice({ workspaceRoot, workspaceId, scopeId, vaultR
142
149
  // `pending` (the view's next preparation records it).
143
150
  export function currentPluginChoice({ workspaceRoot, workspaceId, scopeId }) {
144
151
  const recorded = readPluginChoice({ workspaceRoot, workspaceId, scopeId })
145
- const change = changeShown(recorded, readCommunityEntry(viewVaultRoot(workspaceRoot, scopeId)).entry)
152
+ // A view whose vault cannot be found (its record cannot be read) shows what was recorded; its view says why.
153
+ let vaultRoot
154
+ try { vaultRoot = viewVaultRoot(workspaceRoot, scopeId, workspaceId) } catch (error) { if (typeof error?.code !== 'string') throw error; return recorded }
155
+ const change = changeShown(recorded, readCommunityEntry(vaultRoot).entry)
146
156
  return change === null ? recorded : { ...change, since: null, pending: true }
147
157
  }
148
158
 
@@ -150,7 +160,9 @@ export function currentPluginChoice({ workspaceRoot, workspaceId, scopeId }) {
150
160
  // no app ran, it is confirmed: an app reads the list, entry and all, when it
151
161
  // next opens the vault. Published through a running app, it is only offered:
152
162
  // that app may not have read it, and the plugin running there confirms it
153
- // (confirmPluginSeen).
163
+ // (confirmPluginSeen). So is one published into a vault no Obsidian listed
164
+ // while one ran (`direct-unheld`): the evidence covers only the lists Atelier
165
+ // can read, not another profile's or another machine's.
154
166
  const IN_PLACE = new Set(['policy-satisfied', 'created', 'published', 'published-external-captured', 'already-current'])
155
167
  export function confirmPluginEntry({ workspaceRoot, workspaceId, scopeId, result, clock }) {
156
168
  const unit = (result?.notes ?? []).find((entry) => entry.path === COMMUNITY_PLUGINS_PATH)
@@ -18,6 +18,10 @@ import { viewVaultRoot } from './plugin-choice.mjs'
18
18
  // where a file goes) is asked about once, not at every tick; a change to it is
19
19
  // asked about again. Where the plugin is turned off in the vault, a pinned
20
20
  // file that is gone is no drift: the publisher never makes one again.
21
+ // `settle(scopeIds)` looks again after each tick, and takes as seen what a
22
+ // publication of that tick could not change, so a drift publishing cannot
23
+ // repair is not published a second time at the next look (at a service's
24
+ // start, say).
21
25
 
22
26
  const segment = (identifier) => identifier.replaceAll(':', '_')
23
27
  const TURNED_OFF = 'turned-off-in-this-vault'
@@ -33,7 +37,7 @@ function pinnedPlugin(workspaceRoot, workspaceId, scopeId) {
33
37
  if (sha256Digest(bytes) !== pointer.manifestDigest) return null
34
38
  const owned = JSON.parse(bytes.toString('utf8'))?.ext?.[OBSIDIAN_EXT_KEY]?.settings?.pluginOwned
35
39
  if (!Array.isArray(owned?.files)) return null
36
- return { files: owned.files.filter((file) => typeof file?.path === 'string' && typeof file?.digest === 'string'), off: owned.withheldBecause === TURNED_OFF }
40
+ return { manifest: pointer.manifestDigest, files: owned.files.filter((file) => typeof file?.path === 'string' && typeof file?.digest === 'string'), off: owned.withheldBecause === TURNED_OFF }
37
41
  } catch {
38
42
  return null
39
43
  }
@@ -53,24 +57,68 @@ function onDisk(vaultRoot, relativePath) {
53
57
  try { return readFileDigest(path.join(vaultRoot, relativePath)) ?? 'absent' } catch { return 'unreadable' }
54
58
  }
55
59
 
60
+ // A look, as it is compared: the vault root, and each pinned file with its pin and what the disk holds.
61
+ const signatureOf = (record) => JSON.stringify([record.root, record.files.map((file) => file.digest), record.files.map((file) => file.state)])
62
+ // Never what a look finds on disk: a path recorded so is asked about at the next look whatever it holds then.
63
+ const NOT_SEEN = 'not-seen'
64
+
56
65
  export function createPluginDriftObserver({ workspaceRoot, workspaceId }) {
66
+ // Per view: the last look ({ root, files: [{ path, digest, state }], manifest }).
57
67
  const looked = new Map()
68
+ // The views the last observe asked to be prepared again: the tick that follows publishes them.
69
+ let asked = new Set()
70
+ // What the pinned plugin files of a view look like on disk now; null when its committed generation pins none.
71
+ const look = (scopeId) => {
72
+ const pinned = pinnedPlugin(workspaceRoot, workspaceId, scopeId)
73
+ if (pinned === null || pinned.files.length === 0) return null
74
+ // The view's vault wherever it is; one that cannot be found is refused by the engine, not observed here.
75
+ let vaultRoot
76
+ try { vaultRoot = viewVaultRoot(workspaceRoot, scopeId, workspaceId) } catch (error) { if (typeof error?.code !== 'string') throw error; return null }
77
+ let root = 'absent'
78
+ try { const stat = fs.lstatSync(vaultRoot); root = stat.isSymbolicLink() ? 'link' : (stat.mode & 0o777).toString(8) } catch { root = 'absent' }
79
+ const files = pinned.files.map((file) => ({ path: file.path, digest: file.digest, state: onDisk(vaultRoot, file.path) }))
80
+ const drifted = (file) => file.state !== file.digest && !(pinned.off && file.state === 'absent')
81
+ return { root, files, manifest: pinned.manifest, drifted, differs: files.some(drifted) }
82
+ }
58
83
  return {
59
84
  observe(scopeIds) {
60
85
  const drifted = []
61
86
  for (const scopeId of scopeIds) {
62
- const pinned = pinnedPlugin(workspaceRoot, workspaceId, scopeId)
63
- if (pinned === null || pinned.files.length === 0) { looked.delete(scopeId); continue }
64
- const vaultRoot = viewVaultRoot(workspaceRoot, scopeId)
65
- let root = 'absent'
66
- try { const stat = fs.lstatSync(vaultRoot); root = stat.isSymbolicLink() ? 'link' : (stat.mode & 0o777).toString(8) } catch { root = 'absent' }
67
- const disk = pinned.files.map((file) => onDisk(vaultRoot, file.path))
68
- const differs = disk.some((found, index) => found !== pinned.files[index].digest && !(pinned.off && found === 'absent'))
69
- const signature = JSON.stringify([root, pinned.files.map((file) => file.digest), disk])
70
- if (differs && looked.get(scopeId) !== signature) drifted.push(scopeId)
71
- looked.set(scopeId, signature)
87
+ const seen = look(scopeId)
88
+ if (seen === null) { looked.delete(scopeId); continue }
89
+ const last = looked.get(scopeId)
90
+ if (seen.differs && (last === undefined || signatureOf(last) !== signatureOf(seen))) drifted.push(scopeId)
91
+ looked.set(scopeId, { root: seen.root, files: seen.files, manifest: seen.manifest })
72
92
  }
93
+ asked = new Set(drifted)
73
94
  return drifted
74
95
  },
96
+ // After a tick. A view this tick published (one the last observe asked for, one whose committed generation
97
+ // changed since the last look, or one never looked at before) had its plugin files written or left for the person
98
+ // by its publisher. Of what the disk shows now, only what that publication could not change is taken as seen,
99
+ // without asking: a file as it pins it, or one that holds what it held before the tick under the same pin and the
100
+ // same vault root. So a drift publishing cannot repair (the data file of a vault root that is a link, say) is not
101
+ // asked about again once it was asked about under its pin. Anything else is not taken as seen, and the next look
102
+ // asks about it: a file another writer removed or changed after the publication, while the tick went on, or
103
+ // brought back to what it held under a pin the tick replaced; a file the publication left for the person, which
104
+ // the person may have repaired before the tick ended; and any file, when the vault root changed during the tick.
105
+ // Asking about a file that is still left writes no vault file: the publisher does not publish a committed
106
+ // generation again for it (pluginFilesDrifted). Any other view keeps its last look.
107
+ settle(scopeIds) {
108
+ for (const scopeId of scopeIds) {
109
+ const seen = look(scopeId)
110
+ if (seen === null) { looked.delete(scopeId); continue }
111
+ const last = looked.get(scopeId)
112
+ if (!asked.has(scopeId) && last !== undefined && last.manifest === seen.manifest) continue
113
+ // Keyed by path and pin: what a file held under a pin this tick replaced says nothing about the new one.
114
+ const key = (file) => `${file.path}\u0000${file.digest}`
115
+ const before = new Map((last?.files ?? []).map((file) => [key(file), file.state]))
116
+ // And only under the same vault root: a root made private during the tick changes what the publisher leaves.
117
+ const sameRoot = last !== undefined && last.root === seen.root
118
+ const files = seen.files.map((file) => (!seen.drifted(file) || (sameRoot && before.get(key(file)) === file.state) ? file : { ...file, state: NOT_SEEN }))
119
+ looked.set(scopeId, { root: seen.root, files, manifest: seen.manifest })
120
+ }
121
+ asked = new Set()
122
+ },
75
123
  }
76
124
  }
@@ -9,6 +9,18 @@ export const PLUGIN_PRESENCE_REASONS = Object.freeze(['live-lease', 'no-live-lea
9
9
  // The two ways back, for a view whose vault turned the plugin off.
10
10
  export const turnPluginOnNext = (scopeId) => `turn Atelier on in Obsidian (Settings, Community plugins), or run \`atelier obsidian plugin on --scope ${scopeId}\`, which restores it at the view's next publication`
11
11
 
12
+ // A view kept `current` while a plugin file its committed generation pins waits for an app that qualifies: the notes
13
+ // are verified, the plugin file (often `data.json`) is not written yet (engine.mjs). Its freshness reason says so.
14
+ export const PLUGIN_FILES_WAIT_FOR_APP = 'verified-by-read-back-plugin-waits-for-app'
15
+ export const PLUGIN_FILES_WAIT_NEXT = 'the view\'s notes are current, but a file of Atelier\'s plugin in this vault is not written yet, since Obsidian could not be coordinated with (it runs without its command line answering, say): it is written once Obsidian answers its command line or is quit, and it is retried automatically'
16
+
17
+ // The presence, with the plugin files that wait for the app when the view's freshness says so (`files`, and the next
18
+ // step unless the presence has its own).
19
+ export function withPluginFilesPending(presence, freshness) {
20
+ if (freshness?.state !== 'current' || freshness.reason !== PLUGIN_FILES_WAIT_FOR_APP) return presence
21
+ return { ...presence, files: 'waits-for-app', next: presence.next ?? PLUGIN_FILES_WAIT_NEXT }
22
+ }
23
+
12
24
  export function pluginPresenceOf(statusDocument, scopeId) {
13
25
  if (!isPlainObject(statusDocument)) return { present: false, reason: 'service-not-running' }
14
26
  const scopes = isPlainObject(statusDocument.plugins) && Array.isArray(statusDocument.plugins.scopes) ? statusDocument.plugins.scopes : null
@@ -40,7 +52,8 @@ export function withPluginReportedVersion(appProbe, readPresence, { maxAgeMs = 1
40
52
  return {
41
53
  async inspect() {
42
54
  const [observation, presence] = await Promise.all([appProbe.inspect(), presenceNow()])
43
- if (presence?.present !== true || observation?.installed !== true || observation?.cli !== true || observation.running === false || typeof observation.version === 'string') return observation
55
+ // A command line that answered it is turned off stays that answer: a plugin's version never hides it.
56
+ if (presence?.present !== true || observation?.installed !== true || observation?.cli !== true || observation.running === false || typeof observation.version === 'string' || observation.cliOff === true) return observation
44
57
  return { ...observation, running: true, version: presence.appVersion, noVaultOpen: false, versionSource: 'plugin' }
45
58
  },
46
59
  vaultState: (input) => appProbe.vaultState(input),
@@ -1,5 +1,6 @@
1
1
  import { execFileSync } from 'node:child_process'
2
2
  import fs from 'node:fs'
3
+ import path from 'node:path'
3
4
  import { executableIdentity } from './service-record.mjs'
4
5
 
5
6
  // Whether a live PID provably runs the executable a service record names,
@@ -32,27 +33,36 @@ export function readProcessCommandLine(pid, { platform = process.platform, readF
32
33
  return null
33
34
  }
34
35
 
35
- // Pure. The command line must name the recorded entry module, and a runtime
36
- // identifier it carries must be the recorded one.
37
- export function commandLineNamesRecord(commandLine, record) {
38
- const entry = record?.executable?.path
39
- if (typeof entry !== 'string' || entry === '' || typeof record.runtimeId !== 'string') return false
36
+ // Pure. The command line must name the recorded entry module, by one of
37
+ // `entries` (its recorded path, and the path it was started by when that is
38
+ // another name for it), and a runtime identifier it carries must be the
39
+ // recorded one.
40
+ export function commandLineNamesRecord(commandLine, record, entries = [record?.executable?.path]) {
41
+ const names = entries.filter((entry) => typeof entry === 'string' && entry !== '')
42
+ if (names.length === 0 || typeof record?.runtimeId !== 'string') return false
40
43
  if (Array.isArray(commandLine)) {
41
- if (!commandLine.slice(1).includes(entry)) return false
44
+ if (!commandLine.slice(1).some((word) => names.includes(word))) return false
42
45
  const named = commandLine.filter((word) => word.startsWith(RUNTIME_ARGUMENT)).map((word) => word.slice(RUNTIME_ARGUMENT.length))
43
46
  return named.every((runtimeId) => runtimeId === record.runtimeId)
44
47
  }
45
48
  if (typeof commandLine !== 'string') return false
46
- if (!` ${commandLine} `.includes(` ${entry} `)) return false
49
+ if (!names.some((entry) => ` ${commandLine} `.includes(` ${entry} `))) return false
47
50
  const named = [...commandLine.matchAll(/(?:^|\s)--runtime-id=(\S+)/g)].map((match) => match[1])
48
51
  return named.every((runtimeId) => runtimeId === record.runtimeId)
49
52
  }
50
53
 
51
54
  // The recorded executable is still the bytes that were recorded, and the live
52
- // process was started on it.
53
- export function processRunsRecordedExecutable(record, { commandLineOf = readProcessCommandLine, identityOf = executableIdentity } = {}) {
55
+ // process was started on it: by its recorded path, or by the path the record
56
+ // says it was started by (a login item names the installed entry by its path),
57
+ // when that still leads to the recorded one.
58
+ export function processRunsRecordedExecutable(record, { commandLineOf = readProcessCommandLine, identityOf = executableIdentity, realpathOf = fs.realpathSync } = {}) {
54
59
  try {
55
60
  if (identityOf(record.executable.path).digest !== record.executable.digest) return false
56
61
  } catch { return false }
57
- return commandLineNamesRecord(commandLineOf(record.pid), record)
62
+ const entries = [record.executable.path]
63
+ const invokedAs = record.executable.ext?.invokedAs
64
+ if (typeof invokedAs === 'string' && path.isAbsolute(invokedAs)) {
65
+ try { if (realpathOf(invokedAs) === record.executable.path) entries.push(invokedAs) } catch { /* not another name for it */ }
66
+ }
67
+ return commandLineNamesRecord(commandLineOf(record.pid), record, entries)
58
68
  }