@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,93 @@
1
+ import type { SerializedIdentity } from "@ternent/identity";
2
+
3
+ export type ArmourIdentityInput = string | SerializedIdentity;
4
+ export type ArmourOutputFormat = "armor" | "binary";
5
+ export type ArmourBinaryInput = Uint8Array | ArrayBuffer | Blob;
6
+
7
+ export type ArmourErrorCode =
8
+ | "ARMOUR_INIT_FAILED"
9
+ | "ARMOUR_EMPTY_DATA"
10
+ | "ARMOUR_EMPTY_RECIPIENTS"
11
+ | "ARMOUR_INVALID_RECIPIENT"
12
+ | "ARMOUR_INVALID_SECRET_KEY"
13
+ | "ARMOUR_EMPTY_PASSPHRASE"
14
+ | "ARMOUR_DATA_TOO_LARGE"
15
+ | "ARMOUR_INVALID_IDENTITY"
16
+ | "ARMOUR_IDENTITY_DERIVATION_FAILED"
17
+ | "ARMOUR_ENCRYPT_FAILED"
18
+ | "ARMOUR_DECRYPT_FAILED";
19
+
20
+ export interface EncryptForRecipientsInput {
21
+ recipients: string[];
22
+ data: Uint8Array;
23
+ output?: ArmourOutputFormat;
24
+ }
25
+
26
+ export interface DecryptWithSecretKeyInput {
27
+ secretKey: string;
28
+ data: Uint8Array;
29
+ }
30
+
31
+ export interface EncryptForIdentitiesInput {
32
+ identities: ArmourIdentityInput[];
33
+ data: Uint8Array;
34
+ output?: ArmourOutputFormat;
35
+ }
36
+
37
+ export interface DecryptWithIdentityInput {
38
+ identity: ArmourIdentityInput;
39
+ data: Uint8Array;
40
+ }
41
+
42
+ export interface EncryptWithPassphraseInput {
43
+ passphrase: string;
44
+ data: Uint8Array;
45
+ output?: ArmourOutputFormat;
46
+ }
47
+
48
+ export interface DecryptWithPassphraseInput {
49
+ passphrase: string;
50
+ data: Uint8Array;
51
+ }
52
+
53
+ export interface EncryptTextForIdentitiesInput {
54
+ identities: ArmourIdentityInput[];
55
+ text: string;
56
+ }
57
+
58
+ export interface DecryptTextWithIdentityInput {
59
+ identity: ArmourIdentityInput;
60
+ data: string | Uint8Array;
61
+ }
62
+
63
+ export interface EncryptTextWithPassphraseInput {
64
+ passphrase: string;
65
+ text: string;
66
+ }
67
+
68
+ export interface DecryptTextWithPassphraseInput {
69
+ passphrase: string;
70
+ data: string | Uint8Array;
71
+ }
72
+
73
+ export interface EncryptBinaryForIdentitiesInput {
74
+ identities: ArmourIdentityInput[];
75
+ data: ArmourBinaryInput;
76
+ output?: ArmourOutputFormat;
77
+ }
78
+
79
+ export interface DecryptBinaryWithIdentityInput {
80
+ identity: ArmourIdentityInput;
81
+ data: ArmourBinaryInput;
82
+ }
83
+
84
+ export interface EncryptBinaryWithPassphraseInput {
85
+ passphrase: string;
86
+ data: ArmourBinaryInput;
87
+ output?: ArmourOutputFormat;
88
+ }
89
+
90
+ export interface DecryptBinaryWithPassphraseInput {
91
+ passphrase: string;
92
+ data: ArmourBinaryInput;
93
+ }
@@ -0,0 +1,270 @@
1
+ import { dirname, resolve } from "node:path";
2
+ import { readFile } from "node:fs/promises";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import { createIdentity, serializeIdentity } from "../../identity-v2/src/index.ts";
6
+
7
+ const testDir = dirname(fileURLToPath(import.meta.url));
8
+ const packageDir = resolve(testDir, "..");
9
+
10
+ async function loadSourceModule() {
11
+ vi.resetModules();
12
+ return import("../src/index.ts");
13
+ }
14
+
15
+ describe("@ternent/armour", () => {
16
+ it("requires manual init before use", async () => {
17
+ const armour = await loadSourceModule();
18
+
19
+ await expect(
20
+ armour.encryptWithPassphrase({
21
+ passphrase: "correct horse battery staple",
22
+ data: new TextEncoder().encode("hello"),
23
+ }),
24
+ ).rejects.toMatchObject({
25
+ name: "ArmourInitError",
26
+ code: "ARMOUR_INIT_FAILED",
27
+ });
28
+ });
29
+
30
+ it("initializes idempotently", async () => {
31
+ const armour = await loadSourceModule();
32
+
33
+ await expect(armour.initArmour()).resolves.toBeUndefined();
34
+ await expect(armour.initArmour()).resolves.toBeUndefined();
35
+ });
36
+
37
+ it("derives age recipients and secrets from identity objects and strings", async () => {
38
+ const armour = await loadSourceModule();
39
+ const identity = await createIdentity("2026-03-17T00:00:00.000Z");
40
+ const serialized = serializeIdentity(identity);
41
+
42
+ const recipientFromObject = await armour.recipientFromIdentity(identity);
43
+ const recipientFromString = await armour.recipientFromIdentity(serialized);
44
+ const secret = await armour.secretKeyFromIdentity(identity);
45
+
46
+ expect(recipientFromObject).toMatch(/^age1/);
47
+ expect(recipientFromString).toBe(recipientFromObject);
48
+ expect(secret).toMatch(/^AGE-SECRET-KEY-1/);
49
+ });
50
+
51
+ it("rejects malformed identity input with structured errors", async () => {
52
+ const armour = await loadSourceModule();
53
+
54
+ await expect(
55
+ armour.recipientFromIdentity('{"format":"ternent-identity","version":"nope"}'),
56
+ ).rejects.toMatchObject({
57
+ name: "ArmourIdentityError",
58
+ code: "ARMOUR_INVALID_IDENTITY",
59
+ });
60
+ });
61
+
62
+ it("encrypts for one identity and decrypts with the same identity", async () => {
63
+ const armour = await loadSourceModule();
64
+ await armour.initArmour();
65
+ const identity = await createIdentity();
66
+
67
+ const ciphertext = await armour.encryptForIdentities({
68
+ identities: [identity],
69
+ data: new TextEncoder().encode("hello"),
70
+ });
71
+ const plaintext = await armour.decryptWithIdentity({
72
+ identity,
73
+ data: ciphertext,
74
+ });
75
+
76
+ expect(new TextDecoder().decode(plaintext)).toBe("hello");
77
+ });
78
+
79
+ it("encrypts for multiple identities in one pass", async () => {
80
+ const armour = await loadSourceModule();
81
+ await armour.initArmour();
82
+ const alice = await createIdentity();
83
+ const bob = await createIdentity();
84
+
85
+ const ciphertext = await armour.encryptForIdentities({
86
+ identities: [alice, bob],
87
+ data: new TextEncoder().encode("shared"),
88
+ });
89
+ const plaintext = await armour.decryptWithIdentity({
90
+ identity: bob,
91
+ data: ciphertext,
92
+ });
93
+
94
+ expect(new TextDecoder().decode(plaintext)).toBe("shared");
95
+ });
96
+
97
+ it("supports raw recipient passthrough and raw secret decryption", async () => {
98
+ const armour = await loadSourceModule();
99
+ await armour.initArmour();
100
+ const identity = await createIdentity();
101
+ const recipient = await armour.recipientFromIdentity(identity);
102
+ const secretKey = await armour.secretKeyFromIdentity(identity);
103
+
104
+ const ciphertext = await armour.encryptForRecipients({
105
+ recipients: [recipient],
106
+ data: new TextEncoder().encode("raw"),
107
+ output: "binary",
108
+ });
109
+ const plaintext = await armour.decryptWithSecretKey({
110
+ secretKey,
111
+ data: ciphertext,
112
+ });
113
+
114
+ expect(new TextDecoder().decode(plaintext)).toBe("raw");
115
+ });
116
+
117
+ it("normalizes recipient validation failures", async () => {
118
+ const armour = await loadSourceModule();
119
+ await armour.initArmour();
120
+
121
+ await expect(
122
+ armour.encryptForRecipients({
123
+ recipients: [],
124
+ data: new TextEncoder().encode("hello"),
125
+ }),
126
+ ).rejects.toMatchObject({
127
+ name: "ArmourValidationError",
128
+ code: "ARMOUR_EMPTY_RECIPIENTS",
129
+ });
130
+ });
131
+
132
+ it("round trips passphrase encryption and rejects wrong passphrases", async () => {
133
+ const armour = await loadSourceModule();
134
+ await armour.initArmour();
135
+
136
+ const ciphertext = await armour.encryptWithPassphrase({
137
+ passphrase: "correct horse battery staple",
138
+ data: new TextEncoder().encode("secret"),
139
+ });
140
+ const plaintext = await armour.decryptWithPassphrase({
141
+ passphrase: "correct horse battery staple",
142
+ data: ciphertext,
143
+ });
144
+
145
+ expect(new TextDecoder().decode(plaintext)).toBe("secret");
146
+
147
+ await expect(
148
+ armour.decryptWithPassphrase({
149
+ passphrase: "wrong",
150
+ data: ciphertext,
151
+ }),
152
+ ).rejects.toMatchObject({
153
+ name: "ArmourDecryptionError",
154
+ code: "ARMOUR_DECRYPT_FAILED",
155
+ });
156
+ });
157
+
158
+ it("rejects empty passphrases with structured validation errors", async () => {
159
+ const armour = await loadSourceModule();
160
+ await armour.initArmour();
161
+
162
+ await expect(
163
+ armour.encryptWithPassphrase({
164
+ passphrase: "",
165
+ data: new TextEncoder().encode("hello"),
166
+ }),
167
+ ).rejects.toMatchObject({
168
+ name: "ArmourValidationError",
169
+ code: "ARMOUR_EMPTY_PASSPHRASE",
170
+ });
171
+ });
172
+
173
+ it("round trips text helpers", async () => {
174
+ const armour = await loadSourceModule();
175
+ await armour.initArmour();
176
+ const identity = await createIdentity();
177
+
178
+ const ciphertext = await armour.encryptTextForIdentities({
179
+ identities: [identity],
180
+ text: "hello utf8",
181
+ });
182
+ const plaintext = await armour.decryptTextWithIdentity({
183
+ identity,
184
+ data: ciphertext,
185
+ });
186
+
187
+ expect(ciphertext).toContain("-----BEGIN AGE ENCRYPTED FILE-----");
188
+ expect(plaintext).toBe("hello utf8");
189
+ });
190
+
191
+ it("accepts ArrayBuffer, Blob, and File-compatible inputs for binary helpers", async () => {
192
+ const armour = await loadSourceModule();
193
+ await armour.initArmour();
194
+ const identity = await createIdentity();
195
+ const fileLike =
196
+ typeof File === "undefined"
197
+ ? new Blob(["blob data"], { type: "text/plain" })
198
+ : new File(["blob data"], "demo.txt", { type: "text/plain" });
199
+
200
+ const ciphertextFromArrayBuffer = await armour.encryptBinaryForIdentities({
201
+ identities: [identity],
202
+ data: new TextEncoder().encode("buffer data").buffer,
203
+ output: "binary",
204
+ });
205
+ const plaintextFromArrayBuffer = await armour.decryptBinaryWithIdentity({
206
+ identity,
207
+ data: ciphertextFromArrayBuffer,
208
+ });
209
+ const ciphertextFromFile = await armour.encryptBinaryForIdentities({
210
+ identities: [identity],
211
+ data: fileLike,
212
+ });
213
+ const plaintextFromFile = await armour.decryptBinaryWithIdentity({
214
+ identity,
215
+ data: ciphertextFromFile,
216
+ });
217
+
218
+ expect(new TextDecoder().decode(plaintextFromArrayBuffer)).toBe("buffer data");
219
+ expect(new TextDecoder().decode(plaintextFromFile)).toBe("blob data");
220
+ });
221
+
222
+ it("keeps the package boundary clean", async () => {
223
+ const sourceFiles = [
224
+ resolve(packageDir, "src/index.ts"),
225
+ resolve(packageDir, "src/identity.ts"),
226
+ resolve(packageDir, "src/recipients.ts"),
227
+ resolve(packageDir, "src/passphrase.ts"),
228
+ resolve(packageDir, "src/text.ts"),
229
+ resolve(packageDir, "src/files.ts"),
230
+ ];
231
+ const source = (await Promise.all(sourceFiles.map((file) => readFile(file, "utf8")))).join(
232
+ "\n",
233
+ );
234
+ const armour = await loadSourceModule();
235
+ const identity = await createIdentity();
236
+
237
+ expect(source).not.toMatch(/@ternent\/seal-/);
238
+ expect(source).not.toMatch(/\benvelope\b/i);
239
+ expect(source).not.toMatch(/\b(sign|verify)(Utf8|Bytes)?\b/);
240
+ expect(source).not.toMatch(/\bencryptText\b(?!ForIdentities|WithPassphrase)/);
241
+ expect(source).not.toMatch(/\bdecryptText\b(?!WithIdentity|WithPassphrase)/);
242
+
243
+ await armour.initArmour();
244
+ await expect(
245
+ armour.encryptForIdentities({
246
+ identities: [identity],
247
+ data: new TextEncoder().encode("async"),
248
+ }),
249
+ ).resolves.toBeInstanceOf(Uint8Array);
250
+ });
251
+
252
+ it("imports from built ESM output without manual path hacks", async () => {
253
+ const entryUrl = pathToFileURL(resolve(packageDir, "dist/index.js")).href;
254
+ const armour = await import(entryUrl);
255
+ const identity = await createIdentity();
256
+
257
+ await armour.initArmour();
258
+
259
+ const ciphertext = await armour.encryptForIdentities({
260
+ identities: [identity],
261
+ data: new TextEncoder().encode("built package"),
262
+ });
263
+ const plaintext = await armour.decryptWithIdentity({
264
+ identity,
265
+ data: ciphertext,
266
+ });
267
+
268
+ expect(new TextDecoder().decode(plaintext)).toBe("built package");
269
+ });
270
+ });
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "emitDeclarationOnly": true
9
+ },
10
+ "include": ["src/**/*.ts"],
11
+ "exclude": ["src/**/*.test.*", "test", "dist", "node_modules"]
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "target": "ES2020",
5
+ "module": "NodeNext",
6
+ "moduleResolution": "NodeNext",
7
+ "lib": ["ES2020", "DOM"],
8
+ "types": ["node", "vitest/globals"]
9
+ },
10
+ "include": ["src/**/*.ts", "test/**/*.ts", "vite.config.ts"],
11
+ "exclude": ["dist", "node_modules"]
12
+ }
@@ -0,0 +1,29 @@
1
+ import { dirname, resolve } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { defineConfig } from "vite";
4
+ import { createPackageExternal, resolvePackageDir } from "../../scripts/vite/package-lib-config";
5
+
6
+ const configDir = dirname(fileURLToPath(import.meta.url));
7
+ const external = createPackageExternal(resolvePackageDir(import.meta.url));
8
+
9
+ export default defineConfig({
10
+ build: {
11
+ outDir: "dist",
12
+ sourcemap: true,
13
+ target: "es2020",
14
+ minify: false,
15
+ lib: {
16
+ entry: resolve(configDir, "src/index.ts"),
17
+ name: "ternentArmour",
18
+ fileName: "index",
19
+ formats: ["es"],
20
+ },
21
+ rollupOptions: {
22
+ external,
23
+ output: {
24
+ format: "es",
25
+ entryFileNames: "index.js",
26
+ },
27
+ },
28
+ },
29
+ });
@@ -0,0 +1,83 @@
1
+ # @ternent/concord
2
+
3
+ ## 0.2.9
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies []:
8
+ - @ternent/ledger@0.1.8
9
+
10
+ ## 0.2.8
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies []:
15
+ - @ternent/ledger@0.1.7
16
+
17
+ ## 0.2.7
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies []:
22
+ - @ternent/ledger@0.1.6
23
+
24
+ ## 0.2.6
25
+
26
+ ### Patch Changes
27
+
28
+ - [`583a65c2dc6917a634019ea53c6987dc3960a8b2`](https://github.com/samternent/home/commit/583a65c2dc6917a634019ea53c6987dc3960a8b2) Thanks [@samternent](https://github.com/samternent)! - Update concord
29
+
30
+ ## 0.2.5
31
+
32
+ ### Patch Changes
33
+
34
+ - [`42a5b52f19d93079ca76a07f44c647d5e6866009`](https://github.com/samternent/home/commit/42a5b52f19d93079ca76a07f44c647d5e6866009) Thanks [@samternent](https://github.com/samternent)! - Align build with identity
35
+
36
+ ## 0.2.4
37
+
38
+ ### Patch Changes
39
+
40
+ - [`cb63d9c1dc91e0cd3924bafd5abcde89cc02992f`](https://github.com/samternent/home/commit/cb63d9c1dc91e0cd3924bafd5abcde89cc02992f) Thanks [@samternent](https://github.com/samternent)! - Fix workflows
41
+
42
+ - Updated dependencies [[`cb63d9c1dc91e0cd3924bafd5abcde89cc02992f`](https://github.com/samternent/home/commit/cb63d9c1dc91e0cd3924bafd5abcde89cc02992f)]:
43
+ - @ternent/ledger@0.1.5
44
+
45
+ ## 0.2.3
46
+
47
+ ### Patch Changes
48
+
49
+ - [`acf974919a1176da235e7ec7468c46df8bc13402`](https://github.com/samternent/home/commit/acf974919a1176da235e7ec7468c46df8bc13402) Thanks [@samternent](https://github.com/samternent)! - Update build configs
50
+
51
+ - Updated dependencies [[`acf974919a1176da235e7ec7468c46df8bc13402`](https://github.com/samternent/home/commit/acf974919a1176da235e7ec7468c46df8bc13402)]:
52
+ - @ternent/ledger@0.1.4
53
+
54
+ ## 0.2.2
55
+
56
+ ### Patch Changes
57
+
58
+ - [`3fd51c9f51ccc3d816d044aa8aa161d9683534c3`](https://github.com/samternent/home/commit/3fd51c9f51ccc3d816d044aa8aa161d9683534c3) Thanks [@samternent](https://github.com/samternent)! - alias utils
59
+
60
+ - Updated dependencies []:
61
+ - @ternent/ledger@0.1.3
62
+
63
+ ## 0.2.1
64
+
65
+ ### Patch Changes
66
+
67
+ - [`109f8c3989b16a161a34b933dcad68390d9219c5`](https://github.com/samternent/home/commit/109f8c3989b16a161a34b933dcad68390d9219c5) Thanks [@samternent](https://github.com/samternent)! - Fix deps
68
+
69
+ - Updated dependencies [[`109f8c3989b16a161a34b933dcad68390d9219c5`](https://github.com/samternent/home/commit/109f8c3989b16a161a34b933dcad68390d9219c5)]:
70
+ - @ternent/ledger@0.1.2
71
+
72
+ ## 0.2.0
73
+
74
+ ### Minor Changes
75
+
76
+ - [`92050833da54cacbd6fe33349d9b8fb66754d82c`](https://github.com/samternent/home/commit/92050833da54cacbd6fe33349d9b8fb66754d82c) Thanks [@samternent](https://github.com/samternent)! - Add the initial Concord command runtime on top of `@ternent/ledger`.
77
+
78
+ ### Patch Changes
79
+
80
+ - [`92050833da54cacbd6fe33349d9b8fb66754d82c`](https://github.com/samternent/home/commit/92050833da54cacbd6fe33349d9b8fb66754d82c) Thanks [@samternent](https://github.com/samternent)! - Concord library migration
81
+
82
+ - Updated dependencies [[`92050833da54cacbd6fe33349d9b8fb66754d82c`](https://github.com/samternent/home/commit/92050833da54cacbd6fe33349d9b8fb66754d82c)]:
83
+ - @ternent/ledger@0.1.1
@@ -0,0 +1,9 @@
1
+ # CLAUDE.md
2
+
3
+ `@ternent/concord` is the command/replay runtime over `@ternent/ledger`.
4
+
5
+ - Do not add app-domain behavior here; keep this package runtime-generic.
6
+ - Replay flow is entry-first: for each entry, every plugin runs `applyEntry` in plugin order.
7
+ - Decrypt capability is identity-derived; plugins receive `plain`, `decrypted`, or `encrypted` replay payloads.
8
+ - Keep integrity strict: invalid committed history must not produce trusted runtime state.
9
+ - Preserve plugin contract stability (`reset/beginReplay/applyEntry/endReplay`).
@@ -0,0 +1,146 @@
1
+ # `@ternent/concord`
2
+
3
+ Command-driven replay runtime for building non-custodial applications on top of `@ternent/ledger`.
4
+
5
+ Concord is replay-first:
6
+
7
+ - ledger history is truth
8
+ - replay is the primary abstraction
9
+ - replay plugins are the only replay consumer type
10
+ - state, reactivity, databases, and indexes are all projection
11
+
12
+ ```ts
13
+ import { createConcordApp, type ConcordReplayPlugin } from "@ternent/concord";
14
+
15
+ const todoPlugin: ConcordReplayPlugin<{
16
+ items: Record<string, { id: string; title: string; completed: boolean }>;
17
+ }> = {
18
+ id: "todo",
19
+ initialState() {
20
+ return { items: {} };
21
+ },
22
+ commands: {
23
+ "todo.create-item": async (_ctx, input: { id: string; title: string }) => ({
24
+ kind: "todo.item.created",
25
+ payload: input,
26
+ }),
27
+ },
28
+ applyEntry(entry, ctx) {
29
+ if (entry.kind !== "todo.item.created" || entry.payload.type !== "plain") {
30
+ return;
31
+ }
32
+
33
+ const payload = entry.payload.data as { id: string; title: string };
34
+ ctx.setState((state) => ({
35
+ items: {
36
+ ...state.items,
37
+ [payload.id]: {
38
+ id: payload.id,
39
+ title: payload.title,
40
+ completed: false,
41
+ },
42
+ },
43
+ }));
44
+ },
45
+ };
46
+
47
+ const app = await createConcordApp({
48
+ identity,
49
+ storage,
50
+ plugins: [todoPlugin],
51
+ });
52
+
53
+ await app.load();
54
+
55
+ await app.command("todo.create-item", {
56
+ id: crypto.randomUUID(),
57
+ title: "Buy milk",
58
+ });
59
+
60
+ await app.commit({
61
+ metadata: {
62
+ message: "Create first todo",
63
+ },
64
+ });
65
+
66
+ const todoState = app.getReplayState("todo");
67
+ ```
68
+
69
+ ## Identity
70
+
71
+ Concord now supports two runtime modes:
72
+
73
+ - read-only: omit `identity` and use Concord for load/import/verify/replay only
74
+ - interactive: provide `identity` and use Concord for signed commands and commits
75
+
76
+ Interactive mode:
77
+
78
+ ```ts
79
+ const app = await createConcordApp({
80
+ identity,
81
+ storage,
82
+ plugins,
83
+ });
84
+ ```
85
+
86
+ Concord derives author, signer, and decrypt capability internally. Command handlers receive the same high-level `SerializedIdentity` at `ctx.identity`. Ledger-facing identity adaptation stays private to Concord.
87
+
88
+ Read-only mode:
89
+
90
+ ```ts
91
+ const app = await createConcordApp({
92
+ storage,
93
+ plugins,
94
+ });
95
+ ```
96
+
97
+ In read-only mode:
98
+
99
+ - `load`, `importLedger`, `verify`, `replay`, and `recompute` are allowed
100
+ - `create`, `command`, `commit`, and `clearStaged` fail with `READ_ONLY_RUNTIME`
101
+ - encrypted entries remain encrypted unless an identity is present for decrypt capability
102
+
103
+ ## Replay Plugins
104
+
105
+ Replay plugins are the only replay consumer type.
106
+
107
+ - a replay plugin may keep replay-derived state with `initialState()` and `ctx.setState(...)`
108
+ - a replay plugin may materialize into external systems like Loki, Vue refs, React stores, or IndexedDB
109
+ - commands remain typed entry producers
110
+
111
+ `reset` has a narrow meaning:
112
+
113
+ - `reset` prepares plugin-local replay workspace for a new replay pass
114
+ - `reset` does not imply clearing previously published external surfaces
115
+ - if a plugin needs external atomic swap behavior, that belongs to the plugin and usually happens in `endReplay`
116
+
117
+ Concord only guarantees atomicity at the published Concord state boundary. External projection atomicity is plugin-owned.
118
+
119
+ ## Replay And Integrity
120
+
121
+ The core lifecycle is:
122
+
123
+ 1. `command(...)` stages one or more entries
124
+ 2. local replay reflects committed truth plus staged truth
125
+ 3. `commit(...)` groups staged entries into a signed commit
126
+ 4. replay rebuilds published runtime state from that history
127
+
128
+ Concord treats committed history as atomic truth. If any reachable committed byte is invalid, Concord does not present the runtime state as trustworthy.
129
+
130
+ ## Partial Replay
131
+
132
+ Concord supports ranged replay options:
133
+
134
+ ```ts
135
+ await app.replay({
136
+ fromEntryId: "entry_a",
137
+ toEntryId: "entry_b",
138
+ });
139
+ ```
140
+
141
+ Two distinctions matter:
142
+
143
+ - replay of any ordered slice is deterministic
144
+ - authoritative full-state reconstruction still requires replay from genesis or a valid checkpoint
145
+
146
+ That distinction is intentional. Partial replay metadata exists so Concord stays compatible with future timeline scrubbing and replay-slider UX without redesigning the plugin contract.