@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,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
@@ -0,0 +1,321 @@
1
+ import { createHash } from 'node:crypto'
2
+
3
+ const ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/
4
+ const RESERVED = new Set(['__proto__', 'constructor', 'prototype'])
5
+ const VERSION = /^1\.[0-9]+\.[0-9]+$/
6
+ const REASONS = new Set(['insufficient-evidence', 'ambiguous', 'no-match', 'budget-exhausted', 'provider-unavailable', 'timeout', 'invalid-response', 'unauthorized'])
7
+ const TOLERANCE = 1e-6
8
+
9
+ // Inspect descriptors before reading values. Invalid in-process inputs such as
10
+ // accessors, symbols, sparse arrays, cycles and custom prototypes are refused
11
+ // without calling their conversion methods or returning their content.
12
+ function jsonSnapshot(input) {
13
+ const ancestors = new Set()
14
+ let nodes = 0
15
+ let bytes = 0
16
+ function count(value) {
17
+ bytes += value
18
+ if (bytes > 16777216) throw new TypeError()
19
+ }
20
+ function copy(value, depth) {
21
+ if (++nodes > 100000 || depth > 32) throw new TypeError()
22
+ if (value === null || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value)) {
23
+ if (typeof value === 'string' && Buffer.byteLength(value) > 16777216) throw new TypeError()
24
+ count(Buffer.byteLength(JSON.stringify(value)))
25
+ return value
26
+ }
27
+ if (!value || typeof value !== 'object' || ancestors.has(value)) throw new TypeError()
28
+ const array = Array.isArray(value)
29
+ const prototype = Object.getPrototypeOf(value)
30
+ if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) throw new TypeError()
31
+ const descriptors = Object.getOwnPropertyDescriptors(value)
32
+ const keys = Reflect.ownKeys(descriptors)
33
+ if (keys.some((key) => typeof key !== 'string' || RESERVED.has(key))) throw new TypeError()
34
+ count(2)
35
+ ancestors.add(value)
36
+ let result
37
+ if (array) {
38
+ const length = descriptors.length?.value
39
+ if (!Number.isSafeInteger(length) || length < 0 || length > 100000 || keys.length !== length + 1) throw new TypeError()
40
+ result = []
41
+ for (let index = 0; index < length; index++) {
42
+ const descriptor = descriptors[String(index)]
43
+ if (!descriptor || !Object.hasOwn(descriptor, 'value') || !descriptor.enumerable) throw new TypeError()
44
+ if (index) count(1)
45
+ result.push(copy(descriptor.value, depth + 1))
46
+ }
47
+ } else {
48
+ result = Object.create(null)
49
+ for (const [index, key] of keys.entries()) {
50
+ const descriptor = descriptors[key]
51
+ if (!Object.hasOwn(descriptor, 'value') || !descriptor.enumerable) throw new TypeError()
52
+ if (Buffer.byteLength(key) > 16777216) throw new TypeError()
53
+ count(Buffer.byteLength(JSON.stringify(key)) + 1 + (index ? 1 : 0))
54
+ result[key] = copy(descriptor.value, depth + 1)
55
+ }
56
+ }
57
+ ancestors.delete(value)
58
+ return result
59
+ }
60
+ return copy(input, 0)
61
+ }
62
+
63
+ function object(value) {
64
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
65
+ }
66
+
67
+ function text(value, maximum, allowEmpty = false) {
68
+ if (typeof value !== 'string' || (!allowEmpty && !value.trim())) return false
69
+ let length = 0
70
+ for (const character of value) {
71
+ void character
72
+ if (++length > maximum) return false
73
+ }
74
+ return true
75
+ }
76
+
77
+ function identifier(value) {
78
+ return typeof value === 'string' && ID.test(value) && !RESERVED.has(value)
79
+ }
80
+
81
+ function report() {
82
+ const errors = []
83
+ return {
84
+ errors,
85
+ check(condition, label) {
86
+ if (!condition && errors.length < 64) errors.push(label)
87
+ return condition
88
+ },
89
+ }
90
+ }
91
+
92
+ function closed(value, required, optional, label, checks) {
93
+ if (!checks.check(object(value), `${label}: expected object`)) return false
94
+ const allowed = new Set([...required, ...optional, 'ext'])
95
+ checks.check(required.every((key) => Object.hasOwn(value, key)), `${label}: required field missing`)
96
+ checks.check(Object.keys(value).every((key) => allowed.has(key)), `${label}: unknown field`)
97
+ if (Object.hasOwn(value, 'ext')) checks.check(object(value.ext), `${label}.ext: expected object`)
98
+ return true
99
+ }
100
+
101
+ function record(value, minimum, maximum, label, checks) {
102
+ if (!checks.check(object(value), `${label}: expected record`)) return false
103
+ const keys = Object.keys(value)
104
+ checks.check(keys.length >= minimum && keys.length <= maximum, `${label}: entry count outside limits`)
105
+ checks.check(keys.every(identifier), `${label}: invalid identifier`)
106
+ return keys.length <= maximum
107
+ }
108
+
109
+ function scope(value, label, checks) {
110
+ if (closed(value, ['workspaceId', 'authorizationRef'], [], label, checks)) {
111
+ checks.check(identifier(value.workspaceId), `${label}.workspaceId: invalid identifier`)
112
+ checks.check(identifier(value.authorizationRef), `${label}.authorizationRef: invalid identifier`)
113
+ }
114
+ }
115
+
116
+ function version(value, label, checks) {
117
+ if (Object.hasOwn(value, 'contractVersion')) {
118
+ checks.check(typeof value.contractVersion === 'string' && VERSION.test(value.contractVersion), `${label}.contractVersion: invalid version`)
119
+ }
120
+ }
121
+
122
+ function inspectRequest(request, checks) {
123
+ if (!closed(request, ['schema', 'id', 'task', 'rubricVersion', 'scope', 'state', 'evidence', 'questions'], ['contractVersion'], 'request', checks)) return
124
+ checks.check(request.schema === 'atelier-decision-request@v1', 'request.schema: unsupported schema')
125
+ version(request, 'request', checks)
126
+ for (const key of ['id', 'task', 'rubricVersion']) checks.check(identifier(request[key]), `request.${key}: invalid identifier`)
127
+ scope(request.scope, 'request.scope', checks)
128
+ checks.check(text(request.state, 32000, true), 'request.state: expected string within limit')
129
+ const evidenceIds = new Set()
130
+ if (checks.check(Array.isArray(request.evidence) && request.evidence.length >= 1 && request.evidence.length <= 256, 'request.evidence: expected 1..256 entries')) {
131
+ for (const evidence of request.evidence) {
132
+ if (!closed(evidence, ['id', 'sourceRef'], [], 'request.evidence[]', checks)) continue
133
+ checks.check(identifier(evidence.id), 'request.evidence[].id: invalid identifier')
134
+ checks.check(!evidenceIds.has(evidence.id), 'request.evidence: duplicate identifier')
135
+ evidenceIds.add(evidence.id)
136
+ checks.check(text(evidence.sourceRef, 2048), 'request.evidence[].sourceRef: expected nonempty reference within limit')
137
+ }
138
+ }
139
+ inspectQuestions(request.questions, checks, 'request.questions', evidenceIds)
140
+ }
141
+
142
+ function inspectQuestions(questions, checks, label, evidenceIds = null) {
143
+ if (!record(questions, 1, 64, label, checks)) return
144
+ for (const question of Object.values(questions)) {
145
+ if (!closed(question, ['type', 'instructions', 'evidenceIds', 'criteria'], [], `${label}[]`, checks)) continue
146
+ checks.check(text(question.instructions, 4000), `${label}[].instructions: expected nonempty string within limit`)
147
+ if (checks.check(Array.isArray(question.evidenceIds) && question.evidenceIds.length >= 1 && question.evidenceIds.length <= 256, `${label}[].evidenceIds: expected 1..256 references`)) {
148
+ checks.check(new Set(question.evidenceIds).size === question.evidenceIds.length, `${label}[].evidenceIds: duplicate reference`)
149
+ checks.check(question.evidenceIds.every(identifier), `${label}[].evidenceIds: invalid reference`)
150
+ if (evidenceIds !== null) checks.check(question.evidenceIds.every((id) => evidenceIds.has(id)), `${label}[].evidenceIds: unknown reference`)
151
+ }
152
+ if (question.type === 'choice') {
153
+ if (record(question.criteria, 2, 64, `${label}[].criteria`, checks)) {
154
+ checks.check(Object.values(question.criteria).every((value) => text(value, 2000)), `${label}[].criteria: expected nonempty descriptions within limit`)
155
+ }
156
+ } else if (question.type === 'score') {
157
+ checks.check(Array.isArray(question.criteria) && question.criteria.length >= 2 && question.criteria.length <= 10 && question.criteria.every((value) => text(value, 2000)), `${label}[].criteria: expected 2..10 nonempty ordered descriptions within limit`)
158
+ } else if (question.type === 'boolean') {
159
+ if (closed(question.criteria, ['true', 'false'], [], `${label}[].criteria`, checks)) {
160
+ checks.check(text(question.criteria.true, 2000) && text(question.criteria.false, 2000), `${label}[].criteria: expected nonempty descriptions within limit`)
161
+ }
162
+ } else {
163
+ checks.check(false, `${label}[].type: unsupported type`)
164
+ }
165
+ }
166
+ }
167
+
168
+ function canonical(value) {
169
+ if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`
170
+ if (object(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`
171
+ return JSON.stringify(value)
172
+ }
173
+
174
+ function digest(request) {
175
+ return createHash('sha256').update(canonical(request), 'utf8').digest('hex')
176
+ }
177
+
178
+ function inspected(input) {
179
+ const checks = report()
180
+ let request
181
+ try {
182
+ request = jsonSnapshot(input)
183
+ inspectRequest(request, checks)
184
+ } catch {
185
+ checks.check(false, 'request: expected bounded plain JSON')
186
+ }
187
+ return { request, checks }
188
+ }
189
+
190
+ /** Validate structure and evidence references; this does not authorize egress. */
191
+ export function validateDecisionRequest(input) {
192
+ const { checks } = inspected(input)
193
+ return { ok: checks.errors.length === 0, errors: checks.errors }
194
+ }
195
+
196
+ /** Hash a valid request, including its scope, evidence, rubric and extensions. */
197
+ export function decisionRequestDigest(input) {
198
+ const { request, checks } = inspected(input)
199
+ if (checks.errors.length) throw new TypeError('Invalid decision request')
200
+ return digest(request)
201
+ }
202
+
203
+ function probability(value) {
204
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1
205
+ }
206
+
207
+ function distribution(values, label, checks) {
208
+ if (!checks.check(values.every(probability), `${label}: values must be probabilities`)) return false
209
+ return checks.check(Math.abs(values.reduce((sum, value) => sum + value, 0) - 1) <= TOLERANCE, `${label}: probabilities must sum to one`)
210
+ }
211
+
212
+ function sameKeys(left, right) {
213
+ const leftKeys = Object.keys(left).sort()
214
+ const rightKeys = Object.keys(right).sort()
215
+ return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index])
216
+ }
217
+
218
+ function answer(question, value, checks, label) {
219
+ if (question.type === 'choice') {
220
+ if (!closed(value, ['type', 'choice', 'probabilities', 'confidence'], [], label, checks)) return
221
+ checks.check(value.type === 'choice', `${label}.type: question type mismatch`)
222
+ checks.check(probability(value.confidence), `${label}.confidence: expected probability`)
223
+ checks.check(identifier(value.choice) && Object.hasOwn(question.criteria, value.choice), `${label}.choice: unknown choice`)
224
+ if (record(value.probabilities, 2, 64, `${label}.probabilities`, checks)) {
225
+ checks.check(sameKeys(value.probabilities, question.criteria), `${label}.probabilities: criteria keys mismatch`)
226
+ const values = Object.values(value.probabilities)
227
+ if (distribution(values, `${label}.probabilities`, checks) && Object.hasOwn(value.probabilities, value.choice)) {
228
+ checks.check(value.probabilities[value.choice] === Math.max(...values), `${label}.choice: choice must have maximum probability`)
229
+ }
230
+ }
231
+ } else if (question.type === 'score') {
232
+ if (!closed(value, ['type', 'score', 'probabilities', 'confidence'], [], label, checks)) return
233
+ checks.check(value.type === 'score', `${label}.type: question type mismatch`)
234
+ checks.check(probability(value.confidence), `${label}.confidence: expected probability`)
235
+ checks.check(typeof value.score === 'number' && Number.isFinite(value.score) && value.score >= 0 && value.score <= question.criteria.length - 1, `${label}.score: outside score range`)
236
+ if (checks.check(Array.isArray(value.probabilities) && value.probabilities.length === question.criteria.length, `${label}.probabilities: criteria count mismatch`)) {
237
+ if (distribution(value.probabilities, `${label}.probabilities`, checks)) {
238
+ const expectation = value.probabilities.reduce((sum, value, index) => sum + value * index, 0)
239
+ checks.check(Math.abs(value.score - expectation) <= TOLERANCE, `${label}.score: weighted expectation mismatch`)
240
+ }
241
+ }
242
+ } else {
243
+ if (!closed(value, ['type', 'probability'], [], label, checks)) return
244
+ checks.check(value.type === 'boolean', `${label}.type: question type mismatch`)
245
+ checks.check(probability(value.probability), `${label}.probability: expected probability`)
246
+ }
247
+ }
248
+
249
+ function inspectAnswers(questions, answers, checks, label) {
250
+ if (!record(answers, 0, 64, label, checks)) return
251
+ checks.check(sameKeys(answers, questions), `${label}: question keys mismatch`)
252
+ for (const [id, question] of Object.entries(questions)) {
253
+ if (Object.hasOwn(answers, id)) answer(question, answers[id], checks, `${label}[]`)
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Validate question/answer shape and distributions without state or hashing.
259
+ * Evidence reference syntax is checked; membership and authority belong to
260
+ * the host that holds the source snapshot and transient request binding.
261
+ */
262
+ export function validateDecisionAnswers(inputQuestions, inputAnswers) {
263
+ const checks = report()
264
+ let questions
265
+ try {
266
+ questions = jsonSnapshot(inputQuestions)
267
+ inspectQuestions(questions, checks, 'questions')
268
+ } catch {
269
+ checks.check(false, 'questions: expected bounded plain JSON')
270
+ }
271
+ if (checks.errors.length) return { ok: false, errors: checks.errors }
272
+ try {
273
+ inspectAnswers(questions, jsonSnapshot(inputAnswers), checks, 'answers')
274
+ } catch {
275
+ checks.check(false, 'answers: expected bounded plain JSON')
276
+ }
277
+ return { ok: checks.errors.length === 0, errors: checks.errors }
278
+ }
279
+
280
+ function inspectResult(request, result, checks) {
281
+ if (!closed(result, ['schema', 'requestId', 'requestDigest', 'task', 'rubricVersion', 'scope', 'provider', 'authority', 'mode', 'status', 'answers', 'usage', 'elapsedMs'], ['reason', 'contractVersion'], 'result', checks)) return
282
+ checks.check(result.schema === 'atelier-decision-result@v1', 'result.schema: unsupported schema')
283
+ version(result, 'result', checks)
284
+ checks.check(result.requestId === request.id, 'result.requestId: request binding mismatch')
285
+ checks.check(result.requestDigest === digest(request), 'result.requestDigest: request binding mismatch')
286
+ checks.check(result.task === request.task && result.rubricVersion === request.rubricVersion, 'result: task or rubric binding mismatch')
287
+ scope(result.scope, 'result.scope', checks)
288
+ checks.check(canonical(result.scope) === canonical(request.scope), 'result.scope: request binding mismatch')
289
+ if (closed(result.provider, ['id', 'model'], [], 'result.provider', checks)) {
290
+ checks.check(identifier(result.provider.id), 'result.provider.id: invalid identifier')
291
+ checks.check(text(result.provider.model, 128), 'result.provider.model: expected nonempty model within limit')
292
+ }
293
+ checks.check(result.authority === 'proposal-only', 'result.authority: proposal-only required')
294
+ checks.check(result.mode === 'shadow' || result.mode === 'advisory', 'result.mode: unsupported mode')
295
+ checks.check(typeof result.elapsedMs === 'number' && Number.isFinite(result.elapsedMs) && result.elapsedMs >= 0, 'result.elapsedMs: expected finite nonnegative number')
296
+ if (result.usage !== null && closed(result.usage, ['inputTokens', 'outputTokens'], [], 'result.usage', checks)) {
297
+ checks.check([result.usage.inputTokens, result.usage.outputTokens].every((value) => Number.isSafeInteger(value) && value >= 0), 'result.usage: expected nonnegative safe integers')
298
+ }
299
+ if (!record(result.answers, 0, 64, 'result.answers', checks)) return
300
+ if (result.status === 'assessed') {
301
+ checks.check(!Object.hasOwn(result, 'reason'), 'result.reason: not allowed for assessed result')
302
+ inspectAnswers(request.questions, result.answers, checks, 'result.answers')
303
+ } else if (result.status === 'abstained') {
304
+ checks.check(Object.keys(result.answers).length === 0, 'result.answers: abstention must have no answers')
305
+ checks.check(REASONS.has(result.reason), 'result.reason: unsupported abstention reason')
306
+ } else {
307
+ checks.check(false, 'result.status: unsupported status')
308
+ }
309
+ }
310
+
311
+ /** Validate integrity and distributions; validity never establishes correctness. */
312
+ export function validateDecisionResult(input, output) {
313
+ const { request, checks } = inspected(input)
314
+ if (checks.errors.length) return { ok: false, errors: ['request: invalid decision request'] }
315
+ try {
316
+ inspectResult(request, jsonSnapshot(output), checks)
317
+ } catch {
318
+ checks.check(false, 'result: expected bounded plain JSON')
319
+ }
320
+ return { ok: checks.errors.length === 0, errors: checks.errors }
321
+ }
@@ -35,6 +35,19 @@ export function compileDisclosurePatterns(patternDocs = []) {
35
35
  })
36
36
  }
37
37
 
38
+ export function scanDisclosureText(text, { denylistPatterns = [] } = {}) {
39
+ if (typeof text !== 'string' || Buffer.byteLength(text) > 4 * 1024 * 1024) throw new Error('disclosure text unavailable or exceeds bounds')
40
+ const findings = []
41
+ const patterns = [...STRUCTURAL_DISCLOSURE_PATTERNS, ...denylistPatterns]
42
+ for (const [index,line] of text.split('\n').entries()) {
43
+ for (const {pattern,label} of patterns) {
44
+ pattern.lastIndex = 0
45
+ if (pattern.test(line)) findings.push({label,line:index+1})
46
+ }
47
+ }
48
+ return {ok:findings.length===0,findings}
49
+ }
50
+
38
51
  export function scanDisclosureContent({
39
52
  root = process.cwd(),
40
53
  staged = false,
@@ -191,8 +191,23 @@ function formatAjvError(error) {
191
191
  return `${location} ${error.message ?? 'failed schema validation'}`
192
192
  }
193
193
 
194
+ // One compiled validator per schema object, for as long as the schema lives:
195
+ // compilation is a pure function of the schema, and a registered contract is
196
+ // validated many times per run (a large manifest, every journal entry).
197
+ const compiledValidators = new WeakMap()
198
+
199
+ function validatorFor(schema) {
200
+ if (schema === null || typeof schema !== 'object') return ajvForSchema().compile(schema)
201
+ let validate = compiledValidators.get(schema)
202
+ if (!validate) {
203
+ validate = ajvForSchema().compile(schema)
204
+ compiledValidators.set(schema, validate)
205
+ }
206
+ return validate
207
+ }
208
+
194
209
  export function validateJsonSchema(schema, doc) {
195
- const validate = ajvForSchema().compile(schema)
210
+ const validate = validatorFor(schema)
196
211
  if (validate(doc)) return []
197
212
  return asArray(validate.errors).map(formatAjvError)
198
213
  }
@@ -0,0 +1,154 @@
1
+ import fs from 'node:fs'
2
+ import { BASE_MIGRATIONS } from '../upgrade/upgrade.mjs'
3
+ import { validReviewArtifact } from '../collaboration/review-contracts.mjs'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { readBoundedSource } from '../readiness-protocols/source-read.mjs'
6
+
7
+ const rootVersion = JSON.parse(
8
+ fs.readFileSync(
9
+ fileURLToPath(new URL('../../package.json', import.meta.url)),
10
+ 'utf8',
11
+ ),
12
+ ).version
13
+
14
+ export function inspectPackLifecycle(project, packs) {
15
+ const declared =
16
+ project.config.ext?.['mnstry.atelier']?.extensionPackLifecycle
17
+ if (!declared)
18
+ return {
19
+ ok: packs.length === 0,
20
+ schema: 'atelier-pack-lifecycle-report@v1',
21
+ rootVersion,
22
+ entries: packs.map((pack) => ({
23
+ id: pack.id,
24
+ status: 'legacy-unqualified',
25
+ admitted: false,
26
+ })),
27
+ errors: packs.length
28
+ ? [
29
+ 'extension-pack lifecycle declaration required for evidence-bound review',
30
+ ]
31
+ : [],
32
+ }
33
+ let policy
34
+ try {
35
+ policy = JSON.parse(readBoundedSource(project.configDir, declared).text)
36
+ } catch {
37
+ return {
38
+ ok: false,
39
+ errors: ['lifecycle declaration unavailable or unsafe'],
40
+ }
41
+ }
42
+ if (
43
+ !validReviewArtifact('lifecycle', policy) ||
44
+ policy.schema !== 'atelier-pack-lifecycle@v1' ||
45
+ !Array.isArray(policy.packs) ||
46
+ policy.packs.length > 100 ||
47
+ Object.keys(policy).some(
48
+ (key) => !['schema', 'packs', 'ext', 'contractVersion'].includes(key),
49
+ )
50
+ )
51
+ return { ok: false, errors: ['invalid lifecycle declaration'] }
52
+ const ids = new Set(),
53
+ errors = []
54
+ for (const entry of policy.packs) {
55
+ if (
56
+ !entry ||
57
+ typeof entry.id !== 'string' ||
58
+ ids.has(entry.id) ||
59
+ !Array.isArray(entry.compatibleRootVersions) ||
60
+ !entry.compatibleRootVersions.length ||
61
+ entry.compatibleRootVersions.some(
62
+ (version) => typeof version !== 'string',
63
+ ) ||
64
+ !['active', 'deprecated', 'retired'].includes(entry.status) ||
65
+ typeof entry.version !== 'string' ||
66
+ !/^sha256:[a-f0-9]{64}$/.test(entry.digest ?? '') ||
67
+ Object.keys(entry).some(
68
+ (key) =>
69
+ ![
70
+ 'id',
71
+ 'version',
72
+ 'digest',
73
+ 'compatibleRootVersions',
74
+ 'status',
75
+ 'replacement',
76
+ 'migrationId',
77
+ 'ext',
78
+ ].includes(key),
79
+ )
80
+ )
81
+ errors.push('invalid or duplicate lifecycle pack entry')
82
+ if (
83
+ entry?.migrationId &&
84
+ !BASE_MIGRATIONS.some(
85
+ (migration) =>
86
+ migration.id === entry.migrationId &&
87
+ migration.class === 'extension_pack' &&
88
+ migration.active !== false,
89
+ )
90
+ )
91
+ errors.push('unknown pack migration; owner migration unavailable')
92
+ ids.add(entry?.id)
93
+ }
94
+ const entries = packs.map((pack) => {
95
+ const entry = policy.packs.find((item) => item?.id === pack.id)
96
+ const admitted = Boolean(
97
+ entry &&
98
+ entry.version === pack.version &&
99
+ entry.digest === pack.digest &&
100
+ entry.compatibleRootVersions?.includes(rootVersion) &&
101
+ entry.status !== 'retired',
102
+ )
103
+ if (!admitted)
104
+ errors.push(`pack ${pack.id} is not qualified for new execution`)
105
+ return {
106
+ id: pack.id,
107
+ status: entry?.status ?? 'legacy-unqualified',
108
+ admitted,
109
+ replacement: entry?.replacement ?? null,
110
+ migrationId: entry?.migrationId ?? null,
111
+ }
112
+ })
113
+ return {
114
+ ok: errors.length === 0,
115
+ schema: 'atelier-pack-lifecycle-report@v1',
116
+ rootVersion,
117
+ entries,
118
+ errors,
119
+ policy,
120
+ }
121
+ }
122
+
123
+ // This report names the existing data-only upgrade operation. It neither
124
+ // changes declarations nor admits a replacement; the source owner applies it.
125
+ export function planPackLifecycleMigration(project, packs) {
126
+ const report = inspectPackLifecycle(project, packs)
127
+ const migration = BASE_MIGRATIONS.find(
128
+ (entry) => entry.class === 'extension_pack' && entry.active !== false,
129
+ )
130
+ return {
131
+ ok: report.ok,
132
+ schema: 'atelier-pack-migration-plan@v1',
133
+ dryRun: true,
134
+ writes: false,
135
+ current: report,
136
+ migration: migration
137
+ ? {
138
+ id: migration.id,
139
+ files: migration.files,
140
+ requiredPostChecks: migration.requiredPostChecks,
141
+ authority: migration.authority,
142
+ }
143
+ : null,
144
+ steps: [
145
+ 'Retain the prior package, pack declarations, lock and historical records.',
146
+ 'Review replacement term and protocol meanings in the source owner workflow.',
147
+ 'Update the declared version, digest and exact compatible root versions together.',
148
+ 'Use the existing upgrade migration to verify and refresh the lock after owner review.',
149
+ 'Create a new evidence-bound run; old runs retain their original meaning.',
150
+ ],
151
+ rollback:
152
+ 'Restore the prior declarations, package and lock together. Retain contribution and evidence history.',
153
+ }
154
+ }