@mnstry/atelier 0.2.0-alpha.6 → 0.2.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (374) hide show
  1. package/CHANGELOG.md +176 -0
  2. package/README.md +59 -15
  3. package/SECURITY.md +6 -1
  4. package/contracts/atelier-adoption-policy.v1.schema.json +49 -0
  5. package/contracts/atelier-coauthor.v1.schema.json +163 -0
  6. package/contracts/atelier-decision-request.v1.schema.json +476 -0
  7. package/contracts/atelier-decision-result.v1.schema.json +500 -0
  8. package/contracts/atelier-guide.v1.schema.json +227 -0
  9. package/contracts/atelier-intake.v1.schema.json +151 -0
  10. package/contracts/atelier-migration.v2.schema.json +61 -0
  11. package/contracts/atelier-obsidian-acceptance-receipt.v1.schema.json +211 -0
  12. package/contracts/atelier-obsidian-apply-policy.v1.schema.json +298 -0
  13. package/contracts/atelier-obsidian-corpus-profile.v1.schema.json +98 -0
  14. package/contracts/atelier-obsidian-edit-operation.v1.schema.json +138 -0
  15. package/contracts/atelier-obsidian-ext-settings.v1.schema.json +287 -0
  16. package/contracts/atelier-obsidian-generation-manifest.v1.schema.json +353 -0
  17. package/contracts/atelier-obsidian-proposal-receipt.v1.schema.json +110 -0
  18. package/contracts/atelier-obsidian-publication-journal.v1.schema.json +167 -0
  19. package/contracts/atelier-obsidian-scope.v1.schema.json +264 -0
  20. package/contracts/atelier-obsidian-service-state.v1.schema.json +149 -0
  21. package/contracts/atelier-obsidian-source-snapshot.v1.schema.json +179 -0
  22. package/contracts/atelier-pane-presentation.v1.schema.json +76 -0
  23. package/contracts/atelier-presentation.v1.schema.json +1293 -0
  24. package/contracts/atelier-review.v1.schema.json +1601 -0
  25. package/contracts/atelier-skill-steward.v1.schema.json +692 -0
  26. package/contracts/atelier-upgrade-plan.v2.schema.json +398 -0
  27. package/contracts/atelier-upgrade-receipt.v1.schema.json +99 -0
  28. package/contracts/atelier-vault-publication.v1.schema.json +58 -0
  29. package/docs/assurance-controls.md +9 -1
  30. package/docs/atelier-sync.md +5 -4
  31. package/docs/blocks/claims.md +15 -10
  32. package/docs/blocks/will-not-do.md +4 -1
  33. package/docs/coauthor-session.md +57 -0
  34. package/docs/connected-composition.md +75 -0
  35. package/docs/decisions.md +167 -0
  36. package/docs/exact-upgrades.md +159 -0
  37. package/docs/extension-pack-lifecycle.md +33 -0
  38. package/docs/guided-upgrades.md +99 -0
  39. package/docs/install.md +65 -9
  40. package/docs/intake-and-guides.md +65 -0
  41. package/docs/integration-and-review-plan.md +409 -0
  42. package/docs/integration-contract-decisions.md +22 -0
  43. package/docs/local-review.md +84 -0
  44. package/docs/local-services.md +227 -0
  45. package/docs/local-state.md +107 -0
  46. package/docs/obsidian-contract.md +687 -0
  47. package/docs/obsidian.md +387 -0
  48. package/docs/portable-presentation.md +328 -0
  49. package/docs/presentation-consumer-boundaries.md +75 -0
  50. package/docs/presentation-review-disposition.md +47 -0
  51. package/docs/project-command-inventory.md +38 -0
  52. package/docs/project-options.md +53 -0
  53. package/docs/release-engineering.md +88 -0
  54. package/docs/review-portability.md +55 -0
  55. package/docs/skill-steward.md +156 -0
  56. package/docs/upgrade.md +55 -2
  57. package/docs/vault-service.md +292 -0
  58. package/fixtures/atelier-coauthor/invalid/authority.v1.json +5 -0
  59. package/fixtures/atelier-coauthor/valid/config.v1.json +12 -0
  60. package/fixtures/atelier-guide/invalid/authority.v1.json +11 -0
  61. package/fixtures/atelier-guide/valid/capability.v1.json +10 -0
  62. package/fixtures/atelier-guide/valid/consent.v1.json +11 -0
  63. package/fixtures/atelier-guide/valid/engagement.v1.json +7 -0
  64. package/fixtures/atelier-guide/valid/offer.v1.json +10 -0
  65. package/fixtures/atelier-intake/invalid/authority.v1.json +9 -0
  66. package/fixtures/atelier-intake/valid/attempt.v1.json +8 -0
  67. package/fixtures/atelier-intake/valid/completion.v1.json +9 -0
  68. package/fixtures/atelier-intake/valid/source.v1.json +7 -0
  69. package/fixtures/atelier-review/invalid/authenticated-identity.v1.json +22 -0
  70. package/fixtures/atelier-review/valid/bound.v1.json +229 -0
  71. package/fixtures/atelier-review/valid/bundle.v1.json +267 -0
  72. package/fixtures/atelier-review/valid/contribution.v1.json +22 -0
  73. package/fixtures/atelier-review/valid/lifecycle.v1.json +14 -0
  74. package/fixtures/atelier-skill-steward/audit/invalid/source-mutation.v1.json +26 -0
  75. package/fixtures/atelier-skill-steward/audit/valid/clean.v1.json +26 -0
  76. package/fixtures/atelier-skill-steward/candidates/invalid/workflow-key.v1.json +37 -0
  77. package/fixtures/atelier-skill-steward/candidates/valid/create.v1.json +37 -0
  78. package/fixtures/atelier-skill-steward/lock/invalid/bad-digest.v1.json +18 -0
  79. package/fixtures/atelier-skill-steward/lock/valid/installed.v1.json +23 -0
  80. package/fixtures/atelier-skill-steward/plan/invalid/delete-action.v1.json +33 -0
  81. package/fixtures/atelier-skill-steward/plan/valid/add.v1.json +33 -0
  82. package/fixtures/atelier-upgrade-transaction/adoption-policy/invalid/example.json +12 -0
  83. package/fixtures/atelier-upgrade-transaction/adoption-policy/valid/example.json +11 -0
  84. package/fixtures/atelier-upgrade-transaction/migration/invalid/example.json +20 -0
  85. package/fixtures/atelier-upgrade-transaction/migration/valid/example.json +19 -0
  86. package/fixtures/atelier-upgrade-transaction/upgrade-plan/invalid/example.json +58 -0
  87. package/fixtures/atelier-upgrade-transaction/upgrade-plan/valid/example.json +57 -0
  88. package/fixtures/atelier-upgrade-transaction/upgrade-receipt/invalid/example.json +15 -0
  89. package/fixtures/atelier-upgrade-transaction/upgrade-receipt/valid/example.json +14 -0
  90. package/fixtures/atelier-vault-publication/invalid/publication.json +5 -0
  91. package/fixtures/atelier-vault-publication/valid/publication.json +11 -0
  92. package/fixtures/decisions/request/invalid/unknown-authority.v1.json +61 -0
  93. package/fixtures/decisions/request/valid/triage.v1.json +60 -0
  94. package/fixtures/decisions/result/invalid/execution-authority.v1.json +50 -0
  95. package/fixtures/decisions/result/valid/abstained.v1.json +23 -0
  96. package/fixtures/decisions/result/valid/assessed.v1.json +50 -0
  97. package/fixtures/obsidian/acceptance/ap05-extra-notes.json +14 -0
  98. package/fixtures/obsidian/acceptance/receipts/G07.valid.v1.json +63 -0
  99. package/fixtures/obsidian/acceptance/receipts/G13.valid.v1.json +69 -0
  100. package/fixtures/obsidian/acceptance/receipts/G14.valid.v1.json +62 -0
  101. package/fixtures/obsidian/acceptance/receipts/G15.valid.v1.json +56 -0
  102. package/fixtures/obsidian/acceptance/receipts/G16.valid.v1.json +73 -0
  103. package/fixtures/obsidian/acceptance/receipts/G17.valid.v1.json +75 -0
  104. package/fixtures/obsidian/acceptance/receipts/G18.valid.v1.json +64 -0
  105. package/fixtures/obsidian/acceptance/service-sentinels.json +10 -0
  106. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/duplicate-evidence-name.v1.json +39 -0
  107. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/evidence-by-absolute-path.v1.json +34 -0
  108. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/missing-candidate-tree.v1.json +33 -0
  109. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/no-evidence.v1.json +28 -0
  110. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/unknown-outcome.v1.json +34 -0
  111. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/unknown-top-level-field.v1.json +35 -0
  112. package/fixtures/obsidian/contracts/acceptance-receipt/valid/node-gate.v1.json +34 -0
  113. package/fixtures/obsidian/contracts/apply-policy/invalid/automatic-without-edit-class.v1.json +27 -0
  114. package/fixtures/obsidian/contracts/apply-policy/invalid/conflict-overwrite.v1.json +29 -0
  115. package/fixtures/obsidian/contracts/apply-policy/invalid/missing-policy-digest.v1.json +28 -0
  116. package/fixtures/obsidian/contracts/apply-policy/invalid/unbounded-batch.v1.json +29 -0
  117. package/fixtures/obsidian/contracts/apply-policy/invalid/unimplemented-edit-class.v1.json +30 -0
  118. package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-mode.v1.json +29 -0
  119. package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-selector-operator.v1.json +22 -0
  120. package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-top-level-field.v1.json +30 -0
  121. package/fixtures/obsidian/contracts/apply-policy/valid/automatic-scoped.v1.json +29 -0
  122. package/fixtures/obsidian/contracts/apply-policy/valid/manual-default.v1.json +20 -0
  123. package/fixtures/obsidian/contracts/corpus-profile/invalid/absolute-path-in-ext.v1.json +30 -0
  124. package/fixtures/obsidian/contracts/corpus-profile/invalid/absolute-repository-root.v1.json +27 -0
  125. package/fixtures/obsidian/contracts/corpus-profile/invalid/duplicate-repository-id.v1.json +27 -0
  126. package/fixtures/obsidian/contracts/corpus-profile/invalid/overlapping-managed-roots.v1.json +27 -0
  127. package/fixtures/obsidian/contracts/corpus-profile/invalid/unknown-enrollment.v1.json +27 -0
  128. package/fixtures/obsidian/contracts/corpus-profile/invalid/unknown-top-level-field.v1.json +28 -0
  129. package/fixtures/obsidian/contracts/corpus-profile/valid/three-repositories.v1.json +27 -0
  130. package/fixtures/obsidian/contracts/edit-operation/invalid/absolute-recovery-ref.v1.json +21 -0
  131. package/fixtures/obsidian/contracts/edit-operation/invalid/inline-observed-bytes.v1.json +22 -0
  132. package/fixtures/obsidian/contracts/edit-operation/invalid/missing-base-digest.v1.json +20 -0
  133. package/fixtures/obsidian/contracts/edit-operation/invalid/short-idempotency-key.v1.json +21 -0
  134. package/fixtures/obsidian/contracts/edit-operation/invalid/unknown-kind.v1.json +21 -0
  135. package/fixtures/obsidian/contracts/edit-operation/valid/pending-body-replacement.v1.json +21 -0
  136. package/fixtures/obsidian/contracts/ext-settings/invalid/absolute-path-prefix.v1.json +28 -0
  137. package/fixtures/obsidian/contracts/ext-settings/invalid/apply-policy-in-portable-settings.v1.json +30 -0
  138. package/fixtures/obsidian/contracts/ext-settings/invalid/duplicate-scope-id.v1.json +27 -0
  139. package/fixtures/obsidian/contracts/ext-settings/invalid/machine-local-vault-path.v1.json +28 -0
  140. package/fixtures/obsidian/contracts/ext-settings/invalid/service-port-in-portable-settings.v1.json +28 -0
  141. package/fixtures/obsidian/contracts/ext-settings/invalid/unknown-default-scope.v1.json +27 -0
  142. package/fixtures/obsidian/contracts/ext-settings/valid/disabled.v1.json +5 -0
  143. package/fixtures/obsidian/contracts/ext-settings/valid/enabled-with-scopes.v1.json +27 -0
  144. package/fixtures/obsidian/contracts/generation-manifest/invalid/absolute-note-path.v1.json +114 -0
  145. package/fixtures/obsidian/contracts/generation-manifest/invalid/complete-with-unwritten-notes.v1.json +114 -0
  146. package/fixtures/obsidian/contracts/generation-manifest/invalid/derived-link-claimed-declared.v1.json +114 -0
  147. package/fixtures/obsidian/contracts/generation-manifest/invalid/duplicate-note-identity.v1.json +114 -0
  148. package/fixtures/obsidian/contracts/generation-manifest/invalid/in-scope-endpoint-missing.v1.json +114 -0
  149. package/fixtures/obsidian/contracts/generation-manifest/invalid/title-only-note-path.v1.json +114 -0
  150. package/fixtures/obsidian/contracts/generation-manifest/invalid/unknown-note-field.v1.json +115 -0
  151. package/fixtures/obsidian/contracts/generation-manifest/invalid/unknown-relation-type.v1.json +114 -0
  152. package/fixtures/obsidian/contracts/generation-manifest/invalid/withheld-endpoint.v1.json +114 -0
  153. package/fixtures/obsidian/contracts/generation-manifest/valid/two-notes.v1.json +114 -0
  154. package/fixtures/obsidian/contracts/oracles/scope-cases.json +412 -0
  155. package/fixtures/obsidian/contracts/proposal-receipt/invalid/accepted-without-proposal-id.v1.json +12 -0
  156. package/fixtures/obsidian/contracts/proposal-receipt/invalid/missing-adapter-operation-id.v1.json +11 -0
  157. package/fixtures/obsidian/contracts/proposal-receipt/invalid/unknown-backpressure-outcome.v1.json +12 -0
  158. package/fixtures/obsidian/contracts/proposal-receipt/invalid/unknown-top-level-field.v1.json +13 -0
  159. package/fixtures/obsidian/contracts/proposal-receipt/valid/accepted-new.v1.json +12 -0
  160. package/fixtures/obsidian/contracts/proposal-receipt/valid/deferred.v1.json +12 -0
  161. package/fixtures/obsidian/contracts/publication-journal/invalid/absolute-recovery-ref.v1.json +47 -0
  162. package/fixtures/obsidian/contracts/publication-journal/invalid/duplicate-sequence.v1.json +47 -0
  163. package/fixtures/obsidian/contracts/publication-journal/invalid/empty-protocol-id.v1.json +47 -0
  164. package/fixtures/obsidian/contracts/publication-journal/invalid/missing-protocol-id.v1.json +46 -0
  165. package/fixtures/obsidian/contracts/publication-journal/invalid/unknown-entry-field.v1.json +48 -0
  166. package/fixtures/obsidian/contracts/publication-journal/invalid/unknown-state.v1.json +47 -0
  167. package/fixtures/obsidian/contracts/publication-journal/valid/committed.v1.json +47 -0
  168. package/fixtures/obsidian/contracts/publication-journal/valid/updating-after-restart.v1.json +22 -0
  169. package/fixtures/obsidian/contracts/scope/invalid/absolute-path-prefix.v1.json +15 -0
  170. package/fixtures/obsidian/contracts/scope/invalid/duplicate-ids.v1.json +18 -0
  171. package/fixtures/obsidian/contracts/scope/invalid/expansion-without-node-budget.v1.json +40 -0
  172. package/fixtures/obsidian/contracts/scope/invalid/full-mode-with-subset.v1.json +8 -0
  173. package/fixtures/obsidian/contracts/scope/invalid/unknown-mode.v1.json +41 -0
  174. package/fixtures/obsidian/contracts/scope/invalid/unknown-selector-key.v1.json +15 -0
  175. package/fixtures/obsidian/contracts/scope/invalid/unknown-selector-operator.v1.json +14 -0
  176. package/fixtures/obsidian/contracts/scope/invalid/unknown-top-level-field.v1.json +42 -0
  177. package/fixtures/obsidian/contracts/scope/valid/explicit-empty.v1.json +8 -0
  178. package/fixtures/obsidian/contracts/scope/valid/focus.v1.json +11 -0
  179. package/fixtures/obsidian/contracts/scope/valid/full.v1.json +8 -0
  180. package/fixtures/obsidian/contracts/scope/valid/scoped-set-expression.v1.json +41 -0
  181. package/fixtures/obsidian/contracts/service-state/invalid/hostname-instead-of-literal-loopback.v1.json +23 -0
  182. package/fixtures/obsidian/contracts/service-state/invalid/missing-consent.v1.json +18 -0
  183. package/fixtures/obsidian/contracts/service-state/invalid/privileged-port.v1.json +23 -0
  184. package/fixtures/obsidian/contracts/service-state/invalid/unknown-top-level-field.v1.json +24 -0
  185. package/fixtures/obsidian/contracts/service-state/invalid/wildcard-bind.v1.json +23 -0
  186. package/fixtures/obsidian/contracts/service-state/valid/healthy.v1.json +23 -0
  187. package/fixtures/obsidian/contracts/source-snapshot/invalid/absolute-file-path.v1.json +48 -0
  188. package/fixtures/obsidian/contracts/source-snapshot/invalid/duplicate-repository-id.v1.json +48 -0
  189. package/fixtures/obsidian/contracts/source-snapshot/invalid/mixed-read.v1.json +48 -0
  190. package/fixtures/obsidian/contracts/source-snapshot/invalid/parent-traversal-path.v1.json +48 -0
  191. package/fixtures/obsidian/contracts/source-snapshot/invalid/unknown-file-field.v1.json +49 -0
  192. package/fixtures/obsidian/contracts/source-snapshot/valid/two-repositories.v1.json +48 -0
  193. package/fixtures/obsidian/edits/workspace.json +65 -0
  194. package/fixtures/obsidian/materialization/expected-full.json +99 -0
  195. package/fixtures/obsidian/materialization/expected-scoped.json +39 -0
  196. package/fixtures/obsidian/materialization/workspace.json +54 -0
  197. package/fixtures/obsidian/proposals/expected-shapes.json +11 -0
  198. package/fixtures/ui/presentation/pane.v1.json +7 -0
  199. package/fixtures/ui/presentation/reference.v1.json +293 -0
  200. package/package.json +57 -6
  201. package/skills/claude/atelier-guided-coauthor/SKILL.md +49 -0
  202. package/skills/claude/atelier-guided-upgrade/SKILL.md +97 -0
  203. package/skills/claude/atelier-open/SKILL.md +5 -0
  204. package/skills/claude/atelier-skill-steward/SKILL.md +62 -0
  205. package/skills/claude/mnstry-readiness/SKILL.md +5 -0
  206. package/skills/codex/atelier-guided-coauthor/SKILL.md +49 -0
  207. package/skills/codex/atelier-guided-upgrade/SKILL.md +97 -0
  208. package/skills/codex/atelier-open/SKILL.md +5 -0
  209. package/skills/codex/atelier-skill-steward/SKILL.md +62 -0
  210. package/skills/codex/mnstry-readiness/SKILL.md +5 -0
  211. package/src/access/preview-enforcer.mjs +84 -0
  212. package/src/boundary/policy.mjs +57 -25
  213. package/src/cli/project-options.mjs +58 -0
  214. package/src/cli/run.mjs +43 -3
  215. package/src/coauthor/session.mjs +161 -0
  216. package/src/coauthor/store.mjs +173 -0
  217. package/src/collaboration/event-ledger.mjs +16 -33
  218. package/src/collaboration/inspection-bundle.mjs +223 -0
  219. package/src/collaboration/review-contracts.mjs +25 -0
  220. package/src/collaboration/review-store.mjs +305 -0
  221. package/src/commands/coauthor.mjs +26 -0
  222. package/src/commands/config.mjs +6 -1
  223. package/src/commands/extension-pack.mjs +15 -8
  224. package/src/commands/init.mjs +44 -5
  225. package/src/commands/obsidian.mjs +368 -0
  226. package/src/commands/review.mjs +147 -0
  227. package/src/commands/setup.mjs +51 -14
  228. package/src/commands/skills.mjs +199 -0
  229. package/src/commands/sync.mjs +8 -1
  230. package/src/composition/registry.mjs +27 -0
  231. package/src/composition/wire.mjs +122 -0
  232. package/src/contracts/corpus.mjs +29 -0
  233. package/src/decisions/contracts.d.mts +96 -0
  234. package/src/decisions/contracts.mjs +321 -0
  235. package/src/disclosure/content-scan.mjs +13 -0
  236. package/src/export/atelier-export-contract.mjs +16 -1
  237. package/src/extension-packs/lifecycle.mjs +154 -0
  238. package/src/graph/graph.mjs +64 -2
  239. package/src/graph/knowledge-graph.mjs +548 -24
  240. package/src/guides/contracts.mjs +54 -0
  241. package/src/intake/store.mjs +119 -0
  242. package/src/preview/channel.mjs +89 -0
  243. package/src/project/config.mjs +26 -27
  244. package/src/project/durable-state.mjs +161 -0
  245. package/src/project/file-class.mjs +166 -0
  246. package/src/project/private-state.mjs +11 -1
  247. package/src/projection/obsidian/contracts.mjs +571 -0
  248. package/src/projection/obsidian/edits/align.mjs +169 -0
  249. package/src/projection/obsidian/edits/apply.mjs +889 -0
  250. package/src/projection/obsidian/edits/arbitrate.mjs +414 -0
  251. package/src/projection/obsidian/edits/contribution.mjs +108 -0
  252. package/src/projection/obsidian/edits/index.mjs +19 -0
  253. package/src/projection/obsidian/edits/object-identity.mjs +61 -0
  254. package/src/projection/obsidian/edits/object-store.mjs +517 -0
  255. package/src/projection/obsidian/edits/observe.mjs +123 -0
  256. package/src/projection/obsidian/edits/policy.mjs +123 -0
  257. package/src/projection/obsidian/edits/regions.mjs +648 -0
  258. package/src/projection/obsidian/materialize/byte-lens.mjs +98 -0
  259. package/src/projection/obsidian/materialize/index.mjs +7 -0
  260. package/src/projection/obsidian/materialize/path-registry.mjs +105 -0
  261. package/src/projection/obsidian/materialize/prepare-view.mjs +746 -0
  262. package/src/projection/obsidian/materialize/settings.mjs +57 -0
  263. package/src/projection/obsidian/materialize/stage.mjs +27 -0
  264. package/src/projection/obsidian/proposals/adapter.mjs +572 -0
  265. package/src/projection/obsidian/proposals/backpressure.mjs +113 -0
  266. package/src/projection/obsidian/proposals/contribution.mjs +55 -0
  267. package/src/projection/obsidian/proposals/index.mjs +23 -0
  268. package/src/projection/obsidian/proposals/observation.mjs +216 -0
  269. package/src/projection/obsidian/proposals/queue.mjs +277 -0
  270. package/src/projection/obsidian/proposals/router.mjs +168 -0
  271. package/src/projection/obsidian/publication/bridge-script.mjs +266 -0
  272. package/src/projection/obsidian/publication/exchange.mjs +117 -0
  273. package/src/projection/obsidian/publication/index.mjs +5 -0
  274. package/src/projection/obsidian/publication/publisher.mjs +588 -0
  275. package/src/projection/obsidian/publication/test-seam.mjs +16 -0
  276. package/src/projection/obsidian/publication/transport.mjs +139 -0
  277. package/src/projection/obsidian/recovery/index.mjs +6 -0
  278. package/src/projection/obsidian/recovery/journal.mjs +134 -0
  279. package/src/projection/obsidian/recovery/late-writer.mjs +46 -0
  280. package/src/projection/obsidian/recovery/restart.mjs +316 -0
  281. package/src/projection/obsidian/recovery/store.mjs +234 -0
  282. package/src/projection/obsidian/selection-ui/conflict-view.mjs +91 -0
  283. package/src/projection/obsidian/selection-ui/contribution.mjs +141 -0
  284. package/src/projection/obsidian/selection-ui/focus.mjs +82 -0
  285. package/src/projection/obsidian/selection-ui/index.mjs +12 -0
  286. package/src/projection/obsidian/selection-ui/policy-setup.mjs +107 -0
  287. package/src/projection/obsidian/selection-ui/receipt.mjs +173 -0
  288. package/src/projection/obsidian/selection-ui/selection-state.mjs +122 -0
  289. package/src/projection/obsidian/selection-ui/selection.mjs +109 -0
  290. package/src/projection/project.mjs +28 -19
  291. package/src/readiness-protocols/evidence.mjs +282 -0
  292. package/src/readiness-protocols/runtime.mjs +4 -3
  293. package/src/readiness-protocols/source-read.mjs +39 -0
  294. package/src/runtime/obsidian/app-capability.mjs +110 -0
  295. package/src/runtime/obsidian/app-production-seams.mjs +96 -0
  296. package/src/runtime/obsidian/contributions/proposal-adapter.mjs +4 -0
  297. package/src/runtime/obsidian/contributions/selection-ui.mjs +4 -0
  298. package/src/runtime/obsidian/contributions/source-apply.mjs +4 -0
  299. package/src/runtime/obsidian/contributions.mjs +27 -0
  300. package/src/runtime/obsidian/documents.mjs +37 -0
  301. package/src/runtime/obsidian/enablement.mjs +53 -0
  302. package/src/runtime/obsidian/engine.mjs +532 -0
  303. package/src/runtime/obsidian/errors.mjs +14 -0
  304. package/src/runtime/obsidian/extension-points.mjs +103 -0
  305. package/src/runtime/obsidian/index.mjs +34 -0
  306. package/src/runtime/obsidian/lifecycle.mjs +258 -0
  307. package/src/runtime/obsidian/machine-settings.mjs +260 -0
  308. package/src/runtime/obsidian/observation.mjs +158 -0
  309. package/src/runtime/obsidian/opening.mjs +243 -0
  310. package/src/runtime/obsidian/pending-edits.mjs +141 -0
  311. package/src/runtime/obsidian/pipeline.mjs +122 -0
  312. package/src/runtime/obsidian/private-lock.mjs +156 -0
  313. package/src/runtime/obsidian/process-identity.mjs +58 -0
  314. package/src/runtime/obsidian/service-client.mjs +66 -0
  315. package/src/runtime/obsidian/service-main.mjs +89 -0
  316. package/src/runtime/obsidian/service-record.mjs +169 -0
  317. package/src/runtime/obsidian/service-server.mjs +121 -0
  318. package/src/runtime/obsidian/service.mjs +216 -0
  319. package/src/runtime/obsidian/startup-adapters.mjs +87 -0
  320. package/src/runtime/obsidian/state-store.mjs +180 -0
  321. package/src/runtime/obsidian/sync-notice.mjs +33 -0
  322. package/src/runtime/obsidian/tick-loop.mjs +84 -0
  323. package/src/runtime/obsidian/watchers.mjs +36 -0
  324. package/src/runtime/supervisor.mjs +16 -1
  325. package/src/server/local-sidecar.mjs +44 -0
  326. package/src/server/server.mjs +1 -0
  327. package/src/skills/steward.mjs +780 -0
  328. package/src/ui/presentation/browser.mjs +278 -0
  329. package/src/ui/presentation/contract.mjs +116 -0
  330. package/src/ui/presentation/index.mjs +7 -0
  331. package/src/ui/presentation/native.mjs +153 -0
  332. package/src/ui/presentation/proof.mjs +57 -0
  333. package/src/ui/presentation/schema-check.mjs +30 -0
  334. package/src/ui/presentation/schema.generated.mjs +1371 -0
  335. package/src/ui/presentation/state.mjs +43 -0
  336. package/src/ui/presentation/styles.mjs +67 -0
  337. package/src/ui/presentation/tokens.mjs +74 -0
  338. package/src/ui/presentation/web.mjs +85 -0
  339. package/src/ui/review-page.mjs +32 -0
  340. package/src/upgrade/explanation.mjs +33 -0
  341. package/src/upgrade/provenance.mjs +338 -0
  342. package/src/upgrade/transaction-files.mjs +103 -0
  343. package/src/upgrade/transaction.mjs +475 -0
  344. package/src/upgrade/upgrade.mjs +63 -28
  345. package/src/vault/hosts.mjs +21 -0
  346. package/src/vault/identity.mjs +19 -0
  347. package/src/vault/index.mjs +7 -0
  348. package/src/vault/interface.mjs +11 -0
  349. package/src/vault/metadata.mjs +31 -0
  350. package/src/vault/privacy.mjs +148 -0
  351. package/src/vault/probe.mjs +89 -0
  352. package/src/vault/service.mjs +131 -0
  353. package/src/vault/source.mjs +45 -0
  354. package/src/vault/storage.mjs +23 -0
  355. package/templates/distribution-workspace/.claude/launch.json +6 -2
  356. package/templates/distribution-workspace/gitignore +1 -0
  357. package/templates/distribution-workspace/project/README.md +2 -2
  358. package/templates/external-project-workspace/README.md +45 -0
  359. package/templates/external-project-workspace/answers.example.json +4 -0
  360. package/templates/external-project-workspace/atelier.project.json +40 -0
  361. package/templates/external-project-workspace/boundary-policy.v1.json +60 -0
  362. package/templates/external-project-workspace/gitignore +8 -0
  363. package/templates/external-project-workspace/pack-lifecycle.json +14 -0
  364. package/templates/external-project-workspace/packs/protocols/contract-gate.v1.json +109 -0
  365. package/templates/external-project-workspace/packs/sample-pack.v1.json +48 -0
  366. package/templates/external-project-workspace/repo-access.v1.json +9 -0
  367. package/templates/external-project-workspace/source/README.md +11 -0
  368. package/templates/launch.json +6 -2
  369. package/templates/private-domain-workspace/.claude/launch.json +6 -2
  370. package/templates/private-domain-workspace/domain/README.md +2 -2
  371. package/templates/private-domain-workspace/gitignore +1 -0
  372. package/templates/shared-project-workspace/.claude/launch.json +6 -2
  373. package/templates/shared-project-workspace/gitignore +1 -0
  374. package/templates/shared-project-workspace/project/README.md +2 -2
@@ -0,0 +1,338 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import crypto from 'node:crypto'
4
+ import { spawnSync } from 'node:child_process'
5
+ import { openRegularFileNoFollow } from '../project/private-state.mjs'
6
+ import { canonicalize } from '../attestation/jcs.mjs'
7
+ import { sanitizedGitEnvironment } from '../runtime/git-adapter.mjs'
8
+
9
+ const digest = (bytes) =>
10
+ `sha256:${crypto.createHash('sha256').update(bytes).digest('hex')}`
11
+ const MAX_BYTES = 32 * 1024 * 1024
12
+ const MAX_FILES = 5000
13
+
14
+ export function safeRepository(value) {
15
+ if (typeof value !== 'string') return null
16
+ const input = value.replace(/^git\+/, '')
17
+ const scp = input.match(/^git@([a-z0-9.-]+):([a-z0-9/_.-]+)$/i)
18
+ if (scp) return `ssh://git@${scp[1]}/${scp[2]}`
19
+ try {
20
+ const url = new URL(input)
21
+ if (!['https:', 'http:', 'ssh:'].includes(url.protocol)) return null
22
+ return new URL(`${url.protocol}//${url.host}${url.pathname}`).href
23
+ } catch {
24
+ return null
25
+ }
26
+ }
27
+
28
+ function git(root, args) {
29
+ const result = spawnSync(
30
+ 'git',
31
+ ['--no-replace-objects', '-c', 'core.fsmonitor=false', '-C', root, ...args],
32
+ {
33
+ encoding: 'utf8',
34
+ env: sanitizedGitEnvironment(process.env),
35
+ maxBuffer: MAX_BYTES,
36
+ },
37
+ )
38
+ return result.status === 0 ? result.stdout.trim() : null
39
+ }
40
+
41
+ // Inventory every installed package file except dependency trees and Git
42
+ // administration. Do not follow links or silently omit unexpected local files.
43
+ function inventory(root) {
44
+ const files = []
45
+ let bytes = 0
46
+ function visit(rel) {
47
+ for (const name of fs.readdirSync(path.join(root, rel)).sort()) {
48
+ if (name === '.git' || name === 'node_modules') continue
49
+ const next = path.posix.join(rel, name)
50
+ const file = path.join(root, next)
51
+ const stat = fs.lstatSync(file)
52
+ if (stat.isSymbolicLink() || (!stat.isDirectory() && !stat.isFile()))
53
+ throw new Error('unsupported package inventory entry')
54
+ if (stat.isDirectory()) {
55
+ visit(next)
56
+ continue
57
+ }
58
+ bytes += stat.size
59
+ if (files.length >= MAX_FILES || bytes > MAX_BYTES)
60
+ throw new Error('package inventory exceeds bounds')
61
+ const descriptor = openRegularFileNoFollow(file)
62
+ let content
63
+ try {
64
+ content = fs.readFileSync(descriptor)
65
+ } finally {
66
+ fs.closeSync(descriptor)
67
+ }
68
+ const after = fs.lstatSync(file)
69
+ if (
70
+ stat.ino !== after.ino ||
71
+ stat.size !== after.size ||
72
+ stat.mtimeMs !== after.mtimeMs
73
+ )
74
+ throw new Error('package changed during inventory')
75
+ files.push({
76
+ path: next,
77
+ digest: digest(content),
78
+ executable: Boolean(stat.mode & 0o111),
79
+ })
80
+ }
81
+ }
82
+ visit('')
83
+ return {
84
+ digest: digest(canonicalize(files)),
85
+ files,
86
+ exclusions: ['.git', 'node_modules'],
87
+ scope: 'package-files-without-dependencies',
88
+ }
89
+ }
90
+
91
+ // Compare observed working bytes with HEAD objects as well as status. Git
92
+ // index hints (assume-unchanged/skip-worktree) must not conceal modified bytes.
93
+ function matchesCommit(root, head, observed) {
94
+ if (!head || !observed) return false
95
+ const tree = git(root, ['ls-tree', '-r', '-z', head])
96
+ if (tree === null) return false
97
+ const entries = tree
98
+ .split('\0')
99
+ .filter(Boolean)
100
+ .map((line) => {
101
+ const separator = line.indexOf('\t'),
102
+ [mode, type, oid] = line.slice(0, separator).split(' ')
103
+ return { mode, type, oid, path: line.slice(separator + 1) }
104
+ })
105
+ .filter(
106
+ (entry) =>
107
+ !entry.path
108
+ .split('/')
109
+ .some((part) => part === '.git' || part === 'node_modules'),
110
+ )
111
+ if (
112
+ entries.length !== observed.files.length ||
113
+ entries.some(
114
+ (entry) =>
115
+ entry.type !== 'blob' || !['100644', '100755'].includes(entry.mode),
116
+ )
117
+ )
118
+ return false
119
+ const result = spawnSync(
120
+ 'git',
121
+ ['--no-replace-objects', '-C', root, 'cat-file', '--batch'],
122
+ {
123
+ input: entries.map((entry) => entry.oid).join('\n') + '\n',
124
+ env: sanitizedGitEnvironment(process.env),
125
+ maxBuffer: MAX_BYTES + MAX_FILES * 100,
126
+ },
127
+ )
128
+ if (result.status !== 0) return false
129
+ let offset = 0
130
+ const files = new Map(observed.files.map((file) => [file.path, file]))
131
+ for (const entry of entries) {
132
+ const end = result.stdout.indexOf(10, offset)
133
+ if (end < 0) return false
134
+ const [oid, type, length] = result.stdout
135
+ .subarray(offset, end)
136
+ .toString('utf8')
137
+ .split(' '),
138
+ size = Number(length)
139
+ if (
140
+ oid !== entry.oid ||
141
+ type !== 'blob' ||
142
+ !Number.isSafeInteger(size) ||
143
+ size < 0
144
+ )
145
+ return false
146
+ const content = result.stdout.subarray(end + 1, end + 1 + size),
147
+ file = files.get(entry.path)
148
+ if (
149
+ !file ||
150
+ content.length !== size ||
151
+ file.digest !== digest(content) ||
152
+ file.executable !== (entry.mode === '100755')
153
+ )
154
+ return false
155
+ offset = end + 1 + size + 1
156
+ }
157
+ return offset === result.stdout.length
158
+ }
159
+
160
+ function declarations(root, realRoot, name) {
161
+ const found = []
162
+ for (let dir = path.dirname(root); ; ) {
163
+ for (const filename of ['npm-shrinkwrap.json', 'package-lock.json']) {
164
+ const file = path.join(dir, filename)
165
+ if (!fs.existsSync(file)) continue
166
+ const stat = fs.lstatSync(file)
167
+ if (!stat.isFile() || stat.size > MAX_BYTES) continue
168
+ let lock
169
+ try {
170
+ lock = JSON.parse(fs.readFileSync(file, 'utf8'))
171
+ } catch {
172
+ continue
173
+ }
174
+ const key = path.relative(dir, root).split(path.sep).join('/')
175
+ const entry = lock.packages?.[key]
176
+ if (!entry || entry.link || (entry.name && entry.name !== name)) continue
177
+ // The lexical install slot must still resolve to this exact instance.
178
+ try {
179
+ if (fs.realpathSync(path.join(dir, key)) !== realRoot) continue
180
+ } catch {
181
+ continue
182
+ }
183
+ const resolved = typeof entry.resolved === 'string' ? entry.resolved : ''
184
+ const sha = /^(?:git\+|git:)/.test(resolved)
185
+ ? resolved.match(/#([0-9a-f]{40})$/)?.[1] ?? null
186
+ : null
187
+ found.push({
188
+ kind: sha ? 'git' : /^https?:/.test(resolved) ? 'npm' : 'local_path',
189
+ repository: safeRepository(resolved),
190
+ gitSha: sha,
191
+ integrity:
192
+ typeof entry.integrity === 'string' &&
193
+ /^(sha256|sha384|sha512)-[A-Za-z0-9+/=]+$/.test(entry.integrity)
194
+ ? entry.integrity
195
+ : null,
196
+ evidence: filename,
197
+ })
198
+ }
199
+ const parent = path.dirname(dir)
200
+ if (parent === dir) break
201
+ dir = parent
202
+ }
203
+ return found
204
+ }
205
+
206
+ export function inspectPackageProvenance(
207
+ packageRoot,
208
+ { includeInventory = true } = {},
209
+ ) {
210
+ const root = path.resolve(packageRoot)
211
+ const realRoot = fs.realpathSync(root)
212
+ const pkg = JSON.parse(
213
+ fs.readFileSync(path.join(realRoot, 'package.json'), 'utf8'),
214
+ )
215
+ const top = git(realRoot, ['rev-parse', '--show-toplevel'])
216
+ // Git may return a different spelling of the same Windows directory (for
217
+ // example a short-name alias). Compare directory identities, not path text;
218
+ // never lowercase paths, since Windows can also host case-sensitive trees.
219
+ const rootStat = fs.statSync(realRoot, { bigint: true })
220
+ const topStat = top === null ? null : fs.statSync(top, { bigint: true })
221
+ const own = topStat !== null && topStat.isDirectory() &&
222
+ (fs.realpathSync(top) === realRoot ||
223
+ (rootStat.ino !== 0n && rootStat.dev === topStat.dev && rootStat.ino === topStat.ino))
224
+ const head = own ? git(realRoot, ['rev-parse', 'HEAD']) : null
225
+ const status = own
226
+ ? git(realRoot, [
227
+ 'status',
228
+ '--porcelain',
229
+ '--untracked-files=all',
230
+ '--ignored=matching',
231
+ ])
232
+ : null
233
+ const declared = declarations(root, realRoot, pkg.name)
234
+ const conflict =
235
+ new Set(
236
+ declared.map((value) =>
237
+ canonicalize({
238
+ kind: value.kind,
239
+ repository: value.repository,
240
+ gitSha: value.gitSha,
241
+ integrity: value.integrity,
242
+ }),
243
+ ),
244
+ ).size > 1
245
+ let observed = null
246
+ const limitations = [
247
+ 'dependency bytes are outside this inventory',
248
+ 'local metadata and digests do not authenticate an upstream publisher',
249
+ ]
250
+ try {
251
+ if (!includeInventory) throw new Error('metadata only')
252
+ const first = inventory(realRoot)
253
+ const second = inventory(realRoot)
254
+ if (first.digest !== second.digest)
255
+ throw new Error('package changed during inventory')
256
+ observed = first
257
+ } catch {
258
+ limitations.push('complete stable package inventory unavailable')
259
+ }
260
+ const afterHead = own ? git(realRoot, ['rev-parse', 'HEAD']) : null
261
+ const afterStatus = own
262
+ ? git(realRoot, [
263
+ 'status',
264
+ '--porcelain',
265
+ '--untracked-files=all',
266
+ '--ignored=matching',
267
+ ])
268
+ : null
269
+ // node_modules is excluded from inventory and never covered by sourceVerified.
270
+ const cleanStatus = (value) =>
271
+ value !== null &&
272
+ value
273
+ .split('\n')
274
+ .filter(Boolean)
275
+ .every((line) => line === '!! node_modules/')
276
+ const clean =
277
+ cleanStatus(status) &&
278
+ cleanStatus(afterStatus) &&
279
+ head !== null &&
280
+ head === afterHead
281
+ const matchesHead = own && matchesCommit(realRoot, head, observed)
282
+ const verified = own && clean && matchesHead && !conflict
283
+ if (own && observed && !matchesHead)
284
+ limitations.push(
285
+ 'observed package bytes differ from the claimed commit tree',
286
+ )
287
+ if (own && !clean)
288
+ limitations.push(
289
+ 'checkout contains changes or ignored/untracked inputs; exact source binding refused',
290
+ )
291
+ if (conflict) limitations.push('conflicting install declarations')
292
+ if (!own && declared.length === 0)
293
+ limitations.push(
294
+ 'install manager metadata unavailable or unsupported; origin unresolved',
295
+ )
296
+ return {
297
+ schema: 'atelier-package-provenance@v1',
298
+ name: pkg.name,
299
+ version: pkg.version,
300
+ level: conflict
301
+ ? 'unresolved'
302
+ : verified
303
+ ? 'verified-checkout'
304
+ : declared.length
305
+ ? 'declared'
306
+ : 'unresolved',
307
+ sourceVerified: verified,
308
+ trackedGitSha: head,
309
+ repository: safeRepository(
310
+ own
311
+ ? git(realRoot, ['remote', 'get-url', 'origin'])
312
+ : pkg.repository?.url,
313
+ ),
314
+ declared,
315
+ inventory: observed,
316
+ limitations,
317
+ }
318
+ }
319
+
320
+ export function legacyPackageSource(report) {
321
+ if (report.trackedGitSha && report.repository)
322
+ return {
323
+ type: 'git',
324
+ repository: report.repository,
325
+ gitSha: report.trackedGitSha,
326
+ }
327
+ const entry = report.level === 'declared' ? report.declared[0] : null
328
+ if (entry?.kind === 'git')
329
+ return { type: 'git', repository: entry.repository, gitSha: entry.gitSha }
330
+ if (entry?.kind === 'npm')
331
+ return { type: 'npm', repository: report.repository, gitSha: null }
332
+ return {
333
+ type: 'local_path',
334
+ path: '.',
335
+ repository: report.repository,
336
+ gitSha: null,
337
+ }
338
+ }
@@ -0,0 +1,103 @@
1
+ // Bounded local filesystem evidence. This is cooperative concurrency control,
2
+ // not a sandbox for arbitrary executors, hooks or other host processes.
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import crypto from 'node:crypto'
6
+ import { canonicalize } from '../attestation/jcs.mjs'
7
+ import { openRegularFileNoFollow, ensureContainedPrivateDirectory } from '../project/private-state.mjs'
8
+
9
+ export const hashBytes = (bytes) => `sha256:${crypto.createHash('sha256').update(bytes).digest('hex')}`
10
+ export const hashObject = (value) => hashBytes(canonicalize(value))
11
+ export const same = (a, b) => canonicalize(a) === canonicalize(b)
12
+ export const jsonText = (value) => `${JSON.stringify(value, null, 2)}\n`
13
+ export const MAX_BYTES = 64 * 1024 * 1024
14
+
15
+ export function contained(root, relative) {
16
+ if (typeof relative !== 'string' || !relative || relative.includes('\\') || relative.includes('\0') || path.isAbsolute(relative) || relative.split('/').some((p) => !p || p === '.' || p === '..' || p === '.git')) throw new Error('invalid contained path')
17
+ let current = root
18
+ for (const part of relative.split('/')) {
19
+ current = path.join(current, part)
20
+ try { if (fs.lstatSync(current).isSymbolicLink()) throw new Error('redirected path refused') } catch (e) { if (e.code !== 'ENOENT') throw e }
21
+ }
22
+ return current
23
+ }
24
+
25
+ export function readBytes(file, { allowLinks = false } = {}) {
26
+ const fd = openRegularFileNoFollow(file)
27
+ try {
28
+ const st = fs.fstatSync(fd)
29
+ if ((!allowLinks && st.nlink !== 1) || st.size > MAX_BYTES) throw new Error('linked or oversized file refused')
30
+ return fs.readFileSync(fd)
31
+ } finally { fs.closeSync(fd) }
32
+ }
33
+
34
+ export function fileState(file, options) {
35
+ try {
36
+ const st = fs.lstatSync(file)
37
+ if (!st.isFile() || st.isSymbolicLink()) throw new Error('non-regular file refused')
38
+ return { digest: hashBytes(readBytes(file, options)), mode: st.mode & 0o111 ? '100755' : '100644' }
39
+ } catch (e) { if (e.code === 'ENOENT') return null; throw e }
40
+ }
41
+
42
+ export function inventory(root, { exclude = [], allowLinks = false } = {}) {
43
+ const entries = []
44
+ let bytes = 0
45
+ function walk(rel) {
46
+ for (const name of fs.readdirSync(path.join(root, rel)).sort()) {
47
+ const next = rel ? `${rel}/${name}` : name
48
+ if (exclude.includes(next)) continue
49
+ const file = contained(root, next)
50
+ const st = fs.lstatSync(file)
51
+ if (st.isDirectory()) walk(next)
52
+ else {
53
+ bytes += st.size
54
+ if (bytes > MAX_BYTES || entries.length >= 4096) throw new Error('inventory capacity exceeded')
55
+ entries.push({ path: next, state: fileState(file, { allowLinks }) })
56
+ }
57
+ }
58
+ }
59
+ walk('')
60
+ return entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
61
+ }
62
+
63
+ export function syncDirectory(directory) {
64
+ const fd = fs.openSync(directory, fs.constants.O_RDONLY)
65
+ try { fs.fsyncSync(fd) } finally { fs.closeSync(fd) }
66
+ }
67
+
68
+ export function publish(file, bytes, mode = 0o600) {
69
+ // Exclusive immutable publication. A crash during this write leaves an
70
+ // invalid record which readers refuse; it never resembles a valid success.
71
+ const fd = openRegularFileNoFollow(file, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, mode)
72
+ try { fs.writeFileSync(fd, bytes); fs.fsyncSync(fd) } finally { fs.closeSync(fd) }
73
+ syncDirectory(path.dirname(file))
74
+ if (!readBytes(file).equals(Buffer.from(bytes))) throw new Error('publication reread mismatch')
75
+ }
76
+
77
+ export function privateDirectory(root, relative) {
78
+ const directory = ensureContainedPrivateDirectory({ workspaceRoot: root, directory: contained(root, relative) })
79
+ let current = directory
80
+ const realRoot = fs.realpathSync(root)
81
+ while (current !== realRoot) { syncDirectory(current); current = path.dirname(current) }
82
+ syncDirectory(realRoot)
83
+ return directory
84
+ }
85
+
86
+ export function replaceExpected(root, entry) {
87
+ const file = contained(root, entry.path)
88
+ if (!same(fileState(file), entry.before)) throw new Error('write preimage changed')
89
+ // Only missing parent directories are created; existing directory modes stay.
90
+ let dir = root
91
+ for (const part of entry.path.split('/').slice(0, -1)) {
92
+ const next = path.join(dir, part)
93
+ if (!fs.existsSync(next)) { fs.mkdirSync(next); syncDirectory(dir) }
94
+ if (!fs.lstatSync(next).isDirectory() || fs.lstatSync(next).isSymbolicLink()) throw new Error('write parent refused')
95
+ dir = next
96
+ }
97
+ const temp = `${file}.atelier-${crypto.randomUUID()}.tmp`
98
+ publish(temp, Buffer.from(entry.content, 'base64'), entry.after.mode === '100755' ? 0o755 : 0o644)
99
+ if (!same(fileState(file), entry.before)) { fs.unlinkSync(temp); throw new Error('write preimage changed') }
100
+ fs.renameSync(temp, file)
101
+ syncDirectory(dir)
102
+ if (!same(fileState(file), entry.after)) throw new Error('write verification failed')
103
+ }