@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,84 @@
1
+ # Local source review
2
+
3
+ The optional review workspace helps a reader inspect claims, ask a question or
4
+ propose a correction, and retain that contribution. It records **locally asserted
5
+ identity**. A typed name is not an authenticated approver. Acceptance does not
6
+ edit source, publish content, grant access or admit a runtime.
7
+
8
+ Start with `atelier init --template external-project --target NEW_DIRECTORY`.
9
+ Use a destination whose ancestors are real directories, not symlinks. The
10
+ starter refuses existing destinations and does not initialize Git or install
11
+ hooks. Initialize its adapter and synthetic source as separate repositories when
12
+ ready. An established repository can replace the synthetic source through
13
+ `--repo-path source=PATH`; this changes location, never read authority.
14
+
15
+ From the adapter, run `atelier graph`, then `atelier project`, followed by:
16
+
17
+ ```sh
18
+ atelier review packs
19
+ atelier review run sample.readiness:contract-gate --answers answers.example.json
20
+ atelier dev --review
21
+ ```
22
+
23
+ Open `/review` on the loopback address printed by the foreground sidecar. Enter
24
+ repository `source` and path `README.md`, copy an exact passage, type a question
25
+ or correction, and choose **Save response**. Save is confirmed only after the
26
+ ledger acknowledges the write. On contention, stale content or service failure,
27
+ the wording remains in the form and the page does not claim success. Retry an
28
+ unchanged contribution after restoring service. A changed draft is a new request.
29
+ Unsaved browser drafts are not durable across tab closure.
30
+
31
+ To resume, save a reading position using the exact passage. Close the page,
32
+ reopen it, enter the same asserted name and choose the recorded **Resume** button.
33
+ The response history remains visible. A position is restored only against the
34
+ same document digest. After a source edit, earlier responses retain their exact
35
+ wording, anchor and original digest; explicitly select a passage in the new
36
+ revision and contribute a new response. Reading and scrolling never imply agreement.
37
+
38
+ Enter the returned run ID to review individual subject/predicate/object claims.
39
+ Each shows its ID, mapped answer fields, source-reference resolution, source
40
+ identity and a bounded excerpt. References in this first cut resolve exact graph
41
+ node IDs. Free text, URLs and other reference schemes remain unresolved; they
42
+ are never silently treated as source evidence. A linked source establishes
43
+ which bytes were read, not whether they logically support the proposed claim.
44
+ Read the document and record your judgment. Missing mapped sources or required
45
+ answers prevent acceptance. Rejection and revision remain available against
46
+ current evidence, so missing evidence can be discussed honestly.
47
+
48
+ Accept/reject/revise requires a reason, request ID and expected version. Every
49
+ contribution is immutable. Subsequent versions link through the same claim/run
50
+ identity and target version; proposed successor wording is retained without
51
+ rewriting the original claim. A later rejection supersedes an earlier acceptance
52
+ for current handoff. An identical retry returns the prior record. Another
53
+ reader's intervening contribution refuses a stale version; reopen the run before
54
+ making a new decision. Previous history remains visible rather than being
55
+ collapsed into a single latest-state approval.
56
+
57
+ **Inspect owner handoff**, or `atelier review handoff REQUEST_ID`, displays the
58
+ proposed relationship, rationale, affected IDs and required evidence digest.
59
+ The source owner decides the concrete edit in its own workflow. Source changes
60
+ remain unapplied and promotion remains unestablished. No owner-application receipt
61
+ or publication authorization is fabricated. Changed source/configuration,
62
+ policy, pack or evaluator inputs make current handoff ineligible.
63
+
64
+ The legacy readiness score and `ready`-shaped packet fields retain their existing
65
+ contract meanings: input completion and draft preparation. They are not evidence
66
+ confidence, independent rule execution, human acceptance or runtime readiness.
67
+ New claims omit the former arbitrary numeric confidence. Bound snapshots store
68
+ protocol content, normalized answers, pack identity, declared source byte digests,
69
+ policy identity, and the shipped source/schema evaluator inventory. Dependency
70
+ bytes and upstream publisher authentication need separate installation proof.
71
+ Historic v1 runs without snapshots remain available through the existing readiness
72
+ commands, but cannot be accepted as current evidence through this review surface.
73
+
74
+ The existing sidecar supplies loopback host/origin checks, session nonce checks
75
+ and private-file protection. `--review` is opt-in. The new ledgers live separately
76
+ from legacy proposals under `.atelier-local/review/`. Each record occupies its own
77
+ immutable event aggregate, so existing compaction retains the complete audit
78
+ history. Per-target versions are checked while holding the ledger write lock.
79
+ Local files and locally asserted identities are not protection from an operator
80
+ who controls that same account and its filesystem.
81
+
82
+ Synthetic installed and browser checks establish software behavior. An unfamiliar
83
+ reader's useful question or correction, assistance required, and actual adopter
84
+ acceptance still need a human observation; automated tests cannot supply it.
@@ -99,3 +99,230 @@ A consumer implementation is not complete until tests prove:
99
99
 
100
100
  Use synthetic fixtures for public Atelier tests. Tenant-specific proof remains
101
101
  in the tenant repository.
102
+
103
+ ## The Obsidian maintenance service
104
+
105
+ The continuous maintenance of Obsidian views (`src/runtime/obsidian/`) is a
106
+ consumer of this contract that ships with Atelier. It keeps one service per
107
+ enabled workspace: a process that ticks the maintenance engine on an interval
108
+ and answers on one loopback port. Its lifecycle is `startService`,
109
+ `serviceStatus` and `stopService` in `src/runtime/obsidian/lifecycle.mjs`.
110
+
111
+ ### Identity record
112
+
113
+ Each workspace has one record, `state/service/runtime.json`, under the
114
+ workspace's private state root in the machine-private data directory. It is
115
+ never inside a repository or a vault, its directory is owner-only, the file is
116
+ mode 0600, and it is replaced atomically. It satisfies
117
+ `contracts/atelier-obsidian-service-state.v1.schema.json`:
118
+
119
+ - the service name, derived from the workspace identity;
120
+ - the literal loopback host, `127.0.0.1` or `::1`, never a hostname and never
121
+ a wildcard address, and the selected port;
122
+ - the executable identity: the path of the service entry module and the
123
+ SHA-256 digest of its bytes;
124
+ - a random runtime identifier and the PID;
125
+ - the state location, a health description, and the startup consent (who
126
+ granted it, when, and whether it covers the service only or the service and
127
+ operating-system startup);
128
+ - in its private part, the random bearer of that one runtime. The bearer exists
129
+ nowhere else, and nothing that `start`, `status` or `stop` returns carries it.
130
+
131
+ The record is validated on every read. A record that does not validate, or
132
+ that names another workspace, service or state location, makes `start`,
133
+ `status` and `stop` refuse with `invalid-service-record`. It is never repaired,
134
+ adopted or removed automatically; a person inspects it and removes it.
135
+
136
+ The port is a machine-specific value. `start` takes an explicit port, or
137
+ selects a free loopback port once and records it beside the consent in
138
+ `state/service/settings.json`, so later starts, `status` and any installed
139
+ unit name the same one. The first start of a workspace refuses without an
140
+ explicit consent that names its actor.
141
+
142
+ ### Health fields and fixed operations
143
+
144
+ | Operation | Method and path | Credential | Answer |
145
+ | --- | --- | --- | --- |
146
+ | Health | `GET /health` | none | service name, workspace identity, runtime identifier, PID, loopback host and port, executable digest, start time, status |
147
+ | Status | `GET /status` | bearer | the health fields, loop state, last tick, last error code, and per-view freshness with held notes counted, not named |
148
+ | Tick now | `POST /tick` | bearer | the state of the tick that ran |
149
+ | Stop | `POST /stop` | bearer | an acknowledgement naming the runtime and PID, then the service ends |
150
+
151
+ Health carries no path, no note title, no source name and no withheld
152
+ identity. There is no other operation: no file serving, no command, no
153
+ evaluation, and no route that takes a path or code from a request. The service
154
+ opens no outbound connection, has no telemetry and depends on nothing remote.
155
+
156
+ The listener refuses a request before looking its operation up when:
157
+
158
+ - `Host` is not exactly the literal loopback authority it is bound to, which
159
+ also refuses a name that resolves to loopback;
160
+ - `Sec-Fetch-Site` is present and is neither `none` nor `same-origin`, or
161
+ `Origin` is present and is not the listener itself;
162
+ - the path is not one of the four, exactly and without a query, or the method
163
+ is not that path's method.
164
+
165
+ Status, tick and stop additionally refuse without the bearer of the running
166
+ runtime. A `POST` payload is a JSON object of at most 1 KiB that names the
167
+ runtime identifier it is meant for, so a request aimed at an earlier runtime on
168
+ the same port does nothing.
169
+
170
+ ### Status values and refusal cases
171
+
172
+ | Status | Meaning | `start` | `stop` |
173
+ | --- | --- | --- | --- |
174
+ | `healthy` | record and health agree on service name, workspace, runtime identifier, PID, executable digest and loopback address, and the PID is alive | reports already running | asks that runtime to stop |
175
+ | `busy` | the recorded port accepts a connection and does not answer health in time, the recorded PID is alive, the recorded entry module still has the recorded digest, and that PID's command line names that entry module (and the recorded runtime identifier when it names one) | reports it as running and busy; starts nothing | refuses with a retry hint; nothing is signalled |
176
+ | `stopped` | no record and nothing listens | starts | nothing to do |
177
+ | `occupied` | something answers on the port without that proof: another program, a silent listener that is not provably ours, or a health answer with any field different | refuses; never takes over | refuses |
178
+ | `stale-record` | the recorded address is closed and the recorded PID is gone | starts; the new service replaces the record once it listens | refuses; nothing is proven to stop |
179
+ | `pid-not-ours` | the recorded address is closed and a process has the recorded PID (the number was reused, or a process outlived its listener) | starts; that PID is never signalled | refuses; that PID is never signalled |
180
+
181
+ `start` is serialized per workspace, spawns the service without a shell, and
182
+ detaches it only when asked for a service that survives the launching command.
183
+ It waits for health to echo the runtime identifier it generated and the PID of
184
+ the child it created. If that proof never arrives it stops only that child, by
185
+ its process handle, and reports the private operational log,
186
+ `state/service/service.log`.
187
+
188
+ A tick is largely synchronous, so a healthy service in a long tick may not
189
+ answer health within the deadline. That is `busy`, not `occupied`: it is
190
+ never adopted, never stopped and never started over, and a `start` whose own
191
+ child went straight into a long first tick reports it as started and busy
192
+ instead of ending it. The command line of another process is read from
193
+ `/proc/<pid>/cmdline` on Linux and from `/bin/ps` on macOS. It is not
194
+ established on Windows, where a silent service still reads as `occupied`.
195
+
196
+ `stop` sends the stop operation to the proven runtime only, waits for that PID
197
+ to end and removes only the generated record. It never looks a process up by
198
+ port, name or pattern. Ending a proven runtime that ignores the request is
199
+ opt-in and is proven again immediately before the signal.
200
+
201
+ The service itself refuses to start beside a runtime of the same workspace
202
+ that proves itself, and ends cleanly when its record no longer names it. Two
203
+ engines can never tick one workspace together: each tick holds a private
204
+ per-workspace lock, and an engine that finds it held writes nothing and
205
+ reports busy.
206
+
207
+ ### What stop and a hard end leave intact
208
+
209
+ On `stop` the tick in flight finishes, within a grace period. Drafts in a
210
+ vault, pending edits, recovery copies, staging and the last good view are left
211
+ exactly as they are. The service never sweeps or deletes anything under
212
+ `staging/` or `recovery/`: not at start, on a tick, at shutdown, after a hard
213
+ kill or when it finds a stale record. A publication cut short by a hard kill
214
+ is settled by the publisher's own journaled restart recovery on a later tick.
215
+
216
+ A tick that fails for a reason nobody typed (a full disk, a crash inside a
217
+ step) does not end the service. Its error code is kept in
218
+ `state/service/last-error.json`, its message only in the private log; the
219
+ delay before the next attempt doubles up to a ceiling, and the next tick
220
+ proceeds.
221
+
222
+ ### Cases that need a person
223
+
224
+ - a record that does not validate, or that belongs to another workspace;
225
+ - an `occupied` port: decide what owns it, or select another port;
226
+ - an engine lock whose holder is a live process that recorded no health
227
+ address, a lock written on another machine, an unreadable ticket, or an
228
+ unknown file in the lock directory. A lock is taken from a holder only with
229
+ proof that its process is gone, or that it was a service whose recorded
230
+ address is closed or answers as another runtime or PID. An address that
231
+ accepts a connection and does not answer in time proves nothing.
232
+ `inspectPrivateGenerationLock` reports the holder without changing anything.
233
+
234
+ ### Operating-system startup
235
+
236
+ `buildStartupAdapter` returns the text of a launchd user agent (macOS) or a
237
+ systemd user unit (Linux) from the values it is given. It writes no file,
238
+ installs nothing, runs no service manager and looks nothing up on the machine.
239
+ Windows startup has not been qualified and refuses with
240
+ `startup-platform-unqualified`. Installing a unit is a separate system change
241
+ that needs explicit user authorization; the service refuses to run with
242
+ `--startup` unless the recorded consent covers startup.
243
+
244
+ The service entry refuses to run without an explicitly selected editor
245
+ adapter. Public Atelier tests start only a test entry whose adapter reports
246
+ that no app runs, in temporary directories, on ephemeral loopback ports.
247
+
248
+ ### The `obsidian` command
249
+
250
+ `atelier obsidian <operation>` is the noninteractive surface over all of the
251
+ above. With `--json` it prints exactly one JSON document, for a refusal too.
252
+ Exit codes: 0 done, 1 an error nobody typed, 2 a typed refusal or a usage
253
+ error, 3 the operation ran and its answer is not success.
254
+
255
+ | Operation | What it does | Writes |
256
+ | --- | --- | --- |
257
+ | `status` | enablement, machine settings, proven service state, per-view freshness, whether an apply operation exists | nothing |
258
+ | `scope list`, `scope show ID` | the declared views | nothing |
259
+ | `audience show`, `audience set A,B`, `audience clear` | the audiences this machine lets into a view; none by default, which publishes an empty view. A change invalidates every view at the next tick | private machine settings |
260
+ | `mode show`, `mode set manual\|automatic` | `automatic` refuses without an installed, matching, active automatic policy | private machine settings |
261
+ | `policy show`, `policy install FILE`, `policy revoke` | `install` validates against the apply-policy contract and stores the policy owner-only beside the machine settings, never in a project, a repository or a note. `revoke` marks the stored policy revoked, which the engine reads before its very next dispatch, and returns the mode to manual | private machine settings |
262
+ | `service start`, `service status`, `service stop` | `startService`, `serviceStatus`, `stopService`. The first start needs `--consent-actor ID` | what the lifecycle writes |
263
+ | `service unit --print` | the text `buildStartupAdapter` returns. Installing a unit is not offered | nothing |
264
+ | `open [--scope ID]` | starts or reconnects the owned service, asks it for a tick, reads the view back, qualifies the installed app, has the vault opened | what the service writes |
265
+
266
+ Reaching the installed app or the operating system is never a default.
267
+ `open`, `service start` and `service unit` refuse with
268
+ `app-adapter-not-selected` unless `--adapter=obsidian-cli` is given, the same
269
+ explicit rule the service entry has, and the modules that talk to an app are
270
+ loaded only after that check. Tests pass their own seams and a guard in the
271
+ test file throws if anything tries to start the app, its command-line tool, an
272
+ operating-system opener or a service manager.
273
+
274
+ `open` answers one typed outcome, each with a one-line explanation and a next
275
+ step. Only `current` is success:
276
+
277
+ | Outcome | Meaning |
278
+ | --- | --- |
279
+ | `current` | the proven service ticked after the request; the view's persisted freshness is `current`; read back independently, the trusted generation is the prepared one and every note has the bytes it was published with; the app meets the minimum version, was asked to open this vault, answers for exactly this vault and has finished reading it |
280
+ | `updating` | a publication is under way, a tick outlasted the wait, or a note differs from the trusted generation and has not been looked at yet |
281
+ | `held-for-your-edit` | an edited note is preserved and held |
282
+ | `stale-readable` | a last good vault exists and reads back, but is not proven to be the present generation |
283
+ | `not-prepared` | no generation of this view has been published |
284
+ | `publisher-conflict` | another publisher or an uncoordinated editor holds the vault |
285
+ | `app-missing`, `app-cli-unavailable`, `app-version-unsupported` | no installation; no command-line capability; below the minimum version, or a version that cannot be read |
286
+ | `launch-failed` | the operating system refused, or the app never answered for this vault |
287
+ | `indexing` | the app answers for this vault and has not finished reading it |
288
+ | `service-unavailable` | the service could not be started or is not provably ours (`occupied`, no consent yet, a start that never proved ownership) |
289
+ | `busy` | the service is ours and in a long tick |
290
+ | `disabled` | the integration is off or not declared |
291
+
292
+ `apply-unavailable` is not an opening outcome. It is reported beside pending
293
+ edits, by `status`, `open`, `mode` and the placeholder `apply` operation, for
294
+ as long as no apply operation is registered. `status` does not repeat a
295
+ persisted `current` while no healthy service proves it: opening a vault
296
+ directly bypasses this command, so an old check is reported as
297
+ `stale-readable` with the reason.
298
+
299
+ The minimum app version is `MINIMUM_APP_VERSION` in
300
+ `src/runtime/obsidian/app-capability.mjs`: 1.13.7, the only version the
301
+ publication protocol was proven on. A prerelease ranks below its release and a
302
+ version that cannot be parsed never passes. The service entry constructs the
303
+ CLI editor adapter only through `createQualifiedAdapterFactory`: for a running
304
+ app below the floor, an unreadable version or a missing command-line
305
+ capability the factory refuses, the engine records that code as the view's
306
+ freshness reason and nothing is published. An app that is positively not
307
+ running needs no version, because nothing is published through it.
308
+
309
+ The production probe and launcher (`app-production-seams.mjs`) are written
310
+ against the documented command-line interface and have not been exercised
311
+ against a running app in this repository's tests, by design. They are to be
312
+ qualified on an isolated host before they are relied on.
313
+
314
+ `atelier sync` does not maintain a vault and does not try. When the enrolled
315
+ project enables the integration, `sync status` gains one member,
316
+ `obsidianMaintenance`, that repeats what maintenance last persisted and points
317
+ at `atelier obsidian status`; it starts no service, ticks no engine and writes
318
+ nothing. With the settings absent or switched off its output is unchanged.
319
+
320
+ ### Registering later work
321
+
322
+ An apply operation, a proposal adapter and further `obsidian` sub-operations
323
+ are contributions: a module in `src/runtime/obsidian/contributions/` whose
324
+ default export is `{ id, register({ extensions, operations }) }`. The command
325
+ and the service entry both load that directory, so an apply operation
326
+ registered there is the one the service dispatches to and the one `status`
327
+ reports. No dispatch file is edited. A built-in operation name cannot be
328
+ taken, except the placeholder `apply`.
@@ -0,0 +1,107 @@
1
+ # Durable local state
2
+
3
+ `@mnstry/atelier/local-state` provides filesystem mechanisms for local adapters.
4
+ The adapter owns paths, schema, content, transport, authentication, state limits,
5
+ and user-facing recovery. These helpers do not create a service or publish data.
6
+
7
+ - `publishPrivateFile(path, bytes)` stages and syncs bytes, publishes with an
8
+ atomic non-overwriting hard link, syncs the directory, and verifies readback.
9
+ An identical existing file is an idempotent success. A differing file refuses.
10
+ - `acquirePrivateLock(path)` returns an idempotent release function. Callers must
11
+ release in `finally`, after all asynchronous work has finished.
12
+ - `withPrivateLock(path, operation)` is for **synchronous** operations only.
13
+ - `createVerifiedFileSequence({directory, initial, apply})` reads immutable
14
+ records in filename order. `initial()` creates reducer state and
15
+ `apply(text, previous, oneBasedIndex, filename)` validates each record and
16
+ returns the next state. The adapter validates names, chain, schema and limits.
17
+ Unchanged prefixes reuse their validated result; file identity and timestamps
18
+ are checked on each call. Startup verifies the complete history.
19
+ - `isPendingPrivateWrite(name)` recognizes incomplete staging names. They are
20
+ never committed records. Preserve them for diagnosis after interruption.
21
+ - `syncPrivateDirectory(path)` flushes a directory where supported. Windows
22
+ directory durability requires target-filesystem qualification; synced file
23
+ contents alone do not prove survival of power loss.
24
+
25
+ Use an ignored private directory whose ancestors have already been validated.
26
+ Leaves refuse symlinks. These are local integrity mechanisms, not a sandbox
27
+ against an actor able to rewrite the same user's files or filesystem metadata.
28
+ Do not run old and new writer implementations concurrently against one store.
29
+
30
+ ## Recovery and compatibility
31
+
32
+ Locks use atomic numbered ownership tickets. A definitely absent local process
33
+ can be succeeded without deleting its ticket. Contending recoverers compete for
34
+ the same next number. Live, reused, foreign-host and unverifiable process IDs
35
+ block. No elapsed timeout grants ownership. The nonce binds release to the
36
+ exact ticket. Legacy PID-only lock records can be bypassed only when the PID is
37
+ definitely absent; unidentified legacy locks require operator diagnosis.
38
+
39
+ Process interruptions can leave staging files or dead-owner tickets. Retain
40
+ these as evidence. Never clear an entire private-state directory to recover a
41
+ single interrupted operation. A corrupted committed record is still an error;
42
+ the reader does not silently skip or truncate it.
43
+
44
+ The collaboration ledger retains its existing bounded NDJSON format but writes
45
+ each complete validated successor atomically. Coauthor and intake reuse the
46
+ same ownership protocol. Immutable intake blobs and coauthor values use atomic
47
+ publication. Existing history is not rewritten by these changes.
48
+
49
+ Adapters should bind retries to the same operation identity and exact input,
50
+ retain newer edits while an earlier save is pending, measure limits in UTF-8
51
+ bytes, and verify runtime build identity before declaring an existing service
52
+ current. Opening a previously verified artifact must not require optional graph
53
+ or projection generation to succeed.
54
+
55
+ ## Evidence boundaries
56
+
57
+ Local tests inject interruption before publication, verify the previous ledger
58
+ remains readable, restart after an owner exits without releasing, refuse live
59
+ and unknown owners, verify binary readback, and exercise cache invalidation.
60
+ These are process-interruption tests, not physical power-loss qualification.
61
+ No cache promises constant memory or constant metadata work for unbounded
62
+ history. A different checkpoint format needs its own migration and replay proof.
63
+
64
+ ## Ownership diagnostics and offline recovery
65
+
66
+ `inspectPrivateLock(path)` is read-only and reports availability, reason, newest
67
+ filename and generation. It does not authorize overriding an unresolved owner.
68
+ Only the newest ticket is checked for process liveness. Its predecessor was
69
+ adjudicated when the successor was published. Release now retains the ticket and
70
+ publishes a matching `.released` file; without that marker, removing a successor
71
+ would expose an obsolete PID again. A matching release proves operation completion
72
+ even if a later host name or PID changes. An unresolved newest ticket on another
73
+ host, a reused PID, or an empty legacy lock still blocks with a specific diagnostic.
74
+ A successful successor binds the old legacy lock's exact bytes and supersedes that
75
+ liveness decision. A newly created or changed legacy lock must be adjudicated again.
76
+
77
+ If the newest owner cannot be established, stop all writers and verify that no
78
+ other machine or process can write this store. Preserve a complete private backup,
79
+ including records and both the legacy lock and ownership directory. With exclusive
80
+ offline custody, move the lock file and its whole `.owners` directory together to
81
+ a uniquely named recovery directory outside the live store. Do not edit individual
82
+ tickets, forge release markers, remove event records or bypass a live owner. Run
83
+ the adapter's history verification before reopening. If exclusive custody cannot
84
+ be established, remain blocked. Do not mix this protocol with an older writer.
85
+
86
+ Ownership tickets and releases accumulate; adapters must monitor their size and
87
+ perform any retirement under the same offline recovery procedure. Automatic
88
+ age-based cleanup cannot establish whether a writer or operation is finished.
89
+
90
+ ## Filesystem and adapter contract
91
+
92
+ Publication requires same-directory hard links and atomic non-overwrite creation.
93
+ Unsupported filesystems must fail; there is no copy/overwrite fallback. Qualify the
94
+ actual target filesystem with a disposable write, exact readback and reopening
95
+ before moving a store there. This does not prove physical power-loss durability.
96
+ Incomplete `.atelier-write-<uuid>.tmp` residue is uncommitted. Preserve it until
97
+ writers are stopped and diagnosis is complete; never reap it by age during writes.
98
+
99
+ `createVerifiedFileSequence` accepts an optional `ignoreFiles` array of exact
100
+ adapter-owned metadata filenames. Those entries must still be regular files.
101
+ All other names reach the adapter validator; unknown or corrupt records must not
102
+ be hidden by a suffix wildcard. Staging names, ownership tickets/release markers
103
+ and synchronous `withPrivateLock` semantics are compatibility obligations of this
104
+ export. Consumers should use the exported helpers rather than infer these formats.
105
+ Locking is non-reentrant. Async callers must acquire/release explicitly in `finally`.
106
+ A release error after a committed operation still reports uncertainty; reconcile
107
+ an operation ID before writing again rather than treating it as evidence of no save.