@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,978 @@
1
+ import { dirname, resolve } from "node:path";
2
+ import { fileURLToPath, pathToFileURL } from "node:url";
3
+ import { describe, expect, it } from "vitest";
4
+ import { recipientFromIdentity } from "@ternent/armour";
5
+ import { createIdentity } from "@ternent/identity";
6
+ import {
7
+ createLedger,
8
+ type LedgerContainer,
9
+ type LedgerInstance,
10
+ type LedgerReplayEntry,
11
+ type LedgerStorageAdapter,
12
+ } from "@ternent/ledger";
13
+ import {
14
+ ConcordBoundaryError,
15
+ createConcordApp,
16
+ type ConcordReplayMetadata,
17
+ type ConcordReplayPlugin,
18
+ } from "../src/index.ts";
19
+
20
+ const testDir = dirname(fileURLToPath(import.meta.url));
21
+ const packageDir = resolve(testDir, "..");
22
+
23
+ function createSequenceNow(values: string[]): () => string {
24
+ let index = 0;
25
+ return () => values[Math.min(index++, values.length - 1)];
26
+ }
27
+
28
+ function createMemoryStorage(): LedgerStorageAdapter & {
29
+ snapshot: { container: LedgerContainer | null; staged: unknown[] } | null;
30
+ } {
31
+ return {
32
+ name: "memory",
33
+ snapshot: null,
34
+ async load() {
35
+ return this.snapshot;
36
+ },
37
+ async save(snapshot) {
38
+ this.snapshot = structuredClone(snapshot);
39
+ },
40
+ async clear() {
41
+ this.snapshot = null;
42
+ },
43
+ };
44
+ }
45
+
46
+ function createTodoPlugin(): ConcordReplayPlugin<{
47
+ items: Record<string, { id: string; title: string; completed: boolean }>;
48
+ }> {
49
+ return {
50
+ id: "todo",
51
+ initialState() {
52
+ return { items: {} };
53
+ },
54
+ commands: {
55
+ "todo.create-item": async (_ctx, input: { id: string; title: string }) => ({
56
+ kind: "todo.item.created",
57
+ payload: input,
58
+ }),
59
+ "todo.complete-item": async (_ctx, input: { id: string }) => ({
60
+ kind: "todo.item.completed",
61
+ payload: input,
62
+ }),
63
+ },
64
+ applyEntry(entry, ctx) {
65
+ if (entry.kind === "todo.item.created" && entry.payload.type === "plain") {
66
+ const payload = entry.payload.data as { id: string; title: string };
67
+ ctx.setState((state) => {
68
+ const current = state as {
69
+ items: Record<string, { id: string; title: string; completed: boolean }>;
70
+ };
71
+ return {
72
+ items: {
73
+ ...current.items,
74
+ [payload.id]: {
75
+ id: payload.id,
76
+ title: payload.title,
77
+ completed: false,
78
+ },
79
+ },
80
+ };
81
+ });
82
+ return;
83
+ }
84
+
85
+ if (entry.kind === "todo.item.completed" && entry.payload.type === "plain") {
86
+ const payload = entry.payload.data as { id: string };
87
+ ctx.setState((state) => {
88
+ const current = state as {
89
+ items: Record<string, { id: string; title: string; completed: boolean }>;
90
+ };
91
+ const item = current.items[payload.id];
92
+ if (!item) {
93
+ return current;
94
+ }
95
+
96
+ return {
97
+ items: {
98
+ ...current.items,
99
+ [payload.id]: {
100
+ ...item,
101
+ completed: true,
102
+ },
103
+ },
104
+ };
105
+ });
106
+ }
107
+ },
108
+ };
109
+ }
110
+
111
+ function createAuditPlugin(): ConcordReplayPlugin<{ entryIds: string[] }> {
112
+ return {
113
+ id: "audit",
114
+ initialState() {
115
+ return { entryIds: [] };
116
+ },
117
+ applyEntry(entry, ctx) {
118
+ ctx.setState((state) => ({
119
+ entryIds: [...(state as { entryIds: string[] }).entryIds, entry.entryId],
120
+ }));
121
+ },
122
+ };
123
+ }
124
+
125
+ function createSecretPlugin(): ConcordReplayPlugin<{ values: string[] }> {
126
+ return {
127
+ id: "secret",
128
+ initialState() {
129
+ return { values: [] };
130
+ },
131
+ commands: {
132
+ "secret.write": async (_ctx, input: { text: string; recipients: string[] }) => ({
133
+ kind: "secret.created",
134
+ payload: { text: input.text },
135
+ protection: {
136
+ type: "recipients",
137
+ recipients: input.recipients,
138
+ encoding: "armor",
139
+ },
140
+ }),
141
+ },
142
+ applyEntry(entry, ctx) {
143
+ if (entry.kind !== "secret.created") {
144
+ return;
145
+ }
146
+
147
+ ctx.setState((state) => {
148
+ const current = state as { values: string[] };
149
+ if (entry.payload.type === "decrypted") {
150
+ const payload = entry.payload.data as { text: string };
151
+ return {
152
+ values: [...current.values, payload.text],
153
+ };
154
+ }
155
+
156
+ return {
157
+ values: [...current.values, entry.payload.type],
158
+ };
159
+ });
160
+ },
161
+ };
162
+ }
163
+
164
+ describe("@ternent/concord", () => {
165
+ it("auto-creates on load, persists, and notifies once per completed operation", async () => {
166
+ const identity = await createIdentity("2026-03-18T12:00:00.000Z");
167
+ const storage = createMemoryStorage();
168
+ const app = await createConcordApp({
169
+ identity,
170
+ storage,
171
+ plugins: [createTodoPlugin(), createAuditPlugin()],
172
+ });
173
+
174
+ const notifications: boolean[] = [];
175
+ app.subscribe((nextState) => {
176
+ notifications.push(nextState.ready);
177
+ });
178
+
179
+ await app.load();
180
+
181
+ expect(storage.snapshot).not.toBeNull();
182
+ expect(app.getState().ready).toBe(true);
183
+ expect(app.getState().integrityValid).toBe(true);
184
+ expect(app.getState().stagedCount).toBe(0);
185
+ expect(notifications).toEqual([true]);
186
+ });
187
+
188
+ it("stages multiple commands by default and commits them explicitly", async () => {
189
+ const identity = await createIdentity("2026-03-18T12:00:00.000Z");
190
+ const storage = createMemoryStorage();
191
+ const app = await createConcordApp({
192
+ identity,
193
+ storage,
194
+ plugins: [createTodoPlugin(), createAuditPlugin()],
195
+ });
196
+
197
+ const notifications: number[] = [];
198
+ app.subscribe(() => {
199
+ notifications.push(notifications.length + 1);
200
+ });
201
+
202
+ await app.load();
203
+
204
+ const first = await app.command("todo.create-item", {
205
+ id: "todo-1",
206
+ title: "Buy milk",
207
+ });
208
+ const second = await app.command("todo.create-item", {
209
+ id: "todo-2",
210
+ title: "Buy oats",
211
+ });
212
+
213
+ expect(first.commitId).toBeUndefined();
214
+ expect(first.stagedCount).toBe(1);
215
+ expect(second.commitId).toBeUndefined();
216
+ expect(second.stagedCount).toBe(2);
217
+ expect(notifications).toHaveLength(3);
218
+ expect(app.getState().stagedCount).toBe(2);
219
+ expect(app.getReplayState("todo")).toEqual({
220
+ items: {
221
+ "todo-1": {
222
+ id: "todo-1",
223
+ title: "Buy milk",
224
+ completed: false,
225
+ },
226
+ "todo-2": {
227
+ id: "todo-2",
228
+ title: "Buy oats",
229
+ completed: false,
230
+ },
231
+ },
232
+ });
233
+ expect(Object.keys((await app.exportLedger()).entries)).toHaveLength(0);
234
+
235
+ const commit = await app.commit({
236
+ metadata: {
237
+ message: "Create first todo batch",
238
+ },
239
+ });
240
+
241
+ expect(commit.entryIds).toHaveLength(2);
242
+ expect(app.getState().stagedCount).toBe(0);
243
+ expect(app.getState().integrityValid).toBe(true);
244
+ expect(notifications).toHaveLength(4);
245
+ expect(app.getReplayState<{ entryIds: string[] }>("audit").entryIds).toHaveLength(2);
246
+
247
+ const exported = await app.exportLedger();
248
+ expect(Object.keys(exported.entries)).toHaveLength(2);
249
+ expect(exported.commits[commit.commitId]?.metadata).toEqual({
250
+ message: "Create first todo batch",
251
+ });
252
+ });
253
+
254
+ it("can still auto-commit when explicitly configured, but it is not the default", async () => {
255
+ const identity = await createIdentity("2026-03-18T12:05:00.000Z");
256
+ const storage = createMemoryStorage();
257
+ const ledger = await createLedger<LedgerReplayEntry[]>({
258
+ identity: {
259
+ signer: { identity },
260
+ authorResolver: () => "did:bob",
261
+ },
262
+ initialProjection: [],
263
+ projector: (entries, entry) => [...entries, entry],
264
+ storage,
265
+ autoCommit: false,
266
+ });
267
+
268
+ const app = await createConcordApp({
269
+ identity,
270
+ plugins: [createTodoPlugin()],
271
+ ledger,
272
+ policy: {
273
+ autoCommit: true,
274
+ },
275
+ });
276
+
277
+ await app.load();
278
+ const result = await app.command("todo.create-item", {
279
+ id: "todo-2",
280
+ title: "Commit me",
281
+ });
282
+
283
+ expect(result.commitId).toEqual(expect.any(String));
284
+ expect(result.stagedCount).toBe(0);
285
+ expect(app.getState().integrityValid).toBe(true);
286
+ expect(ledger.getState().staged).toHaveLength(0);
287
+ expect(Object.keys((await app.exportLedger()).entries)).toHaveLength(1);
288
+ });
289
+
290
+ it("uses a single injected clock for command context and internal ledger timing", async () => {
291
+ const identity = await createIdentity("2026-03-18T12:07:00.000Z");
292
+ const now = createSequenceNow([
293
+ "2026-03-18T12:07:00.000Z",
294
+ "2026-03-18T12:07:01.000Z",
295
+ "2026-03-18T12:07:02.000Z",
296
+ "2026-03-18T12:07:03.000Z",
297
+ ]);
298
+ const storage = createMemoryStorage();
299
+ const app = await createConcordApp({
300
+ identity,
301
+ now,
302
+ storage,
303
+ plugins: [
304
+ {
305
+ id: "clock",
306
+ initialState() {
307
+ return { timestamps: [] as string[] };
308
+ },
309
+ commands: {
310
+ "clock.tick": async (ctx) => ({
311
+ kind: "clock.ticked",
312
+ payload: {
313
+ observedAt: ctx.now(),
314
+ actorKeyId: ctx.identity.keyId,
315
+ },
316
+ }),
317
+ },
318
+ applyEntry(entry, ctx) {
319
+ if (entry.kind !== "clock.ticked" || entry.payload.type !== "plain") {
320
+ return;
321
+ }
322
+
323
+ ctx.setState((state) => ({
324
+ timestamps: [
325
+ ...(state as { timestamps: string[] }).timestamps,
326
+ String(
327
+ (
328
+ entry.payload.data as {
329
+ observedAt: string;
330
+ actorKeyId: string;
331
+ }
332
+ ).observedAt,
333
+ ),
334
+ ],
335
+ }));
336
+ },
337
+ },
338
+ ],
339
+ });
340
+
341
+ await app.load();
342
+ const staged = await app.command("clock.tick", undefined);
343
+ const commit = await app.commit({
344
+ metadata: {
345
+ message: "Tick clock",
346
+ },
347
+ });
348
+
349
+ const exported = await app.exportLedger();
350
+ const [entryId] = Object.keys(exported.entries);
351
+
352
+ expect(staged.stagedCount).toBe(1);
353
+ expect(commit.entryIds).toEqual([entryId]);
354
+ expect(app.getReplayState<{ timestamps: string[] }>("clock").timestamps).toEqual([
355
+ "2026-03-18T12:07:01.000Z",
356
+ ]);
357
+ expect(exported.entries[entryId]?.authoredAt).toBe("2026-03-18T12:07:02.000Z");
358
+ expect(exported.commits[commit.commitId]?.committedAt).toBe("2026-03-18T12:07:03.000Z");
359
+ });
360
+
361
+ it("create replaces currently loaded replay state in memory", async () => {
362
+ const identity = await createIdentity("2026-03-18T12:10:00.000Z");
363
+ const storage = createMemoryStorage();
364
+ const app = await createConcordApp({
365
+ identity,
366
+ storage,
367
+ plugins: [createTodoPlugin()],
368
+ });
369
+
370
+ await app.load();
371
+ await app.command("todo.create-item", {
372
+ id: "todo-3",
373
+ title: "Reset me",
374
+ });
375
+
376
+ expect(
377
+ app.getReplayState<{ items: Record<string, { title: string }> }>("todo").items["todo-3"]
378
+ ?.title,
379
+ ).toBe("Reset me");
380
+
381
+ await app.create();
382
+
383
+ expect(app.getReplayState<{ items: Record<string, unknown> }>("todo").items).toEqual({});
384
+ expect(app.getState().integrityValid).toBe(true);
385
+ });
386
+
387
+ it("lets replay plugins buffer external materialization and publish in endReplay", async () => {
388
+ const identity = await createIdentity("2026-03-18T12:15:00.000Z");
389
+ const storage = createMemoryStorage();
390
+ const source = await createConcordApp({
391
+ identity,
392
+ storage,
393
+ plugins: [createTodoPlugin()],
394
+ });
395
+
396
+ await source.load();
397
+ await source.command("todo.create-item", {
398
+ id: "todo-a",
399
+ title: "A",
400
+ });
401
+ await source.command("todo.create-item", {
402
+ id: "todo-b",
403
+ title: "B",
404
+ });
405
+ await source.commit({
406
+ metadata: {
407
+ message: "Commit imported todos",
408
+ },
409
+ });
410
+
411
+ const events: string[] = [];
412
+ let working: string[] = [];
413
+ let published: string[] = ["stale"];
414
+ const materializer: ConcordReplayPlugin<undefined> = {
415
+ id: "memory-index",
416
+ reset() {
417
+ events.push("reset");
418
+ working = [];
419
+ },
420
+ beginReplay(ctx) {
421
+ events.push(`begin:${ctx.replay.entryCount}`);
422
+ },
423
+ applyEntry(entry) {
424
+ events.push(`apply:${entry.entryId}`);
425
+ working.push(entry.entryId);
426
+ },
427
+ endReplay() {
428
+ events.push("end");
429
+ published = [...working];
430
+ },
431
+ };
432
+
433
+ const targetApp = await createConcordApp({
434
+ identity,
435
+ storage: createMemoryStorage(),
436
+ plugins: [createTodoPlugin(), materializer],
437
+ });
438
+
439
+ await targetApp.importLedger(await source.exportLedger());
440
+
441
+ expect(events).toEqual([
442
+ "reset",
443
+ "begin:2",
444
+ expect.stringMatching(/^apply:/),
445
+ expect.stringMatching(/^apply:/),
446
+ "end",
447
+ ]);
448
+ expect(published).toHaveLength(2);
449
+ expect(published).not.toEqual(["stale"]);
450
+ });
451
+
452
+ it("fails the operation when a replay plugin throws in beginReplay and does not publish partial state", async () => {
453
+ const identity = await createIdentity("2026-03-18T12:20:00.000Z");
454
+ const storage = createMemoryStorage();
455
+ const app = await createConcordApp({
456
+ identity,
457
+ storage,
458
+ plugins: [
459
+ createTodoPlugin(),
460
+ {
461
+ id: "broken",
462
+ beginReplay() {
463
+ throw new Error("plugin begin failed");
464
+ },
465
+ },
466
+ ],
467
+ });
468
+
469
+ const notifications: number[] = [];
470
+ app.subscribe(() => {
471
+ notifications.push(notifications.length + 1);
472
+ });
473
+
474
+ await expect(app.load()).rejects.toThrow("plugin begin failed");
475
+ expect(notifications).toHaveLength(0);
476
+ expect(app.getState().ready).toBe(false);
477
+ expect(app.getReplayState<{ items: Record<string, unknown> }>("todo").items).toEqual({});
478
+ });
479
+
480
+ it("keeps the last published state untouched when a replay plugin throws in applyEntry", async () => {
481
+ const identity = await createIdentity("2026-03-18T12:20:00.000Z");
482
+ const storage = createMemoryStorage();
483
+ let shouldThrow = false;
484
+ const app = await createConcordApp({
485
+ identity,
486
+ storage,
487
+ plugins: [
488
+ createTodoPlugin(),
489
+ {
490
+ id: "broken",
491
+ applyEntry(entry) {
492
+ if (shouldThrow && entry.kind === "todo.item.created") {
493
+ throw new Error("plugin apply failed");
494
+ }
495
+ },
496
+ },
497
+ ],
498
+ });
499
+
500
+ await app.load();
501
+ await app.command("todo.create-item", {
502
+ id: "todo-good",
503
+ title: "Safe state",
504
+ });
505
+ await app.commit({
506
+ metadata: {
507
+ message: "Initial good state",
508
+ },
509
+ });
510
+
511
+ const before = structuredClone(
512
+ app.getReplayState<{ items: Record<string, { title: string }> }>("todo"),
513
+ );
514
+ shouldThrow = true;
515
+
516
+ await expect(
517
+ app.command("todo.create-item", {
518
+ id: "todo-bad",
519
+ title: "Break replay",
520
+ }),
521
+ ).rejects.toThrow("plugin apply failed");
522
+
523
+ expect(app.getReplayState("todo")).toEqual(before);
524
+ expect(app.getState().stagedCount).toBe(0);
525
+ expect(Object.keys((await app.exportLedger()).entries)).toHaveLength(1);
526
+ });
527
+
528
+ it("keeps the last published state untouched when a replay plugin throws in endReplay", async () => {
529
+ const identity = await createIdentity("2026-03-18T12:22:00.000Z");
530
+ const storage = createMemoryStorage();
531
+ const source = await createConcordApp({
532
+ identity,
533
+ storage,
534
+ plugins: [createTodoPlugin()],
535
+ });
536
+
537
+ await source.load();
538
+ await source.command("todo.create-item", {
539
+ id: "todo-end",
540
+ title: "End fails",
541
+ });
542
+ await source.commit({
543
+ metadata: {
544
+ message: "Commit end failure source",
545
+ },
546
+ });
547
+
548
+ let shouldThrow = false;
549
+ const target = await createConcordApp({
550
+ identity,
551
+ storage: createMemoryStorage(),
552
+ plugins: [
553
+ createTodoPlugin(),
554
+ {
555
+ id: "broken",
556
+ endReplay() {
557
+ if (shouldThrow) {
558
+ throw new Error("plugin end failed");
559
+ }
560
+ },
561
+ },
562
+ ],
563
+ });
564
+
565
+ await target.load();
566
+ const before = structuredClone(target.getState());
567
+ shouldThrow = true;
568
+
569
+ await expect(target.importLedger(await source.exportLedger())).rejects.toThrow(
570
+ "plugin end failed",
571
+ );
572
+
573
+ expect(target.getState()).toEqual(before);
574
+ });
575
+
576
+ it("notifies subscribers with a final reset state on destroy, then clears listeners", async () => {
577
+ const identity = await createIdentity("2026-03-18T12:23:00.000Z");
578
+ const storage = createMemoryStorage();
579
+ const app = await createConcordApp({
580
+ identity,
581
+ storage,
582
+ plugins: [createTodoPlugin()],
583
+ });
584
+
585
+ const notifications: boolean[] = [];
586
+ app.subscribe((nextState) => {
587
+ notifications.push(nextState.ready);
588
+ });
589
+
590
+ await app.load();
591
+ await app.destroy();
592
+
593
+ expect(notifications).toEqual([true, false]);
594
+ expect(app.getState().ready).toBe(false);
595
+ expect(app.getState().integrityValid).toBe(false);
596
+ expect(app.getReplayState<{ items: Record<string, unknown> }>("todo").items).toEqual({});
597
+ });
598
+
599
+ it("preserves persisted storage across destroy and reload", async () => {
600
+ const identity = await createIdentity("2026-03-18T12:24:00.000Z");
601
+ const storage = createMemoryStorage();
602
+ const first = await createConcordApp({
603
+ identity,
604
+ storage,
605
+ plugins: [createTodoPlugin()],
606
+ });
607
+
608
+ await first.load();
609
+ await first.command("todo.create-item", {
610
+ id: "todo-persisted",
611
+ title: "Still here after destroy",
612
+ });
613
+ await first.commit({
614
+ metadata: {
615
+ message: "Persist before destroy",
616
+ },
617
+ });
618
+
619
+ expect(storage.snapshot?.container).not.toBeNull();
620
+
621
+ await first.destroy();
622
+
623
+ expect(storage.snapshot?.container).not.toBeNull();
624
+
625
+ const second = await createConcordApp({
626
+ identity,
627
+ storage,
628
+ plugins: [createTodoPlugin()],
629
+ });
630
+
631
+ await second.load();
632
+
633
+ expect(
634
+ second.getReplayState<{
635
+ items: Record<string, { id: string; title: string; completed: boolean }>;
636
+ }>("todo").items,
637
+ ).toEqual({
638
+ "todo-persisted": {
639
+ id: "todo-persisted",
640
+ title: "Still here after destroy",
641
+ completed: false,
642
+ },
643
+ });
644
+ });
645
+
646
+ it("restores staged entries across destroy and reload", async () => {
647
+ const identity = await createIdentity("2026-03-18T12:24:10.000Z");
648
+ const storage = createMemoryStorage();
649
+ const first = await createConcordApp({
650
+ identity,
651
+ storage,
652
+ plugins: [createTodoPlugin()],
653
+ });
654
+
655
+ await first.load();
656
+ await first.command("todo.create-item", {
657
+ id: "todo-staged",
658
+ title: "Still staged after reload",
659
+ });
660
+
661
+ expect(first.getState().stagedCount).toBe(1);
662
+ expect(storage.snapshot?.staged).toHaveLength(1);
663
+
664
+ await first.destroy();
665
+
666
+ const second = await createConcordApp({
667
+ identity,
668
+ storage,
669
+ plugins: [createTodoPlugin()],
670
+ });
671
+
672
+ await second.load();
673
+
674
+ expect(second.getState().stagedCount).toBe(1);
675
+ expect(
676
+ second.getReplayState<{
677
+ items: Record<string, { id: string; title: string; completed: boolean }>;
678
+ }>("todo").items,
679
+ ).toEqual({
680
+ "todo-staged": {
681
+ id: "todo-staged",
682
+ title: "Still staged after reload",
683
+ completed: false,
684
+ },
685
+ });
686
+ expect(Object.keys((await second.exportLedger()).entries)).toHaveLength(0);
687
+ });
688
+
689
+ it("supports read-only load, verify, and replay without an identity", async () => {
690
+ const identity = await createIdentity("2026-03-18T12:24:30.000Z");
691
+ const sourceStorage = createMemoryStorage();
692
+ const source = await createConcordApp({
693
+ identity,
694
+ storage: sourceStorage,
695
+ plugins: [createTodoPlugin()],
696
+ });
697
+
698
+ await source.load();
699
+ await source.command("todo.create-item", {
700
+ id: "todo-readonly",
701
+ title: "Readable without signer",
702
+ });
703
+ await source.commit({
704
+ metadata: {
705
+ message: "Persist read-only sample",
706
+ },
707
+ });
708
+
709
+ const targetStorage = createMemoryStorage();
710
+ targetStorage.snapshot = structuredClone(sourceStorage.snapshot);
711
+
712
+ const target = await createConcordApp({
713
+ storage: targetStorage,
714
+ plugins: [createTodoPlugin()],
715
+ });
716
+
717
+ await target.load();
718
+
719
+ expect(
720
+ target.getReplayState<{
721
+ items: Record<string, { id: string; title: string; completed: boolean }>;
722
+ }>("todo").items["todo-readonly"]?.title,
723
+ ).toBe("Readable without signer");
724
+ expect(target.getState().ready).toBe(true);
725
+ expect(target.getState().integrityValid).toBe(true);
726
+
727
+ const verification = await target.verify();
728
+ expect(verification.committedHistoryValid).toBe(true);
729
+
730
+ await expect(
731
+ target.command("todo.create-item", {
732
+ id: "todo-fail",
733
+ title: "No signer",
734
+ }),
735
+ ).rejects.toMatchObject({
736
+ name: "ConcordBoundaryError",
737
+ code: "READ_ONLY_RUNTIME",
738
+ });
739
+ });
740
+
741
+ it("fails fast when internal ledger requirements are missing", async () => {
742
+ await expect(
743
+ createConcordApp({
744
+ identity: { keyId: "" } as never,
745
+ storage: createMemoryStorage(),
746
+ plugins: [createTodoPlugin()],
747
+ }),
748
+ ).rejects.toMatchObject({
749
+ name: "ConcordBoundaryError",
750
+ code: "INVALID_IDENTITY",
751
+ });
752
+ });
753
+
754
+ it("fails fast when a supplied ledger does not replay LedgerReplayEntry[]", async () => {
755
+ const fakeLedger = {
756
+ async create() {},
757
+ async load() {},
758
+ async loadFromStorage() {
759
+ return false;
760
+ },
761
+ async append() {
762
+ throw new Error("not used");
763
+ },
764
+ async appendMany() {
765
+ throw new Error("not used");
766
+ },
767
+ async commit() {
768
+ throw new Error("not used");
769
+ },
770
+ async replay() {
771
+ return { invalid: true };
772
+ },
773
+ async recompute() {
774
+ return { invalid: true };
775
+ },
776
+ async verify() {
777
+ return {
778
+ valid: true,
779
+ committedHistoryValid: true,
780
+ commitChainValid: true,
781
+ commitProofsValid: true,
782
+ entriesValid: true,
783
+ entryProofsValid: true,
784
+ payloadHashesValid: true,
785
+ proofsValid: true,
786
+ invalidCommitIds: [],
787
+ invalidEntryIds: [],
788
+ };
789
+ },
790
+ async export() {
791
+ throw new Error("not used");
792
+ },
793
+ async import() {},
794
+ getState() {
795
+ return {
796
+ container: null,
797
+ staged: [],
798
+ projection: { invalid: true },
799
+ verification: null,
800
+ };
801
+ },
802
+ subscribe() {
803
+ return () => {};
804
+ },
805
+ async clearStaged() {},
806
+ async destroy() {},
807
+ } as unknown as LedgerInstance<LedgerReplayEntry[]>;
808
+
809
+ const app = await createConcordApp({
810
+ identity: await createIdentity("2026-03-18T12:24:00.000Z"),
811
+ storage: createMemoryStorage(),
812
+ plugins: [createTodoPlugin()],
813
+ ledger: fakeLedger,
814
+ });
815
+
816
+ await expect(app.load()).rejects.toMatchObject({
817
+ name: "ConcordBoundaryError",
818
+ code: "INVALID_LEDGER_PROJECTION",
819
+ });
820
+ expect(app.getState().ready).toBe(false);
821
+ expect(app.getState().integrityValid).toBe(false);
822
+ });
823
+
824
+ it("replays encrypted entries as decrypted using identity-derived decrypt capability", async () => {
825
+ const identity = await createIdentity("2026-03-18T12:25:00.000Z");
826
+ const recipient = await recipientFromIdentity(identity);
827
+ const encryptedApp = await createConcordApp({
828
+ identity,
829
+ storage: createMemoryStorage(),
830
+ plugins: [createSecretPlugin()],
831
+ });
832
+
833
+ await encryptedApp.load();
834
+ await encryptedApp.command("secret.write", {
835
+ text: "secret note",
836
+ recipients: [recipient],
837
+ });
838
+ await encryptedApp.commit({
839
+ metadata: {
840
+ message: "Commit secret note",
841
+ },
842
+ });
843
+
844
+ expect(encryptedApp.getReplayState<{ values: string[] }>("secret").values).toEqual([
845
+ "secret note",
846
+ ]);
847
+ });
848
+
849
+ it("marks imported corrupted history as globally invalid and does not project it", async () => {
850
+ const identity = await createIdentity("2026-03-18T12:30:00.000Z");
851
+ const source = await createConcordApp({
852
+ identity,
853
+ storage: createMemoryStorage(),
854
+ plugins: [createTodoPlugin()],
855
+ });
856
+
857
+ await source.load();
858
+ await source.command("todo.create-item", {
859
+ id: "todo-5",
860
+ title: "Verify me",
861
+ });
862
+ await source.commit({
863
+ metadata: {
864
+ message: "Commit verification example",
865
+ },
866
+ });
867
+
868
+ const tampered = structuredClone(await source.exportLedger());
869
+ const [entryId] = Object.keys(tampered.entries);
870
+ tampered.entries[entryId] = {
871
+ ...tampered.entries[entryId],
872
+ payload: {
873
+ type: "plain",
874
+ data: { id: "todo-5", title: "tampered" },
875
+ },
876
+ };
877
+
878
+ const target = await createConcordApp({
879
+ identity,
880
+ storage: createMemoryStorage(),
881
+ plugins: [createTodoPlugin()],
882
+ });
883
+
884
+ await target.importLedger(tampered);
885
+
886
+ expect(target.getState().ready).toBe(false);
887
+ expect(target.getState().integrityValid).toBe(false);
888
+ expect(target.getReplayState<{ items: Record<string, unknown> }>("todo").items).toEqual({});
889
+ expect(target.getState().verification?.valid).toBe(false);
890
+ expect(target.getState().verification?.committedHistoryValid).toBe(false);
891
+
892
+ const verification = await target.verify();
893
+
894
+ expect(verification.valid).toBe(false);
895
+ expect(verification.committedHistoryValid).toBe(false);
896
+ expect(target.getState().verification).toEqual(verification);
897
+ expect(() => target.getReplayState("missing")).toThrow(ConcordBoundaryError);
898
+ });
899
+
900
+ it("passes ranged replay metadata to replay plugins", async () => {
901
+ const identity = await createIdentity("2026-03-18T12:35:00.000Z");
902
+ const storage = createMemoryStorage();
903
+ const seen: ConcordReplayMetadata[] = [];
904
+ const app = await createConcordApp({
905
+ identity,
906
+ storage,
907
+ plugins: [
908
+ createTodoPlugin(),
909
+ {
910
+ id: "range-probe",
911
+ applyEntry(_entry, ctx) {
912
+ seen.push(structuredClone(ctx.replay));
913
+ },
914
+ },
915
+ ],
916
+ });
917
+
918
+ await app.load();
919
+ await app.command("todo.create-item", {
920
+ id: "todo-r1",
921
+ title: "One",
922
+ });
923
+ await app.command("todo.create-item", {
924
+ id: "todo-r2",
925
+ title: "Two",
926
+ });
927
+ const commit = await app.commit({
928
+ metadata: {
929
+ message: "Range probe",
930
+ },
931
+ });
932
+
933
+ const exported = await app.exportLedger();
934
+ const [firstEntryId, secondEntryId] = commit.entryIds;
935
+ expect(Object.keys(exported.entries)).toEqual([firstEntryId, secondEntryId]);
936
+
937
+ seen.length = 0;
938
+ await app.replay({
939
+ fromEntryId: secondEntryId,
940
+ toEntryId: secondEntryId,
941
+ });
942
+
943
+ expect(seen).toHaveLength(1);
944
+ expect(seen[0]).toMatchObject({
945
+ phase: "applyEntry",
946
+ entryIndex: 0,
947
+ entryCount: 1,
948
+ fromEntryId: secondEntryId,
949
+ toEntryId: secondEntryId,
950
+ isPartial: true,
951
+ });
952
+ });
953
+
954
+ it("imports from built ESM output without manual path hacks", async () => {
955
+ const entryUrl = pathToFileURL(resolve(packageDir, "dist/index.js")).href;
956
+ const concord = await import(entryUrl);
957
+ const identity = await createIdentity("2026-03-18T12:40:00.000Z");
958
+ const recipient = await recipientFromIdentity(identity);
959
+ const app = await concord.createConcordApp({
960
+ identity,
961
+ storage: createMemoryStorage(),
962
+ plugins: [createSecretPlugin()],
963
+ });
964
+
965
+ await app.load();
966
+ await app.command("secret.write", {
967
+ text: "built concord",
968
+ recipients: [recipient],
969
+ });
970
+ await app.commit({
971
+ metadata: {
972
+ message: "Built package commit",
973
+ },
974
+ });
975
+
976
+ expect(app.getReplayState<{ values: string[] }>("secret").values).toEqual(["built concord"]);
977
+ });
978
+ });