@mnstry/atelier 0.2.0-alpha.1 → 0.2.0-alpha.10

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 (414) hide show
  1. package/CHANGELOG.md +372 -0
  2. package/README.md +402 -205
  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/contracts/public-api-baseline.json +57 -0
  31. package/docs/assurance-controls.md +49 -0
  32. package/docs/atelier-runtime.md +28 -2
  33. package/docs/atelier-sync.md +172 -0
  34. package/docs/blocks/audience-visibility.md +9 -0
  35. package/docs/blocks/claims.md +61 -0
  36. package/docs/blocks/conformance-admission.md +15 -0
  37. package/docs/blocks/will-not-do.md +18 -0
  38. package/docs/coauthor-session.md +57 -0
  39. package/docs/connected-composition.md +75 -0
  40. package/docs/continuity.md +3 -4
  41. package/docs/decisions.md +167 -0
  42. package/docs/design.md +167 -0
  43. package/docs/exact-upgrades.md +159 -0
  44. package/docs/extension-pack-lifecycle.md +33 -0
  45. package/docs/guided-upgrades.md +99 -0
  46. package/docs/install.md +99 -16
  47. package/docs/intake-and-guides.md +65 -0
  48. package/docs/integration-and-review-plan.md +409 -0
  49. package/docs/integration-contract-decisions.md +22 -0
  50. package/docs/knowledge-graph.md +8 -4
  51. package/docs/local-review.md +84 -0
  52. package/docs/local-services.md +328 -0
  53. package/docs/local-state.md +107 -0
  54. package/docs/obsidian-contract.md +690 -0
  55. package/docs/obsidian.md +423 -0
  56. package/docs/portable-presentation.md +328 -0
  57. package/docs/presentation-consumer-boundaries.md +75 -0
  58. package/docs/presentation-review-disposition.md +47 -0
  59. package/docs/project-command-inventory.md +38 -0
  60. package/docs/project-options.md +53 -0
  61. package/docs/release-engineering.md +174 -12
  62. package/docs/repo-boundary-guard.md +12 -2
  63. package/docs/review-portability.md +55 -0
  64. package/docs/skill-steward.md +156 -0
  65. package/docs/upgrade.md +146 -5
  66. package/docs/vault-service.md +292 -0
  67. package/fixtures/atelier-coauthor/invalid/authority.v1.json +5 -0
  68. package/fixtures/atelier-coauthor/valid/config.v1.json +12 -0
  69. package/fixtures/atelier-guide/invalid/authority.v1.json +11 -0
  70. package/fixtures/atelier-guide/valid/capability.v1.json +10 -0
  71. package/fixtures/atelier-guide/valid/consent.v1.json +11 -0
  72. package/fixtures/atelier-guide/valid/engagement.v1.json +7 -0
  73. package/fixtures/atelier-guide/valid/offer.v1.json +10 -0
  74. package/fixtures/atelier-intake/invalid/authority.v1.json +9 -0
  75. package/fixtures/atelier-intake/valid/attempt.v1.json +8 -0
  76. package/fixtures/atelier-intake/valid/completion.v1.json +9 -0
  77. package/fixtures/atelier-intake/valid/source.v1.json +7 -0
  78. package/fixtures/atelier-repository-observation/invalid/complete-with-blocker.v1.json +18 -0
  79. package/fixtures/atelier-repository-observation/valid/complete-local.v1.json +48 -0
  80. package/fixtures/atelier-review/invalid/authenticated-identity.v1.json +22 -0
  81. package/fixtures/atelier-review/valid/bound.v1.json +229 -0
  82. package/fixtures/atelier-review/valid/bundle.v1.json +267 -0
  83. package/fixtures/atelier-review/valid/contribution.v1.json +22 -0
  84. package/fixtures/atelier-review/valid/lifecycle.v1.json +14 -0
  85. package/fixtures/atelier-skill-steward/audit/invalid/source-mutation.v1.json +26 -0
  86. package/fixtures/atelier-skill-steward/audit/valid/clean.v1.json +26 -0
  87. package/fixtures/atelier-skill-steward/candidates/invalid/workflow-key.v1.json +37 -0
  88. package/fixtures/atelier-skill-steward/candidates/valid/create.v1.json +37 -0
  89. package/fixtures/atelier-skill-steward/lock/invalid/bad-digest.v1.json +18 -0
  90. package/fixtures/atelier-skill-steward/lock/valid/installed.v1.json +23 -0
  91. package/fixtures/atelier-skill-steward/plan/invalid/delete-action.v1.json +33 -0
  92. package/fixtures/atelier-skill-steward/plan/valid/add.v1.json +33 -0
  93. package/fixtures/atelier-upgrade-transaction/adoption-policy/invalid/example.json +12 -0
  94. package/fixtures/atelier-upgrade-transaction/adoption-policy/valid/example.json +11 -0
  95. package/fixtures/atelier-upgrade-transaction/migration/invalid/example.json +20 -0
  96. package/fixtures/atelier-upgrade-transaction/migration/valid/example.json +19 -0
  97. package/fixtures/atelier-upgrade-transaction/upgrade-plan/invalid/example.json +58 -0
  98. package/fixtures/atelier-upgrade-transaction/upgrade-plan/valid/example.json +57 -0
  99. package/fixtures/atelier-upgrade-transaction/upgrade-receipt/invalid/example.json +15 -0
  100. package/fixtures/atelier-upgrade-transaction/upgrade-receipt/valid/example.json +14 -0
  101. package/fixtures/atelier-vault-publication/invalid/publication.json +5 -0
  102. package/fixtures/atelier-vault-publication/valid/publication.json +11 -0
  103. package/fixtures/decisions/request/invalid/unknown-authority.v1.json +61 -0
  104. package/fixtures/decisions/request/valid/triage.v1.json +60 -0
  105. package/fixtures/decisions/result/invalid/execution-authority.v1.json +50 -0
  106. package/fixtures/decisions/result/valid/abstained.v1.json +23 -0
  107. package/fixtures/decisions/result/valid/assessed.v1.json +50 -0
  108. package/fixtures/obsidian/acceptance/ap05-extra-notes.json +14 -0
  109. package/fixtures/obsidian/acceptance/receipts/G07.valid.v1.json +63 -0
  110. package/fixtures/obsidian/acceptance/receipts/G13.valid.v1.json +69 -0
  111. package/fixtures/obsidian/acceptance/receipts/G14.valid.v1.json +62 -0
  112. package/fixtures/obsidian/acceptance/receipts/G15.valid.v1.json +56 -0
  113. package/fixtures/obsidian/acceptance/receipts/G16.valid.v1.json +73 -0
  114. package/fixtures/obsidian/acceptance/receipts/G17.valid.v1.json +75 -0
  115. package/fixtures/obsidian/acceptance/receipts/G18.valid.v1.json +64 -0
  116. package/fixtures/obsidian/acceptance/service-sentinels.json +10 -0
  117. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/duplicate-evidence-name.v1.json +39 -0
  118. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/evidence-by-absolute-path.v1.json +34 -0
  119. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/missing-candidate-tree.v1.json +33 -0
  120. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/no-evidence.v1.json +28 -0
  121. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/unknown-outcome.v1.json +34 -0
  122. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/unknown-top-level-field.v1.json +35 -0
  123. package/fixtures/obsidian/contracts/acceptance-receipt/valid/node-gate.v1.json +34 -0
  124. package/fixtures/obsidian/contracts/apply-policy/invalid/automatic-without-edit-class.v1.json +27 -0
  125. package/fixtures/obsidian/contracts/apply-policy/invalid/conflict-overwrite.v1.json +29 -0
  126. package/fixtures/obsidian/contracts/apply-policy/invalid/missing-policy-digest.v1.json +28 -0
  127. package/fixtures/obsidian/contracts/apply-policy/invalid/unbounded-batch.v1.json +29 -0
  128. package/fixtures/obsidian/contracts/apply-policy/invalid/unimplemented-edit-class.v1.json +30 -0
  129. package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-mode.v1.json +29 -0
  130. package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-selector-operator.v1.json +22 -0
  131. package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-top-level-field.v1.json +30 -0
  132. package/fixtures/obsidian/contracts/apply-policy/valid/automatic-scoped.v1.json +29 -0
  133. package/fixtures/obsidian/contracts/apply-policy/valid/manual-default.v1.json +20 -0
  134. package/fixtures/obsidian/contracts/corpus-profile/invalid/absolute-path-in-ext.v1.json +30 -0
  135. package/fixtures/obsidian/contracts/corpus-profile/invalid/absolute-repository-root.v1.json +27 -0
  136. package/fixtures/obsidian/contracts/corpus-profile/invalid/duplicate-repository-id.v1.json +27 -0
  137. package/fixtures/obsidian/contracts/corpus-profile/invalid/overlapping-managed-roots.v1.json +27 -0
  138. package/fixtures/obsidian/contracts/corpus-profile/invalid/unknown-enrollment.v1.json +27 -0
  139. package/fixtures/obsidian/contracts/corpus-profile/invalid/unknown-top-level-field.v1.json +28 -0
  140. package/fixtures/obsidian/contracts/corpus-profile/valid/three-repositories.v1.json +27 -0
  141. package/fixtures/obsidian/contracts/edit-operation/invalid/absolute-recovery-ref.v1.json +21 -0
  142. package/fixtures/obsidian/contracts/edit-operation/invalid/inline-observed-bytes.v1.json +22 -0
  143. package/fixtures/obsidian/contracts/edit-operation/invalid/missing-base-digest.v1.json +20 -0
  144. package/fixtures/obsidian/contracts/edit-operation/invalid/short-idempotency-key.v1.json +21 -0
  145. package/fixtures/obsidian/contracts/edit-operation/invalid/unknown-kind.v1.json +21 -0
  146. package/fixtures/obsidian/contracts/edit-operation/valid/pending-body-replacement.v1.json +21 -0
  147. package/fixtures/obsidian/contracts/ext-settings/invalid/absolute-path-prefix.v1.json +28 -0
  148. package/fixtures/obsidian/contracts/ext-settings/invalid/apply-policy-in-portable-settings.v1.json +30 -0
  149. package/fixtures/obsidian/contracts/ext-settings/invalid/duplicate-scope-id.v1.json +27 -0
  150. package/fixtures/obsidian/contracts/ext-settings/invalid/machine-local-vault-path.v1.json +28 -0
  151. package/fixtures/obsidian/contracts/ext-settings/invalid/service-port-in-portable-settings.v1.json +28 -0
  152. package/fixtures/obsidian/contracts/ext-settings/invalid/unknown-default-scope.v1.json +27 -0
  153. package/fixtures/obsidian/contracts/ext-settings/valid/disabled.v1.json +5 -0
  154. package/fixtures/obsidian/contracts/ext-settings/valid/enabled-with-scopes.v1.json +27 -0
  155. package/fixtures/obsidian/contracts/generation-manifest/invalid/absolute-note-path.v1.json +114 -0
  156. package/fixtures/obsidian/contracts/generation-manifest/invalid/complete-with-unwritten-notes.v1.json +114 -0
  157. package/fixtures/obsidian/contracts/generation-manifest/invalid/derived-link-claimed-declared.v1.json +114 -0
  158. package/fixtures/obsidian/contracts/generation-manifest/invalid/duplicate-note-identity.v1.json +114 -0
  159. package/fixtures/obsidian/contracts/generation-manifest/invalid/in-scope-endpoint-missing.v1.json +114 -0
  160. package/fixtures/obsidian/contracts/generation-manifest/invalid/title-only-note-path.v1.json +114 -0
  161. package/fixtures/obsidian/contracts/generation-manifest/invalid/unknown-note-field.v1.json +115 -0
  162. package/fixtures/obsidian/contracts/generation-manifest/invalid/unknown-relation-type.v1.json +114 -0
  163. package/fixtures/obsidian/contracts/generation-manifest/invalid/withheld-endpoint.v1.json +114 -0
  164. package/fixtures/obsidian/contracts/generation-manifest/valid/two-notes.v1.json +114 -0
  165. package/fixtures/obsidian/contracts/oracles/scope-cases.json +412 -0
  166. package/fixtures/obsidian/contracts/proposal-receipt/invalid/accepted-without-proposal-id.v1.json +12 -0
  167. package/fixtures/obsidian/contracts/proposal-receipt/invalid/missing-adapter-operation-id.v1.json +11 -0
  168. package/fixtures/obsidian/contracts/proposal-receipt/invalid/unknown-backpressure-outcome.v1.json +12 -0
  169. package/fixtures/obsidian/contracts/proposal-receipt/invalid/unknown-top-level-field.v1.json +13 -0
  170. package/fixtures/obsidian/contracts/proposal-receipt/valid/accepted-new.v1.json +12 -0
  171. package/fixtures/obsidian/contracts/proposal-receipt/valid/deferred.v1.json +12 -0
  172. package/fixtures/obsidian/contracts/publication-journal/invalid/absolute-recovery-ref.v1.json +47 -0
  173. package/fixtures/obsidian/contracts/publication-journal/invalid/duplicate-sequence.v1.json +47 -0
  174. package/fixtures/obsidian/contracts/publication-journal/invalid/empty-protocol-id.v1.json +47 -0
  175. package/fixtures/obsidian/contracts/publication-journal/invalid/missing-protocol-id.v1.json +46 -0
  176. package/fixtures/obsidian/contracts/publication-journal/invalid/unknown-entry-field.v1.json +48 -0
  177. package/fixtures/obsidian/contracts/publication-journal/invalid/unknown-state.v1.json +47 -0
  178. package/fixtures/obsidian/contracts/publication-journal/valid/committed.v1.json +47 -0
  179. package/fixtures/obsidian/contracts/publication-journal/valid/updating-after-restart.v1.json +22 -0
  180. package/fixtures/obsidian/contracts/scope/invalid/absolute-path-prefix.v1.json +15 -0
  181. package/fixtures/obsidian/contracts/scope/invalid/duplicate-ids.v1.json +18 -0
  182. package/fixtures/obsidian/contracts/scope/invalid/expansion-without-node-budget.v1.json +40 -0
  183. package/fixtures/obsidian/contracts/scope/invalid/full-mode-with-subset.v1.json +8 -0
  184. package/fixtures/obsidian/contracts/scope/invalid/unknown-mode.v1.json +41 -0
  185. package/fixtures/obsidian/contracts/scope/invalid/unknown-selector-key.v1.json +15 -0
  186. package/fixtures/obsidian/contracts/scope/invalid/unknown-selector-operator.v1.json +14 -0
  187. package/fixtures/obsidian/contracts/scope/invalid/unknown-top-level-field.v1.json +42 -0
  188. package/fixtures/obsidian/contracts/scope/valid/explicit-empty.v1.json +8 -0
  189. package/fixtures/obsidian/contracts/scope/valid/focus.v1.json +11 -0
  190. package/fixtures/obsidian/contracts/scope/valid/full.v1.json +8 -0
  191. package/fixtures/obsidian/contracts/scope/valid/scoped-set-expression.v1.json +41 -0
  192. package/fixtures/obsidian/contracts/service-state/invalid/hostname-instead-of-literal-loopback.v1.json +23 -0
  193. package/fixtures/obsidian/contracts/service-state/invalid/missing-consent.v1.json +18 -0
  194. package/fixtures/obsidian/contracts/service-state/invalid/privileged-port.v1.json +23 -0
  195. package/fixtures/obsidian/contracts/service-state/invalid/unknown-top-level-field.v1.json +24 -0
  196. package/fixtures/obsidian/contracts/service-state/invalid/wildcard-bind.v1.json +23 -0
  197. package/fixtures/obsidian/contracts/service-state/valid/healthy.v1.json +23 -0
  198. package/fixtures/obsidian/contracts/source-snapshot/invalid/absolute-file-path.v1.json +48 -0
  199. package/fixtures/obsidian/contracts/source-snapshot/invalid/duplicate-repository-id.v1.json +48 -0
  200. package/fixtures/obsidian/contracts/source-snapshot/invalid/mixed-read.v1.json +48 -0
  201. package/fixtures/obsidian/contracts/source-snapshot/invalid/parent-traversal-path.v1.json +48 -0
  202. package/fixtures/obsidian/contracts/source-snapshot/invalid/unknown-file-field.v1.json +49 -0
  203. package/fixtures/obsidian/contracts/source-snapshot/valid/two-repositories.v1.json +48 -0
  204. package/fixtures/obsidian/edits/workspace.json +65 -0
  205. package/fixtures/obsidian/materialization/expected-full.json +99 -0
  206. package/fixtures/obsidian/materialization/expected-scoped.json +39 -0
  207. package/fixtures/obsidian/materialization/workspace.json +54 -0
  208. package/fixtures/obsidian/proposals/expected-shapes.json +11 -0
  209. package/fixtures/projects/sample-workspace/content/source.html.kg.json +4 -1
  210. package/fixtures/projects/source-formats-workspace/content/data.json.kg.json +4 -1
  211. package/fixtures/projects/source-formats-workspace/content/logo.png.kg.json +4 -1
  212. package/fixtures/projects/source-formats-workspace/content/metrics.csv.kg.json +4 -1
  213. package/fixtures/projects/source-formats-workspace/content/pipeline.yaml.kg.json +4 -1
  214. package/fixtures/ui/presentation/pane.v1.json +7 -0
  215. package/fixtures/ui/presentation/reference.v1.json +293 -0
  216. package/package.json +86 -10
  217. package/skills/claude/atelier-guided-coauthor/SKILL.md +49 -0
  218. package/skills/claude/atelier-guided-upgrade/SKILL.md +97 -0
  219. package/skills/claude/atelier-local-service/SKILL.md +47 -0
  220. package/skills/claude/atelier-open/SKILL.md +11 -5
  221. package/skills/claude/atelier-public-boundary/SKILL.md +31 -0
  222. package/skills/claude/atelier-skill-steward/SKILL.md +62 -0
  223. package/skills/claude/mnstry-readiness/SKILL.md +5 -0
  224. package/skills/codex/atelier-guided-coauthor/SKILL.md +49 -0
  225. package/skills/codex/atelier-guided-upgrade/SKILL.md +97 -0
  226. package/skills/codex/atelier-local-service/SKILL.md +47 -0
  227. package/skills/codex/atelier-open/SKILL.md +11 -5
  228. package/skills/codex/atelier-public-boundary/SKILL.md +31 -0
  229. package/skills/codex/atelier-skill-steward/SKILL.md +62 -0
  230. package/skills/codex/mnstry-readiness/SKILL.md +5 -0
  231. package/src/access/preview-enforcer.mjs +84 -0
  232. package/src/boundary/content-rules.mjs +283 -20
  233. package/src/boundary/policy.mjs +213 -91
  234. package/src/cli/execute-command.mjs +36 -0
  235. package/src/cli/project-options.mjs +58 -0
  236. package/src/cli/run.mjs +78 -10
  237. package/src/coauthor/session.mjs +161 -0
  238. package/src/coauthor/store.mjs +173 -0
  239. package/src/collaboration/event-ledger.mjs +348 -0
  240. package/src/collaboration/index.mjs +17 -0
  241. package/src/collaboration/inspection-bundle.mjs +223 -0
  242. package/src/collaboration/proposals.mjs +265 -65
  243. package/src/collaboration/review-contracts.mjs +25 -0
  244. package/src/collaboration/review-store.mjs +305 -0
  245. package/src/commands/attestation.mjs +20 -6
  246. package/src/commands/coauthor.mjs +26 -0
  247. package/src/commands/config.mjs +6 -1
  248. package/src/commands/disclosure.mjs +133 -0
  249. package/src/commands/distribution.mjs +2 -1
  250. package/src/commands/extension-pack.mjs +17 -9
  251. package/src/commands/init.mjs +46 -6
  252. package/src/commands/obsidian.mjs +371 -0
  253. package/src/commands/review.mjs +147 -0
  254. package/src/commands/server.mjs +1 -4
  255. package/src/commands/setup.mjs +51 -14
  256. package/src/commands/skills.mjs +199 -0
  257. package/src/commands/sync.mjs +107 -0
  258. package/src/composition/registry.mjs +27 -0
  259. package/src/composition/wire.mjs +122 -0
  260. package/src/contracts/corpus.mjs +35 -0
  261. package/src/decisions/contracts.d.mts +96 -0
  262. package/src/decisions/contracts.mjs +321 -0
  263. package/src/disclosure/content-scan.mjs +206 -0
  264. package/src/egress/check.mjs +7 -38
  265. package/src/egress/forbidden-egress.mjs +32 -18
  266. package/src/export/atelier-export-contract.mjs +16 -1
  267. package/src/extension-packs/lifecycle.mjs +154 -0
  268. package/src/graph/graph.mjs +176 -316
  269. package/src/graph/knowledge-graph.mjs +643 -43
  270. package/src/guides/contracts.mjs +54 -0
  271. package/src/harness/context-client.mjs +9 -1
  272. package/src/index.mjs +41 -0
  273. package/src/intake/store.mjs +119 -0
  274. package/src/preview/channel.mjs +89 -0
  275. package/src/project/config.mjs +112 -52
  276. package/src/project/durable-state.mjs +161 -0
  277. package/src/project/file-class.mjs +180 -0
  278. package/src/project/package-root.mjs +10 -0
  279. package/src/project/path-match.mjs +38 -15
  280. package/src/project/private-state.mjs +120 -0
  281. package/src/projection/obsidian/contracts.mjs +571 -0
  282. package/src/projection/obsidian/edits/align.mjs +169 -0
  283. package/src/projection/obsidian/edits/apply.mjs +889 -0
  284. package/src/projection/obsidian/edits/arbitrate.mjs +414 -0
  285. package/src/projection/obsidian/edits/contribution.mjs +108 -0
  286. package/src/projection/obsidian/edits/index.mjs +19 -0
  287. package/src/projection/obsidian/edits/object-identity.mjs +61 -0
  288. package/src/projection/obsidian/edits/object-store.mjs +517 -0
  289. package/src/projection/obsidian/edits/observe.mjs +123 -0
  290. package/src/projection/obsidian/edits/policy.mjs +123 -0
  291. package/src/projection/obsidian/edits/regions.mjs +648 -0
  292. package/src/projection/obsidian/materialize/byte-lens.mjs +98 -0
  293. package/src/projection/obsidian/materialize/index.mjs +7 -0
  294. package/src/projection/obsidian/materialize/path-registry.mjs +105 -0
  295. package/src/projection/obsidian/materialize/prepare-view.mjs +746 -0
  296. package/src/projection/obsidian/materialize/settings.mjs +57 -0
  297. package/src/projection/obsidian/materialize/stage.mjs +27 -0
  298. package/src/projection/obsidian/proposals/adapter.mjs +572 -0
  299. package/src/projection/obsidian/proposals/backpressure.mjs +113 -0
  300. package/src/projection/obsidian/proposals/contribution.mjs +55 -0
  301. package/src/projection/obsidian/proposals/index.mjs +23 -0
  302. package/src/projection/obsidian/proposals/observation.mjs +216 -0
  303. package/src/projection/obsidian/proposals/queue.mjs +277 -0
  304. package/src/projection/obsidian/proposals/router.mjs +168 -0
  305. package/src/projection/obsidian/publication/bridge-script.mjs +266 -0
  306. package/src/projection/obsidian/publication/exchange.mjs +117 -0
  307. package/src/projection/obsidian/publication/index.mjs +5 -0
  308. package/src/projection/obsidian/publication/publisher.mjs +588 -0
  309. package/src/projection/obsidian/publication/test-seam.mjs +16 -0
  310. package/src/projection/obsidian/publication/transport.mjs +160 -0
  311. package/src/projection/obsidian/recovery/index.mjs +6 -0
  312. package/src/projection/obsidian/recovery/journal.mjs +134 -0
  313. package/src/projection/obsidian/recovery/late-writer.mjs +46 -0
  314. package/src/projection/obsidian/recovery/restart.mjs +316 -0
  315. package/src/projection/obsidian/recovery/store.mjs +234 -0
  316. package/src/projection/obsidian/selection-ui/conflict-view.mjs +91 -0
  317. package/src/projection/obsidian/selection-ui/contribution.mjs +141 -0
  318. package/src/projection/obsidian/selection-ui/focus.mjs +82 -0
  319. package/src/projection/obsidian/selection-ui/index.mjs +12 -0
  320. package/src/projection/obsidian/selection-ui/policy-setup.mjs +107 -0
  321. package/src/projection/obsidian/selection-ui/receipt.mjs +173 -0
  322. package/src/projection/obsidian/selection-ui/selection-state.mjs +122 -0
  323. package/src/projection/obsidian/selection-ui/selection.mjs +109 -0
  324. package/src/projection/project.mjs +28 -19
  325. package/src/readiness-protocols/evidence.mjs +282 -0
  326. package/src/readiness-protocols/runtime.mjs +4 -3
  327. package/src/readiness-protocols/source-read.mjs +39 -0
  328. package/src/runtime/git-adapter.mjs +189 -0
  329. package/src/runtime/local-state.mjs +439 -0
  330. package/src/runtime/obsidian/app-capability.mjs +129 -0
  331. package/src/runtime/obsidian/app-production-seams.mjs +105 -0
  332. package/src/runtime/obsidian/contributions/proposal-adapter.mjs +4 -0
  333. package/src/runtime/obsidian/contributions/selection-ui.mjs +4 -0
  334. package/src/runtime/obsidian/contributions/source-apply.mjs +4 -0
  335. package/src/runtime/obsidian/contributions.mjs +27 -0
  336. package/src/runtime/obsidian/documents.mjs +37 -0
  337. package/src/runtime/obsidian/enablement.mjs +53 -0
  338. package/src/runtime/obsidian/engine.mjs +532 -0
  339. package/src/runtime/obsidian/errors.mjs +14 -0
  340. package/src/runtime/obsidian/extension-points.mjs +103 -0
  341. package/src/runtime/obsidian/index.mjs +34 -0
  342. package/src/runtime/obsidian/lifecycle.mjs +258 -0
  343. package/src/runtime/obsidian/machine-settings.mjs +260 -0
  344. package/src/runtime/obsidian/observation.mjs +158 -0
  345. package/src/runtime/obsidian/opening.mjs +263 -0
  346. package/src/runtime/obsidian/pending-edits.mjs +141 -0
  347. package/src/runtime/obsidian/pipeline.mjs +122 -0
  348. package/src/runtime/obsidian/private-lock.mjs +156 -0
  349. package/src/runtime/obsidian/process-identity.mjs +58 -0
  350. package/src/runtime/obsidian/service-client.mjs +66 -0
  351. package/src/runtime/obsidian/service-entry-path.mjs +9 -0
  352. package/src/runtime/obsidian/service-main.mjs +91 -0
  353. package/src/runtime/obsidian/service-record.mjs +169 -0
  354. package/src/runtime/obsidian/service-server.mjs +121 -0
  355. package/src/runtime/obsidian/service.mjs +216 -0
  356. package/src/runtime/obsidian/startup-adapters.mjs +87 -0
  357. package/src/runtime/obsidian/state-store.mjs +180 -0
  358. package/src/runtime/obsidian/sync-notice.mjs +33 -0
  359. package/src/runtime/obsidian/tick-loop.mjs +84 -0
  360. package/src/runtime/obsidian/watchers.mjs +36 -0
  361. package/src/runtime/repository-observation.mjs +491 -0
  362. package/src/runtime/supervisor.mjs +803 -0
  363. package/src/server/local-sidecar.mjs +125 -59
  364. package/src/server/security.mjs +89 -4
  365. package/src/server/server.mjs +4 -2
  366. package/src/skills/steward.mjs +780 -0
  367. package/src/support/feedback-report.mjs +4 -3
  368. package/src/ui/presentation/browser.mjs +278 -0
  369. package/src/ui/presentation/contract.mjs +116 -0
  370. package/src/ui/presentation/index.mjs +7 -0
  371. package/src/ui/presentation/native.mjs +153 -0
  372. package/src/ui/presentation/proof.mjs +57 -0
  373. package/src/ui/presentation/schema-check.mjs +30 -0
  374. package/src/ui/presentation/schema.generated.mjs +1371 -0
  375. package/src/ui/presentation/state.mjs +43 -0
  376. package/src/ui/presentation/styles.mjs +67 -0
  377. package/src/ui/presentation/tokens.mjs +74 -0
  378. package/src/ui/presentation/web.mjs +85 -0
  379. package/src/ui/review-page.mjs +32 -0
  380. package/src/upgrade/explanation.mjs +33 -0
  381. package/src/upgrade/provenance.mjs +338 -0
  382. package/src/upgrade/transaction-files.mjs +103 -0
  383. package/src/upgrade/transaction.mjs +475 -0
  384. package/src/upgrade/upgrade.mjs +65 -29
  385. package/src/vault/hosts.mjs +21 -0
  386. package/src/vault/identity.mjs +19 -0
  387. package/src/vault/index.mjs +7 -0
  388. package/src/vault/interface.mjs +11 -0
  389. package/src/vault/metadata.mjs +31 -0
  390. package/src/vault/privacy.mjs +148 -0
  391. package/src/vault/probe.mjs +89 -0
  392. package/src/vault/service.mjs +131 -0
  393. package/src/vault/source.mjs +45 -0
  394. package/src/vault/storage.mjs +23 -0
  395. package/templates/distribution-workspace/.claude/launch.json +6 -2
  396. package/templates/distribution-workspace/gitignore +1 -0
  397. package/templates/distribution-workspace/project/README.md +2 -2
  398. package/templates/external-project-workspace/README.md +45 -0
  399. package/templates/external-project-workspace/answers.example.json +4 -0
  400. package/templates/external-project-workspace/atelier.project.json +40 -0
  401. package/templates/external-project-workspace/boundary-policy.v1.json +60 -0
  402. package/templates/external-project-workspace/gitignore +8 -0
  403. package/templates/external-project-workspace/pack-lifecycle.json +14 -0
  404. package/templates/external-project-workspace/packs/protocols/contract-gate.v1.json +109 -0
  405. package/templates/external-project-workspace/packs/sample-pack.v1.json +48 -0
  406. package/templates/external-project-workspace/repo-access.v1.json +9 -0
  407. package/templates/external-project-workspace/source/README.md +11 -0
  408. package/templates/launch.json +6 -2
  409. package/templates/private-domain-workspace/.claude/launch.json +6 -2
  410. package/templates/private-domain-workspace/domain/README.md +2 -2
  411. package/templates/private-domain-workspace/gitignore +1 -0
  412. package/templates/shared-project-workspace/.claude/launch.json +6 -2
  413. package/templates/shared-project-workspace/gitignore +1 -0
  414. package/templates/shared-project-workspace/project/README.md +2 -2
@@ -0,0 +1,348 @@
1
+ import crypto from 'node:crypto'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { withPrivateLock } from '../project/durable-state.mjs'
5
+ import { canonicalize } from '../attestation/jcs.mjs'
6
+ import {
7
+ atomicReplacePrivateText,
8
+ ensureContainedPrivateDirectory,
9
+ openRegularFileNoFollow,
10
+ } from '../project/private-state.mjs'
11
+
12
+ export const ATELIER_COLLABORATION_EVENT_SCHEMA = 'atelier-collaboration-event@v1'
13
+ export const COLLABORATION_LEDGER_LIMITS = Object.freeze({
14
+ maxLineBytes: 256 * 1024,
15
+ maxBytes: 16 * 1024 * 1024,
16
+ maxEvents: 10_000,
17
+ retainPerAggregate: 50,
18
+ retainDays: 180,
19
+ })
20
+
21
+ function isRecord(value) {
22
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
23
+ }
24
+
25
+ function validTimestamp(value) {
26
+ return typeof value === 'string' && Number.isFinite(Date.parse(value))
27
+ }
28
+
29
+ function secureAppendLine(file, line) {
30
+ let previous = '';
31
+ try {
32
+ const fd = openRegularFileNoFollow(file);
33
+ try { previous = fs.readFileSync(fd, 'utf8'); } finally { fs.closeSync(fd); }
34
+ } catch (error) { if (error.code !== 'ENOENT') throw error; }
35
+ // The ledger is bounded. Replace its complete validated prefix atomically.
36
+ atomicReplacePrivateText(file, previous + line);
37
+ }
38
+
39
+ function eventId(event) {
40
+ const authoritative = {
41
+ schema: event.schema,
42
+ aggregateId: event.aggregateId,
43
+ version: event.version,
44
+ type: event.type,
45
+ actor: event.actor,
46
+ at: event.at,
47
+ payload: event.payload,
48
+ }
49
+ const digest = crypto
50
+ .createHash('sha256')
51
+ .update(canonicalize(authoritative))
52
+ .digest('hex')
53
+ .slice(0, 32)
54
+ return `event-${digest}`
55
+ }
56
+
57
+ function boundedLimit(value, hardLimit) {
58
+ return Number.isFinite(value) && value > 0 ? Math.min(Math.floor(value), hardLimit) : hardLimit
59
+ }
60
+
61
+ export function validateCollaborationEvent(event) {
62
+ const issues = []
63
+ if (!isRecord(event)) return { ok: false, issues: ['event must be an object'] }
64
+ if (event.schema !== ATELIER_COLLABORATION_EVENT_SCHEMA) issues.push('event schema is unsupported')
65
+ if (typeof event.id !== 'string' || !/^event-[a-f0-9]{32}$/.test(event.id)) issues.push('event id is invalid')
66
+ if (typeof event.aggregateId !== 'string' || !event.aggregateId) issues.push('aggregateId is required')
67
+ if (!Number.isInteger(event.version) || event.version < 1) issues.push('version must be a positive integer')
68
+ if (typeof event.type !== 'string' || !/^[a-z][a-z0-9.-]*$/.test(event.type)) issues.push('event type is invalid')
69
+ if (typeof event.actor !== 'string' || !event.actor.trim()) issues.push('actor is required')
70
+ if (!validTimestamp(event.at)) issues.push('event timestamp is invalid')
71
+ if (!isRecord(event.payload)) issues.push('event payload must be an object')
72
+ if (
73
+ typeof event.id === 'string' && /^event-[a-f0-9]{32}$/.test(event.id) &&
74
+ typeof event.aggregateId === 'string' && event.aggregateId &&
75
+ Number.isInteger(event.version) && event.version > 0 &&
76
+ typeof event.type === 'string' && /^[a-z][a-z0-9.-]*$/.test(event.type) &&
77
+ typeof event.actor === 'string' && event.actor.trim() &&
78
+ validTimestamp(event.at) && isRecord(event.payload)
79
+ ) {
80
+ try {
81
+ if (event.id !== eventId(event)) issues.push('event id does not match authoritative event content')
82
+ } catch {
83
+ issues.push('authoritative event content cannot be canonicalized')
84
+ }
85
+ }
86
+ return issues.length === 0 ? { ok: true, value: event } : { ok: false, issues }
87
+ }
88
+
89
+ export function createCollaborationEventLedger({
90
+ workspaceRoot = process.cwd(),
91
+ ledgerPath = path.join(workspaceRoot, '.atelier-proposals', 'events.ndjson'),
92
+ clock = () => new Date().toISOString(),
93
+ maxLineBytes = COLLABORATION_LEDGER_LIMITS.maxLineBytes,
94
+ maxBytes = COLLABORATION_LEDGER_LIMITS.maxBytes,
95
+ maxEvents = COLLABORATION_LEDGER_LIMITS.maxEvents,
96
+ } = {}) {
97
+ const root = fs.realpathSync(workspaceRoot)
98
+ const resolvedLedger = path.resolve(ledgerPath)
99
+ const ledgerDirReal = ensureContainedPrivateDirectory({
100
+ workspaceRoot,
101
+ directory: path.dirname(resolvedLedger),
102
+ label: 'collaboration ledger directory',
103
+ })
104
+ const ledgerReal = path.join(ledgerDirReal, path.basename(resolvedLedger))
105
+ const relative = path.relative(root, ledgerReal)
106
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
107
+ throw new Error('collaboration ledger escapes workspace')
108
+ }
109
+
110
+ const limits = Object.freeze({
111
+ maxLineBytes: boundedLimit(maxLineBytes, COLLABORATION_LEDGER_LIMITS.maxLineBytes),
112
+ maxBytes: boundedLimit(maxBytes, COLLABORATION_LEDGER_LIMITS.maxBytes),
113
+ maxEvents: boundedLimit(maxEvents, COLLABORATION_LEDGER_LIMITS.maxEvents),
114
+ })
115
+ const lockPath = `${ledgerReal}.lock`
116
+
117
+ function failure(status, error, diagnostics = [], stats = {}) {
118
+ return { ok: false, status, error, events: [], diagnostics, stats }
119
+ }
120
+
121
+ function readAll() {
122
+ const startedAt = performance.now()
123
+ if (!fs.existsSync(ledgerReal)) {
124
+ return { ok: true, status: 200, events: [], diagnostics: [], stats: { bytes: 0, eventCount: 0, durationMs: performance.now() - startedAt } }
125
+ }
126
+
127
+ let descriptor
128
+ let bytes
129
+ try {
130
+ descriptor = openRegularFileNoFollow(ledgerReal, fs.constants.O_RDONLY)
131
+ bytes = fs.fstatSync(descriptor).size
132
+ } catch (error) {
133
+ return failure(500, `collaboration ledger cannot be inspected: ${error.message}`)
134
+ }
135
+ if (bytes > limits.maxBytes) {
136
+ fs.closeSync(descriptor)
137
+ return failure(413, `collaboration ledger exceeds ${limits.maxBytes} byte hard ceiling`, [{ code: 'ledger-byte-limit', bytes }], { bytes, eventCount: 0 })
138
+ }
139
+
140
+ let raw
141
+ try {
142
+ raw = fs.readFileSync(descriptor, 'utf8')
143
+ } catch (error) {
144
+ return failure(500, `collaboration ledger cannot be read: ${error.message}`, [], { bytes, eventCount: 0 })
145
+ } finally {
146
+ fs.closeSync(descriptor)
147
+ }
148
+
149
+ const diagnostics = []
150
+ const events = []
151
+ const versions = new Map()
152
+ const eventIds = new Set()
153
+ const lines = raw.split('\n')
154
+ for (let index = 0; index < lines.length; index += 1) {
155
+ const line = lines[index]
156
+ if (!line) continue
157
+ const lineNumber = index + 1
158
+ const lineBytes = Buffer.byteLength(line)
159
+ if (lineBytes > limits.maxLineBytes) {
160
+ diagnostics.push({ line: lineNumber, code: 'ledger-line-limit', message: `line exceeds ${limits.maxLineBytes} bytes` })
161
+ continue
162
+ }
163
+ if (events.length >= limits.maxEvents) {
164
+ diagnostics.push({ line: lineNumber, code: 'ledger-event-limit', message: `ledger exceeds ${limits.maxEvents} events` })
165
+ break
166
+ }
167
+ let event
168
+ try {
169
+ event = JSON.parse(line)
170
+ } catch (error) {
171
+ diagnostics.push({ line: lineNumber, code: 'ledger-json-invalid', message: error.message })
172
+ continue
173
+ }
174
+ const validation = validateCollaborationEvent(event)
175
+ if (!validation.ok) {
176
+ diagnostics.push({ line: lineNumber, code: 'ledger-event-invalid', message: validation.issues.join('; ') })
177
+ continue
178
+ }
179
+ if (eventIds.has(event.id)) {
180
+ diagnostics.push({ line: lineNumber, code: 'ledger-event-duplicate', message: `duplicate event id ${event.id}` })
181
+ continue
182
+ }
183
+ const previousVersion = versions.get(event.aggregateId) ?? 0
184
+ if (event.version <= previousVersion) {
185
+ diagnostics.push({ line: lineNumber, code: 'ledger-version-order', message: `version ${event.version} does not advance aggregate ${event.aggregateId}` })
186
+ continue
187
+ }
188
+ eventIds.add(event.id)
189
+ versions.set(event.aggregateId, event.version)
190
+ events.push(event)
191
+ }
192
+
193
+ const stats = { bytes, eventCount: events.length, durationMs: performance.now() - startedAt }
194
+ if (diagnostics.length) {
195
+ return { ok: false, status: 422, error: `collaboration ledger validation failed at ${diagnostics.length} line(s)`, events, diagnostics, stats }
196
+ }
197
+ return { ok: true, status: 200, events, diagnostics: [], stats }
198
+ }
199
+
200
+ function eventsFor(aggregateId) {
201
+ const id = String(aggregateId || '').trim()
202
+ if (!id) return failure(400, 'aggregateId is required')
203
+ const result = readAll()
204
+ if (!result.ok) return result
205
+ const events = result.events.filter((event) => event.aggregateId === id)
206
+ return {
207
+ ...result,
208
+ events,
209
+ currentVersion: events.at(-1)?.version ?? 0,
210
+ }
211
+ }
212
+
213
+ function withWriteLock(operation) {
214
+ try { return withPrivateLock(lockPath, operation); }
215
+ catch (error) { return { ok: false, status: error.code === 'EEXIST' ? 423 : 500,
216
+ error: `collaboration ledger lock/write failed: ${error.message}` }; }
217
+ }
218
+
219
+ function append({ aggregateId, expectedVersion = 0, type, actor, at = clock(), payload = {}, precondition = null }) {
220
+ return withWriteLock(() => {
221
+ const current = eventsFor(aggregateId)
222
+ if (!current.ok) return current
223
+ if (current.currentVersion !== expectedVersion) {
224
+ return {
225
+ ok: false,
226
+ status: 409,
227
+ error: `stale collaboration event refused: expected ${expectedVersion}, current ${current.currentVersion}`,
228
+ }
229
+ }
230
+ if (precondition !== null) {
231
+ if (typeof precondition !== 'function') return { ok: false, status: 400, error: 'append precondition must be a function' }
232
+ const permitted = precondition()
233
+ if (!permitted?.ok) return { ok: false, status: permitted?.status ?? 409, error: permitted?.error ?? 'append precondition refused' }
234
+ }
235
+ if (current.stats.eventCount >= limits.maxEvents) {
236
+ return { ok: false, status: 413, error: `collaboration ledger reached ${limits.maxEvents} event hard ceiling; compact it explicitly before appending` }
237
+ }
238
+ const version = current.currentVersion + 1
239
+ const event = {
240
+ schema: ATELIER_COLLABORATION_EVENT_SCHEMA,
241
+ aggregateId,
242
+ version,
243
+ type,
244
+ actor: String(actor || '').trim(),
245
+ at,
246
+ payload,
247
+ }
248
+ try {
249
+ event.id = eventId(event)
250
+ } catch {
251
+ return { ok: false, status: 400, error: 'authoritative event content cannot be canonicalized' }
252
+ }
253
+ const validation = validateCollaborationEvent(event)
254
+ if (!validation.ok) return { ok: false, status: 400, error: validation.issues.join('; ') }
255
+ const line = `${JSON.stringify(event)}\n`
256
+ const lineBytes = Buffer.byteLength(line)
257
+ if (lineBytes > limits.maxLineBytes) {
258
+ return { ok: false, status: 413, error: `collaboration event exceeds ${limits.maxLineBytes} byte line ceiling` }
259
+ }
260
+ if (current.stats.bytes + lineBytes > limits.maxBytes) {
261
+ return { ok: false, status: 413, error: `collaboration ledger would exceed ${limits.maxBytes} byte hard ceiling; compact it explicitly before appending` }
262
+ }
263
+ try {
264
+ secureAppendLine(ledgerReal, line)
265
+ } catch (error) {
266
+ return { ok: false, status: 500, error: `collaboration ledger cannot be appended: ${error.message}` }
267
+ }
268
+ return { ok: true, status: 200, event }
269
+ })
270
+ }
271
+
272
+ function materialize(aggregateId, reducer, initial = null) {
273
+ if (typeof reducer !== 'function') return { ok: false, status: 400, error: 'reducer is required', value: initial }
274
+ const result = eventsFor(aggregateId)
275
+ if (!result.ok) return { ...result, value: initial }
276
+ return {
277
+ ...result,
278
+ value: result.events.reduce((state, event) => reducer(state, event), initial),
279
+ }
280
+ }
281
+
282
+ function compact({
283
+ now = clock(),
284
+ retainPerAggregate = COLLABORATION_LEDGER_LIMITS.retainPerAggregate,
285
+ retainDays = COLLABORATION_LEDGER_LIMITS.retainDays,
286
+ } = {}) {
287
+ return withWriteLock(() => {
288
+ const current = readAll()
289
+ if (!current.ok) return current
290
+ const perAggregate = Math.max(2, boundedLimit(retainPerAggregate, COLLABORATION_LEDGER_LIMITS.retainPerAggregate))
291
+ const days = boundedLimit(retainDays, COLLABORATION_LEDGER_LIMITS.retainDays)
292
+ const cutoff = Date.parse(now) - days * 24 * 60 * 60 * 1000
293
+ if (!Number.isFinite(cutoff)) return { ok: false, status: 400, error: 'compaction timestamp is invalid' }
294
+
295
+ const grouped = new Map()
296
+ for (const event of current.events) {
297
+ const group = grouped.get(event.aggregateId) ?? []
298
+ group.push(event)
299
+ grouped.set(event.aggregateId, group)
300
+ }
301
+ const kept = []
302
+ for (const events of grouped.values()) {
303
+ const first = events[0]
304
+ const latest = events.at(-1)
305
+ const recent = events.filter((event) => Date.parse(event.at) >= cutoff)
306
+ const candidates = [first, ...recent, latest]
307
+ const unique = [...new Map(candidates.map((event) => [event.id, event])).values()]
308
+ .sort((left, right) => left.version - right.version)
309
+ const selected = unique.length <= perAggregate
310
+ ? unique
311
+ : [unique[0], ...unique.slice(-(perAggregate - 1))]
312
+ kept.push(...selected)
313
+ }
314
+ // `at` is contributor-controlled metadata and may regress. The physical
315
+ // ledger must retain each aggregate's causal version order after rewrite.
316
+ kept.sort((left, right) => (
317
+ left.aggregateId.localeCompare(right.aggregateId) ||
318
+ left.version - right.version ||
319
+ left.id.localeCompare(right.id)
320
+ ))
321
+ const text = kept.length ? `${kept.map((event) => JSON.stringify(event)).join('\n')}\n` : ''
322
+ if (Buffer.byteLength(text) > limits.maxBytes) {
323
+ return { ok: false, status: 413, error: 'compacted collaboration ledger still exceeds the byte hard ceiling' }
324
+ }
325
+ atomicReplacePrivateText(ledgerReal, text)
326
+ return {
327
+ ok: true,
328
+ status: 200,
329
+ before: current.events.length,
330
+ after: kept.length,
331
+ removed: current.events.length - kept.length,
332
+ retainPerAggregate: perAggregate,
333
+ retainDays: days,
334
+ }
335
+ })
336
+ }
337
+
338
+ return Object.freeze({
339
+ ledgerPath: ledgerReal,
340
+ lockPath,
341
+ limits,
342
+ readAll,
343
+ eventsFor,
344
+ append,
345
+ materialize,
346
+ compact,
347
+ })
348
+ }
@@ -0,0 +1,17 @@
1
+ export {
2
+ ATELIER_COLLABORATION_EVENT_SCHEMA,
3
+ createCollaborationEventLedger,
4
+ validateCollaborationEvent,
5
+ } from './event-ledger.mjs'
6
+
7
+ export {
8
+ ATELIER_PROPOSAL_SCHEMA,
9
+ ATELIER_PROPOSALS_SCHEMA,
10
+ COPY_ONLY_PROPOSAL_CAPABILITY,
11
+ PROPOSAL_REVIEW_STATUSES,
12
+ acceptedProposalCopy,
13
+ canTransitionProposal,
14
+ copyOnlyActionSummary,
15
+ createProposalStore,
16
+ validateCopyOnlyProposalAuthority,
17
+ } from './proposals.mjs'
@@ -0,0 +1,223 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import {
4
+ loadBoundRun,
5
+ hashEvidence,
6
+ validBoundRun,
7
+ } from '../readiness-protocols/evidence.mjs'
8
+ import { validReviewArtifact } from './review-contracts.mjs'
9
+ import { createReviewStore, validContribution } from './review-store.mjs'
10
+ import {
11
+ compileDisclosurePatterns,
12
+ scanDisclosureText,
13
+ } from '../disclosure/content-scan.mjs'
14
+ import { openRegularFileNoFollow } from '../project/private-state.mjs'
15
+
16
+ function prohibitedKeys(value, depth = 0) {
17
+ if (
18
+ typeof value === 'string' &&
19
+ (/^(?:[A-Za-z]:[\\/]|\\\\|\/(?!\/))/.test(value) ||
20
+ /[a-z][a-z0-9+.-]*:\/\/[^/\s]*@/i.test(value))
21
+ )
22
+ throw new Error(
23
+ 'machine path or credential-bearing URL in selected content',
24
+ )
25
+ if (depth > 32) throw new Error('bundle nesting exceeds bounds')
26
+ if (value && typeof value === 'object')
27
+ for (const [key, child] of Object.entries(value)) {
28
+ if (
29
+ /nonce|session|grant|credential|password|token|secret|private.?key|authorization/i.test(
30
+ key,
31
+ )
32
+ )
33
+ throw new Error('nontransferable field in selected content')
34
+ prohibitedKeys(child, depth + 1)
35
+ }
36
+ }
37
+ export function prepareInspectionBundle(
38
+ project,
39
+ runIds,
40
+ { denylistDocument, responseIds = [] } = {},
41
+ ) {
42
+ if (!denylistDocument || !Array.isArray(denylistDocument.patterns))
43
+ throw new Error('private disclosure policy required before export')
44
+ if (
45
+ !Array.isArray(runIds) ||
46
+ !runIds.length ||
47
+ runIds.length > 20 ||
48
+ new Set(runIds).size !== runIds.length
49
+ )
50
+ throw new Error('select 1 to 20 unique run IDs')
51
+ const history = createReviewStore(project).records()
52
+ if (!history.ok) throw new Error(history.error)
53
+ if (
54
+ !Array.isArray(responseIds) ||
55
+ responseIds.length > 2000 ||
56
+ new Set(responseIds).size !== responseIds.length
57
+ )
58
+ throw new Error('invalid response selection')
59
+ const responses = responseIds.map((id) => {
60
+ const record = history.records.find(
61
+ (entry) =>
62
+ entry.input.requestId === id && entry.input.kind !== 'decision',
63
+ )
64
+ if (!record) throw new Error('selected response unavailable')
65
+ return record
66
+ })
67
+ prohibitedKeys(responses)
68
+ const members = runIds.map((id) => {
69
+ const bound = loadBoundRun(project, id)
70
+ if (!bound.ok) throw new Error(bound.error)
71
+ const value = {
72
+ schema: bound.schema,
73
+ run: bound.run,
74
+ runDigest: bound.runDigest,
75
+ snapshot: bound.snapshot,
76
+ decisions: history.records.filter(
77
+ (record) =>
78
+ record.input.kind === 'decision' && record.input.runId === id,
79
+ ),
80
+ }
81
+ prohibitedKeys(value)
82
+ return {
83
+ id,
84
+ bytes: Buffer.byteLength(JSON.stringify(value)),
85
+ digest: hashEvidence(value),
86
+ value,
87
+ }
88
+ })
89
+ const body = {
90
+ schema: 'atelier-inspection-bundle@v1',
91
+ purpose: 'historical-inspection-only',
92
+ classification: 'owner-selected-private-review',
93
+ members,
94
+ responses,
95
+ }
96
+ const bytes = JSON.stringify(body, null, 2)
97
+ const disclosure = scanDisclosureText(bytes, {
98
+ denylistPatterns: compileDisclosurePatterns(denylistDocument.patterns),
99
+ })
100
+ if (!disclosure.ok)
101
+ throw new Error('selected content failed disclosure policy; export refused')
102
+ const bundle = { ...body, digest: hashEvidence(body) }
103
+ inspectBundle(bundle)
104
+ return bundle
105
+ }
106
+ export function inspectBundle(bundle) {
107
+ const serialized = JSON.stringify(bundle)
108
+ if (Buffer.byteLength(serialized) > 4 * 1024 * 1024)
109
+ throw new Error('bundle exceeds byte bound')
110
+ if (!validReviewArtifact('bundle', bundle))
111
+ throw new Error('inspection bundle contract invalid')
112
+ if (
113
+ !bundle ||
114
+ bundle.schema !== 'atelier-inspection-bundle@v1' ||
115
+ bundle.purpose !== 'historical-inspection-only' ||
116
+ !Array.isArray(bundle.members) ||
117
+ !bundle.members.length ||
118
+ bundle.members.length > 20 ||
119
+ Object.keys(bundle).some(
120
+ (key) =>
121
+ ![
122
+ 'schema',
123
+ 'purpose',
124
+ 'classification',
125
+ 'members',
126
+ 'responses',
127
+ 'digest',
128
+ 'ext',
129
+ 'contractVersion',
130
+ ].includes(key),
131
+ )
132
+ )
133
+ throw new Error('unsupported inspection bundle')
134
+ const { digest, ...body } = bundle
135
+ if (digest !== hashEvidence(body)) throw new Error('bundle digest mismatch')
136
+ const ids = new Set(),
137
+ requests = new Set()
138
+ const verifyRecord = (record) => {
139
+ if (!validContribution(record) || requests.has(record.input.requestId))
140
+ throw new Error('invalid or duplicate historical contribution')
141
+ requests.add(record.input.requestId)
142
+ }
143
+ prohibitedKeys(bundle.responses)
144
+ for (const record of bundle.responses) {
145
+ verifyRecord(record)
146
+ if (record.input.kind === 'decision')
147
+ throw new Error('decision outside run member')
148
+ }
149
+ for (const member of bundle.members) {
150
+ if (
151
+ !member ||
152
+ !/^readiness-run-[a-z0-9-]+$/.test(member.id ?? '') ||
153
+ ids.has(member.id) ||
154
+ Object.keys(member).some(
155
+ (key) => !['id', 'bytes', 'digest', 'value', 'ext'].includes(key),
156
+ ) ||
157
+ member.digest !== hashEvidence(member.value) ||
158
+ member.bytes !== Buffer.byteLength(JSON.stringify(member.value))
159
+ )
160
+ throw new Error('invalid, duplicate or changed bundle member')
161
+ prohibitedKeys(member.value)
162
+ ids.add(member.id)
163
+ const value = member.value,
164
+ { digest: snapshotDigest, ...snapshot } = value.snapshot ?? {}
165
+ const { decisions, ...bound } = value
166
+ if (
167
+ !validBoundRun(bound) ||
168
+ value.schema !== 'atelier-bound-run@v1' ||
169
+ value.run?.runId !== member.id ||
170
+ value.runDigest !== hashEvidence(value.run) ||
171
+ snapshotDigest !== hashEvidence(snapshot) ||
172
+ !Array.isArray(value.decisions)
173
+ )
174
+ throw new Error('invalid historical evidence')
175
+ for (const record of value.decisions) {
176
+ verifyRecord(record)
177
+ if (
178
+ record.input.kind !== 'decision' ||
179
+ record.input.runId !== member.id ||
180
+ record.input.evidenceDigest !== snapshotDigest ||
181
+ !value.run.claims.some(
182
+ (claim) => claim.claimId === record.input.targetId,
183
+ )
184
+ )
185
+ throw new Error('decision does not bind selected evidence')
186
+ }
187
+ }
188
+ return {
189
+ ok: true,
190
+ schema: 'atelier-inspection-report@v1',
191
+ approvalAuthority: false,
192
+ authorship: 'unverified-historical-assertions',
193
+ writes: false,
194
+ members: bundle.members,
195
+ responses: bundle.responses,
196
+ }
197
+ }
198
+ export function readInspectionBundle(file) {
199
+ const descriptor = openRegularFileNoFollow(file)
200
+ try {
201
+ if (fs.fstatSync(descriptor).size > 4 * 1024 * 1024)
202
+ throw new Error('bundle exceeds byte bound')
203
+ return inspectBundle(JSON.parse(fs.readFileSync(descriptor, 'utf8')))
204
+ } finally {
205
+ fs.closeSync(descriptor)
206
+ }
207
+ }
208
+ export function writeInspectionBundle(file, bundle) {
209
+ inspectBundle(bundle)
210
+ if (path.extname(file) !== '.json')
211
+ throw new Error('inspection bundle output must be a JSON file')
212
+ const descriptor = openRegularFileNoFollow(
213
+ file,
214
+ fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL,
215
+ 0o600,
216
+ )
217
+ try {
218
+ fs.writeFileSync(descriptor, JSON.stringify(bundle, null, 2) + '\n')
219
+ fs.fsyncSync(descriptor)
220
+ } finally {
221
+ fs.closeSync(descriptor)
222
+ }
223
+ }