@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,327 @@
1
+ import { identityLineTexts } from '../contracts.mjs'
2
+ import { refuse } from './byte-lens.mjs'
3
+
4
+ // The redaction guard: the last check before a prepared view is returned. It
5
+ // judges the assembled result, every note included, whether the note was
6
+ // emitted on this call or reused from the preparation cache, and refuses the
7
+ // view with `redaction-failure`, naming the in-view note (or file) and the
8
+ // rule, never the value. See "The redaction guard" in docs/obsidian-contract.md.
9
+ //
10
+ // allow-list every path the emitter wrote (note and attachment paths,
11
+ // rewritten link and embed targets, link targets in generated
12
+ // regions) is one allocated to this view, and each identity
13
+ // block names exactly its own note
14
+ // deny-list the free text of the generated regions, read as a reader
15
+ // sees it, names no unambiguous identifier of anything the
16
+ // audience may not see (refused); an ambiguous one, and any
17
+ // identifier of what the audience may see but the view does
18
+ // not select, is reported
19
+ // coverage both run over every note of the view
20
+ //
21
+ // REDACTION_RULES are the rules production uses. The mutation controls in
22
+ // test/obsidian-materialization.test.mjs replace one at a time through
23
+ // createViewPreparationForOracleTests to prove each is load-bearing.
24
+
25
+ const EXT = 'mnstry.atelier.obsidian'
26
+ const RELATIONS_HEADING_LINE = '## Relations (generated)'
27
+ const RELATIONS_NOTE_LINE = '%% Generated from the canonical graph. Edits to this section are not applied to any source. %%'
28
+ const RELATION_OUT = /^- [a-z_]+ → \[\[([^[\]|]*)\|(.*)\]\]$/
29
+ const RELATION_IN = /^- [a-z_]+ ← (.*)$/
30
+ const OUTSIDE_COUNT = /^Relationships leading outside this view: [0-9]+$/
31
+ const ORIGINAL_LINK = /^- Original: \[\[([^[\]|]*)\|Open the original file\]\]$/
32
+ const EMBED_LINE = /^!\[\[([^[\]|]*)\]\]$/
33
+ // Every link a generated region holds: a wikilink or embed not escaped by a
34
+ // backslash, and a Markdown link target. Generated free text escapes `[`, `]`
35
+ // and `|`, so only links the emitter wrote remain.
36
+ const GENERATED_WIKILINK = /(?<!\\)!?\[\[([^[\]\n]*?)\]\]/g
37
+ const GENERATED_MARKDOWN_LINK = /(?<!\\)\]\(([^()\n]*)\)/g
38
+
39
+ const linesOf = (text) => text.split('\n').map((line) => (line.endsWith('\r') ? line.slice(0, -1) : line))
40
+
41
+ // Generated prose escapes Markdown punctuation with a backslash (see plainText
42
+ // in prepare-view.mjs). A reader sees the text without those backslashes, and
43
+ // the deny-list reads it the same way.
44
+ const ESCAPED = /\\([\\`*_[\]<>#|^$%~=!&])/g
45
+ const asRead = (text) => text.replace(ESCAPED, '$1')
46
+
47
+ // An identity or a path matches only as a whole token, never inside a longer
48
+ // one. A letter or digit beside the match, or one of `. _ : / -` joined to a
49
+ // letter or digit beside it, continues the token; sentence punctuation after
50
+ // it (a full stop, a colon before a space) does not.
51
+ const ALNUM = /^[\p{L}\p{N}]$/u
52
+ const JOINERS = new Set(['.', '_', ':', '/', '-'])
53
+
54
+ function codePointAt(text, index) {
55
+ if (index < 0 || index >= text.length) return ''
56
+ const code = text.codePointAt(index)
57
+ // A low surrogate is the second half of the character before it.
58
+ if (code >= 0xdc00 && code <= 0xdfff && index > 0) return String.fromCodePoint(text.codePointAt(index - 1))
59
+ return String.fromCodePoint(code)
60
+ }
61
+ const isAlnumAt = (text, index) => ALNUM.test(codePointAt(text, index))
62
+ const continuesAfter = (text, end) => isAlnumAt(text, end) || (JOINERS.has(text[end]) && isAlnumAt(text, end + 1))
63
+ const continuesBefore = (text, start) => isAlnumAt(text, start - 1) || (JOINERS.has(text[start - 1]) && isAlnumAt(text, start - 2))
64
+
65
+ const REFUSE = 3
66
+ const DIAGNOSE = 2
67
+ const NOTICE = 1
68
+ const UNITS = 0x10000
69
+
70
+ // One Aho–Corasick automaton over every value, read in UTF-16 code units, with
71
+ // the token boundary tested at each hit: building it is linear in the values,
72
+ // and a text is read once, whatever their number. `refuse` values refuse a
73
+ // view; `diagnose` and `notice` values are reported, each under its own code.
74
+ // The matcher answers the strongest of what a text holds ('refuse', 'diagnose',
75
+ // 'notice' or null). Values and texts are compared in NFC.
76
+ export function createDenyMatcher({ refuse: refused = [], diagnose = [], notice = [] } = {}) {
77
+ const next = new Map()
78
+ const parent = [0]
79
+ const unit = [0]
80
+ const depth = [0]
81
+ const output = [null]
82
+ const add = (raw, strength) => {
83
+ if (typeof raw !== 'string' || raw === '') return
84
+ const value = raw.normalize('NFC')
85
+ let state = 0
86
+ for (let index = 0; index < value.length; index += 1) {
87
+ const key = state * UNITS + value.charCodeAt(index)
88
+ let child = next.get(key)
89
+ if (child === undefined) {
90
+ child = parent.length
91
+ next.set(key, child)
92
+ parent.push(state)
93
+ unit.push(value.charCodeAt(index))
94
+ depth.push(depth[state] + 1)
95
+ output.push(null)
96
+ }
97
+ state = child
98
+ }
99
+ if (output[state] === null) output[state] = { length: value.length, strength }
100
+ else output[state].strength = Math.max(output[state].strength, strength)
101
+ }
102
+ for (const value of refused) add(value, REFUSE)
103
+ for (const value of diagnose) add(value, DIAGNOSE)
104
+ for (const value of notice) add(value, NOTICE)
105
+ // Failure and output links, state by state in order of depth.
106
+ const fail = new Int32Array(parent.length)
107
+ const outputLink = new Int32Array(parent.length).fill(-1)
108
+ const byDepth = []
109
+ for (let state = 1; state < parent.length; state += 1) (byDepth[depth[state]] ??= []).push(state)
110
+ for (const states of byDepth) {
111
+ for (const state of states ?? []) {
112
+ let target = 0
113
+ if (parent[state] !== 0) {
114
+ let from = fail[parent[state]]
115
+ while (from !== 0 && !next.has(from * UNITS + unit[state])) from = fail[from]
116
+ target = next.get(from * UNITS + unit[state]) ?? 0
117
+ }
118
+ fail[state] = target
119
+ outputLink[state] = output[target] !== null ? target : outputLink[target]
120
+ }
121
+ }
122
+ return function match(raw) {
123
+ const text = String(raw).normalize('NFC')
124
+ let state = 0
125
+ let found = 0
126
+ for (let index = 0; index < text.length; index += 1) {
127
+ const code = text.charCodeAt(index)
128
+ while (state !== 0 && !next.has(state * UNITS + code)) state = fail[state]
129
+ state = next.get(state * UNITS + code) ?? 0
130
+ for (let hit = output[state] !== null ? state : outputLink[state]; hit > 0; hit = outputLink[hit]) {
131
+ const { length, strength } = output[hit]
132
+ if (strength <= found || continuesBefore(text, index - length + 1) || continuesAfter(text, index + 1)) continue
133
+ found = strength
134
+ if (found === REFUSE) return 'refuse'
135
+ }
136
+ }
137
+ return found === REFUSE ? 'refuse' : found === DIAGNOSE ? 'diagnose' : found === NOTICE ? 'notice' : null
138
+ }
139
+ }
140
+
141
+ // What one generated region of one note says: the link targets it holds and
142
+ // its free text, the author's words that reached it. A line that is not one
143
+ // of the shapes the emitter writes is free text as a whole.
144
+ function readGeneratedRegion({ kind, text, fence, own }) {
145
+ const links = [...text.matchAll(GENERATED_WIKILINK)].map((match) => match[1].split('|')[0].split('#')[0])
146
+ .concat([...text.matchAll(GENERATED_MARKDOWN_LINK)].map((match) => match[1]))
147
+ const freeText = []
148
+ for (const line of linesOf(text)) {
149
+ if (line === '' || line === fence) continue
150
+ if (kind === 'representation') {
151
+ if (line.startsWith('# ')) freeText.push(line.slice(2))
152
+ else if (line === own.format || line === own.source || ORIGINAL_LINK.test(line) || EMBED_LINE.test(line)) continue
153
+ else if (line.startsWith('- Tags: ')) freeText.push(line.slice('- Tags: '.length))
154
+ else freeText.push(line)
155
+ continue
156
+ }
157
+ if (line === RELATIONS_HEADING_LINE || line === RELATIONS_NOTE_LINE || OUTSIDE_COUNT.test(line)) continue
158
+ const outgoing = RELATION_OUT.exec(line)
159
+ if (outgoing) { freeText.push(outgoing[2]); continue }
160
+ const incoming = RELATION_IN.exec(line)
161
+ if (incoming) { freeText.push(incoming[1]); continue }
162
+ freeText.push(line.replace(GENERATED_WIKILINK, ' '))
163
+ }
164
+ return { links, freeText: freeText.map(asRead) }
165
+ }
166
+
167
+ // Whether an identity region holds exactly the identity lines of its own note
168
+ // and nothing but the front-matter delimiters, the comment delimiters of the
169
+ // end-of-note placement, blank lines and a fence closure around them.
170
+ function identityRegionIsOwn(text, node, fence) {
171
+ const expected = identityLineTexts(node)
172
+ const lines = linesOf(text)
173
+ const named = lines.filter((line) => line.startsWith('atelier-'))
174
+ if (named.length !== expected.length || named.some((line, index) => line !== expected[index])) return false
175
+ return lines.every((line) => named.includes(line) || line === '' || line === '---' || line === '%%' || (fence !== null && line === fence))
176
+ }
177
+
178
+ // Everything the rules judge about one note, read once.
179
+ export function readNoteForRedaction({ note, bytes, node, own }) {
180
+ const regions = []
181
+ for (const region of note.regions.generated) {
182
+ if (region.kind === 'identity') continue
183
+ const fence = region.ext?.[EXT]?.fenceClosure?.fence ?? null
184
+ regions.push(readGeneratedRegion({ kind: region.kind, text: bytes.subarray(region.range.start, region.range.end).toString('utf8'), fence, own }))
185
+ }
186
+ const identity = note.regions.identity
187
+ const identityRegion = note.regions.generated.find((region) => region.kind === 'identity')
188
+ const identityFence = identityRegion?.ext?.[EXT]?.fenceClosure?.fence ?? null
189
+ return {
190
+ note,
191
+ links: regions.flatMap((region) => region.links),
192
+ freeText: regions.flatMap((region) => region.freeText),
193
+ identity: identity === undefined ? null : { own: identityRegionIsOwn(bytes.subarray(identity.start, identity.end).toString('utf8'), node, identityFence) },
194
+ }
195
+ }
196
+
197
+ // A refusal names the rule and the in-view note (or file) it concerns, never
198
+ // the value: `where` is a path allocated to this view, or null when the only
199
+ // path at hand is the one in question.
200
+ const refuseAt = (rule, where, message) => refuse('redaction-failure', where === null ? `${message} (${rule})` : `${message} (${rule}, ${where})`,
201
+ { rule, ...(where === null ? {} : where.endsWith('.md') ? { notePath: where } : { filePath: where }) })
202
+
203
+ // Rule 1. `view.allocatedPathOf(nodeId)` is the path allocated to a node of
204
+ // this view (null for any other), `view.attachments` the files it holds and
205
+ // `view.linkTargets` every spelling a rewritten or generated link of this
206
+ // view may use.
207
+ function allowList({ manifest, notes, read, view, layoutVersion }) {
208
+ const judgedIds = new Set(notes.map((note) => note.nodeId))
209
+ const pathOfNode = new Map(manifest.notes.map((note) => [note.nodeId, note.path]))
210
+ for (const note of notes) {
211
+ if (view.allocatedPathOf(note.nodeId) !== note.path) refuseAt('allow-list', view.allocatedPathOf(note.nodeId), 'a note path is not the one allocated to a note of this view')
212
+ for (const embed of note.ext?.[EXT]?.assetEmbeds ?? []) {
213
+ if (!view.attachments.has(embed.attachment)) refuseAt('allow-list', note.path, 'an embed names a file that is not part of this view')
214
+ }
215
+ }
216
+ for (const attachment of manifest.attachments) {
217
+ if (!view.attachments.has(attachment.path)) refuseAt('allow-list', null, 'an attachment is not a file of this view')
218
+ }
219
+ for (const link of manifest.links.filter((item) => judgedIds.has(item.sourceNodeId))) {
220
+ for (const inversion of link.inversions ?? []) checkInversion(inversion, pathOfNode.get(link.sourceNodeId), view)
221
+ }
222
+ for (const note of notes) {
223
+ for (const embed of note.ext?.[EXT]?.assetEmbeds ?? []) for (const inversion of embed.inversions) checkInversion(inversion, note.path, view)
224
+ }
225
+ for (const entry of read) {
226
+ for (const target of entry.links) if (!view.linkTargets.has(target)) refuseAt('allow-list', entry.note.path, 'a generated link names a file that is not part of this view')
227
+ if (layoutVersion >= 2 && (entry.identity === null || !entry.identity.own)) refuseAt('identity-block', entry.note.path, 'an identity block does not name exactly its own note')
228
+ }
229
+ }
230
+
231
+ function checkInversion(inversion, notePath, view) {
232
+ const ext = inversion.ext?.[EXT] ?? {}
233
+ const emitted = Buffer.from(ext.emitted ?? '', 'base64url').toString('utf8')
234
+ // The words an author chose, appended after a rewritten wikilink target: authored bytes, not a path.
235
+ if ((ext.original ?? '') === '' && emitted.startsWith('|')) return
236
+ if (!view.linkTargets.has(emitted)) refuseAt('allow-list', notePath, 'an emitted link names a file that is not part of this view')
237
+ }
238
+
239
+ // Rule 2. A refused value refuses the view and names the note whose generated
240
+ // region holds it; a reported one is recorded through `report`, once a note
241
+ // and code, and the view goes on: an ambiguous identifier of what the audience
242
+ // may not see (a bare word, a root file name) as bare-identity-in-generated-text,
243
+ // an identifier of what the audience may see but the view does not select as
244
+ // unselected-identity-in-generated-text.
245
+ const REPORTED = { diagnose: 'bare-identity-in-generated-text', notice: 'unselected-identity-in-generated-text' }
246
+
247
+ function denyList({ read, deny, report }) {
248
+ for (const entry of read) {
249
+ const reported = new Set()
250
+ for (const text of entry.freeText) {
251
+ const found = deny(text)
252
+ if (found === 'refuse') refuseAt('deny-list', entry.note.path, 'generated text names an identity that is not part of this view')
253
+ const code = REPORTED[found]
254
+ if (code !== undefined && !reported.has(code)) {
255
+ reported.add(code)
256
+ report({ code, rule: 'deny-list', repoId: entry.note.repoId, nodeId: entry.note.nodeId, notePath: entry.note.path })
257
+ }
258
+ }
259
+ }
260
+ }
261
+
262
+ export const REDACTION_RULES = Object.freeze({
263
+ // Rule 3: which notes the other rules judge. Every note of the view.
264
+ judged: ({ notes }) => notes,
265
+ allowList,
266
+ denyList,
267
+ })
268
+
269
+ // Returns what rule 2 reported.
270
+ export function assertViewRedaction({ manifest, files, reused, nodeOf, ownOf, view, deny, layoutVersion }, rules = REDACTION_RULES) {
271
+ const notes = rules.judged({ notes: manifest.notes, reused })
272
+ const byPath = new Map(files.map((file) => [file.path, file.bytes]))
273
+ const read = notes.map((note) => readNoteForRedaction({ note, bytes: byPath.get(note.path), node: nodeOf(note.nodeId), own: ownOf(note.nodeId) }))
274
+ const reported = []
275
+ rules.allowList({ manifest, notes, read, view, layoutVersion })
276
+ rules.denyList({ read, deny, report: (item) => reported.push(item) })
277
+ return reported
278
+ }
279
+
280
+ // Layout 1 only, as the earlier release checked it. Two rules, because the two
281
+ // kinds of bytes differ: a substring the emitter itself produced from an
282
+ // allocated path (note and attachment paths, rewritten link targets) may name
283
+ // only a note or asset of this view; free text in a generated region (titles,
284
+ // tags, source paths, which are the author's words) is refused only when it
285
+ // names an identity of the census that is outside this view. Author text that
286
+ // merely looks like an identity suffix, such as a content-hashed asset name,
287
+ // is not a redaction failure.
288
+ export function assertOnlyVaultIdentities({ manifest, files, vaultSuffixes, forbiddenSuffixes }) {
289
+ const index = (suffixes) => {
290
+ const byPrefix = new Map()
291
+ for (const full of suffixes) byPrefix.set(full.slice(0, 12), [...(byPrefix.get(full.slice(0, 12)) ?? []), full])
292
+ return (suffix) => (byPrefix.get(suffix.slice(0, 12)) ?? []).some((full) => full.startsWith(suffix))
293
+ }
294
+ const allowed = index(vaultSuffixes)
295
+ const forbidden = index(forbiddenSuffixes)
296
+ const suffixesIn = (text) => [...text.matchAll(/--([0-9a-f]{12,64})(?![0-9a-f])/g)].map((match) => match[1])
297
+ // An emitted path ends in the allocated suffix (before the extension); the
298
+ // readable part before it is the author's title and may look like anything.
299
+ // A wikilink rewrite may append `|<the author's own words>`; only the part
300
+ // before the first `|` is an emitted path.
301
+ const checkEmitted = (text) => {
302
+ for (const segment of text.split('|')[0].split('#')[0].split('/')) {
303
+ const trailing = /--([0-9a-f]{12,64})(?:\.[^./]+)?$/.exec(segment)
304
+ if (trailing && !allowed(trailing[1])) refuse('redaction-failure', 'an emitted path names a note that is not part of this view')
305
+ }
306
+ for (const suffix of suffixesIn(text)) if (forbidden(suffix)) refuse('redaction-failure', 'an emitted path names a note that is not part of this view')
307
+ }
308
+ const checkText = (text) => {
309
+ for (const suffix of suffixesIn(text)) if (forbidden(suffix)) refuse('redaction-failure', 'generated output names a note that is not part of this view')
310
+ }
311
+ const byPath = new Map(files.map((file) => [file.path, file.bytes]))
312
+ for (const note of manifest.notes) {
313
+ const bytes = byPath.get(note.path)
314
+ for (const region of note.regions.generated) checkText(bytes.subarray(region.range.start, region.range.end).toString('utf8'))
315
+ }
316
+ for (const link of manifest.links) {
317
+ for (const inversion of link.inversions ?? []) checkEmitted(Buffer.from(inversion.ext[EXT].emitted, 'base64url').toString('utf8'))
318
+ }
319
+ for (const note of manifest.notes) {
320
+ for (const embed of note.ext[EXT].assetEmbeds ?? []) {
321
+ checkEmitted(embed.attachment)
322
+ for (const inversion of embed.inversions) checkEmitted(Buffer.from(inversion.ext[EXT].emitted, 'base64url').toString('utf8'))
323
+ }
324
+ }
325
+ for (const note of manifest.notes) checkEmitted(note.path)
326
+ for (const attachment of manifest.attachments) checkEmitted(attachment.path)
327
+ }
@@ -1,55 +1,134 @@
1
+ import { ObsidianContractRefusal } from '../contracts.mjs'
2
+ import { PLUGIN_DATA_FILE, PLUGIN_DIRECTORY, PLUGIN_ID, PLUGIN_SOURCE_FILES } from '../plugin-bridge/channel.mjs'
1
3
  import { refuse, sha256Digest } from './byte-lens.mjs'
2
4
 
3
- // Policy-owned Obsidian settings. Atelier owns exactly two keys of one file:
4
- // the built-in Sync and Publish core plugins stay disabled, because a generated
5
- // view must not leave the machine through the editor. Every other path under
6
- // the settings directory, and every other key of that file, belongs to the
7
- // person using the vault and is never prepared, replaced or removed here.
5
+ // Policy-owned Obsidian settings. Atelier owns exactly these, and nothing
6
+ // else under the settings directory:
7
+ //
8
+ // core-plugins.json two keys: the built-in Sync and Publish core
9
+ // plugins stay disabled, because a generated view
10
+ // must not leave the machine through the editor
11
+ // community-plugins.json one entry: Atelier's own plugin is enabled
12
+ // plugins/atelier-projection/{manifest.json,main.js,styles.css,data.json}
13
+ // Atelier's own plugin, whole files
14
+ //
15
+ // Every other path under the settings directory, every other key of
16
+ // core-plugins.json and every other entry of community-plugins.json belongs to
17
+ // the person using the vault and is never prepared, replaced or removed here.
18
+ // The two settings files are merged with the bytes on disk when they are
19
+ // published; the plugin files are Atelier's bytes and their digests are pinned
20
+ // in the generation manifest.
21
+ //
22
+ // The community entry is Atelier's only while the person wants the plugin in
23
+ // that vault. The caller says so per view (see plugin-choice.mjs): the list is
24
+ // then prepared from the exact bytes it held when that was decided, and it is
25
+ // published only over exactly those bytes; or the list is withheld and never
26
+ // touched.
8
27
 
9
28
  export const SETTINGS_ROOT = '.obsidian'
10
29
  export const POLICY_SETTINGS_PATH = `${SETTINGS_ROOT}/core-plugins.json`
11
30
  export const POLICY_DISABLED_CORE_PLUGINS = Object.freeze(['publish', 'sync'])
31
+ export const COMMUNITY_PLUGINS_PATH = `${SETTINGS_ROOT}/community-plugins.json`
32
+ export const POLICY_ENABLED_COMMUNITY_PLUGINS = Object.freeze([PLUGIN_ID])
33
+ export const POLICY_SETTINGS_PATHS = Object.freeze([POLICY_SETTINGS_PATH, COMMUNITY_PLUGINS_PATH])
34
+ export const PLUGIN_OWNED_PATHS = Object.freeze([...PLUGIN_SOURCE_FILES, PLUGIN_DATA_FILE].map((name) => `${PLUGIN_DIRECTORY}/${name}`))
12
35
 
13
36
  export function isPolicySettingsPath(relativePath) {
14
- return relativePath === POLICY_SETTINGS_PATH
37
+ return POLICY_SETTINGS_PATHS.includes(relativePath)
38
+ }
39
+
40
+ export function isPluginOwnedPath(relativePath) {
41
+ return PLUGIN_OWNED_PATHS.includes(relativePath)
15
42
  }
16
43
 
17
44
  export function isUserOwnedSettingsPath(relativePath) {
18
- return (relativePath === SETTINGS_ROOT || relativePath.startsWith(`${SETTINGS_ROOT}/`)) && !isPolicySettingsPath(relativePath)
45
+ return (relativePath === SETTINGS_ROOT || relativePath.startsWith(`${SETTINGS_ROOT}/`)) && !isPolicySettingsPath(relativePath) && !isPluginOwnedPath(relativePath)
19
46
  }
20
47
 
21
48
  const serialize = (value) => Buffer.from(`${JSON.stringify(value, null, 2)}\n`, 'utf8')
22
49
 
23
- // `existing` is the current bytes of the policy file when the vault already
24
- // has one, so keys the person set survive. Bytes that already satisfy the
25
- // policy are returned untouched.
26
- export function prepareSettings({ existing = null } = {}) {
27
- let bytes
28
- if (existing === null || existing === undefined) {
29
- bytes = serialize(Object.fromEntries(POLICY_DISABLED_CORE_PLUGINS.map((id) => [id, false])))
30
- } else {
31
- if (!Buffer.isBuffer(existing)) refuse('invalid-settings', 'existing settings must be supplied as a Buffer')
32
- let current
33
- try {
34
- current = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(existing))
35
- } catch {
36
- refuse('invalid-settings', 'existing core plugin settings do not parse; they are left alone')
37
- }
38
- if (Array.isArray(current)) {
39
- // Older list form: membership means enabled.
40
- const kept = current.filter((id) => !POLICY_DISABLED_CORE_PLUGINS.includes(id))
41
- bytes = kept.length === current.length ? existing : serialize(kept)
42
- } else if (current !== null && typeof current === 'object') {
43
- const satisfied = POLICY_DISABLED_CORE_PLUGINS.every((id) => current[id] === false)
44
- bytes = satisfied ? existing : serialize({ ...current, ...Object.fromEntries(POLICY_DISABLED_CORE_PLUGINS.map((id) => [id, false])) })
45
- } else {
46
- refuse('invalid-settings', 'existing core plugin settings have an unknown shape; they are left alone')
47
- }
50
+ function parseExisting(existing, label) {
51
+ if (!Buffer.isBuffer(existing)) refuse('invalid-settings', 'existing settings must be supplied as a Buffer')
52
+ try {
53
+ return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(existing))
54
+ } catch {
55
+ return refuse('invalid-settings', `existing ${label} do not parse; they are left alone`)
56
+ }
57
+ }
58
+
59
+ // core-plugins.json: the owned keys set to false, every other key kept.
60
+ function coreCandidate(existing) {
61
+ if (existing === null || existing === undefined) return serialize(Object.fromEntries(POLICY_DISABLED_CORE_PLUGINS.map((id) => [id, false])))
62
+ const current = parseExisting(existing, 'core plugin settings')
63
+ if (Array.isArray(current)) {
64
+ // Older list form: membership means enabled.
65
+ const kept = current.filter((id) => !POLICY_DISABLED_CORE_PLUGINS.includes(id))
66
+ return kept.length === current.length ? existing : serialize(kept)
67
+ }
68
+ if (current !== null && typeof current === 'object') {
69
+ const satisfied = POLICY_DISABLED_CORE_PLUGINS.every((id) => current[id] === false)
70
+ return satisfied ? existing : serialize({ ...current, ...Object.fromEntries(POLICY_DISABLED_CORE_PLUGINS.map((id) => [id, false])) })
71
+ }
72
+ return refuse('invalid-settings', 'existing core plugin settings have an unknown shape; they are left alone')
73
+ }
74
+
75
+ // community-plugins.json: the list of enabled plugins, in the person's order,
76
+ // with Atelier's own appended when it is missing.
77
+ function communityCandidate(existing) {
78
+ if (existing === null || existing === undefined) return serialize([...POLICY_ENABLED_COMMUNITY_PLUGINS])
79
+ const current = parseExisting(existing, 'community plugin settings')
80
+ if (!Array.isArray(current)) refuse('invalid-settings', 'existing community plugin settings are not a list; they are left alone')
81
+ const missing = POLICY_ENABLED_COMMUNITY_PLUGINS.filter((id) => !current.includes(id))
82
+ return missing.length === 0 ? existing : serialize([...current, ...missing])
83
+ }
84
+
85
+ const CANDIDATES = Object.freeze({ [POLICY_SETTINGS_PATH]: coreCandidate, [COMMUNITY_PLUGINS_PATH]: communityCandidate })
86
+ const OWNED = Object.freeze({ [POLICY_SETTINGS_PATH]: { ownedKeys: [...POLICY_DISABLED_CORE_PLUGINS] }, [COMMUNITY_PLUGINS_PATH]: { ownedEntries: [...POLICY_ENABLED_COMMUNITY_PLUGINS] } })
87
+
88
+ // The bytes one policy settings file should hold, given its bytes on disk now.
89
+ // Bytes that already satisfy the policy are returned untouched.
90
+ export function preparePolicySettingsFile(relativePath, existing = null) {
91
+ if (!isPolicySettingsPath(relativePath)) refuse('invalid-settings', 'only a policy settings file is prepared here')
92
+ const bytes = CANDIDATES[relativePath](existing)
93
+ return { path: relativePath, kind: 'settings', bytes, digest: sha256Digest(bytes) }
94
+ }
95
+
96
+ // The community plugin list as it will be published from the bytes it held
97
+ // when the decision was made. `expectedDigest` binds the publication to those
98
+ // bytes (null: to the file being absent). A list whose shape the policy does
99
+ // not know is left alone when it is published; its own bytes stand for it here.
100
+ function prepareCommunityFile(existing) {
101
+ let file
102
+ try { file = preparePolicySettingsFile(COMMUNITY_PLUGINS_PATH, existing) } catch (error) {
103
+ if (!(error instanceof ObsidianContractRefusal) || existing === null) throw error
104
+ file = { path: COMMUNITY_PLUGINS_PATH, kind: 'settings', bytes: existing, digest: sha256Digest(existing) }
105
+ }
106
+ return { ...file, expectedDigest: existing === null ? null : sha256Digest(existing) }
107
+ }
108
+
109
+ // `existing` is the current bytes of core-plugins.json when the vault already
110
+ // has one, so keys the person set survive. `plugin`, when given, is Atelier's
111
+ // plugin for this vault (preparePluginFiles): its files are carried and their
112
+ // digests are pinned. `plugin.community` says what happens to its entry:
113
+ //
114
+ // absent the entry is offered; the list is merged with the bytes on disk when published
115
+ // { entry: 'owned', existing: bytes|null } the entry is owned; the list is prepared from these bytes and published only over them
116
+ // { entry: 'withheld', reason } the list is not carried at all: the person turned the plugin off, or it cannot be read
117
+ export function prepareSettings({ existing = null, plugin = null } = {}) {
118
+ if (plugin !== null && (!Array.isArray(plugin.files) || plugin.files.some((file) => file.kind !== 'plugin' || !isPluginOwnedPath(file.path)))) {
119
+ refuse('invalid-settings', 'plugin files must be Atelier\'s own plugin files')
120
+ }
121
+ const community = plugin?.community ?? null
122
+ if (community !== null && !((community.entry === 'owned' && (community.existing === null || Buffer.isBuffer(community.existing))) || community.entry === 'withheld')) {
123
+ refuse('invalid-settings', 'the community entry is owned from known bytes, or withheld')
48
124
  }
125
+ const listed = plugin === null ? [] : community === null ? [preparePolicySettingsFile(COMMUNITY_PLUGINS_PATH, null)] : community.entry === 'owned' ? [prepareCommunityFile(community.existing)] : []
126
+ const settings = [preparePolicySettingsFile(POLICY_SETTINGS_PATH, existing), ...listed]
49
127
  return {
50
- files: [{ path: POLICY_SETTINGS_PATH, kind: 'settings', bytes, digest: sha256Digest(bytes) }],
128
+ files: [...settings, ...(plugin === null ? [] : plugin.files)],
51
129
  ownership: {
52
- policyOwned: [{ path: POLICY_SETTINGS_PATH, ownedKeys: [...POLICY_DISABLED_CORE_PLUGINS], digest: sha256Digest(bytes), byteLength: bytes.length }],
130
+ policyOwned: settings.map((file) => ({ path: file.path, ...OWNED[file.path], digest: file.digest, byteLength: file.bytes.length, ...(Object.hasOwn(file, 'expectedDigest') ? { expectedDigest: file.expectedDigest } : {}) })),
131
+ ...(plugin === null ? {} : { pluginOwned: { ...plugin.ownership, entry: community?.entry === 'withheld' ? 'withheld' : 'owned', ...(community?.entry === 'withheld' ? { withheldBecause: String(community.reason ?? 'unstated') } : {}) } }),
53
132
  userOwnedRoot: SETTINGS_ROOT,
54
133
  rule: 'every-other-path-and-key-is-user-owned',
55
134
  },