@mnstry/atelier 0.2.0-alpha.5 → 0.2.0-alpha.8

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 (383) hide show
  1. package/CHANGELOG.md +191 -0
  2. package/README.md +86 -25
  3. package/SECURITY.md +6 -1
  4. package/contracts/atelier-adoption-policy.v1.schema.json +49 -0
  5. package/contracts/atelier-coauthor.v1.schema.json +163 -0
  6. package/contracts/atelier-decision-request.v1.schema.json +476 -0
  7. package/contracts/atelier-decision-result.v1.schema.json +500 -0
  8. package/contracts/atelier-guide.v1.schema.json +227 -0
  9. package/contracts/atelier-intake.v1.schema.json +151 -0
  10. package/contracts/atelier-migration.v2.schema.json +61 -0
  11. package/contracts/atelier-obsidian-acceptance-receipt.v1.schema.json +211 -0
  12. package/contracts/atelier-obsidian-apply-policy.v1.schema.json +298 -0
  13. package/contracts/atelier-obsidian-corpus-profile.v1.schema.json +98 -0
  14. package/contracts/atelier-obsidian-edit-operation.v1.schema.json +138 -0
  15. package/contracts/atelier-obsidian-ext-settings.v1.schema.json +287 -0
  16. package/contracts/atelier-obsidian-generation-manifest.v1.schema.json +353 -0
  17. package/contracts/atelier-obsidian-proposal-receipt.v1.schema.json +110 -0
  18. package/contracts/atelier-obsidian-publication-journal.v1.schema.json +167 -0
  19. package/contracts/atelier-obsidian-scope.v1.schema.json +264 -0
  20. package/contracts/atelier-obsidian-service-state.v1.schema.json +149 -0
  21. package/contracts/atelier-obsidian-source-snapshot.v1.schema.json +179 -0
  22. package/contracts/atelier-pane-presentation.v1.schema.json +76 -0
  23. package/contracts/atelier-presentation.v1.schema.json +1293 -0
  24. package/contracts/atelier-repository-observation.v1.schema.json +163 -0
  25. package/contracts/atelier-review.v1.schema.json +1601 -0
  26. package/contracts/atelier-skill-steward.v1.schema.json +692 -0
  27. package/contracts/atelier-upgrade-plan.v2.schema.json +398 -0
  28. package/contracts/atelier-upgrade-receipt.v1.schema.json +99 -0
  29. package/contracts/atelier-vault-publication.v1.schema.json +58 -0
  30. package/docs/assurance-controls.md +12 -2
  31. package/docs/atelier-runtime.md +13 -2
  32. package/docs/atelier-sync.md +172 -0
  33. package/docs/blocks/claims.md +28 -18
  34. package/docs/blocks/will-not-do.md +12 -3
  35. package/docs/coauthor-session.md +57 -0
  36. package/docs/connected-composition.md +75 -0
  37. package/docs/decisions.md +167 -0
  38. package/docs/exact-upgrades.md +159 -0
  39. package/docs/extension-pack-lifecycle.md +33 -0
  40. package/docs/guided-upgrades.md +99 -0
  41. package/docs/install.md +65 -9
  42. package/docs/intake-and-guides.md +65 -0
  43. package/docs/integration-and-review-plan.md +409 -0
  44. package/docs/integration-contract-decisions.md +22 -0
  45. package/docs/local-review.md +84 -0
  46. package/docs/local-services.md +227 -0
  47. package/docs/local-state.md +107 -0
  48. package/docs/obsidian-contract.md +687 -0
  49. package/docs/obsidian.md +387 -0
  50. package/docs/portable-presentation.md +328 -0
  51. package/docs/presentation-consumer-boundaries.md +75 -0
  52. package/docs/presentation-review-disposition.md +47 -0
  53. package/docs/project-command-inventory.md +38 -0
  54. package/docs/project-options.md +53 -0
  55. package/docs/release-engineering.md +99 -2
  56. package/docs/review-portability.md +55 -0
  57. package/docs/skill-steward.md +156 -0
  58. package/docs/upgrade.md +70 -2
  59. package/docs/vault-service.md +292 -0
  60. package/fixtures/atelier-coauthor/invalid/authority.v1.json +5 -0
  61. package/fixtures/atelier-coauthor/valid/config.v1.json +12 -0
  62. package/fixtures/atelier-guide/invalid/authority.v1.json +11 -0
  63. package/fixtures/atelier-guide/valid/capability.v1.json +10 -0
  64. package/fixtures/atelier-guide/valid/consent.v1.json +11 -0
  65. package/fixtures/atelier-guide/valid/engagement.v1.json +7 -0
  66. package/fixtures/atelier-guide/valid/offer.v1.json +10 -0
  67. package/fixtures/atelier-intake/invalid/authority.v1.json +9 -0
  68. package/fixtures/atelier-intake/valid/attempt.v1.json +8 -0
  69. package/fixtures/atelier-intake/valid/completion.v1.json +9 -0
  70. package/fixtures/atelier-intake/valid/source.v1.json +7 -0
  71. package/fixtures/atelier-repository-observation/invalid/complete-with-blocker.v1.json +18 -0
  72. package/fixtures/atelier-repository-observation/valid/complete-local.v1.json +48 -0
  73. package/fixtures/atelier-review/invalid/authenticated-identity.v1.json +22 -0
  74. package/fixtures/atelier-review/valid/bound.v1.json +229 -0
  75. package/fixtures/atelier-review/valid/bundle.v1.json +267 -0
  76. package/fixtures/atelier-review/valid/contribution.v1.json +22 -0
  77. package/fixtures/atelier-review/valid/lifecycle.v1.json +14 -0
  78. package/fixtures/atelier-skill-steward/audit/invalid/source-mutation.v1.json +26 -0
  79. package/fixtures/atelier-skill-steward/audit/valid/clean.v1.json +26 -0
  80. package/fixtures/atelier-skill-steward/candidates/invalid/workflow-key.v1.json +37 -0
  81. package/fixtures/atelier-skill-steward/candidates/valid/create.v1.json +37 -0
  82. package/fixtures/atelier-skill-steward/lock/invalid/bad-digest.v1.json +18 -0
  83. package/fixtures/atelier-skill-steward/lock/valid/installed.v1.json +23 -0
  84. package/fixtures/atelier-skill-steward/plan/invalid/delete-action.v1.json +33 -0
  85. package/fixtures/atelier-skill-steward/plan/valid/add.v1.json +33 -0
  86. package/fixtures/atelier-upgrade-transaction/adoption-policy/invalid/example.json +12 -0
  87. package/fixtures/atelier-upgrade-transaction/adoption-policy/valid/example.json +11 -0
  88. package/fixtures/atelier-upgrade-transaction/migration/invalid/example.json +20 -0
  89. package/fixtures/atelier-upgrade-transaction/migration/valid/example.json +19 -0
  90. package/fixtures/atelier-upgrade-transaction/upgrade-plan/invalid/example.json +58 -0
  91. package/fixtures/atelier-upgrade-transaction/upgrade-plan/valid/example.json +57 -0
  92. package/fixtures/atelier-upgrade-transaction/upgrade-receipt/invalid/example.json +15 -0
  93. package/fixtures/atelier-upgrade-transaction/upgrade-receipt/valid/example.json +14 -0
  94. package/fixtures/atelier-vault-publication/invalid/publication.json +5 -0
  95. package/fixtures/atelier-vault-publication/valid/publication.json +11 -0
  96. package/fixtures/decisions/request/invalid/unknown-authority.v1.json +61 -0
  97. package/fixtures/decisions/request/valid/triage.v1.json +60 -0
  98. package/fixtures/decisions/result/invalid/execution-authority.v1.json +50 -0
  99. package/fixtures/decisions/result/valid/abstained.v1.json +23 -0
  100. package/fixtures/decisions/result/valid/assessed.v1.json +50 -0
  101. package/fixtures/obsidian/acceptance/ap05-extra-notes.json +14 -0
  102. package/fixtures/obsidian/acceptance/receipts/G07.valid.v1.json +63 -0
  103. package/fixtures/obsidian/acceptance/receipts/G13.valid.v1.json +69 -0
  104. package/fixtures/obsidian/acceptance/receipts/G14.valid.v1.json +62 -0
  105. package/fixtures/obsidian/acceptance/receipts/G15.valid.v1.json +56 -0
  106. package/fixtures/obsidian/acceptance/receipts/G16.valid.v1.json +73 -0
  107. package/fixtures/obsidian/acceptance/receipts/G17.valid.v1.json +75 -0
  108. package/fixtures/obsidian/acceptance/receipts/G18.valid.v1.json +64 -0
  109. package/fixtures/obsidian/acceptance/service-sentinels.json +10 -0
  110. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/duplicate-evidence-name.v1.json +39 -0
  111. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/evidence-by-absolute-path.v1.json +34 -0
  112. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/missing-candidate-tree.v1.json +33 -0
  113. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/no-evidence.v1.json +28 -0
  114. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/unknown-outcome.v1.json +34 -0
  115. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/unknown-top-level-field.v1.json +35 -0
  116. package/fixtures/obsidian/contracts/acceptance-receipt/valid/node-gate.v1.json +34 -0
  117. package/fixtures/obsidian/contracts/apply-policy/invalid/automatic-without-edit-class.v1.json +27 -0
  118. package/fixtures/obsidian/contracts/apply-policy/invalid/conflict-overwrite.v1.json +29 -0
  119. package/fixtures/obsidian/contracts/apply-policy/invalid/missing-policy-digest.v1.json +28 -0
  120. package/fixtures/obsidian/contracts/apply-policy/invalid/unbounded-batch.v1.json +29 -0
  121. package/fixtures/obsidian/contracts/apply-policy/invalid/unimplemented-edit-class.v1.json +30 -0
  122. package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-mode.v1.json +29 -0
  123. package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-selector-operator.v1.json +22 -0
  124. package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-top-level-field.v1.json +30 -0
  125. package/fixtures/obsidian/contracts/apply-policy/valid/automatic-scoped.v1.json +29 -0
  126. package/fixtures/obsidian/contracts/apply-policy/valid/manual-default.v1.json +20 -0
  127. package/fixtures/obsidian/contracts/corpus-profile/invalid/absolute-path-in-ext.v1.json +30 -0
  128. package/fixtures/obsidian/contracts/corpus-profile/invalid/absolute-repository-root.v1.json +27 -0
  129. package/fixtures/obsidian/contracts/corpus-profile/invalid/duplicate-repository-id.v1.json +27 -0
  130. package/fixtures/obsidian/contracts/corpus-profile/invalid/overlapping-managed-roots.v1.json +27 -0
  131. package/fixtures/obsidian/contracts/corpus-profile/invalid/unknown-enrollment.v1.json +27 -0
  132. package/fixtures/obsidian/contracts/corpus-profile/invalid/unknown-top-level-field.v1.json +28 -0
  133. package/fixtures/obsidian/contracts/corpus-profile/valid/three-repositories.v1.json +27 -0
  134. package/fixtures/obsidian/contracts/edit-operation/invalid/absolute-recovery-ref.v1.json +21 -0
  135. package/fixtures/obsidian/contracts/edit-operation/invalid/inline-observed-bytes.v1.json +22 -0
  136. package/fixtures/obsidian/contracts/edit-operation/invalid/missing-base-digest.v1.json +20 -0
  137. package/fixtures/obsidian/contracts/edit-operation/invalid/short-idempotency-key.v1.json +21 -0
  138. package/fixtures/obsidian/contracts/edit-operation/invalid/unknown-kind.v1.json +21 -0
  139. package/fixtures/obsidian/contracts/edit-operation/valid/pending-body-replacement.v1.json +21 -0
  140. package/fixtures/obsidian/contracts/ext-settings/invalid/absolute-path-prefix.v1.json +28 -0
  141. package/fixtures/obsidian/contracts/ext-settings/invalid/apply-policy-in-portable-settings.v1.json +30 -0
  142. package/fixtures/obsidian/contracts/ext-settings/invalid/duplicate-scope-id.v1.json +27 -0
  143. package/fixtures/obsidian/contracts/ext-settings/invalid/machine-local-vault-path.v1.json +28 -0
  144. package/fixtures/obsidian/contracts/ext-settings/invalid/service-port-in-portable-settings.v1.json +28 -0
  145. package/fixtures/obsidian/contracts/ext-settings/invalid/unknown-default-scope.v1.json +27 -0
  146. package/fixtures/obsidian/contracts/ext-settings/valid/disabled.v1.json +5 -0
  147. package/fixtures/obsidian/contracts/ext-settings/valid/enabled-with-scopes.v1.json +27 -0
  148. package/fixtures/obsidian/contracts/generation-manifest/invalid/absolute-note-path.v1.json +114 -0
  149. package/fixtures/obsidian/contracts/generation-manifest/invalid/complete-with-unwritten-notes.v1.json +114 -0
  150. package/fixtures/obsidian/contracts/generation-manifest/invalid/derived-link-claimed-declared.v1.json +114 -0
  151. package/fixtures/obsidian/contracts/generation-manifest/invalid/duplicate-note-identity.v1.json +114 -0
  152. package/fixtures/obsidian/contracts/generation-manifest/invalid/in-scope-endpoint-missing.v1.json +114 -0
  153. package/fixtures/obsidian/contracts/generation-manifest/invalid/title-only-note-path.v1.json +114 -0
  154. package/fixtures/obsidian/contracts/generation-manifest/invalid/unknown-note-field.v1.json +115 -0
  155. package/fixtures/obsidian/contracts/generation-manifest/invalid/unknown-relation-type.v1.json +114 -0
  156. package/fixtures/obsidian/contracts/generation-manifest/invalid/withheld-endpoint.v1.json +114 -0
  157. package/fixtures/obsidian/contracts/generation-manifest/valid/two-notes.v1.json +114 -0
  158. package/fixtures/obsidian/contracts/oracles/scope-cases.json +412 -0
  159. package/fixtures/obsidian/contracts/proposal-receipt/invalid/accepted-without-proposal-id.v1.json +12 -0
  160. package/fixtures/obsidian/contracts/proposal-receipt/invalid/missing-adapter-operation-id.v1.json +11 -0
  161. package/fixtures/obsidian/contracts/proposal-receipt/invalid/unknown-backpressure-outcome.v1.json +12 -0
  162. package/fixtures/obsidian/contracts/proposal-receipt/invalid/unknown-top-level-field.v1.json +13 -0
  163. package/fixtures/obsidian/contracts/proposal-receipt/valid/accepted-new.v1.json +12 -0
  164. package/fixtures/obsidian/contracts/proposal-receipt/valid/deferred.v1.json +12 -0
  165. package/fixtures/obsidian/contracts/publication-journal/invalid/absolute-recovery-ref.v1.json +47 -0
  166. package/fixtures/obsidian/contracts/publication-journal/invalid/duplicate-sequence.v1.json +47 -0
  167. package/fixtures/obsidian/contracts/publication-journal/invalid/empty-protocol-id.v1.json +47 -0
  168. package/fixtures/obsidian/contracts/publication-journal/invalid/missing-protocol-id.v1.json +46 -0
  169. package/fixtures/obsidian/contracts/publication-journal/invalid/unknown-entry-field.v1.json +48 -0
  170. package/fixtures/obsidian/contracts/publication-journal/invalid/unknown-state.v1.json +47 -0
  171. package/fixtures/obsidian/contracts/publication-journal/valid/committed.v1.json +47 -0
  172. package/fixtures/obsidian/contracts/publication-journal/valid/updating-after-restart.v1.json +22 -0
  173. package/fixtures/obsidian/contracts/scope/invalid/absolute-path-prefix.v1.json +15 -0
  174. package/fixtures/obsidian/contracts/scope/invalid/duplicate-ids.v1.json +18 -0
  175. package/fixtures/obsidian/contracts/scope/invalid/expansion-without-node-budget.v1.json +40 -0
  176. package/fixtures/obsidian/contracts/scope/invalid/full-mode-with-subset.v1.json +8 -0
  177. package/fixtures/obsidian/contracts/scope/invalid/unknown-mode.v1.json +41 -0
  178. package/fixtures/obsidian/contracts/scope/invalid/unknown-selector-key.v1.json +15 -0
  179. package/fixtures/obsidian/contracts/scope/invalid/unknown-selector-operator.v1.json +14 -0
  180. package/fixtures/obsidian/contracts/scope/invalid/unknown-top-level-field.v1.json +42 -0
  181. package/fixtures/obsidian/contracts/scope/valid/explicit-empty.v1.json +8 -0
  182. package/fixtures/obsidian/contracts/scope/valid/focus.v1.json +11 -0
  183. package/fixtures/obsidian/contracts/scope/valid/full.v1.json +8 -0
  184. package/fixtures/obsidian/contracts/scope/valid/scoped-set-expression.v1.json +41 -0
  185. package/fixtures/obsidian/contracts/service-state/invalid/hostname-instead-of-literal-loopback.v1.json +23 -0
  186. package/fixtures/obsidian/contracts/service-state/invalid/missing-consent.v1.json +18 -0
  187. package/fixtures/obsidian/contracts/service-state/invalid/privileged-port.v1.json +23 -0
  188. package/fixtures/obsidian/contracts/service-state/invalid/unknown-top-level-field.v1.json +24 -0
  189. package/fixtures/obsidian/contracts/service-state/invalid/wildcard-bind.v1.json +23 -0
  190. package/fixtures/obsidian/contracts/service-state/valid/healthy.v1.json +23 -0
  191. package/fixtures/obsidian/contracts/source-snapshot/invalid/absolute-file-path.v1.json +48 -0
  192. package/fixtures/obsidian/contracts/source-snapshot/invalid/duplicate-repository-id.v1.json +48 -0
  193. package/fixtures/obsidian/contracts/source-snapshot/invalid/mixed-read.v1.json +48 -0
  194. package/fixtures/obsidian/contracts/source-snapshot/invalid/parent-traversal-path.v1.json +48 -0
  195. package/fixtures/obsidian/contracts/source-snapshot/invalid/unknown-file-field.v1.json +49 -0
  196. package/fixtures/obsidian/contracts/source-snapshot/valid/two-repositories.v1.json +48 -0
  197. package/fixtures/obsidian/edits/workspace.json +65 -0
  198. package/fixtures/obsidian/materialization/expected-full.json +99 -0
  199. package/fixtures/obsidian/materialization/expected-scoped.json +39 -0
  200. package/fixtures/obsidian/materialization/workspace.json +54 -0
  201. package/fixtures/obsidian/proposals/expected-shapes.json +11 -0
  202. package/fixtures/ui/presentation/pane.v1.json +7 -0
  203. package/fixtures/ui/presentation/reference.v1.json +293 -0
  204. package/package.json +61 -6
  205. package/skills/claude/atelier-guided-coauthor/SKILL.md +49 -0
  206. package/skills/claude/atelier-guided-upgrade/SKILL.md +97 -0
  207. package/skills/claude/atelier-open/SKILL.md +5 -0
  208. package/skills/claude/atelier-skill-steward/SKILL.md +62 -0
  209. package/skills/claude/mnstry-readiness/SKILL.md +5 -0
  210. package/skills/codex/atelier-guided-coauthor/SKILL.md +49 -0
  211. package/skills/codex/atelier-guided-upgrade/SKILL.md +97 -0
  212. package/skills/codex/atelier-open/SKILL.md +5 -0
  213. package/skills/codex/atelier-skill-steward/SKILL.md +62 -0
  214. package/skills/codex/mnstry-readiness/SKILL.md +5 -0
  215. package/src/access/preview-enforcer.mjs +84 -0
  216. package/src/boundary/content-rules.mjs +9 -4
  217. package/src/boundary/policy.mjs +74 -42
  218. package/src/cli/project-options.mjs +58 -0
  219. package/src/cli/run.mjs +61 -3
  220. package/src/coauthor/session.mjs +161 -0
  221. package/src/coauthor/store.mjs +173 -0
  222. package/src/collaboration/event-ledger.mjs +16 -33
  223. package/src/collaboration/inspection-bundle.mjs +223 -0
  224. package/src/collaboration/review-contracts.mjs +25 -0
  225. package/src/collaboration/review-store.mjs +305 -0
  226. package/src/commands/coauthor.mjs +26 -0
  227. package/src/commands/config.mjs +6 -1
  228. package/src/commands/extension-pack.mjs +15 -8
  229. package/src/commands/init.mjs +44 -5
  230. package/src/commands/obsidian.mjs +368 -0
  231. package/src/commands/review.mjs +147 -0
  232. package/src/commands/setup.mjs +51 -14
  233. package/src/commands/skills.mjs +199 -0
  234. package/src/commands/sync.mjs +107 -0
  235. package/src/composition/registry.mjs +27 -0
  236. package/src/composition/wire.mjs +122 -0
  237. package/src/contracts/corpus.mjs +35 -0
  238. package/src/decisions/contracts.d.mts +96 -0
  239. package/src/decisions/contracts.mjs +321 -0
  240. package/src/disclosure/content-scan.mjs +13 -0
  241. package/src/export/atelier-export-contract.mjs +16 -1
  242. package/src/extension-packs/lifecycle.mjs +154 -0
  243. package/src/graph/graph.mjs +64 -2
  244. package/src/graph/knowledge-graph.mjs +548 -24
  245. package/src/guides/contracts.mjs +54 -0
  246. package/src/index.mjs +29 -0
  247. package/src/intake/store.mjs +119 -0
  248. package/src/preview/channel.mjs +89 -0
  249. package/src/project/config.mjs +50 -49
  250. package/src/project/durable-state.mjs +161 -0
  251. package/src/project/file-class.mjs +166 -0
  252. package/src/project/private-state.mjs +11 -1
  253. package/src/projection/obsidian/contracts.mjs +571 -0
  254. package/src/projection/obsidian/edits/align.mjs +169 -0
  255. package/src/projection/obsidian/edits/apply.mjs +889 -0
  256. package/src/projection/obsidian/edits/arbitrate.mjs +414 -0
  257. package/src/projection/obsidian/edits/contribution.mjs +108 -0
  258. package/src/projection/obsidian/edits/index.mjs +19 -0
  259. package/src/projection/obsidian/edits/object-identity.mjs +61 -0
  260. package/src/projection/obsidian/edits/object-store.mjs +517 -0
  261. package/src/projection/obsidian/edits/observe.mjs +123 -0
  262. package/src/projection/obsidian/edits/policy.mjs +123 -0
  263. package/src/projection/obsidian/edits/regions.mjs +648 -0
  264. package/src/projection/obsidian/materialize/byte-lens.mjs +98 -0
  265. package/src/projection/obsidian/materialize/index.mjs +7 -0
  266. package/src/projection/obsidian/materialize/path-registry.mjs +105 -0
  267. package/src/projection/obsidian/materialize/prepare-view.mjs +746 -0
  268. package/src/projection/obsidian/materialize/settings.mjs +57 -0
  269. package/src/projection/obsidian/materialize/stage.mjs +27 -0
  270. package/src/projection/obsidian/proposals/adapter.mjs +572 -0
  271. package/src/projection/obsidian/proposals/backpressure.mjs +113 -0
  272. package/src/projection/obsidian/proposals/contribution.mjs +55 -0
  273. package/src/projection/obsidian/proposals/index.mjs +23 -0
  274. package/src/projection/obsidian/proposals/observation.mjs +216 -0
  275. package/src/projection/obsidian/proposals/queue.mjs +277 -0
  276. package/src/projection/obsidian/proposals/router.mjs +168 -0
  277. package/src/projection/obsidian/publication/bridge-script.mjs +266 -0
  278. package/src/projection/obsidian/publication/exchange.mjs +117 -0
  279. package/src/projection/obsidian/publication/index.mjs +5 -0
  280. package/src/projection/obsidian/publication/publisher.mjs +588 -0
  281. package/src/projection/obsidian/publication/test-seam.mjs +16 -0
  282. package/src/projection/obsidian/publication/transport.mjs +139 -0
  283. package/src/projection/obsidian/recovery/index.mjs +6 -0
  284. package/src/projection/obsidian/recovery/journal.mjs +134 -0
  285. package/src/projection/obsidian/recovery/late-writer.mjs +46 -0
  286. package/src/projection/obsidian/recovery/restart.mjs +316 -0
  287. package/src/projection/obsidian/recovery/store.mjs +234 -0
  288. package/src/projection/obsidian/selection-ui/conflict-view.mjs +91 -0
  289. package/src/projection/obsidian/selection-ui/contribution.mjs +141 -0
  290. package/src/projection/obsidian/selection-ui/focus.mjs +82 -0
  291. package/src/projection/obsidian/selection-ui/index.mjs +12 -0
  292. package/src/projection/obsidian/selection-ui/policy-setup.mjs +107 -0
  293. package/src/projection/obsidian/selection-ui/receipt.mjs +173 -0
  294. package/src/projection/obsidian/selection-ui/selection-state.mjs +122 -0
  295. package/src/projection/obsidian/selection-ui/selection.mjs +109 -0
  296. package/src/projection/project.mjs +28 -19
  297. package/src/readiness-protocols/evidence.mjs +282 -0
  298. package/src/readiness-protocols/runtime.mjs +4 -3
  299. package/src/readiness-protocols/source-read.mjs +39 -0
  300. package/src/runtime/git-adapter.mjs +189 -0
  301. package/src/runtime/local-state.mjs +439 -0
  302. package/src/runtime/obsidian/app-capability.mjs +110 -0
  303. package/src/runtime/obsidian/app-production-seams.mjs +96 -0
  304. package/src/runtime/obsidian/contributions/proposal-adapter.mjs +4 -0
  305. package/src/runtime/obsidian/contributions/selection-ui.mjs +4 -0
  306. package/src/runtime/obsidian/contributions/source-apply.mjs +4 -0
  307. package/src/runtime/obsidian/contributions.mjs +27 -0
  308. package/src/runtime/obsidian/documents.mjs +37 -0
  309. package/src/runtime/obsidian/enablement.mjs +53 -0
  310. package/src/runtime/obsidian/engine.mjs +532 -0
  311. package/src/runtime/obsidian/errors.mjs +14 -0
  312. package/src/runtime/obsidian/extension-points.mjs +103 -0
  313. package/src/runtime/obsidian/index.mjs +34 -0
  314. package/src/runtime/obsidian/lifecycle.mjs +258 -0
  315. package/src/runtime/obsidian/machine-settings.mjs +260 -0
  316. package/src/runtime/obsidian/observation.mjs +158 -0
  317. package/src/runtime/obsidian/opening.mjs +243 -0
  318. package/src/runtime/obsidian/pending-edits.mjs +141 -0
  319. package/src/runtime/obsidian/pipeline.mjs +122 -0
  320. package/src/runtime/obsidian/private-lock.mjs +156 -0
  321. package/src/runtime/obsidian/process-identity.mjs +58 -0
  322. package/src/runtime/obsidian/service-client.mjs +66 -0
  323. package/src/runtime/obsidian/service-main.mjs +89 -0
  324. package/src/runtime/obsidian/service-record.mjs +169 -0
  325. package/src/runtime/obsidian/service-server.mjs +121 -0
  326. package/src/runtime/obsidian/service.mjs +216 -0
  327. package/src/runtime/obsidian/startup-adapters.mjs +87 -0
  328. package/src/runtime/obsidian/state-store.mjs +180 -0
  329. package/src/runtime/obsidian/sync-notice.mjs +33 -0
  330. package/src/runtime/obsidian/tick-loop.mjs +84 -0
  331. package/src/runtime/obsidian/watchers.mjs +36 -0
  332. package/src/runtime/repository-observation.mjs +491 -0
  333. package/src/runtime/supervisor.mjs +803 -0
  334. package/src/server/local-sidecar.mjs +44 -0
  335. package/src/server/server.mjs +1 -0
  336. package/src/skills/steward.mjs +780 -0
  337. package/src/ui/presentation/browser.mjs +278 -0
  338. package/src/ui/presentation/contract.mjs +116 -0
  339. package/src/ui/presentation/index.mjs +7 -0
  340. package/src/ui/presentation/native.mjs +153 -0
  341. package/src/ui/presentation/proof.mjs +57 -0
  342. package/src/ui/presentation/schema-check.mjs +30 -0
  343. package/src/ui/presentation/schema.generated.mjs +1371 -0
  344. package/src/ui/presentation/state.mjs +43 -0
  345. package/src/ui/presentation/styles.mjs +67 -0
  346. package/src/ui/presentation/tokens.mjs +74 -0
  347. package/src/ui/presentation/web.mjs +85 -0
  348. package/src/ui/review-page.mjs +32 -0
  349. package/src/upgrade/explanation.mjs +33 -0
  350. package/src/upgrade/provenance.mjs +338 -0
  351. package/src/upgrade/transaction-files.mjs +103 -0
  352. package/src/upgrade/transaction.mjs +475 -0
  353. package/src/upgrade/upgrade.mjs +63 -28
  354. package/src/vault/hosts.mjs +21 -0
  355. package/src/vault/identity.mjs +19 -0
  356. package/src/vault/index.mjs +7 -0
  357. package/src/vault/interface.mjs +11 -0
  358. package/src/vault/metadata.mjs +31 -0
  359. package/src/vault/privacy.mjs +148 -0
  360. package/src/vault/probe.mjs +89 -0
  361. package/src/vault/service.mjs +131 -0
  362. package/src/vault/source.mjs +45 -0
  363. package/src/vault/storage.mjs +23 -0
  364. package/templates/distribution-workspace/.claude/launch.json +6 -2
  365. package/templates/distribution-workspace/gitignore +1 -0
  366. package/templates/distribution-workspace/project/README.md +2 -2
  367. package/templates/external-project-workspace/README.md +45 -0
  368. package/templates/external-project-workspace/answers.example.json +4 -0
  369. package/templates/external-project-workspace/atelier.project.json +40 -0
  370. package/templates/external-project-workspace/boundary-policy.v1.json +60 -0
  371. package/templates/external-project-workspace/gitignore +8 -0
  372. package/templates/external-project-workspace/pack-lifecycle.json +14 -0
  373. package/templates/external-project-workspace/packs/protocols/contract-gate.v1.json +109 -0
  374. package/templates/external-project-workspace/packs/sample-pack.v1.json +48 -0
  375. package/templates/external-project-workspace/repo-access.v1.json +9 -0
  376. package/templates/external-project-workspace/source/README.md +11 -0
  377. package/templates/launch.json +6 -2
  378. package/templates/private-domain-workspace/.claude/launch.json +6 -2
  379. package/templates/private-domain-workspace/domain/README.md +2 -2
  380. package/templates/private-domain-workspace/gitignore +1 -0
  381. package/templates/shared-project-workspace/.claude/launch.json +6 -2
  382. package/templates/shared-project-workspace/gitignore +1 -0
  383. package/templates/shared-project-workspace/project/README.md +2 -2
@@ -9,8 +9,11 @@ import {
9
9
  firstString,
10
10
  parseArgs,
11
11
  resolveProjectConfig,
12
+ validateProjectConfigDoc,
12
13
  writeJson,
13
14
  } from '../project/config.mjs'
15
+ import { writeAtelierLock, loadAtelierLock, checkAtelierLock } from '../upgrade/upgrade.mjs'
16
+ import { loadBoundaryPolicy, validateBoundaryPolicy } from '../boundary/policy.mjs'
14
17
  import { auditRepoIdentities } from '../project/repo-identity.mjs'
15
18
 
16
19
  const PROFILE_SET = new Set(['single-repo', 'private-domain', 'shared-project', 'multi-repo', 'monorepo', 'control-workspace'])
@@ -121,8 +124,10 @@ function runSetup(argv) {
121
124
  }
122
125
 
123
126
  function baseBoundaryPolicy({ repoName, profile, actor }) {
124
- const privateDomain = profile === 'shared-project' ? `${actor}-private` : repoName
125
127
  const shared = profile === 'shared-project'
128
+ const privateDomain = shared
129
+ ? (repoName === `${actor}-private` ? `${actor}-private-domain` : `${actor}-private`)
130
+ : repoName
126
131
  return {
127
132
  schema: 'mnstry.atelier-boundary-policy@v1',
128
133
  mode: 'strict',
@@ -134,6 +139,11 @@ function baseBoundaryPolicy({ repoName, profile, actor }) {
134
139
  },
135
140
  },
136
141
  repos: {
142
+ ...(shared ? { [privateDomain]: {
143
+ kind: 'private_domain', ownerActor: actor, readBoundary: 'private',
144
+ allowedAudiences: ['private', 'sensitive', 'team', 'operator', 'staff', 'public'],
145
+ forbiddenAudiences: [], autoCommit: 'guarded',
146
+ } } : {}),
137
147
  [repoName]: {
138
148
  kind: shared ? 'shared' : 'private_domain',
139
149
  ownerActor: shared ? undefined : actor,
@@ -178,23 +188,42 @@ function runAdopt(argv) {
178
188
  if (profile === 'monorepo' && !firstString(args.include, args.includes)) {
179
189
  throw new Error('monorepo adopt requires --include so Atelier does not scan the whole repo by accident')
180
190
  }
181
- fs.mkdirSync(target, { recursive: true })
182
- appendIgnoreLines(target)
183
191
  const repoName = slug(firstString(args.name) || path.basename(target))
184
192
  const actor = slug(firstString(args.actor) || process.env.USER || 'owner')
185
193
  const projectPath = path.join(target, 'atelier.project.json')
186
- if (!fs.existsSync(projectPath)) {
187
- writeJson(projectPath, {
188
- schema: 'mnstry.atelier-project-config@v1',
189
- name: repoName,
190
- roots: { workspace: '.', repoOps: '.' },
191
- graph: { repoAccessPath: 'repo-access.v1.json', outputPath: 'atelier-output/knowledge.graph.json' },
192
- projection: { outputRoot: 'atelier-output', readinessPath: 'atelier-output/atelier-readiness.json' },
193
- boundaries: { policyPath: 'boundary-policy.v1.json', governanceLedgerPath: 'governance/repo-boundary-ledger.md', strictNewRepos: true },
194
- setup: { profile, include: firstString(args.include, args.includes) || null, exclude: firstString(args.exclude, args.excludes) || null },
195
- repos: [{ name: repoName, path: '.', readBoundary: profile === 'shared-project' ? 'team' : 'private', role: profile }],
196
- })
194
+ const existingProject = fs.existsSync(projectPath)
195
+ ? resolveProjectConfig({ cwd: target, argv: ['--project', projectPath], writeLocalState: false }) : null
196
+ const proposedConfig = existingProject?.config || {
197
+ schema: 'mnstry.atelier-project-config@v1',
198
+ name: repoName,
199
+ roots: { workspace: '.', repoOps: '.' },
200
+ graph: { repoAccessPath: 'repo-access.v1.json', outputPath: 'atelier-output/knowledge.graph.json' },
201
+ projection: { outputRoot: 'atelier-output', readinessPath: 'atelier-output/atelier-readiness.json' },
202
+ boundaries: { policyPath: 'boundary-policy.v1.json', governanceLedgerPath: 'governance/repo-boundary-ledger.md', strictNewRepos: true },
203
+ setup: { profile, include: firstString(args.include, args.includes) || null, exclude: firstString(args.exclude, args.excludes) || null },
204
+ repos: [{ name: repoName, path: '.', readBoundary: profile === 'shared-project' ? 'team' : 'private', role: profile }],
205
+ }
206
+ const configErrors = validateProjectConfigDoc(proposedConfig)
207
+ if (configErrors.length) throw new Error(`adopt requires a valid project config: ${configErrors.join('; ')}`)
208
+ const proposedProject = existingProject || { configDir: target, config: proposedConfig, repos: proposedConfig.repos }
209
+ // Parse and validate retained state before any scaffold or overlay writes.
210
+ const prior = loadAtelierLock(existingProject || { configDir: target })
211
+ if (fs.existsSync(prior.lockPath) && !prior.lock) throw new Error('existing lock must contain a valid lock object')
212
+ if (prior.lock && !existingProject) throw new Error('existing lock requires an existing project configuration')
213
+ const priorPolicy = loadBoundaryPolicy(proposedProject)
214
+ if (existingProject || fs.existsSync(priorPolicy.policyPath)) {
215
+ if (!priorPolicy.ok) throw new Error(`adopt requires a valid boundary policy: ${priorPolicy.errors.join('; ')}`)
197
216
  }
217
+ const proposedPolicy = priorPolicy.ok ? priorPolicy.policy : cleanUndefined(baseBoundaryPolicy({ repoName, profile, actor }))
218
+ const proposedPolicyErrors = validateBoundaryPolicy(proposedPolicy, proposedProject)
219
+ if (proposedPolicyErrors.length) throw new Error(`adopt requires a valid boundary policy: ${proposedPolicyErrors.join('; ')}`)
220
+ if (prior.lock) {
221
+ const report = checkAtelierLock(existingProject)
222
+ if (!report.ok) throw new Error(`adopt cannot accept existing lock drift: ${report.errors.join('; ')}`)
223
+ }
224
+ fs.mkdirSync(target, { recursive: true })
225
+ appendIgnoreLines(target)
226
+ if (!existingProject) writeJson(projectPath, proposedConfig)
198
227
  if (!fs.existsSync(path.join(target, 'repo-access.v1.json'))) {
199
228
  writeJson(path.join(target, 'repo-access.v1.json'), {
200
229
  schema: 'mnstry.atelier-repo-access@v1',
@@ -206,8 +235,16 @@ function runAdopt(argv) {
206
235
  writeJson(path.join(target, 'boundary-policy.v1.json'), cleanUndefined(baseBoundaryPolicy({ repoName, profile, actor })))
207
236
  }
208
237
  const project = resolveProjectConfig({ cwd: target, argv: ['--project', projectPath] })
238
+ const loadedPolicy = loadBoundaryPolicy(project)
239
+ const policyErrors = loadedPolicy.ok ? validateBoundaryPolicy(loadedPolicy.policy, project) : loadedPolicy.errors
240
+ if (policyErrors.length) throw new Error(`adopt requires a valid boundary policy: ${policyErrors.join('; ')}`)
209
241
  ensureLocalState(project, { write: true })
210
242
  writeLocalOverlay(project)
243
+ // Adoption creates the first lock, but never accepts drift in an existing one.
244
+ const { lockPath } = loadAtelierLock(project)
245
+ if (!fs.existsSync(lockPath)) writeAtelierLock({ project, templateId: `adopt:${profile}` })
246
+ const lockReport = checkAtelierLock(project)
247
+ if (!lockReport.ok) throw new Error(`adopt lock check failed: ${lockReport.errors.join('; ')}`)
211
248
  console.log(JSON.stringify({ ok: true, command: 'adopt', profile, target, projectPath }, null, 2))
212
249
  }
213
250
 
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'node:path'
4
+
5
+ import {
6
+ applySkillSync,
7
+ auditSkillCatalog,
8
+ buildSkillCandidates,
9
+ planSkillSync,
10
+ recordSkillObservation,
11
+ } from '../skills/steward.mjs'
12
+
13
+ const USAGE = `Usage: atelier skills <subcommand>
14
+
15
+ Subcommands:
16
+ audit [--root DIR] [--peer DIR] [--json]
17
+ Audit required metadata, bundle safety, relative resources, and peer parity.
18
+ With no roots, audits the bundled Codex and Claude skill surfaces.
19
+
20
+ observe --workflow KEY --signal SIGNAL [--skill NAME] [--outcome OUTCOME]
21
+ Append one content-free local observation under ignored .atelier-local state.
22
+ Prompts, transcripts, free-form summaries, and source content are not accepted.
23
+
24
+ candidates [--all] [--json]
25
+ Aggregate local observations into evidence-thresholded create, improve,
26
+ reconcile, or retire candidates. This command never edits a skill.
27
+
28
+ sync [--source DIR] [--target .agents/skills] [--json]
29
+ Produce a content-bound plan for updating a repo-scoped managed projection.
30
+ Apply only with --apply --confirm PLAN_DIGEST. Source skills are never changed,
31
+ unmanaged collisions and locally drifted managed skills block, and removals are
32
+ moved to ignored quarantine rather than deleted.
33
+
34
+ Exit codes: 0 clean or applied, 1 findings or blockers, 2 usage error.`
35
+
36
+ const OPTION_SPEC = {
37
+ root: 'value',
38
+ peer: 'value',
39
+ source: 'value',
40
+ target: 'value',
41
+ workflow: 'value',
42
+ signal: 'value',
43
+ skill: 'value',
44
+ outcome: 'value',
45
+ confirm: 'value',
46
+ json: 'flag',
47
+ all: 'flag',
48
+ apply: 'flag',
49
+ 'dry-run': 'flag',
50
+ }
51
+
52
+ function usageError(message) {
53
+ console.error(`${message}\n\n${USAGE}`)
54
+ process.exit(2)
55
+ }
56
+
57
+ function parseOptions(argv, allowed) {
58
+ const options = {}
59
+ for (let index = 0; index < argv.length; index += 1) {
60
+ const arg = argv[index]
61
+ if (arg === '--help' || arg === '-h') {
62
+ console.log(USAGE)
63
+ process.exit(0)
64
+ }
65
+ if (!arg.startsWith('--')) usageError(`unexpected argument: ${arg}`)
66
+ const [rawName, inline] = arg.slice(2).split(/=(.*)/s, 2)
67
+ if (!allowed.has(rawName) || !(rawName in OPTION_SPEC)) usageError(`unknown option --${rawName}`)
68
+ if (OPTION_SPEC[rawName] === 'flag') {
69
+ if (inline !== undefined) usageError(`--${rawName} does not take a value`)
70
+ options[rawName] = true
71
+ continue
72
+ }
73
+ let value = inline
74
+ if (value === undefined) {
75
+ index += 1
76
+ value = argv[index]
77
+ }
78
+ if (value === undefined || value.startsWith('--')) usageError(`--${rawName} requires a value`)
79
+ options[rawName] = value
80
+ }
81
+ return options
82
+ }
83
+
84
+ function printAudit(report, json) {
85
+ if (json) {
86
+ console.log(JSON.stringify(report, null, 2))
87
+ return
88
+ }
89
+ console.log(`skills: ${report.summary.skills}; errors: ${report.summary.errors}; warnings: ${report.summary.warnings}`)
90
+ for (const finding of report.findings) {
91
+ console.log(`${finding.severity}: ${finding.code} ${finding.surface}/${finding.skill ?? '-'} ${finding.path}`)
92
+ }
93
+ }
94
+
95
+ function runAudit(argv) {
96
+ const options = parseOptions(argv, new Set(['root', 'peer', 'json']))
97
+ if (options.peer && !options.root) usageError('--peer requires --root')
98
+ const surfaces = options.root
99
+ ? [
100
+ { name: 'source', root: path.resolve(options.root) },
101
+ ...(options.peer ? [{ name: 'peer', root: path.resolve(options.peer) }] : []),
102
+ ]
103
+ : undefined
104
+ const report = auditSkillCatalog({ surfaces, reportRoot: process.cwd() })
105
+ printAudit(report, options.json)
106
+ return (process.exitCode = report.ok ? 0 : 1)
107
+ }
108
+
109
+ function runObserve(argv) {
110
+ const options = parseOptions(argv, new Set(['workflow', 'signal', 'skill', 'outcome', 'json']))
111
+ if (!options.workflow) usageError('--workflow is required')
112
+ if (!options.signal) usageError('--signal is required')
113
+ try {
114
+ const receipt = recordSkillObservation({
115
+ workspaceRoot: process.cwd(),
116
+ workflowKey: options.workflow,
117
+ signal: options.signal,
118
+ skill: options.skill ?? null,
119
+ outcome: options.outcome ?? 'unknown',
120
+ })
121
+ if (options.json) console.log(JSON.stringify(receipt, null, 2))
122
+ else console.log(`recorded ${receipt.observation.signal} for ${receipt.observation.workflowKey} as local observation ${receipt.observation.id}`)
123
+ return (process.exitCode = 0)
124
+ } catch (error) {
125
+ console.error(error.message)
126
+ process.exit(1)
127
+ }
128
+ }
129
+
130
+ function runCandidates(argv) {
131
+ const options = parseOptions(argv, new Set(['all', 'json']))
132
+ try {
133
+ const report = buildSkillCandidates({ workspaceRoot: process.cwd(), includeInsufficient: Boolean(options.all) })
134
+ if (options.json) console.log(JSON.stringify(report, null, 2))
135
+ else if (!report.candidates.length) console.log('no skill candidates meet the evidence threshold')
136
+ else {
137
+ for (const candidate of report.candidates) {
138
+ console.log(`${candidate.status}: ${candidate.kind} ${candidate.workflowKey}${candidate.skill ? ` (${candidate.skill})` : ''} from ${candidate.evidenceCount} observation(s)`)
139
+ }
140
+ }
141
+ return (process.exitCode = 0)
142
+ } catch (error) {
143
+ console.error(error.message)
144
+ process.exit(1)
145
+ }
146
+ }
147
+
148
+ function printPlan(plan, json) {
149
+ if (json) {
150
+ console.log(JSON.stringify(plan, null, 2))
151
+ return
152
+ }
153
+ console.log(`plan: ${plan.planDigest}`)
154
+ console.log(`target: ${plan.target.path}`)
155
+ for (const action of plan.actions) console.log(`${action.type}: ${action.name}`)
156
+ for (const blocker of plan.blockers) console.log(`blocker: ${blocker}`)
157
+ if (!plan.actions.length && !plan.blockers.length) console.log('no managed skill changes')
158
+ }
159
+
160
+ function runSync(argv) {
161
+ const options = parseOptions(argv, new Set(['source', 'target', 'confirm', 'json', 'apply', 'dry-run']))
162
+ if (options.apply && options['dry-run']) usageError('--apply and --dry-run are mutually exclusive')
163
+ if (options.confirm && !options.apply) usageError('--confirm requires --apply')
164
+ if (options.apply && !options.confirm) usageError('--apply requires --confirm PLAN_DIGEST')
165
+ const settings = {
166
+ workspaceRoot: process.cwd(),
167
+ sourceRoot: options.source ? path.resolve(options.source) : undefined,
168
+ target: options.target ?? path.join('.agents', 'skills'),
169
+ }
170
+ try {
171
+ if (options.apply) {
172
+ const result = applySkillSync({ ...settings, confirm: options.confirm })
173
+ if (options.json) console.log(JSON.stringify(result, null, 2))
174
+ else {
175
+ console.log(`applied: ${result.plan.planDigest}`)
176
+ for (const quarantined of result.quarantined) console.log(`quarantined: ${quarantined}`)
177
+ }
178
+ return (process.exitCode = 0)
179
+ }
180
+ const plan = planSkillSync(settings)
181
+ printPlan(plan, options.json)
182
+ return (process.exitCode = plan.applyAllowed ? 0 : 1)
183
+ } catch (error) {
184
+ console.error(error.message)
185
+ process.exit(1)
186
+ }
187
+ }
188
+
189
+ const argv = process.argv.slice(2)
190
+ const subcommand = argv[0]
191
+ if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
192
+ console.log(USAGE)
193
+ process.exit(0)
194
+ }
195
+ if (subcommand === 'audit' || subcommand === undefined) runAudit(argv.slice(subcommand ? 1 : 0))
196
+ else if (subcommand === 'observe') runObserve(argv.slice(1))
197
+ else if (subcommand === 'candidates') runCandidates(argv.slice(1))
198
+ else if (subcommand === 'sync') runSync(argv.slice(1))
199
+ else usageError(`unknown skills subcommand: ${subcommand}`)
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ enrollRepository,
4
+ executeUserConfirmedCommit,
5
+ obsidianMaintenanceNotice,
6
+ operationTrace,
7
+ planUserConfirmedCommit,
8
+ reconcileRepository,
9
+ runtimeStatus,
10
+ setRepositoryPaused,
11
+ } from '../runtime/supervisor.mjs'
12
+ import { firstString, parseArgs } from '../project/config.mjs'
13
+
14
+ function valuesFor(argv, name) {
15
+ const values = []
16
+ for (let index = 0; index < argv.length; index += 1) {
17
+ const arg = argv[index]
18
+ if (arg === `--${name}` && argv[index + 1] && !argv[index + 1].startsWith('--')) {
19
+ values.push(argv[index + 1])
20
+ index += 1
21
+ } else if (arg.startsWith(`--${name}=`)) {
22
+ values.push(arg.slice(name.length + 3))
23
+ }
24
+ }
25
+ return values
26
+ }
27
+
28
+ function integer(value, fallback) {
29
+ if (value == null || value === true) return fallback
30
+ const parsed = Number(value)
31
+ if (!Number.isInteger(parsed) || parsed < 1) throw new Error('interval and retry values must be positive integers')
32
+ return parsed
33
+ }
34
+
35
+ function print(value) {
36
+ console.log(JSON.stringify(value, null, 2))
37
+ }
38
+
39
+ // Status only gains a member when the enrolled project enables Obsidian maintenance; otherwise it is unchanged.
40
+ function withMaintenanceNotice(result) {
41
+ const notice = obsidianMaintenanceNotice({ repoPath })
42
+ return notice === null ? result : { ...result, obsidianMaintenance: notice }
43
+ }
44
+
45
+ async function runLoop({ repoPath, intervalMs, fetchAttempts, once }) {
46
+ do {
47
+ const result = reconcileRepository({ repoPath, fetchAttempts })
48
+ print(result)
49
+ if (once) return result.ok ? 0 : 1
50
+ await new Promise((resolve) => setTimeout(resolve, intervalMs))
51
+ } while (true)
52
+ }
53
+
54
+ const argv = process.argv.slice(2)
55
+ const args = parseArgs(argv)
56
+ const subcommand = args._[0] || 'status'
57
+ const repoPath = firstString(args.repo) || process.cwd()
58
+
59
+ try {
60
+ if (subcommand === 'enroll') {
61
+ const result = enrollRepository({ repoPath, projectConfig: firstString(args.project, args['project-config']), gitExecutable: firstString(args.git) })
62
+ print(result)
63
+ process.exitCode = result.ok ? 0 : 1
64
+ } else if (subcommand === 'status' || subcommand === 'audit') {
65
+ const result = runtimeStatus({ repoPath })
66
+ print(withMaintenanceNotice(result))
67
+ process.exitCode = result.ok ? 0 : 1
68
+ } else if (subcommand === 'reconcile') {
69
+ const result = reconcileRepository({ repoPath, fetchAttempts: integer(args.retries, 3) })
70
+ print(result)
71
+ process.exitCode = result.ok ? 0 : 1
72
+ } else if (subcommand === 'run') {
73
+ process.exitCode = await runLoop({
74
+ repoPath,
75
+ intervalMs: integer(args.interval, 30) * 1000,
76
+ fetchAttempts: integer(args.retries, 3),
77
+ once: Boolean(args.once),
78
+ })
79
+ } else if (subcommand === 'plan') {
80
+ print(planUserConfirmedCommit({
81
+ repoPath,
82
+ paths: valuesFor(argv, 'path'),
83
+ message: firstString(args.message),
84
+ publish: Boolean(args.publish),
85
+ fetchAttempts: integer(args.retries, 3),
86
+ }))
87
+ } else if (subcommand === 'commit') {
88
+ const result = executeUserConfirmedCommit({
89
+ repoPath,
90
+ operationId: firstString(args.operation),
91
+ confirmation: firstString(args.confirm),
92
+ })
93
+ print(result)
94
+ process.exitCode = result.ok ? 0 : 1
95
+ } else if (subcommand === 'pause' || subcommand === 'freeze') {
96
+ print(setRepositoryPaused({ repoPath, paused: true, reason: firstString(args.reason) || (subcommand === 'freeze' ? 'frozen by user' : 'paused by user') }))
97
+ } else if (subcommand === 'resume') {
98
+ print(setRepositoryPaused({ repoPath, paused: false }))
99
+ } else if (subcommand === 'trace') {
100
+ print({ ok: true, operations: operationTrace({ repoPath }) })
101
+ } else {
102
+ throw new Error(`unknown sync command: ${subcommand}`)
103
+ }
104
+ } catch (error) {
105
+ console.error(error.message)
106
+ process.exitCode = 1
107
+ }
@@ -0,0 +1,27 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { parseDefinition, parsePlacement, need, isRef, deepFreeze } from './wire.mjs';
3
+ export const digestBytes = bytes => createHash('sha256').update(bytes).digest('hex');
4
+
5
+ // A host-owned immutable snapshot, not a mutable catalogue or remote importer.
6
+ export function createRegistrySnapshot({ definitionBytes, rendererBytes, revision, lifecycle = 'active', allowDeprecated = false, validateProps, validateIntent }) {
7
+ const definition = deepFreeze(parseDefinition(definitionBytes));
8
+ need(isRef(revision) && ['active', 'deprecated', 'withdrawn', 'revoked'].includes(lifecycle));
9
+ need(typeof validateProps === 'function' && typeof validateIntent === 'function');
10
+ const definitionDigest = digestBytes(definitionBytes), rendererDigest = digestBytes(rendererBytes);
11
+ return Object.freeze({ definition, revision, definitionDigest, rendererDigest, lifecycle,
12
+ resolve({ sourceBytes, sourceRef, request, operationRef, serviceRef }) {
13
+ need(lifecycle === 'active' || (lifecycle === 'deprecated' && allowDeprecated));
14
+ const placement = deepFreeze(parsePlacement(sourceBytes));
15
+ need(isRef(sourceRef) && isRef(serviceRef) && isRef(operationRef));
16
+ need(placement.componentId === definition.componentId && placement.componentVersion === definition.version);
17
+ need(placement.placementId === request.placementId && digestBytes(sourceBytes) === request.sourceDigest);
18
+ need(definition.previewModes.includes(request.mode) && definition.projections.includes('web'));
19
+ need(validateProps(placement.props) === true);
20
+ need(placement.dataBindings.length === 1 && definition.dataContractRefs.includes(placement.dataBindings[0].contractRef));
21
+ if (request.intentRef) need(validateIntent(request.intentRef, request.payload) === true);
22
+ return deepFreeze({ resourceRef: placement.dataBindings[0].resourceRef, operationRef, serviceRef, sourceRef,
23
+ placementRef: placement.placementId, sourceDigest: request.sourceDigest, descriptorDigest: definitionDigest,
24
+ rendererDigest, registryRevision: revision, warning: lifecycle === 'deprecated' ? 'deprecated' : null });
25
+ },
26
+ });
27
+ }
@@ -0,0 +1,122 @@
1
+ // Opt-in experimental wire parser. No identity, URL resolution or execution.
2
+ export const MODES = Object.freeze(['synthetic', 'connected-read-only', 'sandbox-interactive']);
3
+ const REF = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,159}$/;
4
+ const HASH = /^[a-f0-9]{64}$/;
5
+ const VERSION = /^\d+\.\d+\.\d+$/;
6
+ const forbidden = new Set(['__proto__', 'constructor', 'prototype']);
7
+ const encoder = new TextEncoder();
8
+ const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
9
+ export function need(value, message = 'Invalid bounded composition data') { if (!value) throw new TypeError(message); }
10
+ export const isRef = value => typeof value === 'string' && REF.test(value);
11
+ export const isDigest = value => typeof value === 'string' && HASH.test(value);
12
+ export function closed(value, required, optional = []) {
13
+ need(value && typeof value === 'object' && !Array.isArray(value));
14
+ need(required.every(key => Object.hasOwn(value, key)) && Object.keys(value).every(key => required.includes(key) || optional.includes(key)));
15
+ }
16
+
17
+ // JSON.parse alone accepts duplicate keys. This small JSON grammar refuses them
18
+ // at every depth, before schema validation; it never evaluates source text.
19
+ export function parseBoundedJSON(input) {
20
+ need(typeof input === 'string' || input instanceof Uint8Array);
21
+ const bytes = typeof input === 'string' ? encoder.encode(input) : input;
22
+ need(bytes.byteLength <= 65536);
23
+ let text = decoder.decode(bytes);
24
+ if (typeof input === 'string') need(text === input, 'Lossy Unicode refused');
25
+ if (text.charCodeAt(0) === 0xfeff) text = text.slice(1);
26
+ let i = 0;
27
+ const ws = () => { while (/[\x20\t\r\n]/.test(text[i] || '\0')) i++; };
28
+ const string = () => {
29
+ const start = i++;
30
+ while (i < text.length) {
31
+ const c = text[i++];
32
+ if (c === '\\') { i++; continue; }
33
+ if (c === '"') {
34
+ const value = JSON.parse(text.slice(start, i));
35
+ need(decoder.decode(encoder.encode(value)) === value, 'Lossy Unicode refused');
36
+ return value;
37
+ }
38
+ }
39
+ throw new TypeError('Unterminated JSON string');
40
+ };
41
+ function value(depth) {
42
+ need(depth <= 8); ws();
43
+ if (text[i] === '"') return string();
44
+ if (text[i] === '{') {
45
+ i++; ws(); const result = Object.create(null);
46
+ if (text[i] === '}') { i++; return result; }
47
+ while (i < text.length) {
48
+ ws(); need(text[i] === '"'); const key = string();
49
+ need(!Object.hasOwn(result, key), 'Duplicate JSON key refused');
50
+ ws(); need(text[i++] === ':'); result[key] = value(depth + 1); ws();
51
+ if (text[i] === '}') { i++; return result; }
52
+ need(text[i++] === ',');
53
+ }
54
+ } else if (text[i] === '[') {
55
+ i++; ws(); const result = [];
56
+ if (text[i] === ']') { i++; return result; }
57
+ while (i < text.length) {
58
+ result.push(value(depth + 1)); ws();
59
+ if (text[i] === ']') { i++; return result; }
60
+ need(text[i++] === ',');
61
+ }
62
+ } else {
63
+ const match = /^(?:true|false|null|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)/.exec(text.slice(i));
64
+ need(match); i += match[0].length;
65
+ const result = JSON.parse(match[0]); need(typeof result !== 'number' || Number.isFinite(result)); return result;
66
+ }
67
+ throw new TypeError('Invalid JSON');
68
+ }
69
+ const result = value(0); ws(); need(i === text.length); return result;
70
+ }
71
+ export function scalarMap(value) {
72
+ need(value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length <= 64);
73
+ for (const [key, v] of Object.entries(value)) {
74
+ need(/^[a-z][a-zA-Z0-9_]{0,79}$/.test(key) && !forbidden.has(key));
75
+ need(v === null || typeof v === 'boolean' || (typeof v === 'number' && Number.isFinite(v)) || (typeof v === 'string' && v.length <= 4096));
76
+ }
77
+ return value;
78
+ }
79
+ function refs(values, maximum, allowed) {
80
+ need(Array.isArray(values) && values.length <= maximum && new Set(values).size === values.length);
81
+ need(values.every(v => allowed ? allowed.includes(v) : isRef(v)));
82
+ }
83
+ function version(v) { need(typeof v === 'string' && v.length <= 32 && VERSION.test(v)); }
84
+ export function parseDefinition(bytes) {
85
+ const v = parseBoundedJSON(bytes);
86
+ closed(v, ['schema', 'componentId', 'version', 'rendererRef', 'propsSchemaRef', 'dataContractRefs', 'projections', 'previewModes']);
87
+ need(v.schema === 'atelier-component-definition/proposal-v1');
88
+ for (const key of ['componentId', 'rendererRef', 'propsSchemaRef']) need(isRef(v[key]));
89
+ version(v.version); refs(v.dataContractRefs, 16); refs(v.projections, 4, ['web', 'native', 'desktop', 'document']); refs(v.previewModes, 3, MODES);
90
+ return v;
91
+ }
92
+ export function parsePlacement(bytes) {
93
+ const v = parseBoundedJSON(bytes);
94
+ closed(v, ['schema', 'placementId', 'componentId', 'componentVersion', 'slotRef', 'props', 'dataBindings']);
95
+ need(v.schema === 'atelier-component-placement/proposal-v1');
96
+ for (const key of ['placementId', 'componentId', 'slotRef']) need(isRef(v[key]));
97
+ version(v.componentVersion); scalarMap(v.props);
98
+ need(Array.isArray(v.dataBindings) && v.dataBindings.length <= 16);
99
+ const seen = new Set();
100
+ for (const binding of v.dataBindings) {
101
+ closed(binding, ['contractRef', 'resourceRef']); need(isRef(binding.contractRef) && isRef(binding.resourceRef) && !seen.has(binding.contractRef)); seen.add(binding.contractRef);
102
+ }
103
+ return v;
104
+ }
105
+ export function parseRequest(bytes) {
106
+ const v = parseBoundedJSON(bytes);
107
+ const action = v?.schema === 'atelier-component-intent/proposal-v1';
108
+ closed(v, ['schema', 'status', 'requestId', 'placementId', 'sourceDigest', 'mode', ...(action ? ['intentRef', 'payload'] : [])]);
109
+ need(action || v.schema === 'atelier-preview-request/proposal-v1'); need(v.status === 'proposed' && MODES.includes(v.mode));
110
+ need(isRef(v.requestId) && isRef(v.placementId) && isDigest(v.sourceDigest));
111
+ if (action) { need(isRef(v.intentRef)); scalarMap(v.payload); need(v.mode === 'sandbox-interactive'); }
112
+ return v;
113
+ }
114
+ export function deepFreeze(value) {
115
+ if (value && typeof value === 'object') { for (const item of Object.values(value)) deepFreeze(item); Object.freeze(value); }
116
+ return value;
117
+ }
118
+ export function canonical(value) {
119
+ if (Array.isArray(value)) return '[' + value.map(canonical).join(',') + ']';
120
+ if (value && typeof value === 'object') return '{' + Object.keys(value).sort().map(key => JSON.stringify(key) + ':' + canonical(value[key])).join(',') + '}';
121
+ return JSON.stringify(value);
122
+ }
@@ -26,6 +26,31 @@ import { fileURLToPath } from 'node:url'
26
26
  // document, not a schema, and is deliberately absent.
27
27
 
28
28
  export const CONTRACT_CORPUS = [
29
+ { name: 'atelier-decision-request', contractFile: 'contracts/atelier-decision-request.v1.schema.json', fixtureRoot: 'fixtures/decisions/request', registry: false },
30
+ { name: 'atelier-decision-result', contractFile: 'contracts/atelier-decision-result.v1.schema.json', fixtureRoot: 'fixtures/decisions/result', registry: false },
31
+ { name: 'atelier-adoption-policy-v1', contractFile: 'contracts/atelier-adoption-policy.v1.schema.json', fixtureRoot: 'fixtures/atelier-upgrade-transaction/adoption-policy', registry: false },
32
+ { name: 'atelier-migration-v2', contractFile: 'contracts/atelier-migration.v2.schema.json', fixtureRoot: 'fixtures/atelier-upgrade-transaction/migration', registry: false },
33
+ { name: 'atelier-upgrade-plan-v2', contractFile: 'contracts/atelier-upgrade-plan.v2.schema.json', fixtureRoot: 'fixtures/atelier-upgrade-transaction/upgrade-plan', registry: false },
34
+ { name: 'atelier-upgrade-receipt-v1', contractFile: 'contracts/atelier-upgrade-receipt.v1.schema.json', fixtureRoot: 'fixtures/atelier-upgrade-transaction/upgrade-receipt', registry: false },
35
+ { name: 'atelier-presentation', contractFile: 'contracts/atelier-presentation.v1.schema.json', validFiles: ['fixtures/ui/presentation/reference.v1.json'], registry: false },
36
+ { name: 'atelier-pane-presentation', contractFile: 'contracts/atelier-pane-presentation.v1.schema.json', validFiles: ['fixtures/ui/presentation/pane.v1.json'], registry: false },
37
+ { name: 'atelier-vault-publication', contractFile: 'contracts/atelier-vault-publication.v1.schema.json', fixtureRoot: 'fixtures/atelier-vault-publication', registry: true },
38
+ ...['guide', 'intake'].map(kind => ({
39
+ name: `atelier-${kind}`, contractFile: `contracts/atelier-${kind}.v1.schema.json`,
40
+ fixtureRoot: `fixtures/atelier-${kind}`, registry: true,
41
+ })),
42
+ ...[['audit', 'auditReport'], ['candidates', 'candidateReport'], ['plan', 'syncPlan'], ['lock', 'syncLock']].map(([kind, definition]) => ({
43
+ name: `atelier-skill-${kind}`, contractFile: 'contracts/atelier-skill-steward.v1.schema.json',
44
+ docPointer: `#/$defs/${definition}`, fixtureRoot: `fixtures/atelier-skill-steward/${kind}`, registry: true,
45
+ })),
46
+ {name: 'atelier-coauthor', contractFile: 'contracts/atelier-coauthor.v1.schema.json', fixtureRoot: 'fixtures/atelier-coauthor', registry: true},
47
+ {name: 'atelier-review', contractFile: 'contracts/atelier-review.v1.schema.json', fixtureRoot: 'fixtures/atelier-review', registry: false},
48
+ {
49
+ name: 'atelier-repository-observation',
50
+ contractFile: 'contracts/atelier-repository-observation.v1.schema.json',
51
+ fixtureRoot: 'fixtures/atelier-repository-observation',
52
+ registry: true,
53
+ },
29
54
  {
30
55
  name: 'atelier-lock',
31
56
  contractFile: 'contracts/atelier-lock.v1.schema.json',
@@ -136,6 +161,16 @@ export const CONTRACT_CORPUS = [
136
161
  invalidFiles: ['fixtures/analysis-adapter/invalid/manifest-hidden-provider.v1.json'],
137
162
  registry: true,
138
163
  },
164
+ // Obsidian projection contracts. test/obsidian-contract.test.mjs owns their
165
+ // positive and negative coverage (several refusals are semantic, beyond what
166
+ // a schema matcher can express), so they carry registry: false.
167
+ ...[
168
+ 'corpus-profile', 'scope', 'source-snapshot', 'generation-manifest', 'publication-journal', 'service-state',
169
+ 'edit-operation', 'apply-policy', 'proposal-receipt', 'acceptance-receipt', 'ext-settings',
170
+ ].map((shape) => ({
171
+ name: `atelier-obsidian-${shape}`, contractFile: `contracts/atelier-obsidian-${shape}.v1.schema.json`,
172
+ fixtureRoot: `fixtures/obsidian/contracts/${shape}`, registry: false,
173
+ })),
139
174
  {
140
175
  name: 'analysis-adapter-claim-output',
141
176
  contractFile: 'contracts/analysis-adapter.v1.schema.json',