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