@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
@@ -0,0 +1,223 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import {
4
+ loadBoundRun,
5
+ hashEvidence,
6
+ validBoundRun,
7
+ } from '../readiness-protocols/evidence.mjs'
8
+ import { validReviewArtifact } from './review-contracts.mjs'
9
+ import { createReviewStore, validContribution } from './review-store.mjs'
10
+ import {
11
+ compileDisclosurePatterns,
12
+ scanDisclosureText,
13
+ } from '../disclosure/content-scan.mjs'
14
+ import { openRegularFileNoFollow } from '../project/private-state.mjs'
15
+
16
+ function prohibitedKeys(value, depth = 0) {
17
+ if (
18
+ typeof value === 'string' &&
19
+ (/^(?:[A-Za-z]:[\\/]|\\\\|\/(?!\/))/.test(value) ||
20
+ /[a-z][a-z0-9+.-]*:\/\/[^/\s]*@/i.test(value))
21
+ )
22
+ throw new Error(
23
+ 'machine path or credential-bearing URL in selected content',
24
+ )
25
+ if (depth > 32) throw new Error('bundle nesting exceeds bounds')
26
+ if (value && typeof value === 'object')
27
+ for (const [key, child] of Object.entries(value)) {
28
+ if (
29
+ /nonce|session|grant|credential|password|token|secret|private.?key|authorization/i.test(
30
+ key,
31
+ )
32
+ )
33
+ throw new Error('nontransferable field in selected content')
34
+ prohibitedKeys(child, depth + 1)
35
+ }
36
+ }
37
+ export function prepareInspectionBundle(
38
+ project,
39
+ runIds,
40
+ { denylistDocument, responseIds = [] } = {},
41
+ ) {
42
+ if (!denylistDocument || !Array.isArray(denylistDocument.patterns))
43
+ throw new Error('private disclosure policy required before export')
44
+ if (
45
+ !Array.isArray(runIds) ||
46
+ !runIds.length ||
47
+ runIds.length > 20 ||
48
+ new Set(runIds).size !== runIds.length
49
+ )
50
+ throw new Error('select 1 to 20 unique run IDs')
51
+ const history = createReviewStore(project).records()
52
+ if (!history.ok) throw new Error(history.error)
53
+ if (
54
+ !Array.isArray(responseIds) ||
55
+ responseIds.length > 2000 ||
56
+ new Set(responseIds).size !== responseIds.length
57
+ )
58
+ throw new Error('invalid response selection')
59
+ const responses = responseIds.map((id) => {
60
+ const record = history.records.find(
61
+ (entry) =>
62
+ entry.input.requestId === id && entry.input.kind !== 'decision',
63
+ )
64
+ if (!record) throw new Error('selected response unavailable')
65
+ return record
66
+ })
67
+ prohibitedKeys(responses)
68
+ const members = runIds.map((id) => {
69
+ const bound = loadBoundRun(project, id)
70
+ if (!bound.ok) throw new Error(bound.error)
71
+ const value = {
72
+ schema: bound.schema,
73
+ run: bound.run,
74
+ runDigest: bound.runDigest,
75
+ snapshot: bound.snapshot,
76
+ decisions: history.records.filter(
77
+ (record) =>
78
+ record.input.kind === 'decision' && record.input.runId === id,
79
+ ),
80
+ }
81
+ prohibitedKeys(value)
82
+ return {
83
+ id,
84
+ bytes: Buffer.byteLength(JSON.stringify(value)),
85
+ digest: hashEvidence(value),
86
+ value,
87
+ }
88
+ })
89
+ const body = {
90
+ schema: 'atelier-inspection-bundle@v1',
91
+ purpose: 'historical-inspection-only',
92
+ classification: 'owner-selected-private-review',
93
+ members,
94
+ responses,
95
+ }
96
+ const bytes = JSON.stringify(body, null, 2)
97
+ const disclosure = scanDisclosureText(bytes, {
98
+ denylistPatterns: compileDisclosurePatterns(denylistDocument.patterns),
99
+ })
100
+ if (!disclosure.ok)
101
+ throw new Error('selected content failed disclosure policy; export refused')
102
+ const bundle = { ...body, digest: hashEvidence(body) }
103
+ inspectBundle(bundle)
104
+ return bundle
105
+ }
106
+ export function inspectBundle(bundle) {
107
+ const serialized = JSON.stringify(bundle)
108
+ if (Buffer.byteLength(serialized) > 4 * 1024 * 1024)
109
+ throw new Error('bundle exceeds byte bound')
110
+ if (!validReviewArtifact('bundle', bundle))
111
+ throw new Error('inspection bundle contract invalid')
112
+ if (
113
+ !bundle ||
114
+ bundle.schema !== 'atelier-inspection-bundle@v1' ||
115
+ bundle.purpose !== 'historical-inspection-only' ||
116
+ !Array.isArray(bundle.members) ||
117
+ !bundle.members.length ||
118
+ bundle.members.length > 20 ||
119
+ Object.keys(bundle).some(
120
+ (key) =>
121
+ ![
122
+ 'schema',
123
+ 'purpose',
124
+ 'classification',
125
+ 'members',
126
+ 'responses',
127
+ 'digest',
128
+ 'ext',
129
+ 'contractVersion',
130
+ ].includes(key),
131
+ )
132
+ )
133
+ throw new Error('unsupported inspection bundle')
134
+ const { digest, ...body } = bundle
135
+ if (digest !== hashEvidence(body)) throw new Error('bundle digest mismatch')
136
+ const ids = new Set(),
137
+ requests = new Set()
138
+ const verifyRecord = (record) => {
139
+ if (!validContribution(record) || requests.has(record.input.requestId))
140
+ throw new Error('invalid or duplicate historical contribution')
141
+ requests.add(record.input.requestId)
142
+ }
143
+ prohibitedKeys(bundle.responses)
144
+ for (const record of bundle.responses) {
145
+ verifyRecord(record)
146
+ if (record.input.kind === 'decision')
147
+ throw new Error('decision outside run member')
148
+ }
149
+ for (const member of bundle.members) {
150
+ if (
151
+ !member ||
152
+ !/^readiness-run-[a-z0-9-]+$/.test(member.id ?? '') ||
153
+ ids.has(member.id) ||
154
+ Object.keys(member).some(
155
+ (key) => !['id', 'bytes', 'digest', 'value', 'ext'].includes(key),
156
+ ) ||
157
+ member.digest !== hashEvidence(member.value) ||
158
+ member.bytes !== Buffer.byteLength(JSON.stringify(member.value))
159
+ )
160
+ throw new Error('invalid, duplicate or changed bundle member')
161
+ prohibitedKeys(member.value)
162
+ ids.add(member.id)
163
+ const value = member.value,
164
+ { digest: snapshotDigest, ...snapshot } = value.snapshot ?? {}
165
+ const { decisions, ...bound } = value
166
+ if (
167
+ !validBoundRun(bound) ||
168
+ value.schema !== 'atelier-bound-run@v1' ||
169
+ value.run?.runId !== member.id ||
170
+ value.runDigest !== hashEvidence(value.run) ||
171
+ snapshotDigest !== hashEvidence(snapshot) ||
172
+ !Array.isArray(value.decisions)
173
+ )
174
+ throw new Error('invalid historical evidence')
175
+ for (const record of value.decisions) {
176
+ verifyRecord(record)
177
+ if (
178
+ record.input.kind !== 'decision' ||
179
+ record.input.runId !== member.id ||
180
+ record.input.evidenceDigest !== snapshotDigest ||
181
+ !value.run.claims.some(
182
+ (claim) => claim.claimId === record.input.targetId,
183
+ )
184
+ )
185
+ throw new Error('decision does not bind selected evidence')
186
+ }
187
+ }
188
+ return {
189
+ ok: true,
190
+ schema: 'atelier-inspection-report@v1',
191
+ approvalAuthority: false,
192
+ authorship: 'unverified-historical-assertions',
193
+ writes: false,
194
+ members: bundle.members,
195
+ responses: bundle.responses,
196
+ }
197
+ }
198
+ export function readInspectionBundle(file) {
199
+ const descriptor = openRegularFileNoFollow(file)
200
+ try {
201
+ if (fs.fstatSync(descriptor).size > 4 * 1024 * 1024)
202
+ throw new Error('bundle exceeds byte bound')
203
+ return inspectBundle(JSON.parse(fs.readFileSync(descriptor, 'utf8')))
204
+ } finally {
205
+ fs.closeSync(descriptor)
206
+ }
207
+ }
208
+ export function writeInspectionBundle(file, bundle) {
209
+ inspectBundle(bundle)
210
+ if (path.extname(file) !== '.json')
211
+ throw new Error('inspection bundle output must be a JSON file')
212
+ const descriptor = openRegularFileNoFollow(
213
+ file,
214
+ fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL,
215
+ 0o600,
216
+ )
217
+ try {
218
+ fs.writeFileSync(descriptor, JSON.stringify(bundle, null, 2) + '\n')
219
+ fs.fsyncSync(descriptor)
220
+ } finally {
221
+ fs.closeSync(descriptor)
222
+ }
223
+ }
@@ -0,0 +1,25 @@
1
+ import fs from 'node:fs'
2
+ import Ajv2020 from 'ajv/dist/2020.js'
3
+ import addFormats from 'ajv-formats'
4
+ const ajv = new Ajv2020({ allErrors: true, strict: false })
5
+ addFormats(ajv)
6
+ for (const name of ['atelier-review'])
7
+ ajv.addSchema(
8
+ JSON.parse(
9
+ fs.readFileSync(
10
+ new URL(`../../contracts/${name}.v1.schema.json`, import.meta.url),
11
+ 'utf8',
12
+ ),
13
+ ),
14
+ )
15
+ const validators = new Map()
16
+ export function validReviewArtifact(kind, value) {
17
+ if (!validators.has(kind))
18
+ validators.set(
19
+ kind,
20
+ ajv.compile({
21
+ $ref: `https://mnstry.ai/schemas/atelier/atelier-review.v1.schema.json#/$defs/${kind}`,
22
+ }),
23
+ )
24
+ return validators.get(kind)(value)
25
+ }
@@ -0,0 +1,305 @@
1
+ import path from 'node:path'
2
+ import { validReviewArtifact } from './review-contracts.mjs'
3
+ import { createCollaborationEventLedger } from './event-ledger.mjs'
4
+ import {
5
+ loadBoundRun,
6
+ currentRunEligibility,
7
+ hashEvidence,
8
+ readBoundedSource,
9
+ } from '../readiness-protocols/evidence.mjs'
10
+ import { buildGraph } from '../graph/graph.mjs'
11
+ import { readRegularTextNoFollow } from '../project/private-state.mjs'
12
+
13
+ const idPattern = /^[a-zA-Z0-9][a-zA-Z0-9:._-]{0,159}$/
14
+ const text = (value, max) =>
15
+ typeof value === 'string' && value.trim().length > 0 && value.length <= max
16
+ const invalid = (error, status = 400) => ({ ok: false, status, error })
17
+
18
+ export const contributionKey = (input) =>
19
+ hashEvidence({
20
+ kind: input.kind,
21
+ targetId: input.targetId,
22
+ runId: input.runId ?? null,
23
+ repo: input.repo ?? null,
24
+ path: input.path ?? null,
25
+ reader: input.kind === 'position' ? input.reviewer : null,
26
+ })
27
+ export function validContribution(record) {
28
+ return (
29
+ validReviewArtifact('record', record) &&
30
+ record.inputDigest === hashEvidence(record.input) &&
31
+ record.key === contributionKey(record.input) &&
32
+ record.version === record.input.expectedVersion + 1 &&
33
+ record.identity.name === record.input.reviewer
34
+ )
35
+ }
36
+
37
+ export function createReviewStore(project) {
38
+ const ledger = createCollaborationEventLedger({
39
+ workspaceRoot: project.configDir,
40
+ ledgerPath: path.join(
41
+ project.configDir,
42
+ '.atelier-local/review/contributions.ndjson',
43
+ ),
44
+ })
45
+ function records() {
46
+ const all = ledger.readAll()
47
+ if (!all.ok) return all
48
+ const result = all.events.map((event) => event.payload)
49
+ const versions = new Map(),
50
+ requests = new Set()
51
+ for (let index = 0; index < result.length; index++) {
52
+ const record = result[index],
53
+ event = all.events[index]
54
+ if (
55
+ !validContribution(record) ||
56
+ requests.has(record.input.requestId) ||
57
+ event.aggregateId !== `contribution:${record.input.requestId}` ||
58
+ event.type !== 'contribution-recorded' ||
59
+ event.actor !== record.input.reviewer ||
60
+ record.version !== (versions.get(record.key) ?? 0) + 1
61
+ )
62
+ return invalid('contribution history is invalid', 422)
63
+ requests.add(record.input.requestId)
64
+ versions.set(record.key, record.version)
65
+ }
66
+ return { ok: true, status: 200, records: result }
67
+ }
68
+ function contribution(input) {
69
+ if (!input || typeof input !== 'object' || Array.isArray(input))
70
+ return invalid('contribution must be an object')
71
+ const keys = [
72
+ 'requestId',
73
+ 'kind',
74
+ 'targetId',
75
+ 'expectedVersion',
76
+ 'reviewer',
77
+ 'rationale',
78
+ 'runId',
79
+ 'evidenceDigest',
80
+ 'decision',
81
+ 'revision',
82
+ 'repo',
83
+ 'path',
84
+ 'documentDigest',
85
+ 'anchor',
86
+ 'responseType',
87
+ 'wording',
88
+ 'position',
89
+ 'ext',
90
+ ]
91
+ if (Object.keys(input).some((key) => !keys.includes(key)))
92
+ return invalid('unknown contribution field')
93
+ if (
94
+ !idPattern.test(input.requestId ?? '') ||
95
+ !idPattern.test(input.targetId ?? '') ||
96
+ !Number.isSafeInteger(input.expectedVersion) ||
97
+ input.expectedVersion < 0 ||
98
+ !text(input.reviewer, 160) ||
99
+ input.reviewer !== input.reviewer.trim()
100
+ )
101
+ return invalid(
102
+ 'request identity, expected version and reviewer are required',
103
+ )
104
+ if (!['decision', 'response', 'position'].includes(input.kind))
105
+ return invalid('unsupported contribution kind')
106
+ if (
107
+ input.kind === 'decision' &&
108
+ (!['accepted', 'rejected', 'revised'].includes(input.decision) ||
109
+ !text(input.rationale, 8000) ||
110
+ !text(input.runId, 160) ||
111
+ !/^sha256:[a-f0-9]{64}$/.test(input.evidenceDigest ?? '') ||
112
+ (input.decision === 'revised' && !text(input.revision, 8000)))
113
+ )
114
+ return invalid('decision, rationale and evidence identity are required')
115
+ if (
116
+ input.kind !== 'decision' &&
117
+ (!text(input.repo, 160) ||
118
+ !text(input.path, 2048) ||
119
+ !/^sha256:[a-f0-9]{64}$/.test(input.documentDigest ?? '') ||
120
+ !text(input.anchor, 8000))
121
+ )
122
+ return invalid('document identity and passage anchor are required')
123
+ if (
124
+ input.kind === 'response' &&
125
+ (!['question', 'correction', 'discussion'].includes(input.responseType) ||
126
+ !text(input.wording, 16000))
127
+ )
128
+ return invalid('response type and original wording are required')
129
+ if (
130
+ input.kind === 'position' &&
131
+ (!Number.isSafeInteger(input.position) || input.position < 0)
132
+ )
133
+ return invalid('reading position is required')
134
+ if (!validReviewArtifact('input', input))
135
+ return invalid(
136
+ 'contribution contract invalid or contains fields for a different kind',
137
+ )
138
+ const inputDigest = hashEvidence(input)
139
+ const history = records()
140
+ if (!history.ok) return history
141
+ const previous = history.records.find(
142
+ (record) => record.input.requestId === input.requestId,
143
+ )
144
+ if (previous)
145
+ return previous.inputDigest === inputDigest
146
+ ? { ok: true, status: 200, record: previous, replayed: true }
147
+ : invalid('request ID reused with different content', 409)
148
+ const key = contributionKey(input)
149
+ const payload = {
150
+ schema: 'atelier-contribution@v1',
151
+ input,
152
+ inputDigest,
153
+ key,
154
+ version: input.expectedVersion + 1,
155
+ identity: { kind: 'local-asserted', name: input.reviewer },
156
+ recordedAt: new Date().toISOString(),
157
+ }
158
+ const appended = ledger.append({
159
+ aggregateId: `contribution:${input.requestId}`,
160
+ expectedVersion: 0,
161
+ type: 'contribution-recorded',
162
+ actor: input.reviewer,
163
+ payload,
164
+ precondition: () => {
165
+ const current = records()
166
+ if (!current.ok) return current
167
+ const count = current.records.filter(
168
+ (record) => record.key === key,
169
+ ).length
170
+ if (count !== input.expectedVersion)
171
+ return invalid('stale contribution version', 409)
172
+ if (input.kind === 'decision') {
173
+ const bound = loadBoundRun(project, input.runId)
174
+ if (!bound.ok) return bound
175
+ if (
176
+ input.decision === 'accepted' &&
177
+ (bound.run.blockers.length ||
178
+ bound.snapshot.claimEvidence[
179
+ bound.run.claims.findIndex(
180
+ (claim) => claim.claimId === input.targetId,
181
+ )
182
+ ]?.status !== 'linked-source')
183
+ )
184
+ return invalid(
185
+ 'required input or mapped source evidence is incomplete; acceptance refused',
186
+ 409,
187
+ )
188
+ if (
189
+ bound.snapshot.digest !== input.evidenceDigest ||
190
+ !bound.run.claims.some(
191
+ (claim) => claim.claimId === input.targetId,
192
+ ) ||
193
+ !currentRunEligibility(project, bound)
194
+ )
195
+ return invalid('claim evidence changed or is unavailable', 409)
196
+ } else {
197
+ const source = document(input.repo, input.path)
198
+ if (!source.ok) return source
199
+ if (
200
+ source.digest !== input.documentDigest ||
201
+ !source.text.includes(input.anchor)
202
+ )
203
+ return invalid(
204
+ 'document changed or anchor is missing; reassociation required',
205
+ 409,
206
+ )
207
+ if (
208
+ input.kind === 'position' &&
209
+ (input.position > source.text.length ||
210
+ !source.text.slice(input.position).startsWith(input.anchor))
211
+ )
212
+ return invalid('position does not match the anchored passage')
213
+ }
214
+ return { ok: true }
215
+ },
216
+ })
217
+ if (!appended.ok) {
218
+ if (appended.status === 409) {
219
+ const latest = records()
220
+ const duplicate =
221
+ latest.ok &&
222
+ latest.records.find(
223
+ (record) => record.input.requestId === input.requestId,
224
+ )
225
+ if (duplicate?.inputDigest === inputDigest)
226
+ return { ok: true, status: 200, record: duplicate, replayed: true }
227
+ }
228
+ return appended
229
+ }
230
+ return { ok: true, status: 200, record: payload, replayed: false }
231
+ }
232
+ function document(repoName, relative) {
233
+ const repo = project.repos.find(
234
+ (entry) => entry.name === repoName && !entry.external,
235
+ )
236
+ if (
237
+ !repo?.path ||
238
+ !/\.(md|txt)$/i.test(relative ?? '') ||
239
+ relative.split(/[\\/]/).some((segment) => segment.startsWith('.'))
240
+ )
241
+ return invalid('document is outside the review surface', 403)
242
+ try {
243
+ if (
244
+ project.configPath &&
245
+ hashEvidence(
246
+ JSON.parse(readRegularTextNoFollow(project.configPath)),
247
+ ) !== hashEvidence(project.config)
248
+ )
249
+ return invalid('project changed; restart review', 409)
250
+ const graph = buildGraph(project)
251
+ if (
252
+ graph.errors.length ||
253
+ !graph.nodes.some(
254
+ (node) => node.repo === repoName && node.path === relative,
255
+ )
256
+ )
257
+ return invalid('document is not in the valid source graph', 403)
258
+ return {
259
+ ok: true,
260
+ status: 200,
261
+ ...readBoundedSource(repo.path, relative),
262
+ }
263
+ } catch {
264
+ return invalid('document unavailable or unsafe', 409)
265
+ }
266
+ }
267
+ function handoff(requestId) {
268
+ const all = records()
269
+ if (!all.ok) return all
270
+ const record = all.records.find(
271
+ (entry) =>
272
+ entry.input.requestId === requestId &&
273
+ entry.input.kind === 'decision' &&
274
+ entry.input.decision === 'accepted',
275
+ )
276
+ if (!record) return invalid('accepted decision unavailable', 404)
277
+ const bound = loadBoundRun(project, record.input.runId)
278
+ if (!bound.ok) return bound
279
+ const latest = all.records
280
+ .filter((entry) => entry.key === record.key)
281
+ .at(-1)
282
+ return {
283
+ ok: true,
284
+ status: 200,
285
+ schema: 'atelier-owner-handoff@v1',
286
+ decision: record,
287
+ claim: bound.run.claims.find(
288
+ (claim) => claim.claimId === record.input.targetId,
289
+ ),
290
+ requiredEvidenceDigest: bound.snapshot.digest,
291
+ current:
292
+ latest.input.requestId === requestId &&
293
+ currentRunEligibility(project, bound),
294
+ supersededBy:
295
+ latest.input.requestId === requestId ? null : latest.input.requestId,
296
+ sourceChangesApplied: false,
297
+ promotion: 'not-established',
298
+ instructions:
299
+ 'Source owner must review the intended edit against current source and record application separately.',
300
+ }
301
+ }
302
+ // One immutable event per aggregate means generic compaction retains every
303
+ // contribution. Per-target versions are checked inside the ledger write lock.
304
+ return { records, contribute: contribution, document, handoff }
305
+ }
@@ -0,0 +1,26 @@
1
+ import { createCoauthorStore } from '../coauthor/store.mjs';
2
+
3
+ const usage = 'Usage: atelier coauthor start|read|event|recover\nRead one JSON request from stdin (maximum 1 MiB). Start: {"config":...}; read/recover: {"sessionId":...}; event: {"sessionId":...,"event":...}. Workspace is the current directory. Saves are private drafts, not source edits.';
4
+ const command = process.argv[2];
5
+ if (command === '--help' || command === 'help') { console.log(usage); process.exit(0); }
6
+ try {
7
+ if (!['start', 'read', 'event', 'recover'].includes(command) || process.argv.length !== 3) throw new Error(usage);
8
+ const chunks = [];
9
+ let count = 0;
10
+ for await (const chunk of process.stdin) {
11
+ count += chunk.length;
12
+ if (count > 1024 * 1024) throw new Error('request exceeds byte ceiling');
13
+ chunks.push(chunk);
14
+ }
15
+ const input = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks)));
16
+ if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('object required');
17
+ const allowed = command === 'start' ? ['config'] : command === 'event' ? ['sessionId', 'event'] : ['sessionId'];
18
+ if (Object.keys(input).some(key => !allowed.includes(key)) || allowed.some(key => !Object.hasOwn(input, key))) throw new Error('invalid request fields');
19
+ const store = createCoauthorStore();
20
+ const state = command === 'start' ? store.start(input.config) : command === 'read' ? store.read(input.sessionId)
21
+ : command === 'recover' ? store.recover(input.sessionId) : store.dispatch(input.sessionId, input.event);
22
+ console.log(JSON.stringify({ ok: true, state, savedMeaning: 'private-draft-only' }));
23
+ } catch (error) {
24
+ console.error(JSON.stringify({ ok: false, error: error.message }));
25
+ process.exitCode = 1;
26
+ }
@@ -1,4 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { commandProject } from '../project/config.mjs'
3
3
  const project = commandProject()
4
- console.log(JSON.stringify({ ok: true, schema: project.schema, configPath: project.configPath, repos: project.repos.map((repo) => repo.name) }, null, 2))
4
+ const report = process.argv.includes('--explain')
5
+ ? { ok: true, schema: project.schema, configSource: project.source, repos: project.repos.map((repo) => ({
6
+ name: repo.name, pathSource: repo.pathSource, resolved: repo.path !== null, readBoundary: repo.readBoundary, external: repo.external,
7
+ })) }
8
+ : { ok: true, schema: project.schema, configPath: project.configPath, repos: project.repos.map((repo) => repo.name) }
9
+ console.log(JSON.stringify(report, null, 2))
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // atelier extension-pack validate|list
3
3
  //
4
- // Self-contained subcommand parser (no shared CLI plumbing) so the module can
5
- // be invoked directly: node src/commands/extension-pack.mjs <subcommand> ...
4
+ // Shared project options also work when this module is invoked directly.
6
5
  //
7
6
  // Exit codes: 0 every enabled pack loads clean (warnings do not fail),
8
7
  // 1 an enabled pack failed to load, 2 usage or project resolution error.
@@ -13,6 +12,7 @@
13
12
  import path from 'node:path'
14
13
  import { ATELIER_EXT_NAMESPACE, loadExtensionPacks } from '../extension-packs/loader.mjs'
15
14
  import { asObject, commandProject } from '../project/config.mjs'
15
+ import { PROJECT_OPTION_SPEC, projectOptionAt } from '../cli/project-options.mjs'
16
16
 
17
17
  const REPORT_SCHEMA = 'mnstry.atelier-extension-pack-report@v1'
18
18
  const LIST_SCHEMA = 'mnstry.atelier-extension-pack-list@v1'
@@ -35,6 +35,10 @@ ext["${ATELIER_EXT_NAMESPACE}"].extensionPacks. Pack protocols are resolvable
35
35
  by full namespaced id only; the machine-local overlay can disable a declared
36
36
  pack but can never enable an undeclared one.
37
37
 
38
+ Project options: --project PATH (alias --project-config PATH) and repeated
39
+ --repo-path NAME=PATH also accept --flag=value. Repo overrides resolve from
40
+ the current directory and do not declare new repos or widen read boundaries.
41
+
38
42
  Exit codes: 0 every enabled pack loads clean (warnings do not fail),
39
43
  1 an enabled pack failed to load, 2 usage or project resolution error.`
40
44
 
@@ -43,10 +47,10 @@ function fail(message) {
43
47
  process.exit(2)
44
48
  }
45
49
 
46
- // --project / --project-config (both --flag VALUE and --flag=VALUE forms) are
50
+ // Shared project options (both --flag VALUE and --flag=VALUE forms) are
47
51
  // recognized here for the unknown-option check only; commandProject reads the
48
52
  // same process.argv itself, so the values need no forwarding.
49
- const OPTION_SPEC = { json: 'flag', project: 'value', 'project-config': 'value' }
53
+ const OPTION_SPEC = { json: 'flag', ...PROJECT_OPTION_SPEC }
50
54
 
51
55
  function parseOptions(argv) {
52
56
  const options = { json: false }
@@ -60,12 +64,15 @@ function parseOptions(argv) {
60
64
  fail(`unexpected argument: ${arg}\n\n${USAGE}`)
61
65
  }
62
66
  const name = arg.slice(2).split('=')[0]
63
- if (!(name in OPTION_SPEC)) fail(`unknown option --${name}\n\n${USAGE}`)
67
+ if (!Object.hasOwn(OPTION_SPEC, name)) fail(`unknown option --${name}\n\n${USAGE}`)
64
68
  if (OPTION_SPEC[name] === 'flag') {
65
69
  options[name] = true
66
- } else if (!arg.includes('=')) {
67
- i += 1
68
- if (argv[i] === undefined) fail(`--${name} requires a value`)
70
+ } else {
71
+ try {
72
+ i = projectOptionAt(argv, i).nextIndex - 1
73
+ } catch (error) {
74
+ fail(error.message)
75
+ }
69
76
  }
70
77
  }
71
78
  return options