@ternent/core 0.0.1

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 (313) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +17 -0
  3. package/.github/workflows/deploy-armour.yml +42 -0
  4. package/.github/workflows/deploy-identity.yml +42 -0
  5. package/.github/workflows/deploy-seal.yml +42 -0
  6. package/.github/workflows/deploy-ui.yml +42 -0
  7. package/.github/workflows/deploy-utils.yml +42 -0
  8. package/.github/workflows/release-create.yml +59 -0
  9. package/.github/workflows/release-publish.yml +54 -0
  10. package/.nvmrc +1 -0
  11. package/.ops/publish.mjs +31 -0
  12. package/package.json +16 -0
  13. package/packages/README.md +0 -0
  14. package/packages/armour/CHANGELOG.md +66 -0
  15. package/packages/armour/CLAUDE.md +8 -0
  16. package/packages/armour/README.md +103 -0
  17. package/packages/armour/SPEC.md +92 -0
  18. package/packages/armour/package.json +45 -0
  19. package/packages/armour/src/constants.ts +5 -0
  20. package/packages/armour/src/deps.d.ts +56 -0
  21. package/packages/armour/src/errors.ts +172 -0
  22. package/packages/armour/src/files.ts +73 -0
  23. package/packages/armour/src/identity.ts +72 -0
  24. package/packages/armour/src/index.ts +56 -0
  25. package/packages/armour/src/init.ts +10 -0
  26. package/packages/armour/src/passphrase.ts +33 -0
  27. package/packages/armour/src/recipients.ts +73 -0
  28. package/packages/armour/src/text.ts +68 -0
  29. package/packages/armour/src/types.ts +93 -0
  30. package/packages/armour/test/armour.test.ts +270 -0
  31. package/packages/armour/tsconfig.build.json +12 -0
  32. package/packages/armour/tsconfig.json +12 -0
  33. package/packages/armour/vite.config.ts +29 -0
  34. package/packages/concord/CHANGELOG.md +83 -0
  35. package/packages/concord/CLAUDE.md +9 -0
  36. package/packages/concord/README.md +146 -0
  37. package/packages/concord/SPEC.md +287 -0
  38. package/packages/concord/package.json +51 -0
  39. package/packages/concord/src/app.ts +717 -0
  40. package/packages/concord/src/errors.ts +9 -0
  41. package/packages/concord/src/index.ts +20 -0
  42. package/packages/concord/src/types.ts +127 -0
  43. package/packages/concord/test/concord.test.ts +978 -0
  44. package/packages/concord/tsconfig.json +12 -0
  45. package/packages/concord/vite.browser.config.ts +27 -0
  46. package/packages/concord/vite.config.ts +35 -0
  47. package/packages/concord/vite.config.ts.timestamp-1774262297922-ffd76e35ea668.mjs +83 -0
  48. package/packages/identity/CHANGELOG.md +47 -0
  49. package/packages/identity/README.md +236 -0
  50. package/packages/identity/package.json +41 -0
  51. package/packages/identity/src/index.ts +538 -0
  52. package/packages/identity/test/identity.test.ts +172 -0
  53. package/packages/identity/tsconfig.build.json +12 -0
  54. package/packages/identity/vite.config.ts +17 -0
  55. package/packages/ledger/CHANGELOG.md +69 -0
  56. package/packages/ledger/CLAUDE.md +9 -0
  57. package/packages/ledger/SPEC.md +304 -0
  58. package/packages/ledger/package.json +48 -0
  59. package/packages/ledger/src/index.ts +2 -0
  60. package/packages/ledger/src/ledger.ts +1286 -0
  61. package/packages/ledger/src/seal-cli.d.ts +25 -0
  62. package/packages/ledger/src/types.ts +294 -0
  63. package/packages/ledger/test/ledger.test.ts +838 -0
  64. package/packages/ledger/tsconfig.json +12 -0
  65. package/packages/ledger/vite.browser.config.ts +27 -0
  66. package/packages/ledger/vite.config.ts +39 -0
  67. package/packages/seal/CHANGELOG.md +137 -0
  68. package/packages/seal/CLAUDE.md +8 -0
  69. package/packages/seal/README.md +258 -0
  70. package/packages/seal/bin/seal +6 -0
  71. package/packages/seal/package.json +59 -0
  72. package/packages/seal/src/artifact.ts +380 -0
  73. package/packages/seal/src/cli.ts +372 -0
  74. package/packages/seal/src/commands/identity.ts +52 -0
  75. package/packages/seal/src/commands/manifest.ts +71 -0
  76. package/packages/seal/src/commands/publicKey.ts +7 -0
  77. package/packages/seal/src/commands/sign.ts +56 -0
  78. package/packages/seal/src/commands/verify.ts +54 -0
  79. package/packages/seal/src/crypto.ts +85 -0
  80. package/packages/seal/src/errors.ts +88 -0
  81. package/packages/seal/src/index.ts +5 -0
  82. package/packages/seal/src/manifest.ts +114 -0
  83. package/packages/seal/src/node.ts +18 -0
  84. package/packages/seal/src/proof.ts +344 -0
  85. package/packages/seal/test/artifact.test.ts +86 -0
  86. package/packages/seal/test/cli.test.ts +208 -0
  87. package/packages/seal/test/crypto.test.ts +21 -0
  88. package/packages/seal/test/manifest.test.ts +32 -0
  89. package/packages/seal/test/proof.test.ts +60 -0
  90. package/packages/seal/tsconfig.json +12 -0
  91. package/packages/seal/vite.config.ts +54 -0
  92. package/packages/ui/CHANGELOG.md +393 -0
  93. package/packages/ui/README.md +57 -0
  94. package/packages/ui/jsconfig.json +19 -0
  95. package/packages/ui/package.json +64 -0
  96. package/packages/ui/scripts/check-tokens.js +56 -0
  97. package/packages/ui/scripts/generate-theme-css.mjs +85 -0
  98. package/packages/ui/src/design-system/base.css +8 -0
  99. package/packages/ui/src/design-system/docs/ACCESSIBILITY_RULES.md +186 -0
  100. package/packages/ui/src/design-system/docs/AI_SYSTEM.md +281 -0
  101. package/packages/ui/src/design-system/docs/PATTERN_RULES.md +83 -0
  102. package/packages/ui/src/design-system/docs/PRIMITIVE_RULES.md +258 -0
  103. package/packages/ui/src/design-system/docs/TOKEN_RULES.md +235 -0
  104. package/packages/ui/src/design-system/docs/VISUAL_DIRECTION.md +68 -0
  105. package/packages/ui/src/design-system/foundation.js +420 -0
  106. package/packages/ui/src/design-system/tokens.css +140 -0
  107. package/packages/ui/src/design-system/tokens.js +327 -0
  108. package/packages/ui/src/design-system/utils.js +246 -0
  109. package/packages/ui/src/main.js +4 -0
  110. package/packages/ui/src/patterns/FeatureCard/FeatureCard.spec.md +24 -0
  111. package/packages/ui/src/patterns/FeatureCard/FeatureCard.types.ts +8 -0
  112. package/packages/ui/src/patterns/FeatureCard/FeatureCard.vue +175 -0
  113. package/packages/ui/src/patterns/FormField/FormField.spec.md +65 -0
  114. package/packages/ui/src/patterns/FormField/FormField.types.ts +11 -0
  115. package/packages/ui/src/patterns/FormField/FormField.vue +87 -0
  116. package/packages/ui/src/patterns/IdentityGlyph/IdentityGlyph.vue +61 -0
  117. package/packages/ui/src/patterns/IdentityGlyph/IdentityHandle.vue +58 -0
  118. package/packages/ui/src/patterns/IdentityGlyph/identityGlyph.types.ts +36 -0
  119. package/packages/ui/src/patterns/IdentityGlyph/identityGlyph.utils.ts +585 -0
  120. package/packages/ui/src/patterns/IdentityGlyph/index.ts +5 -0
  121. package/packages/ui/src/patterns/KeyValueList/KeyValueList.spec.md +28 -0
  122. package/packages/ui/src/patterns/KeyValueList/KeyValueList.types.ts +16 -0
  123. package/packages/ui/src/patterns/KeyValueList/KeyValueList.vue +50 -0
  124. package/packages/ui/src/patterns/LandingPage/LandingIcon.vue +90 -0
  125. package/packages/ui/src/patterns/LandingPage/LandingPage.spec.md +24 -0
  126. package/packages/ui/src/patterns/LandingPage/LandingPage.types.ts +212 -0
  127. package/packages/ui/src/patterns/LandingPage/LandingPage.vue +599 -0
  128. package/packages/ui/src/patterns/ListWorkspaceLayout/ListWorkspaceLayout.test.ts +33 -0
  129. package/packages/ui/src/patterns/ListWorkspaceLayout/ListWorkspaceLayout.vue +44 -0
  130. package/packages/ui/src/patterns/Logo/Logo.spec.md +22 -0
  131. package/packages/ui/src/patterns/Logo/Logo.vue +160 -0
  132. package/packages/ui/src/patterns/PageSurface/PageSurface.spec.md +15 -0
  133. package/packages/ui/src/patterns/PageSurface/PageSurface.vue +85 -0
  134. package/packages/ui/src/patterns/PanelChrome/PanelChrome.spec.md +39 -0
  135. package/packages/ui/src/patterns/PanelChrome/PanelChrome.types.ts +1 -0
  136. package/packages/ui/src/patterns/PanelChrome/PanelChrome.vue +187 -0
  137. package/packages/ui/src/patterns/PreviewPanel/PreviewPanel.spec.md +31 -0
  138. package/packages/ui/src/patterns/PreviewPanel/PreviewPanel.types.ts +23 -0
  139. package/packages/ui/src/patterns/PreviewPanel/PreviewPanel.vue +354 -0
  140. package/packages/ui/src/patterns/RecordList/RecordList.spec.md +35 -0
  141. package/packages/ui/src/patterns/RecordList/RecordList.test.ts +42 -0
  142. package/packages/ui/src/patterns/RecordList/RecordList.types.ts +9 -0
  143. package/packages/ui/src/patterns/RecordList/RecordList.utils.ts +5 -0
  144. package/packages/ui/src/patterns/RecordList/RecordList.vue +134 -0
  145. package/packages/ui/src/patterns/SectionClarifier/SectionClarifier.vue +85 -0
  146. package/packages/ui/src/patterns/SectionIntro/SectionIntro.spec.md +25 -0
  147. package/packages/ui/src/patterns/SectionIntro/SectionIntro.types.ts +7 -0
  148. package/packages/ui/src/patterns/SectionIntro/SectionIntro.vue +141 -0
  149. package/packages/ui/src/patterns/SidebarNav/SidebarNav.spec.md +34 -0
  150. package/packages/ui/src/patterns/SidebarNav/SidebarNav.types.ts +17 -0
  151. package/packages/ui/src/patterns/SidebarNav/SidebarNav.vue +110 -0
  152. package/packages/ui/src/patterns/SplitView/SplitView.spec.md +28 -0
  153. package/packages/ui/src/patterns/SplitView/SplitView.test.ts +22 -0
  154. package/packages/ui/src/patterns/SplitView/SplitView.types.ts +3 -0
  155. package/packages/ui/src/patterns/SplitView/SplitView.utils.ts +13 -0
  156. package/packages/ui/src/patterns/SplitView/SplitView.vue +39 -0
  157. package/packages/ui/src/patterns/StepList/StepList.spec.md +15 -0
  158. package/packages/ui/src/patterns/StepList/StepList.types.ts +4 -0
  159. package/packages/ui/src/patterns/StepList/StepList.vue +91 -0
  160. package/packages/ui/src/patterns/Verification/VerificationBadge.vue +97 -0
  161. package/packages/ui/src/patterns/Verification/VerificationComponents.test.ts +153 -0
  162. package/packages/ui/src/patterns/Verification/VerificationDetailsPanel.vue +270 -0
  163. package/packages/ui/src/patterns/Verification/VerificationSummary.vue +171 -0
  164. package/packages/ui/src/patterns/Verification/index.ts +6 -0
  165. package/packages/ui/src/patterns/Verification/verification.types.ts +8 -0
  166. package/packages/ui/src/patterns/Verification/verification.utils.test.ts +37 -0
  167. package/packages/ui/src/patterns/Verification/verification.utils.ts +75 -0
  168. package/packages/ui/src/patterns/index.ts +25 -0
  169. package/packages/ui/src/primitives/Accordian/Accordian.vue +11 -0
  170. package/packages/ui/src/primitives/Accordian/AccordianItem.vue +14 -0
  171. package/packages/ui/src/primitives/Accordion/Accordion.props.ts +21 -0
  172. package/packages/ui/src/primitives/Accordion/Accordion.spec.md +50 -0
  173. package/packages/ui/src/primitives/Accordion/Accordion.types.ts +4 -0
  174. package/packages/ui/src/primitives/Accordion/Accordion.variants.ts +12 -0
  175. package/packages/ui/src/primitives/Accordion/Accordion.vue +71 -0
  176. package/packages/ui/src/primitives/Accordion/AccordionItem.props.ts +14 -0
  177. package/packages/ui/src/primitives/Accordion/AccordionItem.vue +40 -0
  178. package/packages/ui/src/primitives/Badge/Badge.props.ts +17 -0
  179. package/packages/ui/src/primitives/Badge/Badge.spec.md +17 -0
  180. package/packages/ui/src/primitives/Badge/Badge.types.ts +15 -0
  181. package/packages/ui/src/primitives/Badge/Badge.variants.ts +48 -0
  182. package/packages/ui/src/primitives/Badge/Badge.vue +31 -0
  183. package/packages/ui/src/primitives/Button/Button.props.ts +29 -0
  184. package/packages/ui/src/primitives/Button/Button.spec.md +139 -0
  185. package/packages/ui/src/primitives/Button/Button.types.ts +19 -0
  186. package/packages/ui/src/primitives/Button/Button.variants.ts +72 -0
  187. package/packages/ui/src/primitives/Button/Button.vue +90 -0
  188. package/packages/ui/src/primitives/Card/Card.props.ts +17 -0
  189. package/packages/ui/src/primitives/Card/Card.spec.md +29 -0
  190. package/packages/ui/src/primitives/Card/Card.types.ts +12 -0
  191. package/packages/ui/src/primitives/Card/Card.variants.ts +27 -0
  192. package/packages/ui/src/primitives/Card/Card.vue +37 -0
  193. package/packages/ui/src/primitives/Checkbox/Checkbox.props.ts +21 -0
  194. package/packages/ui/src/primitives/Checkbox/Checkbox.spec.md +51 -0
  195. package/packages/ui/src/primitives/Checkbox/Checkbox.types.ts +4 -0
  196. package/packages/ui/src/primitives/Checkbox/Checkbox.variants.ts +34 -0
  197. package/packages/ui/src/primitives/Checkbox/Checkbox.vue +92 -0
  198. package/packages/ui/src/primitives/Dialog/Dialog.props.ts +29 -0
  199. package/packages/ui/src/primitives/Dialog/Dialog.spec.md +52 -0
  200. package/packages/ui/src/primitives/Dialog/Dialog.types.ts +3 -0
  201. package/packages/ui/src/primitives/Dialog/Dialog.variants.ts +27 -0
  202. package/packages/ui/src/primitives/Dialog/Dialog.vue +78 -0
  203. package/packages/ui/src/primitives/Drawer/Drawer.props.ts +33 -0
  204. package/packages/ui/src/primitives/Drawer/Drawer.spec.md +50 -0
  205. package/packages/ui/src/primitives/Drawer/Drawer.types.ts +5 -0
  206. package/packages/ui/src/primitives/Drawer/Drawer.variants.ts +35 -0
  207. package/packages/ui/src/primitives/Drawer/Drawer.vue +88 -0
  208. package/packages/ui/src/primitives/FieldMessage/FieldMessage.props.ts +17 -0
  209. package/packages/ui/src/primitives/FieldMessage/FieldMessage.spec.md +35 -0
  210. package/packages/ui/src/primitives/FieldMessage/FieldMessage.types.ts +5 -0
  211. package/packages/ui/src/primitives/FieldMessage/FieldMessage.variants.ts +14 -0
  212. package/packages/ui/src/primitives/FieldMessage/FieldMessage.vue +40 -0
  213. package/packages/ui/src/primitives/FileInput/FileInput.props.ts +41 -0
  214. package/packages/ui/src/primitives/FileInput/FileInput.types.ts +6 -0
  215. package/packages/ui/src/primitives/FileInput/FileInput.variants.ts +46 -0
  216. package/packages/ui/src/primitives/FileInput/FileInput.vue +163 -0
  217. package/packages/ui/src/primitives/Input/Input.props.ts +29 -0
  218. package/packages/ui/src/primitives/Input/Input.spec.md +79 -0
  219. package/packages/ui/src/primitives/Input/Input.types.ts +13 -0
  220. package/packages/ui/src/primitives/Input/Input.variants.ts +54 -0
  221. package/packages/ui/src/primitives/Input/Input.vue +99 -0
  222. package/packages/ui/src/primitives/Label/Label.props.ts +25 -0
  223. package/packages/ui/src/primitives/Label/Label.spec.md +31 -0
  224. package/packages/ui/src/primitives/Label/Label.types.ts +3 -0
  225. package/packages/ui/src/primitives/Label/Label.variants.ts +17 -0
  226. package/packages/ui/src/primitives/Label/Label.vue +38 -0
  227. package/packages/ui/src/primitives/Menu/Menu.props.ts +17 -0
  228. package/packages/ui/src/primitives/Menu/Menu.spec.md +38 -0
  229. package/packages/ui/src/primitives/Menu/Menu.types.ts +10 -0
  230. package/packages/ui/src/primitives/Menu/Menu.variants.ts +10 -0
  231. package/packages/ui/src/primitives/Menu/Menu.vue +57 -0
  232. package/packages/ui/src/primitives/Popover/Popover.props.ts +25 -0
  233. package/packages/ui/src/primitives/Popover/Popover.spec.md +49 -0
  234. package/packages/ui/src/primitives/Popover/Popover.types.ts +3 -0
  235. package/packages/ui/src/primitives/Popover/Popover.variants.ts +18 -0
  236. package/packages/ui/src/primitives/Popover/Popover.vue +74 -0
  237. package/packages/ui/src/primitives/RadioGroup/RadioGroup.props.ts +29 -0
  238. package/packages/ui/src/primitives/RadioGroup/RadioGroup.spec.md +50 -0
  239. package/packages/ui/src/primitives/RadioGroup/RadioGroup.types.ts +12 -0
  240. package/packages/ui/src/primitives/RadioGroup/RadioGroup.variants.ts +48 -0
  241. package/packages/ui/src/primitives/RadioGroup/RadioGroup.vue +87 -0
  242. package/packages/ui/src/primitives/Separator/Separator.props.ts +9 -0
  243. package/packages/ui/src/primitives/Separator/Separator.spec.md +15 -0
  244. package/packages/ui/src/primitives/Separator/Separator.types.ts +3 -0
  245. package/packages/ui/src/primitives/Separator/Separator.variants.ts +8 -0
  246. package/packages/ui/src/primitives/Separator/Separator.vue +23 -0
  247. package/packages/ui/src/primitives/Skeleton/Skeleton.props.ts +21 -0
  248. package/packages/ui/src/primitives/Skeleton/Skeleton.spec.md +18 -0
  249. package/packages/ui/src/primitives/Skeleton/Skeleton.types.ts +5 -0
  250. package/packages/ui/src/primitives/Skeleton/Skeleton.variants.ts +18 -0
  251. package/packages/ui/src/primitives/Skeleton/Skeleton.vue +37 -0
  252. package/packages/ui/src/primitives/Spinner/Spinner.props.ts +13 -0
  253. package/packages/ui/src/primitives/Spinner/Spinner.spec.md +16 -0
  254. package/packages/ui/src/primitives/Spinner/Spinner.types.ts +5 -0
  255. package/packages/ui/src/primitives/Spinner/Spinner.variants.ts +15 -0
  256. package/packages/ui/src/primitives/Spinner/Spinner.vue +33 -0
  257. package/packages/ui/src/primitives/SplitButton/SplitButton.vue +108 -0
  258. package/packages/ui/src/primitives/Switch/Switch.props.ts +21 -0
  259. package/packages/ui/src/primitives/Switch/Switch.spec.md +49 -0
  260. package/packages/ui/src/primitives/Switch/Switch.types.ts +3 -0
  261. package/packages/ui/src/primitives/Switch/Switch.variants.ts +34 -0
  262. package/packages/ui/src/primitives/Switch/Switch.vue +71 -0
  263. package/packages/ui/src/primitives/Tabs/Tabs.props.ts +25 -0
  264. package/packages/ui/src/primitives/Tabs/Tabs.spec.md +48 -0
  265. package/packages/ui/src/primitives/Tabs/Tabs.types.ts +11 -0
  266. package/packages/ui/src/primitives/Tabs/Tabs.variants.ts +28 -0
  267. package/packages/ui/src/primitives/Tabs/Tabs.vue +59 -0
  268. package/packages/ui/src/primitives/Textarea/Textarea.props.ts +33 -0
  269. package/packages/ui/src/primitives/Textarea/Textarea.spec.md +59 -0
  270. package/packages/ui/src/primitives/Textarea/Textarea.types.ts +5 -0
  271. package/packages/ui/src/primitives/Textarea/Textarea.variants.ts +27 -0
  272. package/packages/ui/src/primitives/Textarea/Textarea.vue +74 -0
  273. package/packages/ui/src/primitives/Tooltip/Tooltip.props.ts +21 -0
  274. package/packages/ui/src/primitives/Tooltip/Tooltip.spec.md +45 -0
  275. package/packages/ui/src/primitives/Tooltip/Tooltip.types.ts +3 -0
  276. package/packages/ui/src/primitives/Tooltip/Tooltip.variants.ts +4 -0
  277. package/packages/ui/src/primitives/Tooltip/Tooltip.vue +31 -0
  278. package/packages/ui/src/primitives/TreeView/TreeView.types.ts +10 -0
  279. package/packages/ui/src/primitives/TreeView/TreeView.vue +113 -0
  280. package/packages/ui/src/primitives/TreeView/TreeViewNode.vue +190 -0
  281. package/packages/ui/src/primitives/index.ts +29 -0
  282. package/packages/ui/src/style.css +7 -0
  283. package/packages/ui/src/style.js +1 -0
  284. package/packages/ui/src/themes/armour.css +147 -0
  285. package/packages/ui/src/themes/aurora.css +147 -0
  286. package/packages/ui/src/themes/citrine-ash.css +147 -0
  287. package/packages/ui/src/themes/concord.css +147 -0
  288. package/packages/ui/src/themes/garnet-honey.css +147 -0
  289. package/packages/ui/src/themes/harbor-rose.css +147 -0
  290. package/packages/ui/src/themes/ledger.css +147 -0
  291. package/packages/ui/src/themes/neon-noir.css +74 -0
  292. package/packages/ui/src/themes/obsidian-iris.css +147 -0
  293. package/packages/ui/src/themes/pixpax.css +147 -0
  294. package/packages/ui/src/themes/print.css +147 -0
  295. package/packages/ui/src/themes/prism.css +147 -0
  296. package/packages/ui/src/themes/proof.css +145 -0
  297. package/packages/ui/src/themes/semanticThemeContract.js +2256 -0
  298. package/packages/ui/src/themes/spruce-ink.css +147 -0
  299. package/packages/ui/src/themes/sunset.css +147 -0
  300. package/packages/ui/tailwind.config.js +64 -0
  301. package/packages/ui/vite.config.js +35 -0
  302. package/packages/ui/vite.config.js.timestamp-1780697224943-89fbc929987bc.mjs +38 -0
  303. package/packages/utils/CHANGELOG.md +111 -0
  304. package/packages/utils/README.md +3 -0
  305. package/packages/utils/package.json +46 -0
  306. package/packages/utils/src/index.test.js +39 -0
  307. package/packages/utils/src/index.ts +289 -0
  308. package/packages/utils/tsconfig.build.json +12 -0
  309. package/packages/utils/vite.config.js +28 -0
  310. package/pnpm-workspace.yaml +8 -0
  311. package/scripts/vite/package-lib-config.ts +59 -0
  312. package/tsconfig.json +24 -0
  313. package/tsconfig.node.json +9 -0
@@ -0,0 +1,838 @@
1
+ import { dirname, resolve } from "node:path";
2
+ import { fileURLToPath, pathToFileURL } from "node:url";
3
+ import { describe, expect, it } from "vitest";
4
+ import { createIdentity } from "@ternent/identity";
5
+ import { recipientFromIdentity } from "@ternent/armour";
6
+ import { deriveCommitId as deriveProtocolCommitId } from "@ternent/concord-protocol";
7
+ import {
8
+ createLedger,
9
+ type LedgerPersistenceSnapshot,
10
+ type LedgerStorageAdapter,
11
+ } from "../src/index.ts";
12
+
13
+ const testDir = dirname(fileURLToPath(import.meta.url));
14
+ const packageDir = resolve(testDir, "..");
15
+
16
+ function createClock(values: string[]) {
17
+ let index = 0;
18
+ return () => values[Math.min(index++, values.length - 1)];
19
+ }
20
+
21
+ function createMemoryStorage(): LedgerStorageAdapter & {
22
+ snapshot: LedgerPersistenceSnapshot | null;
23
+ } {
24
+ return {
25
+ name: "memory",
26
+ snapshot: null,
27
+ async load() {
28
+ return this.snapshot;
29
+ },
30
+ async save(snapshot) {
31
+ this.snapshot = structuredClone(snapshot);
32
+ },
33
+ async clear() {
34
+ this.snapshot = null;
35
+ },
36
+ };
37
+ }
38
+
39
+ describe("@ternent/ledger", () => {
40
+ it("creates deterministic plain entry history across fresh instances", async () => {
41
+ const identity = await createIdentity("2026-03-18T10:00:00.000Z");
42
+ const createProjection = () => ({ ids: [] as string[] });
43
+
44
+ const makeLedger = () =>
45
+ createLedger({
46
+ identity: {
47
+ signer: { identity },
48
+ authorResolver: () => "did:alice",
49
+ },
50
+ initialProjection: createProjection(),
51
+ projector: (projection, entry) => ({
52
+ ids: [...projection.ids, String((entry.payload as { data: { id: string } }).data.id)],
53
+ }),
54
+ now: createClock([
55
+ "2026-03-18T10:00:00.000Z",
56
+ "2026-03-18T10:00:01.000Z",
57
+ "2026-03-18T10:00:02.000Z",
58
+ ]),
59
+ });
60
+
61
+ const ledgerA = await makeLedger();
62
+ await ledgerA.create();
63
+ await ledgerA.append({
64
+ kind: "todo.item.created",
65
+ payload: { id: "todo-1" },
66
+ });
67
+ const committedA = await ledgerA.commit();
68
+ expect(await ledgerA.replay()).toEqual({ ids: ["todo-1"] });
69
+ const exportedA = await ledgerA.export();
70
+
71
+ const ledgerB = await makeLedger();
72
+ await ledgerB.create();
73
+ await ledgerB.append({
74
+ kind: "todo.item.created",
75
+ payload: { id: "todo-1" },
76
+ });
77
+ await ledgerB.commit();
78
+ expect(await ledgerB.replay()).toEqual({ ids: ["todo-1"] });
79
+ const exportedB = await ledgerB.export();
80
+
81
+ expect(committedA.commit.seal.signature).toEqual(expect.any(String));
82
+ expect(
83
+ await deriveProtocolCommitId({
84
+ parent: committedA.commit.parentCommitId,
85
+ timestamp: committedA.commit.committedAt,
86
+ metadata: committedA.commit.metadata,
87
+ entries: committedA.commit.entryIds,
88
+ signature: null,
89
+ }),
90
+ ).toBe(committedA.commit.commitId);
91
+ expect(exportedB).toEqual(exportedA);
92
+ });
93
+
94
+ it("keeps staged entries out of committed history until an explicit commit and replays them last", async () => {
95
+ const identity = await createIdentity("2026-03-18T10:03:00.000Z");
96
+ const ledger = await createLedger({
97
+ identity: {
98
+ signer: { identity },
99
+ authorResolver: () => "did:stage",
100
+ },
101
+ initialProjection: [] as string[],
102
+ projector: (projection, entry) => [
103
+ ...projection,
104
+ String((entry.payload as { data: { id: string } }).data.id),
105
+ ],
106
+ now: createClock([
107
+ "2026-03-18T10:03:00.000Z",
108
+ "2026-03-18T10:03:01.000Z",
109
+ "2026-03-18T10:03:02.000Z",
110
+ "2026-03-18T10:03:03.000Z",
111
+ "2026-03-18T10:03:04.000Z",
112
+ ]),
113
+ });
114
+
115
+ await ledger.create();
116
+ await ledger.append({
117
+ kind: "todo.item.created",
118
+ payload: { id: "committed-1" },
119
+ });
120
+ await ledger.commit();
121
+ await ledger.append({
122
+ kind: "todo.item.created",
123
+ payload: { id: "staged-2" },
124
+ });
125
+
126
+ const exported = await ledger.export();
127
+ expect(Object.keys(exported.entries)).toHaveLength(1);
128
+ expect(ledger.getState().staged).toHaveLength(1);
129
+ expect(await ledger.replay()).toEqual(["committed-1", "staged-2"]);
130
+ });
131
+
132
+ it("persists staged entries before an explicit commit", async () => {
133
+ const identity = await createIdentity("2026-03-18T10:04:00.000Z");
134
+ const storage = createMemoryStorage();
135
+ const ledger = await createLedger({
136
+ identity: {
137
+ signer: { identity },
138
+ authorResolver: () => "did:local",
139
+ },
140
+ initialProjection: [] as string[],
141
+ projector: (projection, entry) => [
142
+ ...projection,
143
+ String((entry.payload as { data: { id: string } }).data.id),
144
+ ],
145
+ storage,
146
+ now: createClock([
147
+ "2026-03-18T10:04:00.000Z",
148
+ "2026-03-18T10:04:01.000Z",
149
+ "2026-03-18T10:04:02.000Z",
150
+ ]),
151
+ });
152
+
153
+ await ledger.create();
154
+ expect(storage.snapshot?.staged).toEqual([]);
155
+
156
+ await ledger.append({
157
+ kind: "todo.item.created",
158
+ payload: { id: "local-only" },
159
+ });
160
+
161
+ expect(ledger.getState().staged).toHaveLength(1);
162
+ expect(storage.snapshot?.staged).toHaveLength(1);
163
+ expect(storage.snapshot?.staged[0]).toMatchObject({
164
+ kind: "todo.item.created",
165
+ });
166
+ expect(storage.snapshot?.container?.entries).toEqual({});
167
+ });
168
+
169
+ it("reloads persisted staged entries from storage", async () => {
170
+ const identity = await createIdentity("2026-03-18T10:04:00.000Z");
171
+ const storage = createMemoryStorage();
172
+
173
+ const first = await createLedger({
174
+ identity: {
175
+ signer: { identity },
176
+ authorResolver: () => "did:local",
177
+ },
178
+ initialProjection: [] as string[],
179
+ projector: (projection, entry) => [
180
+ ...projection,
181
+ String((entry.payload as { data: { id: string } }).data.id),
182
+ ],
183
+ storage,
184
+ now: createClock([
185
+ "2026-03-18T10:04:00.000Z",
186
+ "2026-03-18T10:04:01.000Z",
187
+ "2026-03-18T10:04:02.000Z",
188
+ ]),
189
+ });
190
+
191
+ await first.create();
192
+ await first.append({
193
+ kind: "todo.item.created",
194
+ payload: { id: "staged-only" },
195
+ });
196
+
197
+ const restored = await createLedger({
198
+ identity: {
199
+ signer: { identity },
200
+ authorResolver: () => "did:local",
201
+ },
202
+ initialProjection: [] as string[],
203
+ projector: (projection, entry) => [
204
+ ...projection,
205
+ String((entry.payload as { data: { id: string } }).data.id),
206
+ ],
207
+ storage,
208
+ now: createClock([
209
+ "2026-03-18T10:04:03.000Z",
210
+ "2026-03-18T10:04:04.000Z",
211
+ "2026-03-18T10:04:05.000Z",
212
+ ]),
213
+ });
214
+
215
+ expect(await restored.loadFromStorage()).toBe(true);
216
+ expect(restored.getState().staged).toHaveLength(1);
217
+ expect(restored.getState().projection).toEqual(["staged-only"]);
218
+ expect(restored.getState().container?.entries).toEqual({});
219
+ });
220
+
221
+ it("replays encrypted entries as encrypted or decrypted based on capability", async () => {
222
+ const identity = await createIdentity("2026-03-18T10:05:00.000Z");
223
+ const recipient = await recipientFromIdentity(identity);
224
+
225
+ const makeLedger = (withDecryptor: boolean) =>
226
+ createLedger({
227
+ identity: {
228
+ signer: { identity },
229
+ authorResolver: () => "did:bob",
230
+ decryptor: withDecryptor ? { identity } : undefined,
231
+ },
232
+ initialProjection: [] as string[],
233
+ projector: (projection, entry) => [
234
+ ...projection,
235
+ entry.payload.type === "decrypted"
236
+ ? String((entry.payload as { data: { text: string } }).data.text)
237
+ : entry.payload.type,
238
+ ],
239
+ now: createClock([
240
+ "2026-03-18T10:05:00.000Z",
241
+ "2026-03-18T10:05:01.000Z",
242
+ "2026-03-18T10:05:02.000Z",
243
+ ]),
244
+ });
245
+
246
+ const encryptedLedger = await makeLedger(false);
247
+ await encryptedLedger.create();
248
+ await encryptedLedger.append({
249
+ kind: "journal.entry.created",
250
+ payload: { text: "secret note" },
251
+ protection: {
252
+ type: "recipients",
253
+ recipients: [recipient],
254
+ encoding: "armor",
255
+ },
256
+ });
257
+ await encryptedLedger.commit();
258
+
259
+ expect(await encryptedLedger.replay()).toEqual(["encrypted"]);
260
+
261
+ const decryptingLedger = await makeLedger(true);
262
+ await decryptingLedger.import(await encryptedLedger.export());
263
+ expect(await decryptingLedger.replay()).toEqual(["secret note"]);
264
+ });
265
+
266
+ it("keeps replay moving when a decryptor is present but cannot decrypt an entry", async () => {
267
+ const alice = await createIdentity("2026-03-18T10:06:00.000Z");
268
+ const bob = await createIdentity("2026-03-18T10:06:30.000Z");
269
+ const recipient = await recipientFromIdentity(alice);
270
+
271
+ const writer = await createLedger({
272
+ identity: {
273
+ signer: { identity: alice },
274
+ authorResolver: () => "did:alice",
275
+ decryptor: { identity: alice },
276
+ },
277
+ initialProjection: [] as string[],
278
+ projector: (projection, entry) => [...projection, entry.payload.type],
279
+ now: createClock([
280
+ "2026-03-18T10:06:00.000Z",
281
+ "2026-03-18T10:06:01.000Z",
282
+ "2026-03-18T10:06:02.000Z",
283
+ ]),
284
+ });
285
+
286
+ await writer.create();
287
+ await writer.append({
288
+ kind: "journal.entry.created",
289
+ payload: { text: "alice only" },
290
+ protection: {
291
+ type: "recipients",
292
+ recipients: [recipient],
293
+ encoding: "armor",
294
+ },
295
+ });
296
+ await writer.commit();
297
+
298
+ const reader = await createLedger({
299
+ identity: {
300
+ signer: { identity: bob },
301
+ authorResolver: () => "did:bob",
302
+ decryptor: { identity: bob },
303
+ },
304
+ initialProjection: [] as string[],
305
+ projector: (projection, entry) => [...projection, entry.payload.type],
306
+ now: createClock([
307
+ "2026-03-18T10:06:30.000Z",
308
+ "2026-03-18T10:06:31.000Z",
309
+ "2026-03-18T10:06:32.000Z",
310
+ ]),
311
+ });
312
+
313
+ await reader.import(await writer.export());
314
+ expect(await reader.replay()).toEqual(["encrypted"]);
315
+ });
316
+
317
+ it("verifies encrypted records deterministically even when ciphertext differs", async () => {
318
+ const identity = await createIdentity("2026-03-18T10:10:00.000Z");
319
+ const recipient = await recipientFromIdentity(identity);
320
+
321
+ const makeLedger = () =>
322
+ createLedger({
323
+ identity: {
324
+ signer: { identity },
325
+ authorResolver: () => "did:carol",
326
+ decryptor: { identity },
327
+ },
328
+ initialProjection: [] as string[],
329
+ projector: (projection, entry) => [...projection, entry.entryId],
330
+ now: createClock([
331
+ "2026-03-18T10:10:00.000Z",
332
+ "2026-03-18T10:10:01.000Z",
333
+ "2026-03-18T10:10:02.000Z",
334
+ ]),
335
+ });
336
+
337
+ const ledgerA = await makeLedger();
338
+ const ledgerB = await makeLedger();
339
+
340
+ await ledgerA.create();
341
+ await ledgerB.create();
342
+
343
+ const entryA = await ledgerA.append({
344
+ kind: "secret.created",
345
+ payload: { text: "same secret" },
346
+ protection: {
347
+ type: "recipients",
348
+ recipients: [recipient],
349
+ encoding: "armor",
350
+ },
351
+ });
352
+ const entryB = await ledgerB.append({
353
+ kind: "secret.created",
354
+ payload: { text: "same secret" },
355
+ protection: {
356
+ type: "recipients",
357
+ recipients: [recipient],
358
+ encoding: "armor",
359
+ },
360
+ });
361
+
362
+ expect(entryA.entry.payload.type).toBe("encrypted");
363
+ expect(entryB.entry.payload.type).toBe("encrypted");
364
+ expect(entryA.entry.payload.data).not.toBe(entryB.entry.payload.data);
365
+
366
+ await ledgerA.commit();
367
+ await ledgerB.commit();
368
+
369
+ expect((await ledgerA.verify()).valid).toBe(true);
370
+ expect((await ledgerB.verify()).valid).toBe(true);
371
+ expect(await ledgerA.replay()).toHaveLength(1);
372
+ expect(await ledgerB.replay()).toHaveLength(1);
373
+ });
374
+
375
+ it("reports invalid ids for tampered commits and entries", async () => {
376
+ const identity = await createIdentity("2026-03-18T10:15:00.000Z");
377
+ const ledger = await createLedger({
378
+ identity: {
379
+ signer: { identity },
380
+ authorResolver: () => "did:dana",
381
+ },
382
+ initialProjection: [] as string[],
383
+ projector: (projection, entry) => [...projection, entry.entryId],
384
+ replayPolicy: {
385
+ verify: false,
386
+ },
387
+ now: createClock([
388
+ "2026-03-18T10:15:00.000Z",
389
+ "2026-03-18T10:15:01.000Z",
390
+ "2026-03-18T10:15:02.000Z",
391
+ ]),
392
+ });
393
+
394
+ await ledger.create();
395
+ const appended = await ledger.append({
396
+ kind: "todo.item.created",
397
+ payload: { id: "todo-2" },
398
+ });
399
+ const committed = await ledger.commit();
400
+ const container = await ledger.export();
401
+
402
+ const tampered = structuredClone(container);
403
+ tampered.commits[committed.commit.commitId] = {
404
+ ...tampered.commits[committed.commit.commitId],
405
+ parentCommitId: "missing-parent",
406
+ };
407
+ tampered.entries[appended.entry.entryId] = {
408
+ ...tampered.entries[appended.entry.entryId],
409
+ payload: {
410
+ type: "plain",
411
+ data: { id: "tampered" },
412
+ },
413
+ };
414
+
415
+ const loaded = await createLedger({
416
+ identity: {
417
+ signer: { identity },
418
+ authorResolver: () => "did:dana",
419
+ },
420
+ initialProjection: [] as string[],
421
+ projector: (projection, entry) => [...projection, entry.entryId],
422
+ replayPolicy: {
423
+ verify: false,
424
+ },
425
+ });
426
+ await loaded.load(tampered);
427
+
428
+ const verification = await loaded.verify();
429
+ expect(verification.valid).toBe(false);
430
+ expect(verification.committedHistoryValid).toBe(false);
431
+ expect(verification.commitChainValid).toBe(false);
432
+ expect(verification.commitProofsValid).toBe(false);
433
+ expect(verification.entryProofsValid).toBe(false);
434
+ expect(verification.invalidCommitIds).toContain(committed.commit.commitId);
435
+ expect(verification.invalidEntryIds).toContain(appended.entry.entryId);
436
+ });
437
+
438
+ it("fails verification when committed entry order is tampered", async () => {
439
+ const identity = await createIdentity("2026-03-18T10:16:00.000Z");
440
+ const ledger = await createLedger({
441
+ identity: {
442
+ signer: { identity },
443
+ authorResolver: () => "did:order",
444
+ },
445
+ initialProjection: [] as string[],
446
+ projector: (projection, entry) => [...projection, entry.entryId],
447
+ replayPolicy: {
448
+ verify: false,
449
+ },
450
+ now: createClock([
451
+ "2026-03-18T10:16:00.000Z",
452
+ "2026-03-18T10:16:01.000Z",
453
+ "2026-03-18T10:16:02.000Z",
454
+ "2026-03-18T10:16:03.000Z",
455
+ ]),
456
+ });
457
+
458
+ await ledger.create();
459
+ await ledger.append({
460
+ kind: "todo.item.created",
461
+ payload: { id: "todo-ordered-1" },
462
+ });
463
+ await ledger.append({
464
+ kind: "todo.item.created",
465
+ payload: { id: "todo-ordered-2" },
466
+ });
467
+ const committed = await ledger.commit();
468
+
469
+ const tampered = structuredClone(await ledger.export());
470
+ tampered.commits[committed.commit.commitId] = {
471
+ ...tampered.commits[committed.commit.commitId],
472
+ entryIds: [...tampered.commits[committed.commit.commitId].entryIds].reverse(),
473
+ };
474
+
475
+ const loaded = await createLedger({
476
+ identity: {
477
+ signer: { identity },
478
+ authorResolver: () => "did:order",
479
+ },
480
+ initialProjection: [] as string[],
481
+ projector: (projection, entry) => [...projection, entry.entryId],
482
+ replayPolicy: {
483
+ verify: false,
484
+ },
485
+ });
486
+
487
+ await loaded.load(tampered);
488
+ const verification = await loaded.verify();
489
+
490
+ expect(verification.valid).toBe(false);
491
+ expect(verification.committedHistoryValid).toBe(false);
492
+ expect(verification.commitChainValid).toBe(false);
493
+ expect(verification.invalidCommitIds).toContain(committed.commit.commitId);
494
+ });
495
+
496
+ it("fails verification when a committed entry id is tampered", async () => {
497
+ const identity = await createIdentity("2026-03-18T10:17:00.000Z");
498
+ const ledger = await createLedger({
499
+ identity: {
500
+ signer: { identity },
501
+ authorResolver: () => "did:entry-id",
502
+ },
503
+ initialProjection: [] as string[],
504
+ projector: (projection, entry) => [...projection, entry.entryId],
505
+ replayPolicy: {
506
+ verify: false,
507
+ },
508
+ now: createClock([
509
+ "2026-03-18T10:17:00.000Z",
510
+ "2026-03-18T10:17:01.000Z",
511
+ "2026-03-18T10:17:02.000Z",
512
+ ]),
513
+ });
514
+
515
+ await ledger.create();
516
+ const appended = await ledger.append({
517
+ kind: "todo.item.created",
518
+ payload: { id: "todo-entry-id" },
519
+ });
520
+ await ledger.commit();
521
+
522
+ const tampered = structuredClone(await ledger.export());
523
+ const tamperedEntry = {
524
+ ...tampered.entries[appended.entry.entryId],
525
+ entryId: "entry_tampered",
526
+ };
527
+ delete tampered.entries[appended.entry.entryId];
528
+ tampered.entries.entry_tampered = tamperedEntry;
529
+
530
+ const loaded = await createLedger({
531
+ identity: {
532
+ signer: { identity },
533
+ authorResolver: () => "did:entry-id",
534
+ },
535
+ initialProjection: [] as string[],
536
+ projector: (projection, entry) => [...projection, entry.entryId],
537
+ replayPolicy: {
538
+ verify: false,
539
+ },
540
+ });
541
+
542
+ await loaded.load(tampered);
543
+ const verification = await loaded.verify();
544
+
545
+ expect(verification.valid).toBe(false);
546
+ expect(verification.committedHistoryValid).toBe(false);
547
+ expect(verification.entriesValid).toBe(false);
548
+ expect(verification.invalidEntryIds).toContain(appended.entry.entryId);
549
+ expect(verification.invalidEntryIds).toContain("entry_tampered");
550
+ });
551
+
552
+ it("fails verification when an encrypted payload hash is tampered", async () => {
553
+ const identity = await createIdentity("2026-03-18T10:17:30.000Z");
554
+ const recipient = await recipientFromIdentity(identity);
555
+ const ledger = await createLedger({
556
+ identity: {
557
+ signer: { identity },
558
+ authorResolver: () => "did:payload-hash",
559
+ decryptor: { identity },
560
+ },
561
+ initialProjection: [] as string[],
562
+ projector: (projection, entry) => [...projection, entry.entryId],
563
+ replayPolicy: {
564
+ verify: false,
565
+ },
566
+ now: createClock([
567
+ "2026-03-18T10:17:30.000Z",
568
+ "2026-03-18T10:17:31.000Z",
569
+ "2026-03-18T10:17:32.000Z",
570
+ ]),
571
+ });
572
+
573
+ await ledger.create();
574
+ const appended = await ledger.append({
575
+ kind: "secret.created",
576
+ payload: { text: "hash me" },
577
+ protection: {
578
+ type: "recipients",
579
+ recipients: [recipient],
580
+ encoding: "armor",
581
+ },
582
+ });
583
+ await ledger.commit();
584
+
585
+ const tampered = structuredClone(await ledger.export());
586
+ tampered.entries[appended.entry.entryId] = {
587
+ ...tampered.entries[appended.entry.entryId],
588
+ payload: {
589
+ ...(tampered.entries[appended.entry.entryId].payload as {
590
+ type: "encrypted";
591
+ scheme: "age";
592
+ mode: "recipients";
593
+ encoding: "armor" | "binary";
594
+ data: string;
595
+ payloadHash: string;
596
+ }),
597
+ payloadHash: "sha256:tampered",
598
+ },
599
+ };
600
+
601
+ const loaded = await createLedger({
602
+ identity: {
603
+ signer: { identity },
604
+ authorResolver: () => "did:payload-hash",
605
+ decryptor: { identity },
606
+ },
607
+ initialProjection: [] as string[],
608
+ projector: (projection, entry) => [...projection, entry.entryId],
609
+ replayPolicy: {
610
+ verify: false,
611
+ },
612
+ });
613
+
614
+ await loaded.load(tampered);
615
+ const verification = await loaded.verify();
616
+
617
+ expect(verification.valid).toBe(false);
618
+ expect(verification.committedHistoryValid).toBe(false);
619
+ expect(verification.payloadHashesValid).toBe(false);
620
+ expect(verification.invalidEntryIds).toContain(appended.entry.entryId);
621
+ });
622
+
623
+ it("fails verification when commit proof material is tampered even if entry payloads remain intact", async () => {
624
+ const identity = await createIdentity("2026-03-18T10:18:00.000Z");
625
+ const ledger = await createLedger({
626
+ identity: {
627
+ signer: { identity },
628
+ authorResolver: () => "did:proofs",
629
+ },
630
+ initialProjection: [] as string[],
631
+ projector: (projection, entry) => [...projection, entry.entryId],
632
+ replayPolicy: {
633
+ verify: false,
634
+ },
635
+ now: createClock([
636
+ "2026-03-18T10:18:00.000Z",
637
+ "2026-03-18T10:18:01.000Z",
638
+ "2026-03-18T10:18:02.000Z",
639
+ ]),
640
+ });
641
+
642
+ await ledger.create();
643
+ await ledger.append({
644
+ kind: "todo.item.created",
645
+ payload: { id: "todo-proof" },
646
+ });
647
+ const committed = await ledger.commit();
648
+
649
+ const tampered = structuredClone(await ledger.export());
650
+ tampered.commits[committed.commit.commitId] = {
651
+ ...tampered.commits[committed.commit.commitId],
652
+ seal: {
653
+ ...tampered.commits[committed.commit.commitId].seal,
654
+ signature: "tampered-signature",
655
+ },
656
+ };
657
+
658
+ const loaded = await createLedger({
659
+ identity: {
660
+ signer: { identity },
661
+ authorResolver: () => "did:proofs",
662
+ },
663
+ initialProjection: [] as string[],
664
+ projector: (projection, entry) => [...projection, entry.entryId],
665
+ replayPolicy: {
666
+ verify: false,
667
+ },
668
+ });
669
+
670
+ await loaded.load(tampered);
671
+ const verification = await loaded.verify();
672
+
673
+ expect(verification.valid).toBe(false);
674
+ expect(verification.committedHistoryValid).toBe(false);
675
+ expect(verification.commitChainValid).toBe(true);
676
+ expect(verification.commitProofsValid).toBe(false);
677
+ expect(verification.invalidCommitIds).toContain(committed.commit.commitId);
678
+ });
679
+
680
+ it("treats later committed history as globally invalid when an earlier committed entry is corrupted", async () => {
681
+ const identity = await createIdentity("2026-03-18T10:19:00.000Z");
682
+ const ledger = await createLedger({
683
+ identity: {
684
+ signer: { identity },
685
+ authorResolver: () => "did:cascade",
686
+ },
687
+ initialProjection: [] as string[],
688
+ projector: (projection, entry) => [...projection, entry.entryId],
689
+ replayPolicy: {
690
+ verify: false,
691
+ },
692
+ now: createClock([
693
+ "2026-03-18T10:19:00.000Z",
694
+ "2026-03-18T10:19:01.000Z",
695
+ "2026-03-18T10:19:02.000Z",
696
+ "2026-03-18T10:19:03.000Z",
697
+ "2026-03-18T10:19:04.000Z",
698
+ ]),
699
+ });
700
+
701
+ await ledger.create();
702
+ const first = await ledger.append({
703
+ kind: "todo.item.created",
704
+ payload: { id: "todo-cascade-1" },
705
+ });
706
+ await ledger.commit({
707
+ metadata: {
708
+ message: "first",
709
+ },
710
+ });
711
+ await ledger.append({
712
+ kind: "todo.item.created",
713
+ payload: { id: "todo-cascade-2" },
714
+ });
715
+ await ledger.commit({
716
+ metadata: {
717
+ message: "second",
718
+ },
719
+ });
720
+
721
+ const tampered = structuredClone(await ledger.export());
722
+ tampered.entries[first.entry.entryId] = {
723
+ ...tampered.entries[first.entry.entryId],
724
+ payload: {
725
+ type: "plain",
726
+ data: { id: "tampered-cascade" },
727
+ },
728
+ };
729
+
730
+ const loaded = await createLedger({
731
+ identity: {
732
+ signer: { identity },
733
+ authorResolver: () => "did:cascade",
734
+ },
735
+ initialProjection: [] as string[],
736
+ projector: (projection, entry) => [...projection, entry.entryId],
737
+ replayPolicy: {
738
+ verify: false,
739
+ },
740
+ });
741
+
742
+ await loaded.load(tampered);
743
+ const verification = await loaded.verify();
744
+
745
+ expect(verification.valid).toBe(false);
746
+ expect(verification.committedHistoryValid).toBe(false);
747
+ await expect(loaded.replay({ verify: true })).rejects.toThrow("Ledger verification failed.");
748
+ });
749
+
750
+ it("round trips persistence without storing projection", async () => {
751
+ const identity = await createIdentity("2026-03-18T10:20:00.000Z");
752
+ const storage = createMemoryStorage();
753
+
754
+ const ledger = await createLedger({
755
+ identity: {
756
+ signer: { identity },
757
+ authorResolver: () => "did:erin",
758
+ },
759
+ initialProjection: { ids: [] as string[] },
760
+ projector: (projection, entry) => ({
761
+ ids: [...projection.ids, entry.entryId],
762
+ }),
763
+ storage,
764
+ now: createClock([
765
+ "2026-03-18T10:20:00.000Z",
766
+ "2026-03-18T10:20:01.000Z",
767
+ "2026-03-18T10:20:02.000Z",
768
+ ]),
769
+ });
770
+
771
+ await ledger.create();
772
+ const staged = await ledger.append({
773
+ kind: "todo.item.created",
774
+ payload: { id: "persisted" },
775
+ });
776
+ await ledger.commit();
777
+
778
+ expect(storage.snapshot).not.toBeNull();
779
+ expect(storage.snapshot).not.toHaveProperty("projection");
780
+ expect(storage.snapshot?.staged).toEqual([]);
781
+
782
+ const restored = await createLedger({
783
+ identity: {
784
+ signer: { identity },
785
+ authorResolver: () => "did:erin",
786
+ },
787
+ initialProjection: { ids: [] as string[] },
788
+ projector: (projection, entry) => ({
789
+ ids: [...projection.ids, entry.entryId],
790
+ }),
791
+ storage,
792
+ });
793
+
794
+ expect(await restored.loadFromStorage()).toBe(true);
795
+ expect(restored.getState()).not.toHaveProperty("signingKey");
796
+ expect(restored.getState().projection.ids).toEqual([staged.entry.entryId]);
797
+ });
798
+
799
+ it("imports from built ESM output without manual path hacks", async () => {
800
+ const entryUrl = pathToFileURL(resolve(packageDir, "dist/index.js")).href;
801
+ const ledgerModule = await import(entryUrl);
802
+ const identity = await createIdentity("2026-03-18T10:21:00.000Z");
803
+ const recipient = await recipientFromIdentity(identity);
804
+ const ledger = await ledgerModule.createLedger({
805
+ identity: {
806
+ signer: { identity },
807
+ authorResolver: () => "did:built",
808
+ decryptor: { identity },
809
+ },
810
+ initialProjection: [] as string[],
811
+ projector: (projection, entry) => [
812
+ ...projection,
813
+ entry.payload.type === "decrypted"
814
+ ? String((entry.payload as { data: { text: string } }).data.text)
815
+ : entry.payload.type,
816
+ ],
817
+ now: createClock([
818
+ "2026-03-18T10:21:00.000Z",
819
+ "2026-03-18T10:21:01.000Z",
820
+ "2026-03-18T10:21:02.000Z",
821
+ ]),
822
+ });
823
+
824
+ await ledger.create();
825
+ await ledger.append({
826
+ kind: "journal.entry.created",
827
+ payload: { text: "built ledger" },
828
+ protection: {
829
+ type: "recipients",
830
+ recipients: [recipient],
831
+ encoding: "armor",
832
+ },
833
+ });
834
+ await ledger.commit();
835
+
836
+ expect(await ledger.replay()).toEqual(["built ledger"]);
837
+ });
838
+ });