@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,49 @@
1
+ ---
2
+ name: atelier-guided-coauthor
3
+ description: Guide an author through a selected packet in Codex, retaining private source-bound draft answers and resuming through the local Atelier coauthor CLI.
4
+ ---
5
+
6
+ # Guided coauthor
7
+
8
+ Use the installed `atelier coauthor` command, not a browser conversation service.
9
+ The author chooses the packet and may read it normally without this workflow.
10
+
11
+ Check the repository's instructions and local package first. Run
12
+ `./node_modules/.bin/atelier coauthor --help` from the intended repository.
13
+ Do not download another package if the command is unavailable. Explain that the
14
+ installed version needs the coauthor capability and preserve ordinary packet review.
15
+
16
+ ## Start or resume
17
+
18
+ - Require an existing Git workspace with ignored, untracked `.atelier-local/`.
19
+ Do not change global skills, sibling repositories, credentials or remote access.
20
+ - Read the selected packet. Use consumer-owned field ids and workspace-relative
21
+ source references. Compute SHA-256 over each source file's exact UTF-8 bytes.
22
+ Send a `start` request containing `config: {id, fields}`; each field has
23
+ `id` and `source: {ref, digest}`. Packet structure stays in the consumer.
24
+ - Requests are JSON on stdin; responses are JSON. Use an ignored request file
25
+ when needed, not shell interpolation of the author's text. Read the returned
26
+ state and revision. `read` takes `sessionId` and resumes retained history.
27
+ - Ask one useful question at a time. Record the author's exact answer with an
28
+ `answer` event; events carry a unique `id`, `expectedRevision` and `type`.
29
+ Do not infer an answer or approval from silence.
30
+ - If proposing edited wording, use `propose` and show the change. Wait for the
31
+ author's explicit confirmation before `confirm`. `reject` restores the
32
+ original. Even apparently stylistic rewrites need confirmation.
33
+ - Send `save` only for the agreed draft. Say “saved as a private draft” only
34
+ after the command returns a saved receipt. The source packet is never edited.
35
+ `continueAfterSave: true` on an answer retains the author's advance intent;
36
+ otherwise `advance` is explicit.
37
+ - `pause` and `resume` preserve the current phase. On restart, a `saving`
38
+ state may use `recover` to reconcile the pending private write. A
39
+ `recovery` state requires explicit `retry`, with one retry available.
40
+ Stop retrying when exhausted and retain all artifacts for inspection.
41
+
42
+ ## Boundaries
43
+
44
+ Never submit store-owned receipt/failure events or fabricate persistence evidence.
45
+ A stale source or revision is a visible conflict, not permission to overwrite it.
46
+ Keep the old history and obtain a newly bound session for revised sources.
47
+ Saved answers are proposals for later owner review, not publication, canonical
48
+ acceptance, guide access, or a completed packet. No saved-answer undo is provided;
49
+ do not erase history to simulate it. Summarize unresolved questions for the meeting.
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: atelier-guided-upgrade
3
+ description: Prepare and explain an Atelier package upgrade in an isolated candidate, retain the owner decision, and apply only the approved effects. Use for owners asking to update an existing Atelier.
4
+ ---
5
+
6
+ # Guided Atelier upgrade
7
+
8
+ The default outcome is a reviewable candidate and an owner decision. Preserve the
9
+ owner's actual request and existing scoped authority; no per-command approval
10
+ loop is needed for actions already authorized. Preparation alone never authorizes
11
+ changing the working installation, merging, publishing or restarting a service.
12
+
13
+ ## Establish the source and workspace
14
+
15
+ Read the selected repository's instructions and upgrade procedure. Identify its
16
+ actual installed scoped package, package-manager lock, Atelier lock, Git state,
17
+ project roots, packs, overlays and running-service ownership. Resolve the package
18
+ from that workspace (including a parent install); never use global Atelier,
19
+ unscoped registry fallback or the first sibling checkout found on disk.
20
+ Read `docs/guided-upgrades.md` and `docs/exact-upgrades.md` from the selected
21
+ installed package when present. If an old install lacks this skill or commands,
22
+ use the owner-selected new package's documentation only after verifying its
23
+ source. Repository instructions and owner authority still govern.
24
+
25
+ Select an exact release through a source already trusted by the owner. Record
26
+ version, source commit when verified, artifact URL/integrity and the dependency
27
+ lock. Mutable dist-tags are discovery hints, not final pins. A declaration in a
28
+ lock or a digest supplied beside a download does not authenticate a publisher.
29
+ An unpublished candidate must be labeled as such; use it only in an expressly
30
+ selected rehearsal. Never invent a release or replace a pinned install with main.
31
+
32
+ ## Prepare without disturbing authoring
33
+
34
+ Use a separate candidate with an identified base. Preserve dirty source and
35
+ private answers; do not stash, reset, clean, stop services or commit another
36
+ writer's edits to create eligibility. If using a snapshot of current edits,
37
+ record and recheck its exact manifest; it is a snapshot, not an adopted commit.
38
+ Ignored answers, browser storage and runtime credentials are not in a Git clone.
39
+ Record that gap without importing them into a public report or projection.
40
+
41
+ Stage the selected package using the owner's package manager and lock procedure,
42
+ with lifecycle scripts disabled; investigate any required scripts before execution.
43
+ Verify the resolved identity and inventory before running its commands. Inspect
44
+ migration/compatibility effects before rewriting the old Atelier lock. Retain the
45
+ old lock and all lineage. Keep installation changes distinct from generated files.
46
+
47
+ Check exact-plan eligibility: Linux/macOS, one managed repository at `.`, config
48
+ and workspace at its root, linked candidate worktree, clean state and supported
49
+ Git settings. Packs, overlays and other unsupported participants require the
50
+ repository's explicit operator procedure. Do not broaden graph roots, remove
51
+ policies or disable filters/hooks to force eligibility. Never silently fall back
52
+ to legacy `upgrade --apply`; it has different guarantees.
53
+
54
+ For an eligible candidate, prepare `upgrade plan --save`, then use
55
+ `upgrade explain --plan SAVED_PLAN_PATH` (JSON) and `--format markdown` (owner
56
+ view). Invoke the verified installed binary with the explicit project path.
57
+ Inspect `bindingsCurrent` and blockers even when the explanation exits zero.
58
+ Keep source/pin/policy installation commits and the saved projection plan separate.
59
+ For an operator procedure, retain an exact before/after file manifest and named
60
+ commands instead; do not call it an exact-plan transaction or invent its digest.
61
+
62
+ ## Explain and retain consent
63
+
64
+ Present the local benefit, exact target, changed files, preserved customizations,
65
+ checks, known limits, recovery coverage and proposed effects. Show installation,
66
+ local commit, merge, publication and activation separately. Ask one concrete
67
+ question covering the effects that still need authority. A permission to prepare
68
+ or a generic earlier “go” does not approve a newly introduced material effect.
69
+ A prior explicit approval of this same current plan and effects remains valid.
70
+
71
+ Retain a new decision record in ignored, untracked
72
+ `.atelier-local/guided-upgrades/` (or a private sibling evidence directory):
73
+ `kind: owner-decision`, report and plan/manifest SHA-256, candidate base, target
74
+ identity, exact owner words, conversation reference, observation time, decision
75
+ (approve/defer/decline), allowed effects, and
76
+ `humanApprovalAuthenticated: false`, `recordedBy: agent`.
77
+ Do not invent identity, words, references or approvals; missing provenance is a
78
+ reported gap. This is an agent-attested record, not an authenticated signature or
79
+ an execution grant. Keep existing records immutable. Silence conveys no consent.
80
+
81
+ ## Apply, validate and close
82
+
83
+ After actual approval, use the saved path and exact confirmation digest literally.
84
+ Recheck bindings; changed or expired plans require a fresh explanation and any
85
+ newly necessary decision. A consumed plan is inspected, never replayed.
86
+ Run the repository's documented acceptance checks and inspect the actual diff.
87
+ Only a verified terminal transaction receipt means a candidate commit completed.
88
+ Honor existing hooks and keep source/answer files within their ownership boundary.
89
+ An operator procedure has its own validation evidence, not a transaction receipt.
90
+
91
+ Continue authorized merge/activation steps only when included in the owner's
92
+ scope. Report prepared, installed-in-candidate, committed, merged and running as
93
+ separate observed states. A local preview passing is not recipient acceptance.
94
+ On failure, preserve the candidate, backups and receipts; explain what changed
95
+ and the next safe action. Recovery inspection does not automatically restore
96
+ files or reset Git. Do not publish source, restart an existing service or advance
97
+ an author's workflow phase merely because upgrade checks passed.
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: atelier-open
3
+ description: Open the local Atelier preview in the current client when asked to show the portal or current page.
4
+ ---
5
+
1
6
  # Open the Atelier
2
7
 
3
8
  Use this skill when someone says "open the atelier", "pull up the atelier",
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: atelier-skill-steward
3
+ description: Audit, create, improve, reconcile, retire, or update an Atelier-backed agent skill catalog using evidence thresholds, behavioral checks, and reversible managed projections.
4
+ ---
5
+
6
+ # Atelier skill steward
7
+
8
+ Use this skill when a repository should maintain its agent skills as a coherent,
9
+ self-improving catalog. Do not use it to turn a one-off preference into a skill,
10
+ capture prompts or transcripts, or grant a skill new authority implicitly.
11
+
12
+ ## Route the work
13
+
14
+ - Audit the current catalog with `atelier skills audit --json`.
15
+ - Record only a content-free signal with `atelier skills observe`; use a stable
16
+ workflow key, enumerated signal, optional skill name, and outcome. Never put a
17
+ summary, prompt, source excerpt, person, client, or private method in the key.
18
+ - Find evidence-thresholded work with `atelier skills candidates --json`.
19
+ - Create or improve an authored skill only when the candidate evidence and the
20
+ current task establish a repeatable input, workflow, and success condition.
21
+ - Update a repo-scoped deployed catalog with `atelier skills sync`. Review the
22
+ plan, then repeat its exact digest with `--apply --confirm`. Never manufacture
23
+ confirmation before inspecting blockers and actions.
24
+
25
+ ## Authoring boundary
26
+
27
+ Keep each skill focused. Give `SKILL.md` lowercase kebab-case `name` and a
28
+ discriminating `description`; put only decision-changing guidance in the body.
29
+ Add scripts or references only when deterministic reuse or progressive
30
+ disclosure justifies them. Test direct triggers, indirect triggers, incomplete
31
+ inputs, non-triggers, and risky edge cases.
32
+
33
+ In the public Atelier repository, read `AGENTS.md`, `CONTRIBUTING.md`, and
34
+ `docs/release-engineering.md` before editing. Re-derive private lessons as
35
+ generic invariants with invented fixtures. Keep Codex and Claude bundles
36
+ byte-identical and run the repository disclosure and release gates required by
37
+ those documents.
38
+
39
+ ## Promotion policy
40
+
41
+ An instruction-only change that retains the same tools, data boundary, and
42
+ external authority may advance after its behavioral checks and catalog audit
43
+ pass. New scripts, tool dependencies, network access, filesystem scope,
44
+ external messages, spending, destructive operations, credential handling, or
45
+ audience changes require explicit human authorization immediately before that
46
+ authority is introduced.
47
+
48
+ Do not treat an audit pass as proof of behavioral quality. Compare the candidate
49
+ against representative positive and negative requests. Reject it when it only
50
+ memorizes one incident, broadens its trigger, weakens a refusal, or cannot name
51
+ observable improvement.
52
+
53
+ ## Retirement and updates
54
+
55
+ Remove obsolete skills from active discovery only after checking dependents and
56
+ supersession. Preserve recovery through Git or the sync quarantine; never
57
+ permanently delete a managed deployed bundle during sync. A locally drifted or
58
+ unmanaged target is a blocker, not permission to overwrite it.
59
+
60
+ The steward has no model provider, telemetry, send path, browser apply endpoint,
61
+ or hidden source-mutation authority. Scheduling may invoke this workflow, but a
62
+ schedule does not widen its permissions or lower its evidence gates.
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: mnstry-readiness
3
+ description: Prepare or review Atelier readiness outputs for a project workspace using its readiness protocols.
4
+ ---
5
+
1
6
  # MNSTRY Readiness
2
7
 
3
8
  Use this skill when preparing or reviewing MNSTRY Atelier readiness outputs for
@@ -0,0 +1,84 @@
1
+ import { parseRequest, parseBoundedJSON, need, isRef, isDigest, canonical, deepFreeze, MODES } from '../composition/wire.mjs';
2
+ import { digestBytes } from '../composition/registry.mjs';
3
+
4
+ const messages = Object.freeze({ loading: 'Checking access.', ready: 'Preview ready.', unauthenticated: 'Sign in to preview.',
5
+ forbidden: 'Preview is not permitted.', expired: 'Session expired.', unavailable: 'Preview unavailable.', stale: 'Preview changed. Refresh to continue.' });
6
+ export class PreviewRefusal extends Error {
7
+ constructor(state) { super('Preview refused'); need(Object.hasOwn(messages, state) && !['ready', 'loading'].includes(state)); this.state = state; }
8
+ }
9
+ function principal(p) { need(p && isRef(p.issuer) && isRef(p.subject) && ['human', 'agent', 'service', 'local-owner'].includes(p.kind)); }
10
+ function sessionCheck(s, now) {
11
+ need(s && Number.isSafeInteger(s.generation) && s.generation >= 0 && Number.isFinite(s.expiresAt));
12
+ for (const k of ['sessionRef', 'workspaceRef', 'audienceRef', 'policyRevision']) need(isRef(s[k]));
13
+ need(s.tenantRef === null || isRef(s.tenantRef));
14
+ // This prototype deliberately cannot admit production or implicit public access.
15
+ need(s.plane === 'preview');
16
+ if (!s.principal) throw new PreviewRefusal('unauthenticated');
17
+ principal(s.principal); principal(s.representedPrincipal);
18
+ if (s.expiresAt <= now) throw new PreviewRefusal('expired');
19
+ }
20
+ function checkDelegation(s, request, target, now) {
21
+ const d = s.delegation;
22
+ if (!d) {
23
+ need(s.principal.kind !== 'agent' && canonical(s.principal) === canonical(s.representedPrincipal)); return;
24
+ }
25
+ need(s.principal.kind === 'agent' && d.active === true && d.redelegationAllowed === false && isRef(d.ref) && isRef(d.revision));
26
+ need(Number.isFinite(d.expiresAt) && d.expiresAt > now && d.expiresAt <= s.expiresAt);
27
+ need(canonical(d.actor) === canonical(s.principal) && canonical(d.representedPrincipal) === canonical(s.representedPrincipal));
28
+ for (const key of ['sessionRef', 'generation', 'tenantRef', 'workspaceRef', 'audienceRef', 'plane']) need(d[key] === s[key]);
29
+ for (const key of ['modes', 'operationRefs', 'resourceRefs']) need(Array.isArray(d[key]) && d[key].length <= 64 && new Set(d[key]).size === d[key].length);
30
+ need(d.modes.every(m => MODES.includes(m)) && d.operationRefs.every(isRef) && d.resourceRefs.every(isRef));
31
+ if (!d.modes.includes(request.mode) || !d.operationRefs.includes(target.operationRef) || !d.resourceRefs.includes(target.resourceRef)) throw new PreviewRefusal('forbidden');
32
+ }
33
+ export function createPreviewEnforcer(ports, { now = Date.now } = {}) {
34
+ for (const key of ['authenticate', 'resolveTarget', 'authorize', 'revalidate', 'perform', 'projectResult', 'audit']) need(typeof ports?.[key] === 'function');
35
+ return Object.freeze({ async handle(channel, bytes, signal) {
36
+ let request, binding, phase = 'parse', performed = false;
37
+ const generation = Number.isSafeInteger(channel?.generation) && channel.generation >= 0 ? channel.generation : 0;
38
+ const state = (name, projectedData) => deepFreeze({ state: {
39
+ schema: 'atelier-preview-state/proposal-v1', requestId: request?.requestId || 'invalid', placementId: request?.placementId || 'invalid',
40
+ sourceDigest: request?.sourceDigest || '0'.repeat(64), mode: request?.mode || 'synthetic', state: name,
41
+ sessionGeneration: generation, publicMessage: messages[name],
42
+ }, ...(name === 'ready' ? { projectedData } : {}) });
43
+ const live = () => { if (signal?.aborted) throw new PreviewRefusal('stale'); if (binding && binding.expiresAt <= now()) throw new PreviewRefusal('expired'); };
44
+ const fresh = async () => { live(); if (await ports.revalidate(binding, signal) !== true) throw new PreviewRefusal('stale'); live(); };
45
+ const audit = (stage, outcome) => ports.audit(deepFreeze({ binding, phase: stage, outcome }));
46
+ try {
47
+ request = deepFreeze(parseRequest(bytes)); live(); phase = 'authenticate';
48
+ const s = deepFreeze(structuredClone(await ports.authenticate(channel, signal)));
49
+ sessionCheck(s, now()); live(); phase = 'resolve';
50
+ const t = deepFreeze(structuredClone(await ports.resolveTarget(s, request, signal)));
51
+ for (const key of ['resourceRef', 'operationRef', 'serviceRef', 'sourceRef', 'placementRef', 'registryRevision']) need(isRef(t?.[key]));
52
+ for (const key of ['sourceDigest', 'descriptorDigest', 'rendererDigest']) need(isDigest(t[key]));
53
+ need(t.sourceDigest === request.sourceDigest && t.placementRef === request.placementId);
54
+ checkDelegation(s, request, t, now());
55
+ binding = deepFreeze({ sessionRef: s.sessionRef, generation: s.generation, principal: s.principal, representedPrincipal: s.representedPrincipal,
56
+ delegationRef: s.delegation?.ref || null, delegationRevision: s.delegation?.revision || null,
57
+ tenantRef: s.tenantRef, workspaceRef: s.workspaceRef, plane: s.plane, mode: request.mode, audienceRef: s.audienceRef,
58
+ target: t, requestRef: request.requestId, requestDigest: digestBytes(canonical(request)), policyRevision: s.policyRevision,
59
+ expiresAt: Math.min(s.expiresAt, s.delegation?.expiresAt ?? Infinity) });
60
+ phase = 'authorize'; live(); await audit('requested', 'pending');
61
+ const decision = deepFreeze(structuredClone(await ports.authorize(binding, signal))); live();
62
+ need(canonical(decision?.binding) === canonical(binding));
63
+ if (decision.outcome !== 'allow') throw new PreviewRefusal(decision.outcome === 'deny' ? 'forbidden' : decision.outcome === 'reauth-required' ? 'expired' : 'unavailable');
64
+ await audit('authorized', 'allowed'); await fresh(); phase = 'perform'; performed = true;
65
+ const raw = await ports.perform(s, request, decision, signal);
66
+ await fresh(); phase = 'project';
67
+ const projected = await ports.projectResult(binding, raw, signal);
68
+ // Trusted projector owns the field allowlist. Root still enforces JSON,
69
+ // byte/depth bounds and cannot accidentally serialize result metadata.
70
+ const data = parseBoundedJSON(JSON.stringify(projected));
71
+ // Preparation is not a browser delivery acknowledgement. Keep the last
72
+ // asynchronous audit inside the revalidation boundary before returning.
73
+ await fresh(); await audit('settled', 'prepared'); await fresh();
74
+ return state('ready', data);
75
+ } catch (error) {
76
+ const name = error instanceof PreviewRefusal ? error.state : 'unavailable';
77
+ if (binding) {
78
+ try { await audit('settled', performed ? 'uncertain' : name === 'stale' ? 'stale' : phase === 'authorize' ? 'denied' : 'failed'); }
79
+ catch { /* No output or automatic effect retry if audit storage fails. */ }
80
+ }
81
+ return state(name);
82
+ }
83
+ } });
84
+ }
@@ -2,6 +2,7 @@ import { spawnSync } from 'node:child_process'
2
2
  import fs from 'node:fs'
3
3
  import path from 'node:path'
4
4
  import { matchesPathPattern, normalizeRelPath } from '../project/path-match.mjs'
5
+ import { sanitizedGitEnvironment } from '../runtime/git-adapter.mjs'
5
6
 
6
7
  export const EMPTY_TREE = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
7
8
  export const ZERO_SHA_RE = /^0{40,}$/
@@ -252,12 +253,13 @@ export function parsePushRefInput(text) {
252
253
 
253
254
  export function gitOutputResult(repoRoot, args, {
254
255
  encoding = 'utf8',
256
+ gitExecutable = 'git',
255
257
  maxBuffer = DIFF_RESULT_MAX_BYTES,
256
258
  runner = spawnSync,
257
259
  } = {}) {
258
260
  let result
259
261
  try {
260
- result = runner('git', ['-C', repoRoot, ...args], { encoding, maxBuffer })
262
+ result = runner(gitExecutable, ['-C', repoRoot, ...args], { encoding, maxBuffer, env: sanitizedGitEnvironment() })
261
263
  } catch (error) {
262
264
  return { ok: false, code: 'git-command-failed', error: error instanceof Error ? error.message : String(error), stdout: null }
263
265
  }
@@ -355,7 +357,7 @@ function scanBinaryBuffer({ buffer, filePath, rules, exceptions, repo }) {
355
357
  return findings
356
358
  }
357
359
 
358
- function readBinaryChanges({ repoRoot, revision, diff, rules, exceptions, repo }) {
360
+ function readBinaryChanges({ repoRoot, revision, diff, rules, exceptions, repo, gitExecutable, gitRunner }) {
359
361
  const findings = []
360
362
  const diagnostics = []
361
363
  let totalBytes = 0
@@ -363,7 +365,9 @@ function readBinaryChanges({ repoRoot, revision, diff, rules, exceptions, repo }
363
365
  const spec = revision === ':' ? `:${filePath}` : `${revision}:${filePath}`
364
366
  const result = gitOutputResult(repoRoot, ['show', spec], {
365
367
  encoding: 'buffer',
368
+ gitExecutable,
366
369
  maxBuffer: BINARY_FILE_MAX_BYTES + 1,
370
+ runner: gitRunner,
367
371
  })
368
372
  if (!result.ok) {
369
373
  diagnostics.push(incompleteDiagnostic({
@@ -403,9 +407,10 @@ export function scanStagedRepository({
403
407
  rules = DEFAULT_CONTENT_RULES,
404
408
  exceptions = [],
405
409
  repo = null,
410
+ gitExecutable = 'git',
406
411
  gitRunner = spawnSync,
407
412
  } = {}) {
408
- const acquired = stagedDiff(repoRoot, { runner: gitRunner })
413
+ const acquired = stagedDiff(repoRoot, { gitExecutable, runner: gitRunner })
409
414
  if (!acquired.ok) {
410
415
  return {
411
416
  findings: [],
@@ -414,7 +419,7 @@ export function scanStagedRepository({
414
419
  }
415
420
  }
416
421
  const files = parseAddedContent(acquired.diff)
417
- const binary = readBinaryChanges({ repoRoot, revision: ':', diff: acquired.diff, rules, exceptions, repo })
422
+ const binary = readBinaryChanges({ repoRoot, revision: ':', diff: acquired.diff, rules, exceptions, repo, gitExecutable, gitRunner })
418
423
  return {
419
424
  findings: [...scanAddedContent({ files, rules, exceptions, repo }), ...binary.findings],
420
425
  diagnostics: binary.diagnostics,
@@ -4,6 +4,7 @@ import path from 'node:path'
4
4
  import { buildGraph } from '../graph/graph.mjs'
5
5
  import { commandProject, firstString, parseArgs, readJson, resolvePathValue, writeJson } from '../project/config.mjs'
6
6
  import { matchesPathPattern } from '../project/path-match.mjs'
7
+ import { sanitizedGitEnvironment } from '../runtime/git-adapter.mjs'
7
8
  import {
8
9
  CHECK_AGGREGATE_MAX_BYTES,
9
10
  parsePushRefInput,
@@ -145,7 +146,7 @@ export function validateBoundaryPolicy(policy, project = null) {
145
146
  const declaredRepos = isObject(repos) ? repos : {}
146
147
  for (const [index, exception] of policy.contentRuleExceptions.entries()) {
147
148
  const name = firstString(exception?.repo)
148
- if (name && Object.keys(declaredRepos).length && !declaredRepos[name]) {
149
+ if (name && Object.keys(declaredRepos).length && !Object.hasOwn(declaredRepos, name)) {
149
150
  errors.push(`contentRuleExceptions[${index}].repo ${name} is not declared in repos`)
150
151
  }
151
152
  }
@@ -162,7 +163,7 @@ export function validateBoundaryPolicy(policy, project = null) {
162
163
  }
163
164
  if (!firstString(actor.privateDomainRepo)) errors.push(`actors.${actorId}.privateDomainRepo is required`)
164
165
  if (firstString(actor.privateDomainRepo) && isObject(repos)) {
165
- const privateRepo = repos[actor.privateDomainRepo]
166
+ const privateRepo = Object.hasOwn(repos, actor.privateDomainRepo) ? repos[actor.privateDomainRepo] : null
166
167
  if (!privateRepo) {
167
168
  errors.push(`actors.${actorId}.privateDomainRepo ${actor.privateDomainRepo} is not declared in repos`)
168
169
  } else if (privateRepo.kind !== 'private_domain') {
@@ -187,7 +188,7 @@ export function validateBoundaryPolicy(policy, project = null) {
187
188
  errors.push(...validAudienceList(repo.forbiddenAudiences, `repos.${repoName}.forbiddenAudiences`))
188
189
  if (repo.kind === 'private_domain') {
189
190
  if (!firstString(repo.ownerActor)) errors.push(`repos.${repoName}.ownerActor is required for private_domain repos`)
190
- if (repo.ownerActor && isObject(actors) && !actors[repo.ownerActor]) errors.push(`repos.${repoName}.ownerActor ${repo.ownerActor} is not declared`)
191
+ if (repo.ownerActor && isObject(actors) && !Object.hasOwn(actors, repo.ownerActor)) errors.push(`repos.${repoName}.ownerActor ${repo.ownerActor} is not declared`)
191
192
  }
192
193
  }
193
194
 
@@ -196,44 +197,69 @@ export function validateBoundaryPolicy(policy, project = null) {
196
197
  if (repo.external) {
197
198
  // Declaring a boundary for a repo nobody manages is the confusion the
198
199
  // external kind exists to remove.
199
- if (repos[repo.name]) errors.push(`policy repos.${repo.name} must not be declared; it is an external (unmanaged) repo`)
200
+ if (Object.hasOwn(repos, repo.name)) errors.push(`policy repos.${repo.name} must not be declared; it is an external (unmanaged) repo`)
200
201
  continue
201
202
  }
202
- if (!repos[repo.name]) errors.push(`policy repos.${repo.name} must be declared`)
203
+ if (!Object.hasOwn(repos, repo.name)) errors.push(`policy repos.${repo.name} must be declared`)
203
204
  }
204
205
  }
205
206
 
206
207
  return errors
207
208
  }
208
209
 
209
- export function resolveCurrentActor({ policy, project, actor = null, env = process.env } = {}) {
210
+ function resolveExplicitActor({ policy, actor, env }) {
210
211
  const actors = policy?.actors ?? {}
211
- const explicit = actor || env.MNSTRY_ATELIER_ACTOR || env.GITHUB_ACTOR
212
- if (explicit && actors[explicit]) return { actorId: explicit, source: 'explicit' }
213
- const gitEmails = gitEmailsForProject(project)
214
- for (const [actorId, info] of Object.entries(actors)) {
215
- const actorEmails = new Set(asArray(info.gitEmails).map((email) => email.toLowerCase()))
216
- if (gitEmails.some((email) => actorEmails.has(email.toLowerCase()))) return { actorId, source: 'git-email', gitEmails }
212
+ const explicit = actor || env.MNSTRY_ATELIER_ACTOR
213
+ const unverified = (reason) => ({ actorId: null, source: 'unverified', reason })
214
+ if (actor && env.MNSTRY_ATELIER_ACTOR && actor !== env.MNSTRY_ATELIER_ACTOR) {
215
+ return unverified('conflicting-explicit-actors')
217
216
  }
218
- const login = env.GITHUB_ACTOR || ghLogin()
217
+ if (explicit && !Object.hasOwn(actors, explicit)) return unverified('unknown-explicit-actor')
218
+ return explicit ? { actorId: explicit, source: 'explicit' } : null
219
+ }
220
+
221
+ export function resolveCurrentActor({ policy, project, actor = null, env = process.env, gitExecutable = 'git', allowNetworkActorResolution = true, allowHistoryActorResolution = false } = {}) {
222
+ const actors = policy?.actors ?? {}
223
+ const explicit = resolveExplicitActor({ policy, actor, env })
224
+ if (explicit) return explicit
225
+ const unverified = (reason) => ({ actorId: null, source: 'unverified', reason })
226
+ // Without an explicit local selector, a platform login precedes Git metadata.
227
+ // It is platform attribution only; this function cannot authenticate its env.
228
+ const platformLogin = env.GITHUB_ACTOR
229
+ if (platformLogin) {
230
+ const matches = Object.entries(actors).filter(([, info]) =>
231
+ !String(info.githubLogin || '').endsWith('_GITHUB_LOGIN_PLACEHOLDER') &&
232
+ String(info.githubLogin || '').toLowerCase() === String(platformLogin).toLowerCase())
233
+ if (matches.length !== 1) return unverified(matches.length ? 'ambiguous-platform-actor' : 'unknown-platform-actor')
234
+ return { actorId: matches[0][0], source: 'github-login', githubLogin: platformLogin }
235
+ }
236
+ // Keep the legacy option accepted by the API, but history is never evidence
237
+ // of the person currently operating a checkout.
238
+ const gitEmails = gitEmailsForProject(project, { gitExecutable, env, allowHistoryActorResolution: false })
239
+ const matches = Object.entries(actors).filter(([, info]) => {
240
+ const actorEmails = new Set(asArray(info.gitEmails).map((email) => email.toLowerCase()))
241
+ return gitEmails.some((email) => actorEmails.has(email.toLowerCase()))
242
+ })
243
+ if (matches.length > 1) return unverified('ambiguous-git-actor')
244
+ if (matches.length === 1) return { actorId: matches[0][0], source: 'git-email', gitEmails }
245
+ const login = allowNetworkActorResolution ? ghLogin() : null
219
246
  if (login) {
220
- for (const [actorId, info] of Object.entries(actors)) {
221
- if (String(info.githubLogin || '').toLowerCase() === String(login).toLowerCase()) return { actorId, source: 'github-login', githubLogin: login }
222
- }
247
+ const matches = Object.entries(actors).filter(([, info]) => !String(info.githubLogin || '').endsWith('_GITHUB_LOGIN_PLACEHOLDER') && String(info.githubLogin || '').toLowerCase() === String(login).toLowerCase())
248
+ if (matches.length !== 1) return unverified(matches.length ? 'ambiguous-platform-actor' : 'unknown-platform-actor')
249
+ return { actorId: matches[0][0], source: 'github-login', githubLogin: login }
223
250
  }
224
- return { actorId: null, source: 'unverified', gitEmails, githubLogin: login || null }
251
+ return { actorId: null, source: 'unverified', gitEmails, githubLogin: null }
225
252
  }
226
253
 
227
- function gitEmailsForProject(project) {
254
+ function gitEmailsForProject(project, { gitExecutable = 'git', env = process.env, allowHistoryActorResolution = false } = {}) {
228
255
  const roots = unique([project?.repoOpsRoot, project?.workspaceRoot, ...managedRepos(project).map((repo) => repo.path)])
229
256
  const emails = []
230
257
  for (const root of roots) {
231
258
  if (!root || !fs.existsSync(root)) continue
232
- for (const args of [
233
- ['config', 'user.email'],
234
- ['log', '-1', '--format=%ae'],
235
- ]) {
236
- const result = spawnSync('git', ['-C', root, ...args], { encoding: 'utf8' })
259
+ const probes = [['config', 'user.email']]
260
+ if (allowHistoryActorResolution) probes.push(['log', '-1', '--format=%ae'])
261
+ for (const args of probes) {
262
+ const result = spawnSync(gitExecutable, ['-C', root, ...args], { encoding: 'utf8', env: sanitizedGitEnvironment(env) })
237
263
  if (result.status === 0 && result.stdout.trim()) emails.push(result.stdout.trim())
238
264
  }
239
265
  }
@@ -249,7 +275,7 @@ function ghLogin() {
249
275
  }
250
276
 
251
277
  function repoPolicy(policy, repoName) {
252
- return policy?.repos?.[repoName] ?? null
278
+ return policy?.repos && Object.hasOwn(policy.repos, repoName) ? policy.repos[repoName] : null
253
279
  }
254
280
 
255
281
  function finding({ severity = 'error', code, message, repo = null, path: itemPath = null, node = null, details = {} }) {
@@ -280,15 +306,21 @@ function nodePlacementFindings({ node, policy }) {
280
306
  return findings
281
307
  }
282
308
 
283
- function actorFindings({ policy, project, actor }) {
309
+ function actorFindings({ policy, project, actor, gitExecutable, allowNetworkActorResolution, allowHistoryActorResolution, forceActorErrors }) {
284
310
  const findings = []
285
- const current = resolveCurrentActor({ policy, project, actor })
286
- const severity = severityFor(policy)
287
- for (const [repoName, repo] of Object.entries(policy.repos ?? {})) {
288
- if (repo.kind !== 'private_domain') continue
289
- if (!repo.ownerActor) continue
311
+ const operatedRepos = new Set(managedRepos(project).map((repo) => repo.name))
312
+ const ownedRepos = Object.entries(policy.repos ?? {}).filter(([name, repo]) =>
313
+ operatedRepos.has(name) && repo?.kind === 'private_domain' && repo.ownerActor)
314
+ const explicit = resolveExplicitActor({ policy, actor, env: process.env })
315
+ if (explicit?.reason) return [finding({ severity: 'error', code: 'actor-resolution-refused', message: explicit.reason })]
316
+ // Shared-only work needs no derived identity and must not invoke Git or gh.
317
+ if (!ownedRepos.length) return findings
318
+ const current = explicit || resolveCurrentActor({ policy, project, actor, gitExecutable, allowNetworkActorResolution, allowHistoryActorResolution })
319
+ const severity = forceActorErrors ? 'error' : severityFor(policy)
320
+ for (const [repoName, repo] of ownedRepos) {
290
321
  if (!current.actorId) {
291
- findings.push(finding({ severity, code: 'private-domain-actor-unverified', repo: repoName, message: `${repoName}: could not verify local actor for private domain repo owned by ${repo.ownerActor}` }))
322
+ findings.push(finding({ severity, code: 'private-domain-actor-unverified', repo: repoName,
323
+ message: `${repoName}: could not verify local actor for private domain repo owned by ${repo.ownerActor}${current.reason ? ` (${current.reason})` : ''}` }))
292
324
  } else if (current.actorId !== repo.ownerActor) {
293
325
  findings.push(finding({ severity, code: 'private-domain-actor-mismatch', repo: repoName, message: `${repoName}: private domain repo is owned by ${repo.ownerActor}, but current actor is ${current.actorId}` }))
294
326
  }
@@ -296,11 +328,11 @@ function actorFindings({ policy, project, actor }) {
296
328
  return findings
297
329
  }
298
330
 
299
- export function stagedPathsForProject(project) {
331
+ export function stagedPathsForProject(project, { gitExecutable = 'git' } = {}) {
300
332
  const paths = []
301
333
  for (const repo of managedRepos(project)) {
302
334
  if (!repo.path || !fs.existsSync(path.join(repo.path, '.git'))) continue
303
- const result = spawnSync('git', ['-C', repo.path, 'diff', '--cached', '--name-only', '--diff-filter=ACMR'], { encoding: 'utf8' })
335
+ const result = spawnSync(gitExecutable, ['-C', repo.path, 'diff', '--cached', '--name-only', '--diff-filter=ACMR'], { encoding: 'utf8', env: sanitizedGitEnvironment() })
304
336
  if (result.status !== 0) continue
305
337
  for (const rel of result.stdout.split('\n').map((line) => line.trim()).filter(Boolean)) {
306
338
  paths.push({ repo: repo.name, repoRoot: repo.path, path: normalize(rel) })
@@ -394,7 +426,7 @@ export function semanticChangesInFile(file) {
394
426
  return changes
395
427
  }
396
428
 
397
- function semanticDiffFindings({ policy, project }) {
429
+ function semanticDiffFindings({ policy, project, gitExecutable = 'git' }) {
398
430
  const findings = []
399
431
  const severity = 'error'
400
432
  for (const repo of managedRepos(project)) {
@@ -402,7 +434,7 @@ function semanticDiffFindings({ policy, project }) {
402
434
  findings.push(unscannableRepoFinding(repo, 'configured path is absent or is not a Git checkout'))
403
435
  continue
404
436
  }
405
- const result = spawnSync('git', ['-C', repo.path, 'diff', '--cached', '--unified=0', '--', '*.md', '*.kg.json'], { encoding: 'utf8' })
437
+ const result = spawnSync(gitExecutable, ['-C', repo.path, 'diff', '--cached', '--unified=0', '--', '*.md', '*.kg.json'], { encoding: 'utf8', env: sanitizedGitEnvironment() })
406
438
  if (result.status !== 0) {
407
439
  findings.push(unscannableRepoFinding(repo, 'git diff --cached failed'))
408
440
  continue
@@ -430,7 +462,7 @@ function semanticDiffFindings({ policy, project }) {
430
462
  return findings
431
463
  }
432
464
 
433
- function stagedContentFindings({ policy, project }) {
465
+ function stagedContentFindings({ policy, project, gitExecutable = 'git' }) {
434
466
  const rules = resolveContentRules(policy)
435
467
  const exceptions = asArray(policy?.contentRuleExceptions)
436
468
  const findings = []
@@ -440,7 +472,7 @@ function stagedContentFindings({ policy, project }) {
440
472
  findings.push(unscannableRepoFinding(repo, 'configured path is absent or is not a Git checkout'))
441
473
  continue
442
474
  }
443
- const result = scanStagedRepository({ repoRoot: repo.path, rules, exceptions, repo: repo.name })
475
+ const result = scanStagedRepository({ repoRoot: repo.path, rules, exceptions, repo: repo.name, gitExecutable })
444
476
  findings.push(...result.findings, ...result.diagnostics)
445
477
  totalBytes += result.bytes
446
478
  if (totalBytes > CHECK_AGGREGATE_MAX_BYTES) {
@@ -603,7 +635,7 @@ function promotionFindings({ policy, project, graph }) {
603
635
  return findings
604
636
  }
605
637
 
606
- export function checkBoundaryPolicy({ project, policy, staged = false, stagedOnly = false, actor = null } = {}) {
638
+ export function checkBoundaryPolicy({ project, policy, staged = false, stagedOnly = false, actor = null, gitExecutable = 'git', allowNetworkActorResolution = true, allowHistoryActorResolution = false, forceActorErrors = false } = {}) {
607
639
  const validationErrors = validateBoundaryPolicy(policy, project)
608
640
  let graph = null
609
641
  const findings = validationErrors.map((message) => finding({ severity: 'error', code: 'boundary-policy-invalid', message }))
@@ -613,12 +645,12 @@ export function checkBoundaryPolicy({ project, policy, staged = false, stagedOnl
613
645
  for (const node of graph.nodes ?? []) findings.push(...nodePlacementFindings({ node, policy }))
614
646
  findings.push(...promotionFindings({ policy, project, graph }))
615
647
  }
616
- findings.push(...actorFindings({ policy, project, actor }))
648
+ findings.push(...actorFindings({ policy, project, actor, gitExecutable, allowNetworkActorResolution, allowHistoryActorResolution, forceActorErrors }))
617
649
  if (staged) {
618
- const stagedPaths = stagedPathsForProject(project)
650
+ const stagedPaths = stagedPathsForProject(project, { gitExecutable })
619
651
  findings.push(...forbiddenPathFindings({ policy, stagedPaths }))
620
- findings.push(...semanticDiffFindings({ policy, project }))
621
- findings.push(...stagedContentFindings({ policy, project }))
652
+ findings.push(...semanticDiffFindings({ policy, project, gitExecutable }))
653
+ findings.push(...stagedContentFindings({ policy, project, gitExecutable }))
622
654
  }
623
655
  const errors = findings.filter((item) => item.severity === 'error')
624
656
  const warnings = findings.filter((item) => item.severity !== 'error')