chainlesschain 0.166.17 → 0.166.18
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.
- package/README.md +14 -7
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-CVW2n2rh.js → AIOps-CQSBl3hY.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DtuM163O.js → ActionButton-IQt-vRLt.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BXvl_qHu.js → Analytics-DkHC8O7k.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-VDBTFKOp.js → AppLayout-CSti2P2T.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-BNz2xCiG.js → Artifacts-C57hxn3v.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DTu9Hn9P.js → Audit-DByLntcf.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-D2OCPzLe.js → BackgroundAgents-D1Bd2EOa.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-C9M-vG7G.js → Backup-VstHzHng.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BUkCNiUQ.js → BaseInput-CLhdexIf.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-OJ2ajF7l.js → Chat-BefC_JZS.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BdZt76Ab.js → ChatBubbleRenderer-DbXMvgmn.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-C1gXKDsc.js → Checkbox-CEY982vx.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BXbznLg7.js → Codegen-Dcm_DTvJ.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CbCq5N0i.js → Col-C8SSu4KA.js} +1 -1
- package/src/assets/web-panel/assets/{Community-V8Ks6Yun.js → Community-BVV9vwRR.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DkeHQTpQ.js → Compact-B7c5Vo18.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-JeLKwn4Q.js → Compliance-Tq9jSZ5a.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-azfetcBz.js → Cowork-BJUjT09Q.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-Q691i_Gp.js → Cron-CjV9PEaS.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BLjDRbr_.js → Crosschain-Dl7ReL8F.js} +1 -1
- package/src/assets/web-panel/assets/{DID-D8ptXplK.js → DID-CG8PjWmM.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Ctj2VVZL.js → Dashboard-CDBM6wmF.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-D8VgkMJ8.js → Dropdown-8_55bXTD.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CQPe5S6q.js → EmailListRenderer-D3qOU1Cm.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-ySA-7SiN.js → FamilyGuardDashboard-Bv5oWMZF.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-1PMD-fBf.js → Federation-hpZ1xkKd.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-C_jr6U4Q.js → FormItemContext-BN7GT191.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Bu3bKf2B.js → GenericCardRenderer-PCRhfA7M.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Dv4Fa_T5.js → Git-CIlP2vy7.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DOEy1n1k.js → Governance-81lgm7YL.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DkvqzWcJ.js → Inference-Cs24xMOV.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DdUJK949.js → KnowledgeGraph-CrJLVCZo.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CRelcdUZ.js → Logs-DLyO8RKT.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-0AHbbTA4.js → MarkdownRenderer-DBOqijrP.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BuuBKsev.js → Marketplace-Dz7AbQZc.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DlgpEmA1.js → McpTools-BM0aFiWv.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-DTfdqUGy.js → Memory-C9SJzENX.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CXLE5ZFO.js → MobileBridge-BO2HnNRk.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Dz25OB0n.js → MobileProjects-CK_jMz0l.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-kEL0fhgD.js → Mtc-CfhrJsdO.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BE4UvFJU.js → MtcAudit-BE2ELxgI.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-CSApSn8c.js → Multisig-CgR_E3Jd.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-D11FpKWx.js → NLProgramming-Cw9445_K.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CDL-D2CS.js → Notes-DZimkTrb.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C0Q6lO-c.js → NotificationSettings-DxHVh4uT.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Iy-pnX56.js +1 -0
- package/src/assets/web-panel/assets/{Organization-Dc-maVAq.js → Organization-pPXJtWo4.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-B5mUpSvi.js → Overflow-YamdTJwA.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-TjXV53wq.js → P2P-BvqP53x7.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-Blz1Xn2n.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-DKRzQ2O6.js → Permissions-CgFRQqFJ.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Dx2eqft8.js → PersonalDataHub-CFeo3-6O.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-CnYZMB5p.js → Pipeline-BkaRojEK.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BonKpPnp.js → Privacy-BNZEx2Qp.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DWO0Du3J.js → ProjectInit-HUBOgEEK.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C9jqyYRv.js → ProjectSettings-CFMmhROS.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-CYpKwXbz.js → Projects-Dc517BXp.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CW-fkwJq.js → Providers-DTLf8bgj.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Cw0J3Zud.js → QrScannerModal-BBNjPszk.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BH4A4IpR.js → QuickAsk-DCsHvk3G.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CDwMwJus.js → Recommend-Dpr5YSop.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DKIPwzTf.js → RemoteSession-DmKnvqGg.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CklDj7qS.js → Reputation-CSRfuurX.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DJWZKsH0.js → Row-CDj3pQ_w.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BfSLCewo.js → RssFeed-DI0a_kNq.js} +2 -2
- package/src/assets/web-panel/assets/{Search-B0IST--x.js → Search-jAy7B2nU.js} +1 -1
- package/src/assets/web-panel/assets/{Security-h1sYNWtt.js → Security-CkHTqYhc.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Gh0-oxI_.js → Services-CP-0Nzga.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CNDV_jOs.js → Skeleton-BkF5-p3n.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-ByCLTh1n.js → Skills-BDu-utDE.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BfyrJTJz.js → Sla-CQvq_2Qb.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BQzo9kNX.js → SpeechSettings-T7Ap512g.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-B6fS6z_1.js → SyncSettings-Bt-PLvot.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-C-uKCP-l.js → Tasks-yLEcCogg.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-D6oDy7wL.js → Templates-ByN3fmbr.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-D3LT2wNQ.js → Tenant-DBI7hITH.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BteLcv_O.js → Terminal-B77_DlGX.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DHHuxfqH.js → TimelineRenderer-CsLTB_QO.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BRQeykXw.js → Tokens-BV2N6cR8.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-p2XaFWEu.js → Trigger-CjXWGH3p.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BxadBMft.js → Trust-uqXIq3WC.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DRBP7LDk.js → UkeySign-D-6qu4vY.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CZ3NZkh7.js → VideoEditing-CxGmx_2e.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-C3NTBN7G.js → Wallet-DSq7WlAg.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C7LVgMlb.js → WebAuthn-CDBiD57O.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-0_Iveog7.js → WorkflowEditor-Z8f63TSb.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DzzluYo3.js → chat-B-UZFBjy.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DSMYXpaj.js → colors-CFk7K-7Q.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-W76u1VSV.js → compact-item-X0IVMvgi.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-otsESjb5.js → createContext-DWg4j2n6.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-B2IW1J_J.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-3sNLrvug.js → hasIn-CkVCwNNF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BdtmEKat.js → index-5OcZHdzc.js} +1 -1
- package/src/assets/web-panel/assets/{index-DS9WYDMf.js → index-B273IY-E.js} +1 -1
- package/src/assets/web-panel/assets/{index-NaO9AE-F.js → index-B3-B3FWD.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcGJtmHM.js → index-BB4KHHcv.js} +1 -1
- package/src/assets/web-panel/assets/index-BCp2ir-1.js +1 -0
- package/src/assets/web-panel/assets/{index-pVm_iUre.js → index-BG-GaZri.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpcQYzP7.js → index-BPBia4b-.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXLx9HfA.js → index-BipwEbTA.js} +1 -1
- package/src/assets/web-panel/assets/{index-uSCkbpZH.js → index-BoskgPt9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMmdIpLA.js → index-Btqk6zWb.js} +1 -1
- package/src/assets/web-panel/assets/index-Bw4iuzRD.js +1 -0
- package/src/assets/web-panel/assets/{index-DLDh1OW3.js → index-BzXjYUmd.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9X_jqXe.js → index-C1ZAOJRW.js} +1 -1
- package/src/assets/web-panel/assets/{index-vBKPcmAP.js → index-CCLwoATF.js} +1 -1
- package/src/assets/web-panel/assets/{index-vUmYkqmu.js → index-CDfSD07S.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7A5U2IH.js → index-CLlP1E4X.js} +1 -1
- package/src/assets/web-panel/assets/{index-ClOFeZ-t.js → index-CMCEXBRh.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cg86EzNp.js → index-CN8HZlLH.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ddd4mird.js → index-CNKFsBlz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CE7tSow-.js → index-CUBkvFPH.js} +1 -1
- package/src/assets/web-panel/assets/{index-GlIOE4ja.js → index-Ca0mij1n.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdU7IkKX.js → index-CbIAWbma.js} +1 -1
- package/src/assets/web-panel/assets/{index-UUHCwfNq.js → index-CbihMThv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRsD1PT2.js → index-CvOa-IFV.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2aht9-M.js → index-Cvwu_rA_.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJsQio5O.js → index-D-EJNxgR.js} +1 -1
- package/src/assets/web-panel/assets/{index-q7ftLqNt.js → index-D8uEL8Uw.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1I1AYJs.js → index-D92g4l3n.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZKAzEge.js → index-DCqpl_E_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKoFyHYQ.js → index-DLSBks8h.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDlDO2px.js → index-Db_5xhrU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CENWwjef.js → index-DcDggpWR.js} +1 -1
- package/src/assets/web-panel/assets/{index-DH4cl52p.js → index-DeWKPQGs.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTA8Iavv.js → index-DhVHoqGL.js} +3 -3
- package/src/assets/web-panel/assets/{index-DmpaC2yk.js → index-DyWlOuak.js} +1 -1
- package/src/assets/web-panel/assets/{index-BA8mZxd-.js → index-LU54bChK.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtFOt5x1.js → index-OjnAwUJo.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5LOYdYm.js → index-P83LDnCz.js} +1 -1
- package/src/assets/web-panel/assets/{index-MhQQ3h8q.js → index-_-9k3Tna.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DF6llUXV.js → initDefaultProps-CFNgY8lJ.js} +1 -1
- package/src/assets/web-panel/assets/{motion-vtDV8_5K.js → motion-D-5RrDzn.js} +1 -1
- package/src/assets/web-panel/assets/{move-B6c0bdy2.js → move-DN-L0Bd-.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DlLYWuBK.js → mtc-parser-6mgLSeCK.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BH0zOxLN.js → omit-ClWW8eTP.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-D9di8_In.js → pickAttrs-Vystq3R8.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BDSngufM.js → placementArrow-D0H9hK4W.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-C_EawMvP.js → responsiveObserve-B3iNRU-a.js} +1 -1
- package/src/assets/web-panel/assets/{slide-B_4wShxA.js → slide-CiMXe6am.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CbQu6qJ-.js → statusUtils-CGDZJ1uT.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CvXJWYXb.js → styleChecker-D2zA6NkY.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-jjY1XknO.js → useFlexGapSupport-C1sYU6QE.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CMXBdsK_.js → useFs-BzUHSjg2.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-YbAhhPXx.js → usePersonalDataHub-CxB5yZCq.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BFCTKc0H.js → vnode-BfUfMwtc.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CY9aBl5O.js → zoom-CFYmj1TO.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/agent.js +80 -2
- package/src/commands/evolution.js +33 -27
- package/src/commands/serve.js +11 -2
- package/src/data/changelog.json +5 -5
- package/src/gateways/ws/session-protocol.js +1 -0
- package/src/gateways/ws/ws-agent-handler.js +70 -2
- package/src/gateways/ws/ws-server.js +8 -0
- package/src/lib/app-server/cli-agent-kernel-adapter.js +4 -0
- package/src/lib/app-server/graph-runtime.js +115 -8
- package/src/lib/app-server/server.js +21 -2
- package/src/lib/app-server/websocket-transport.js +2 -0
- package/src/lib/evolution/agent-evolution-ingress.js +300 -0
- package/src/lib/evolution/agent-evolution-runtime-composition-brand.js +22 -0
- package/src/lib/evolution/agent-evolution-runtime-composition.js +369 -0
- package/src/lib/evolution/evidence-backed-wiki-maintainer.js +752 -0
- package/src/lib/evolution/evolution-artifact-ports.js +48 -0
- package/src/lib/evolution/evolution-eval-gate.js +11 -4
- package/src/lib/evolution/evolution-evidence-artifact-adapter.js +499 -0
- package/src/lib/evolution/evolution-evidence-projector.js +61 -1
- package/src/lib/evolution/evolution-file-witness.js +601 -0
- package/src/lib/evolution/evolution-ledger-file-backend.js +176 -0
- package/src/lib/evolution/evolution-ledger-ports.js +353 -16
- package/src/lib/evolution/evolution-ledger.js +1355 -54
- package/src/lib/evolution/evolution-run-ledger-adapter.js +409 -0
- package/src/lib/evolution/evolution-run-wiki-maintenance-source.js +300 -0
- package/src/lib/evolution/scheduled-batch-wiki-maintenance-source.js +368 -0
- package/src/lib/evolution/skill-candidate-registry.js +649 -0
- package/src/lib/evolution/skill-promotion-controller.js +109 -38
- package/src/lib/evolution/skill-promotion-review-ledger-adapter.js +451 -0
- package/src/lib/evolution/skill-promotion-review.js +793 -0
- package/src/lib/evolution/skill-registry-transition-capability.js +55 -0
- package/src/lib/evolution/skill-registry-transition-ledger-adapter.js +1280 -0
- package/src/lib/evolution/skill-registry-transition-source.js +372 -0
- package/src/lib/evolution/skill-release-registry.js +2191 -11
- package/src/lib/evolution/skill-target-matrix-eval.js +133 -10
- package/src/lib/evolution/structured-memory-agent-control-plane.js +222 -0
- package/src/lib/evolution/structured-memory-authority-ledger-adapter.js +351 -0
- package/src/lib/evolution/structured-memory-ledger-adapter.js +220 -0
- package/src/lib/evolution/structured-memory-policy-receipt-writer.js +164 -0
- package/src/lib/evolution/structured-memory-post-compact-hook.js +87 -0
- package/src/lib/evolution/structured-memory-promotion-receipt-writer.js +221 -0
- package/src/lib/evolution/structured-memory-semantic-review-pipeline.js +313 -0
- package/src/lib/evolution/wiki-maintainer-ledger-adapter.js +200 -0
- package/src/lib/evolution/wiki-maintenance-trigger-ledger-adapter.js +687 -0
- package/src/lib/evolution-system.js +65 -30
- package/src/lib/mcp-lifecycle-authority.js +21 -2
- package/src/lib/remote-approval-bridge.js +24 -0
- package/src/lib/scheduler-kernel/store.js +62 -14
- package/src/repl/agent-repl.js +45 -0
- package/src/runtime/agent-runtime.js +114 -1
- package/src/runtime/headless-runner.js +19 -0
- package/src/runtime/headless-stream.js +43 -0
- package/src/runtime/runtime-factory.js +19 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-ZRE0OKbj.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-DvV2btIr.js +0 -7
- package/src/assets/web-panel/assets/devWarning-BztaXF84.js +0 -1
- package/src/assets/web-panel/assets/index-4jP_hG_4.js +0 -1
- package/src/assets/web-panel/assets/index-zxwuAXL5.js +0 -1
- package/src/lib/learning/learning-hooks.js +0 -123
|
@@ -26,6 +26,14 @@ export const EVOLUTION_LEDGER_WITNESS_ANCESTRY_SCHEMA =
|
|
|
26
26
|
"chainlesschain.evolution-ledger-witness-ancestry/v1";
|
|
27
27
|
export const EVOLUTION_LEDGER_STORE_MARKER_SCHEMA =
|
|
28
28
|
"chainlesschain.evolution-ledger-store-marker/v1";
|
|
29
|
+
export const EVOLUTION_LEDGER_AUDIT_EXPORT_SCHEMA =
|
|
30
|
+
"chainlesschain.evolution-ledger-audit-export/v1";
|
|
31
|
+
export const EVOLUTION_LEDGER_AUDIT_ARTIFACT_SCHEMA =
|
|
32
|
+
"chainlesschain.evolution-ledger-audit-artifact/v1";
|
|
33
|
+
export const EVOLUTION_LEDGER_AUDIT_VERIFICATION_SCHEMA =
|
|
34
|
+
"chainlesschain.evolution-ledger-audit-verification/v1";
|
|
35
|
+
export const EVOLUTION_LEDGER_STATE_SNAPSHOT_SCHEMA =
|
|
36
|
+
"chainlesschain.evolution-ledger-state-snapshot/v1";
|
|
29
37
|
export const EVOLUTION_ARTIFACT_REF_SCHEMA =
|
|
30
38
|
"chainlesschain.content-addressed-artifact-ref/v1";
|
|
31
39
|
export const EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA =
|
|
@@ -52,9 +60,15 @@ const DISCARD_ACCUMULATOR_DOMAIN =
|
|
|
52
60
|
"chainlesschain.evolution-witness-discard-accumulator/v1\0";
|
|
53
61
|
const ARTIFACT_VALIDATION_DOMAIN =
|
|
54
62
|
"chainlesschain.evolution-artifact-validation/v1\0";
|
|
63
|
+
const AUDIT_EXPORT_DOMAIN = "chainlesschain.evolution-ledger-audit-export/v1\0";
|
|
64
|
+
const STATE_SNAPSHOT_DOMAIN =
|
|
65
|
+
"chainlesschain.evolution-ledger-state-snapshot/v1\0";
|
|
66
|
+
const STATE_SNAPSHOT_CONTENT_DOMAIN =
|
|
67
|
+
"chainlesschain.evolution-ledger-state-snapshot-content/v1\0";
|
|
55
68
|
const IDENTITY_FILE_NAME = "identity-v1.json";
|
|
56
69
|
const STORE_MARKER_FILE_NAME = "store-marker-v1.json";
|
|
57
70
|
const HEAD_FILE_NAME = "head-v1.json";
|
|
71
|
+
const STATE_SNAPSHOT_FILE_PREFIX = "state-snapshot-v1-";
|
|
58
72
|
const SEGMENT_DIRECTORY_NAME = "segments-v1";
|
|
59
73
|
const ANCHOR_DIRECTORY_NAME = "head-anchors-v1";
|
|
60
74
|
const LOCK_TARGET_NAME = "ledger-v2";
|
|
@@ -68,10 +82,15 @@ const SIGNATURE_VALUE_PATTERN = /^[A-Za-z0-9_-]{22,8192}$/u;
|
|
|
68
82
|
const RANDOM_TOKEN_PATTERN = /^[A-Za-z0-9-]{16,128}$/u;
|
|
69
83
|
const SEGMENT_FILE_PATTERN = /^(\d{12})-([a-f0-9]{64})\.json$/u;
|
|
70
84
|
const ANCHOR_FILE_PATTERN = /^(\d{12})-([a-f0-9]{64})\.json$/u;
|
|
85
|
+
const STATE_SNAPSHOT_FILE_PATTERN = /^state-snapshot-v1-([a-f0-9]{64})\.json$/u;
|
|
71
86
|
const STAGE_FILE_PATTERN = /^\.stage-(.+)\.([A-Za-z0-9-]{16,128})\.tmp$/u;
|
|
72
87
|
const HEAD_STAGE_FILE_PATTERN =
|
|
73
88
|
/^\.replace-head-v1\.json\.([A-Za-z0-9-]{16,128})\.tmp$/u;
|
|
74
89
|
const MAX_SOURCE_REFS = 256;
|
|
90
|
+
const MAX_AUDIT_ARTIFACT_RECORDS = 1_000_000;
|
|
91
|
+
const MAX_LEDGER_BATCH_QUERIES = 10_000;
|
|
92
|
+
const MAX_STATE_SNAPSHOT_BYTES = 256 * 1024 * 1024;
|
|
93
|
+
const STATE_QUERY_INDEXES = new WeakMap();
|
|
75
94
|
const TYPED_ARRAY_PROTOTYPE = Object.getPrototypeOf(Uint8Array.prototype);
|
|
76
95
|
const TYPED_ARRAY_BUFFER_GETTER = Object.getOwnPropertyDescriptor(
|
|
77
96
|
TYPED_ARRAY_PROTOTYPE,
|
|
@@ -371,6 +390,67 @@ const STORE_MARKER_RECORD_KEYS = new Set([
|
|
|
371
390
|
const QUERY_SELECTOR_KEYS = new Set(["eventDigest", "eventId", "sequence"]);
|
|
372
391
|
const QUERY_OPTION_KEYS = new Set(["issueReceipt"]);
|
|
373
392
|
const VERIFY_RECEIPT_OPTION_KEYS = new Set(["requireCurrentHead"]);
|
|
393
|
+
const AUDIT_EXPORT_CORE_KEYS = new Set([
|
|
394
|
+
"anchors",
|
|
395
|
+
"artifacts",
|
|
396
|
+
"events",
|
|
397
|
+
"exportedAt",
|
|
398
|
+
"identity",
|
|
399
|
+
"schema",
|
|
400
|
+
"storeMarker",
|
|
401
|
+
"witness",
|
|
402
|
+
]);
|
|
403
|
+
const AUDIT_EXPORT_RECORD_KEYS = new Set([
|
|
404
|
+
...AUDIT_EXPORT_CORE_KEYS,
|
|
405
|
+
"exportDigest",
|
|
406
|
+
]);
|
|
407
|
+
const AUDIT_ARTIFACT_KEYS = new Set([
|
|
408
|
+
"artifactRef",
|
|
409
|
+
"byteLength",
|
|
410
|
+
"bytesBase64",
|
|
411
|
+
"eventSequence",
|
|
412
|
+
"receiptDigest",
|
|
413
|
+
"schema",
|
|
414
|
+
"tenantId",
|
|
415
|
+
]);
|
|
416
|
+
const AUDIT_VERIFY_PORT_KEYS = new Set([
|
|
417
|
+
"trust",
|
|
418
|
+
"verifySignature",
|
|
419
|
+
"verifyWitnessSignature",
|
|
420
|
+
"witnessTrust",
|
|
421
|
+
]);
|
|
422
|
+
const STATE_SNAPSHOT_CORE_KEYS = new Set([
|
|
423
|
+
"algorithm",
|
|
424
|
+
"anchorDigest",
|
|
425
|
+
"createdAt",
|
|
426
|
+
"epoch",
|
|
427
|
+
"headDigest",
|
|
428
|
+
"identityDigest",
|
|
429
|
+
"keyId",
|
|
430
|
+
"ledgerId",
|
|
431
|
+
"schema",
|
|
432
|
+
"sequence",
|
|
433
|
+
"stateDigest",
|
|
434
|
+
"storeMarkerDigest",
|
|
435
|
+
"storeMarkerEntryDigest",
|
|
436
|
+
"trustPolicyDigest",
|
|
437
|
+
"witnessDigest",
|
|
438
|
+
"witnessGeneration",
|
|
439
|
+
"witnessId",
|
|
440
|
+
]);
|
|
441
|
+
const STATE_SNAPSHOT_RECORD_KEYS = new Set([
|
|
442
|
+
...STATE_SNAPSHOT_CORE_KEYS,
|
|
443
|
+
"signature",
|
|
444
|
+
"snapshotDigest",
|
|
445
|
+
"state",
|
|
446
|
+
]);
|
|
447
|
+
const STATE_SNAPSHOT_CONTENT_KEYS = new Set([
|
|
448
|
+
"anchorFiles",
|
|
449
|
+
"anchors",
|
|
450
|
+
"events",
|
|
451
|
+
"segmentFiles",
|
|
452
|
+
]);
|
|
453
|
+
const STATE_SNAPSHOT_FILE_KEYS = new Set(["contentDigest", "name"]);
|
|
374
454
|
|
|
375
455
|
const REQUIRED_FS_METHODS = Object.freeze([
|
|
376
456
|
"closeSync",
|
|
@@ -1236,6 +1316,42 @@ function normalizeAnchorCore(value) {
|
|
|
1236
1316
|
};
|
|
1237
1317
|
}
|
|
1238
1318
|
|
|
1319
|
+
function normalizeStateSnapshotCore(value) {
|
|
1320
|
+
assertExactKeys(value, STATE_SNAPSHOT_CORE_KEYS, "state snapshot core");
|
|
1321
|
+
if (value.schema !== EVOLUTION_LEDGER_STATE_SNAPSHOT_SCHEMA) {
|
|
1322
|
+
throw ledgerError(
|
|
1323
|
+
"CC_EVOLUTION_LEDGER_SCHEMA_INVALID",
|
|
1324
|
+
"state snapshot schema is unsupported",
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
return {
|
|
1328
|
+
algorithm: boundedString(value.algorithm, "algorithm", 64),
|
|
1329
|
+
anchorDigest: digest(value.anchorDigest, "anchorDigest"),
|
|
1330
|
+
createdAt: canonicalTimestamp(value.createdAt, "createdAt"),
|
|
1331
|
+
epoch: identifier(value.epoch, "epoch"),
|
|
1332
|
+
headDigest: digest(value.headDigest, "headDigest", { nullable: true }),
|
|
1333
|
+
identityDigest: digest(value.identityDigest, "identityDigest"),
|
|
1334
|
+
keyId: boundedString(value.keyId, "keyId", 256),
|
|
1335
|
+
ledgerId: identifier(value.ledgerId, "ledgerId"),
|
|
1336
|
+
schema: EVOLUTION_LEDGER_STATE_SNAPSHOT_SCHEMA,
|
|
1337
|
+
sequence: safeSequence(value.sequence, "sequence", { allowZero: true }),
|
|
1338
|
+
stateDigest: digest(value.stateDigest, "stateDigest"),
|
|
1339
|
+
storeMarkerDigest: digest(value.storeMarkerDigest, "storeMarkerDigest"),
|
|
1340
|
+
storeMarkerEntryDigest: digest(
|
|
1341
|
+
value.storeMarkerEntryDigest,
|
|
1342
|
+
"storeMarkerEntryDigest",
|
|
1343
|
+
),
|
|
1344
|
+
trustPolicyDigest: digest(value.trustPolicyDigest, "trustPolicyDigest"),
|
|
1345
|
+
witnessDigest: digest(value.witnessDigest, "witnessDigest"),
|
|
1346
|
+
witnessGeneration: safeSequence(
|
|
1347
|
+
value.witnessGeneration,
|
|
1348
|
+
"witnessGeneration",
|
|
1349
|
+
{ allowZero: true },
|
|
1350
|
+
),
|
|
1351
|
+
witnessId: identifier(value.witnessId, "witnessId"),
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1239
1355
|
function normalizeReceiptCore(value, trust, witnessTrust) {
|
|
1240
1356
|
assertExactKeys(value, RECEIPT_CORE_KEYS, "receipt core");
|
|
1241
1357
|
if (value.schema !== EVOLUTION_LEDGER_RECEIPT_SCHEMA) {
|
|
@@ -1676,6 +1792,10 @@ function anchorFileName(sequence, anchorDigest) {
|
|
|
1676
1792
|
return `${sequenceName(sequence)}-${anchorDigest.slice("sha256:".length)}.json`;
|
|
1677
1793
|
}
|
|
1678
1794
|
|
|
1795
|
+
function stateSnapshotFileName(witnessDigest) {
|
|
1796
|
+
return `${STATE_SNAPSHOT_FILE_PREFIX}${witnessDigest.slice("sha256:".length)}.json`;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1679
1799
|
function defaultRootDir() {
|
|
1680
1800
|
return path.join(getHomeDir(), "evolution", "ledger", "events");
|
|
1681
1801
|
}
|
|
@@ -2179,7 +2299,7 @@ export class EvolutionLedger {
|
|
|
2179
2299
|
return deepFreeze({ ...core, signature, storeMarkerDigest });
|
|
2180
2300
|
}
|
|
2181
2301
|
|
|
2182
|
-
#verifyEvent(record, previous, identity) {
|
|
2302
|
+
#verifyEvent(record, previous, identity, { verifySignature = true } = {}) {
|
|
2183
2303
|
assertPlainDataObject(record, "event record");
|
|
2184
2304
|
const schema = Object.getOwnPropertyDescriptor(record, "schema")?.value;
|
|
2185
2305
|
const domainEvent = schema === EVOLUTION_LEDGER_DOMAIN_EVENT_SCHEMA;
|
|
@@ -2210,16 +2330,23 @@ export class EvolutionLedger {
|
|
|
2210
2330
|
{ sequence: core.sequence },
|
|
2211
2331
|
);
|
|
2212
2332
|
}
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2333
|
+
if (verifySignature) {
|
|
2334
|
+
this.#verifyMessage({
|
|
2335
|
+
digest: eventDigest,
|
|
2336
|
+
message,
|
|
2337
|
+
purpose: domainEvent ? "domain-event" : "event",
|
|
2338
|
+
signature,
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2219
2341
|
return deepFreeze({ ...core, eventDigest, signature });
|
|
2220
2342
|
}
|
|
2221
2343
|
|
|
2222
|
-
#verifyAnchor(
|
|
2344
|
+
#verifyAnchor(
|
|
2345
|
+
record,
|
|
2346
|
+
previous,
|
|
2347
|
+
identity,
|
|
2348
|
+
{ standalone = false, verifySignature = true } = {},
|
|
2349
|
+
) {
|
|
2223
2350
|
assertExactKeys(record, ANCHOR_RECORD_KEYS, "anchor record");
|
|
2224
2351
|
const core = normalizeAnchorCore(
|
|
2225
2352
|
Object.fromEntries(
|
|
@@ -2247,12 +2374,14 @@ export class EvolutionLedger {
|
|
|
2247
2374
|
{ sequence: core.sequence },
|
|
2248
2375
|
);
|
|
2249
2376
|
}
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2377
|
+
if (verifySignature) {
|
|
2378
|
+
this.#verifyMessage({
|
|
2379
|
+
digest: anchorDigest,
|
|
2380
|
+
message,
|
|
2381
|
+
purpose: "anchor",
|
|
2382
|
+
signature,
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2256
2385
|
return deepFreeze({ ...core, anchorDigest, signature });
|
|
2257
2386
|
}
|
|
2258
2387
|
|
|
@@ -3004,7 +3133,10 @@ export class EvolutionLedger {
|
|
|
3004
3133
|
this.#cleanupDirectoryDebris(this.#paths.anchorDir, ANCHOR_FILE_PATTERN);
|
|
3005
3134
|
this.#cleanupDirectoryDebris(
|
|
3006
3135
|
this.#paths.authorityRootDir,
|
|
3007
|
-
new RegExp(
|
|
3136
|
+
new RegExp(
|
|
3137
|
+
`^(?:${IDENTITY_FILE_NAME.replace(".", "\\.")}|${STATE_SNAPSHOT_FILE_PATTERN.source.slice(1, -1)})$`,
|
|
3138
|
+
"u",
|
|
3139
|
+
),
|
|
3008
3140
|
);
|
|
3009
3141
|
this.#cleanupDirectoryDebris(
|
|
3010
3142
|
this.#paths.rootDir,
|
|
@@ -3249,6 +3381,331 @@ export class EvolutionLedger {
|
|
|
3249
3381
|
}
|
|
3250
3382
|
}
|
|
3251
3383
|
|
|
3384
|
+
#snapshotFileRecords(values, label, expectedNames) {
|
|
3385
|
+
const entries = readDenseDataArray(
|
|
3386
|
+
values,
|
|
3387
|
+
label,
|
|
3388
|
+
EVOLUTION_LEDGER_MAX_EVENTS + 1,
|
|
3389
|
+
);
|
|
3390
|
+
if (entries.length !== expectedNames.length) {
|
|
3391
|
+
throw ledgerError(
|
|
3392
|
+
"CC_EVOLUTION_LEDGER_CORRUPT",
|
|
3393
|
+
`${label} does not cover the current immutable prefix`,
|
|
3394
|
+
);
|
|
3395
|
+
}
|
|
3396
|
+
return entries.map((entry, index) => {
|
|
3397
|
+
assertExactKeys(entry, STATE_SNAPSHOT_FILE_KEYS, `${label} entry`);
|
|
3398
|
+
const normalized = {
|
|
3399
|
+
contentDigest: digest(entry.contentDigest, "contentDigest"),
|
|
3400
|
+
name: boundedString(entry.name, "name", 256),
|
|
3401
|
+
};
|
|
3402
|
+
if (normalized.name !== expectedNames[index]) {
|
|
3403
|
+
throw ledgerError(
|
|
3404
|
+
"CC_EVOLUTION_LEDGER_CORRUPT",
|
|
3405
|
+
`${label} is missing or reordered at index ${index}`,
|
|
3406
|
+
);
|
|
3407
|
+
}
|
|
3408
|
+
return Object.freeze(normalized);
|
|
3409
|
+
});
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
#verifyStateSnapshot(
|
|
3413
|
+
record,
|
|
3414
|
+
identity,
|
|
3415
|
+
storeMarker,
|
|
3416
|
+
witnessed,
|
|
3417
|
+
head,
|
|
3418
|
+
anchorNames,
|
|
3419
|
+
segmentNames,
|
|
3420
|
+
) {
|
|
3421
|
+
assertExactKeys(record, STATE_SNAPSHOT_RECORD_KEYS, "state snapshot");
|
|
3422
|
+
const core = normalizeStateSnapshotCore(
|
|
3423
|
+
Object.fromEntries(
|
|
3424
|
+
[...STATE_SNAPSHOT_CORE_KEYS].map((key) => [key, record[key]]),
|
|
3425
|
+
),
|
|
3426
|
+
);
|
|
3427
|
+
const snapshotDigest = digest(record.snapshotDigest, "snapshotDigest");
|
|
3428
|
+
const signature = normalizeSignature(record.signature, this.#trust);
|
|
3429
|
+
const message = signedMessage(STATE_SNAPSHOT_DOMAIN, core);
|
|
3430
|
+
if (snapshotDigest !== sha256(message)) {
|
|
3431
|
+
throw ledgerError(
|
|
3432
|
+
"CC_EVOLUTION_LEDGER_CORRUPT",
|
|
3433
|
+
"state snapshot digest does not match its signed core",
|
|
3434
|
+
);
|
|
3435
|
+
}
|
|
3436
|
+
this.#verifyMessage({
|
|
3437
|
+
digest: snapshotDigest,
|
|
3438
|
+
message,
|
|
3439
|
+
purpose: "state-snapshot",
|
|
3440
|
+
signature,
|
|
3441
|
+
});
|
|
3442
|
+
if (
|
|
3443
|
+
core.algorithm !== this.#trust.algorithm ||
|
|
3444
|
+
core.keyId !== this.#trust.keyId ||
|
|
3445
|
+
core.trustPolicyDigest !== this.#trust.trustPolicyDigest ||
|
|
3446
|
+
core.ledgerId !== identity.ledgerId ||
|
|
3447
|
+
core.epoch !== identity.epoch ||
|
|
3448
|
+
core.identityDigest !== identity.identityDigest ||
|
|
3449
|
+
core.storeMarkerDigest !== storeMarker.storeMarkerDigest ||
|
|
3450
|
+
core.storeMarkerEntryDigest !== identity.storeMarkerEntryDigest ||
|
|
3451
|
+
core.witnessId !== witnessed.witnessId ||
|
|
3452
|
+
core.witnessDigest !== witnessed.witnessDigest ||
|
|
3453
|
+
core.witnessGeneration !== witnessed.generation ||
|
|
3454
|
+
core.sequence !== witnessed.sequence ||
|
|
3455
|
+
core.sequence !== head.sequence ||
|
|
3456
|
+
core.anchorDigest !== head.anchorDigest ||
|
|
3457
|
+
core.headDigest !== head.headDigest
|
|
3458
|
+
) {
|
|
3459
|
+
throw ledgerError(
|
|
3460
|
+
"CC_EVOLUTION_LEDGER_CORRUPT",
|
|
3461
|
+
"state snapshot is stale or not bound to the current authority",
|
|
3462
|
+
);
|
|
3463
|
+
}
|
|
3464
|
+
|
|
3465
|
+
assertExactKeys(
|
|
3466
|
+
record.state,
|
|
3467
|
+
STATE_SNAPSHOT_CONTENT_KEYS,
|
|
3468
|
+
"state snapshot content",
|
|
3469
|
+
);
|
|
3470
|
+
if (
|
|
3471
|
+
core.stateDigest !==
|
|
3472
|
+
domainDigest(STATE_SNAPSHOT_CONTENT_DOMAIN, record.state)
|
|
3473
|
+
) {
|
|
3474
|
+
throw ledgerError(
|
|
3475
|
+
"CC_EVOLUTION_LEDGER_CORRUPT",
|
|
3476
|
+
"state snapshot content digest does not match its signed core",
|
|
3477
|
+
);
|
|
3478
|
+
}
|
|
3479
|
+
const anchorRecords = readDenseDataArray(
|
|
3480
|
+
record.state.anchors,
|
|
3481
|
+
"state snapshot anchors",
|
|
3482
|
+
EVOLUTION_LEDGER_MAX_EVENTS + 1,
|
|
3483
|
+
);
|
|
3484
|
+
const eventRecords = readDenseDataArray(
|
|
3485
|
+
record.state.events,
|
|
3486
|
+
"state snapshot events",
|
|
3487
|
+
EVOLUTION_LEDGER_MAX_EVENTS,
|
|
3488
|
+
);
|
|
3489
|
+
if (
|
|
3490
|
+
anchorRecords.length !== eventRecords.length + 1 ||
|
|
3491
|
+
eventRecords.length !== core.sequence
|
|
3492
|
+
) {
|
|
3493
|
+
throw ledgerError(
|
|
3494
|
+
"CC_EVOLUTION_LEDGER_CORRUPT",
|
|
3495
|
+
"state snapshot chain length does not match its authority checkpoint",
|
|
3496
|
+
);
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
const anchors = [];
|
|
3500
|
+
const events = [];
|
|
3501
|
+
const eventIds = new Set();
|
|
3502
|
+
for (let index = 0; index < anchorRecords.length; index += 1) {
|
|
3503
|
+
const anchor = this.#verifyAnchor(
|
|
3504
|
+
anchorRecords[index],
|
|
3505
|
+
anchors.at(-1),
|
|
3506
|
+
identity,
|
|
3507
|
+
{ verifySignature: false },
|
|
3508
|
+
);
|
|
3509
|
+
anchors.push(anchor);
|
|
3510
|
+
if (index === 0) continue;
|
|
3511
|
+
const event = this.#verifyEvent(
|
|
3512
|
+
eventRecords[index - 1],
|
|
3513
|
+
events.at(-1),
|
|
3514
|
+
identity,
|
|
3515
|
+
{ verifySignature: false },
|
|
3516
|
+
);
|
|
3517
|
+
if (
|
|
3518
|
+
event.sequence !== anchor.sequence ||
|
|
3519
|
+
event.eventDigest !== anchor.headDigest ||
|
|
3520
|
+
domainDigest(SEGMENT_DOMAIN, event) !== anchor.segmentDigest ||
|
|
3521
|
+
eventIds.has(event.eventId)
|
|
3522
|
+
) {
|
|
3523
|
+
throw ledgerError(
|
|
3524
|
+
"CC_EVOLUTION_LEDGER_CORRUPT",
|
|
3525
|
+
`state snapshot event/anchor binding failed at sequence ${index}`,
|
|
3526
|
+
);
|
|
3527
|
+
}
|
|
3528
|
+
eventIds.add(event.eventId);
|
|
3529
|
+
events.push(event);
|
|
3530
|
+
}
|
|
3531
|
+
if (anchors.at(-1).anchorDigest !== core.anchorDigest) {
|
|
3532
|
+
throw ledgerError(
|
|
3533
|
+
"CC_EVOLUTION_LEDGER_CORRUPT",
|
|
3534
|
+
"state snapshot chain does not terminate at its signed anchor",
|
|
3535
|
+
);
|
|
3536
|
+
}
|
|
3537
|
+
const derivedAnchorNames = anchors.map((anchor) =>
|
|
3538
|
+
anchorFileName(anchor.sequence, anchor.anchorDigest),
|
|
3539
|
+
);
|
|
3540
|
+
const derivedSegmentNames = anchors
|
|
3541
|
+
.slice(1)
|
|
3542
|
+
.map((anchor) => segmentFileName(anchor.sequence, anchor.segmentDigest));
|
|
3543
|
+
if (
|
|
3544
|
+
derivedAnchorNames.some((name, index) => name !== anchorNames[index]) ||
|
|
3545
|
+
derivedSegmentNames.some((name, index) => name !== segmentNames[index])
|
|
3546
|
+
) {
|
|
3547
|
+
throw ledgerError(
|
|
3548
|
+
"CC_EVOLUTION_LEDGER_CORRUPT",
|
|
3549
|
+
"state snapshot records do not address the current immutable files",
|
|
3550
|
+
);
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
const anchorFiles = this.#snapshotFileRecords(
|
|
3554
|
+
record.state.anchorFiles,
|
|
3555
|
+
"state snapshot anchor files",
|
|
3556
|
+
anchorNames,
|
|
3557
|
+
);
|
|
3558
|
+
const segmentFiles = this.#snapshotFileRecords(
|
|
3559
|
+
record.state.segmentFiles,
|
|
3560
|
+
"state snapshot segment files",
|
|
3561
|
+
segmentNames,
|
|
3562
|
+
);
|
|
3563
|
+
const anchorFingerprints = {};
|
|
3564
|
+
const segmentFingerprints = {};
|
|
3565
|
+
for (let index = 0; index < anchorFiles.length; index += 1) {
|
|
3566
|
+
const entry = anchorFiles[index];
|
|
3567
|
+
const snapshotRecordDigest = sha256(
|
|
3568
|
+
Buffer.from(`${canonicalJson(anchorRecords[index])}\n`, "utf8"),
|
|
3569
|
+
);
|
|
3570
|
+
if (entry.contentDigest !== snapshotRecordDigest) {
|
|
3571
|
+
throw ledgerError(
|
|
3572
|
+
"CC_EVOLUTION_LEDGER_CORRUPT",
|
|
3573
|
+
`state snapshot anchor bytes are not bound at index ${index}`,
|
|
3574
|
+
);
|
|
3575
|
+
}
|
|
3576
|
+
const file = this.#readCanonicalFile(
|
|
3577
|
+
path.join(this.#paths.anchorDir, entry.name),
|
|
3578
|
+
64 * 1024,
|
|
3579
|
+
`state snapshot anchor file ${entry.name}`,
|
|
3580
|
+
);
|
|
3581
|
+
if (file.contentDigest !== entry.contentDigest) {
|
|
3582
|
+
throw ledgerError(
|
|
3583
|
+
"CC_EVOLUTION_LEDGER_CORRUPT",
|
|
3584
|
+
`state snapshot anchor file changed: ${entry.name}`,
|
|
3585
|
+
);
|
|
3586
|
+
}
|
|
3587
|
+
anchorFingerprints[entry.name] = Object.freeze({
|
|
3588
|
+
contentDigest: file.contentDigest,
|
|
3589
|
+
fingerprint: file.fingerprint,
|
|
3590
|
+
});
|
|
3591
|
+
}
|
|
3592
|
+
for (let index = 0; index < segmentFiles.length; index += 1) {
|
|
3593
|
+
const entry = segmentFiles[index];
|
|
3594
|
+
const snapshotRecordDigest = sha256(
|
|
3595
|
+
Buffer.from(`${canonicalJson(eventRecords[index])}\n`, "utf8"),
|
|
3596
|
+
);
|
|
3597
|
+
if (entry.contentDigest !== snapshotRecordDigest) {
|
|
3598
|
+
throw ledgerError(
|
|
3599
|
+
"CC_EVOLUTION_LEDGER_CORRUPT",
|
|
3600
|
+
`state snapshot event bytes are not bound at index ${index}`,
|
|
3601
|
+
);
|
|
3602
|
+
}
|
|
3603
|
+
const file = this.#readCanonicalFile(
|
|
3604
|
+
path.join(this.#paths.segmentDir, entry.name),
|
|
3605
|
+
EVOLUTION_LEDGER_MAX_EVENT_BYTES,
|
|
3606
|
+
`state snapshot segment file ${entry.name}`,
|
|
3607
|
+
);
|
|
3608
|
+
if (file.contentDigest !== entry.contentDigest) {
|
|
3609
|
+
throw ledgerError(
|
|
3610
|
+
"CC_EVOLUTION_LEDGER_CORRUPT",
|
|
3611
|
+
`state snapshot segment file changed: ${entry.name}`,
|
|
3612
|
+
);
|
|
3613
|
+
}
|
|
3614
|
+
segmentFingerprints[entry.name] = Object.freeze({
|
|
3615
|
+
contentDigest: file.contentDigest,
|
|
3616
|
+
fingerprint: file.fingerprint,
|
|
3617
|
+
});
|
|
3618
|
+
}
|
|
3619
|
+
|
|
3620
|
+
const maximumAnchor = anchors.at(-1);
|
|
3621
|
+
const maximumEvent = events.at(-1) || null;
|
|
3622
|
+
this.#assertWitnessSnapshot(
|
|
3623
|
+
witnessed,
|
|
3624
|
+
identity,
|
|
3625
|
+
storeMarker,
|
|
3626
|
+
maximumAnchor,
|
|
3627
|
+
maximumEvent,
|
|
3628
|
+
);
|
|
3629
|
+
const state = deepFreeze({
|
|
3630
|
+
anchors,
|
|
3631
|
+
events,
|
|
3632
|
+
head,
|
|
3633
|
+
identity,
|
|
3634
|
+
storeMarker,
|
|
3635
|
+
witness: witnessed,
|
|
3636
|
+
});
|
|
3637
|
+
return this.#rememberState(
|
|
3638
|
+
state,
|
|
3639
|
+
anchorNames,
|
|
3640
|
+
segmentNames,
|
|
3641
|
+
anchorFingerprints,
|
|
3642
|
+
segmentFingerprints,
|
|
3643
|
+
);
|
|
3644
|
+
}
|
|
3645
|
+
|
|
3646
|
+
#tryStateSnapshot(
|
|
3647
|
+
identity,
|
|
3648
|
+
storeMarker,
|
|
3649
|
+
witnessed,
|
|
3650
|
+
head,
|
|
3651
|
+
anchorNames,
|
|
3652
|
+
segmentNames,
|
|
3653
|
+
) {
|
|
3654
|
+
if (witnessed.status !== "committed") return null;
|
|
3655
|
+
const snapshotPath = path.join(
|
|
3656
|
+
this.#paths.authorityRootDir,
|
|
3657
|
+
stateSnapshotFileName(witnessed.witnessDigest),
|
|
3658
|
+
);
|
|
3659
|
+
try {
|
|
3660
|
+
const snapshotFile = this.#readCanonicalFile(
|
|
3661
|
+
snapshotPath,
|
|
3662
|
+
MAX_STATE_SNAPSHOT_BYTES,
|
|
3663
|
+
"state snapshot",
|
|
3664
|
+
{ allowMissing: true },
|
|
3665
|
+
);
|
|
3666
|
+
if (!snapshotFile) return null;
|
|
3667
|
+
return this.#verifyStateSnapshot(
|
|
3668
|
+
snapshotFile.record,
|
|
3669
|
+
identity,
|
|
3670
|
+
storeMarker,
|
|
3671
|
+
witnessed,
|
|
3672
|
+
head,
|
|
3673
|
+
anchorNames,
|
|
3674
|
+
segmentNames,
|
|
3675
|
+
);
|
|
3676
|
+
} catch (cause) {
|
|
3677
|
+
if (cause?.code === "CC_EVOLUTION_LEDGER_STORE_UNSAFE") throw cause;
|
|
3678
|
+
return null;
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
|
|
3682
|
+
#pruneStateSnapshots(currentName) {
|
|
3683
|
+
let changed = false;
|
|
3684
|
+
for (const entry of this.#fs.readdirSync(this.#paths.authorityRootDir, {
|
|
3685
|
+
withFileTypes: true,
|
|
3686
|
+
})) {
|
|
3687
|
+
if (!STATE_SNAPSHOT_FILE_PATTERN.test(entry.name)) continue;
|
|
3688
|
+
const snapshotPath = path.join(this.#paths.authorityRootDir, entry.name);
|
|
3689
|
+
const stat = this.#fs.lstatSync(snapshotPath);
|
|
3690
|
+
if (
|
|
3691
|
+
!entry.isFile() ||
|
|
3692
|
+
entry.isSymbolicLink() ||
|
|
3693
|
+
!stat.isFile() ||
|
|
3694
|
+
stat.isSymbolicLink() ||
|
|
3695
|
+
stat.nlink !== 1
|
|
3696
|
+
) {
|
|
3697
|
+
throw ledgerError(
|
|
3698
|
+
"CC_EVOLUTION_LEDGER_STORE_UNSAFE",
|
|
3699
|
+
`state snapshot retention encountered an unsafe entry: ${entry.name}`,
|
|
3700
|
+
);
|
|
3701
|
+
}
|
|
3702
|
+
if (entry.name === currentName) continue;
|
|
3703
|
+
this.#fs.unlinkSync(snapshotPath);
|
|
3704
|
+
changed = true;
|
|
3705
|
+
}
|
|
3706
|
+
if (changed) syncDirectory(this.#fs, this.#paths.authorityRootDir);
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3252
3709
|
#cachedPrefix(identity, anchorNames, segmentNames, incremental) {
|
|
3253
3710
|
const cached = incremental ? this.#stateCache : null;
|
|
3254
3711
|
if (
|
|
@@ -3291,6 +3748,13 @@ export class EvolutionLedger {
|
|
|
3291
3748
|
anchorFingerprints,
|
|
3292
3749
|
segmentFingerprints,
|
|
3293
3750
|
) {
|
|
3751
|
+
const byEventDigest = new Map();
|
|
3752
|
+
const byEventId = new Map();
|
|
3753
|
+
for (const event of state.events) {
|
|
3754
|
+
byEventDigest.set(event.eventDigest, event);
|
|
3755
|
+
byEventId.set(event.eventId, event);
|
|
3756
|
+
}
|
|
3757
|
+
STATE_QUERY_INDEXES.set(state, Object.freeze({ byEventDigest, byEventId }));
|
|
3294
3758
|
this.#stateCache = Object.freeze({
|
|
3295
3759
|
anchorFingerprints: Object.freeze({ ...anchorFingerprints }),
|
|
3296
3760
|
anchorNames: Object.freeze([...anchorNames]),
|
|
@@ -3416,12 +3880,29 @@ export class EvolutionLedger {
|
|
|
3416
3880
|
);
|
|
3417
3881
|
}
|
|
3418
3882
|
|
|
3883
|
+
const authenticatedHead = this.#verifyAnchor(
|
|
3884
|
+
headFile.record,
|
|
3885
|
+
null,
|
|
3886
|
+
identity,
|
|
3887
|
+
{ standalone: true },
|
|
3888
|
+
);
|
|
3419
3889
|
const cached = this.#cachedPrefix(
|
|
3420
3890
|
identity,
|
|
3421
3891
|
anchorNames,
|
|
3422
3892
|
segmentNames,
|
|
3423
3893
|
incremental,
|
|
3424
3894
|
);
|
|
3895
|
+
if (!cached) {
|
|
3896
|
+
const snapshotted = this.#tryStateSnapshot(
|
|
3897
|
+
identity,
|
|
3898
|
+
storeMarker,
|
|
3899
|
+
witnessed,
|
|
3900
|
+
authenticatedHead,
|
|
3901
|
+
anchorNames,
|
|
3902
|
+
segmentNames,
|
|
3903
|
+
);
|
|
3904
|
+
if (snapshotted) return snapshotted;
|
|
3905
|
+
}
|
|
3425
3906
|
const anchors = cached ? [...cached.state.anchors] : [];
|
|
3426
3907
|
const events = cached ? [...cached.state.events] : [];
|
|
3427
3908
|
const anchorFingerprints = cached ? { ...cached.anchorFingerprints } : {};
|
|
@@ -3536,9 +4017,7 @@ export class EvolutionLedger {
|
|
|
3536
4017
|
}
|
|
3537
4018
|
if (removedOrphan) syncDirectory(this.#fs, this.#paths.segmentDir);
|
|
3538
4019
|
|
|
3539
|
-
const head =
|
|
3540
|
-
standalone: true,
|
|
3541
|
-
});
|
|
4020
|
+
const head = authenticatedHead;
|
|
3542
4021
|
const matchingHead = anchors.find(
|
|
3543
4022
|
(anchor) => anchor.anchorDigest === head.anchorDigest,
|
|
3544
4023
|
);
|
|
@@ -3713,8 +4192,9 @@ export class EvolutionLedger {
|
|
|
3713
4192
|
);
|
|
3714
4193
|
}
|
|
3715
4194
|
|
|
3716
|
-
#
|
|
4195
|
+
#resolveArtifactEvidence(input, identity, { includeBytes = false } = {}) {
|
|
3717
4196
|
const validations = [];
|
|
4197
|
+
const artifacts = [];
|
|
3718
4198
|
const artifactTenantId = Object.hasOwn(input, "artifactTenantId")
|
|
3719
4199
|
? input.artifactTenantId
|
|
3720
4200
|
: input.tenantId;
|
|
@@ -3755,7 +4235,7 @@ export class EvolutionLedger {
|
|
|
3755
4235
|
`artifact bytes do not match their digest: ${ref.ref}`,
|
|
3756
4236
|
);
|
|
3757
4237
|
}
|
|
3758
|
-
|
|
4238
|
+
const validation = {
|
|
3759
4239
|
byteLength: bytes.length,
|
|
3760
4240
|
digest: ref.digest,
|
|
3761
4241
|
receiptDigest: digest(
|
|
@@ -3763,7 +4243,18 @@ export class EvolutionLedger {
|
|
|
3763
4243
|
"artifact resolution receiptDigest",
|
|
3764
4244
|
),
|
|
3765
4245
|
ref: ref.ref,
|
|
3766
|
-
}
|
|
4246
|
+
};
|
|
4247
|
+
validations.push(validation);
|
|
4248
|
+
if (includeBytes) {
|
|
4249
|
+
artifacts.push({
|
|
4250
|
+
artifactRef: ref,
|
|
4251
|
+
byteLength: validation.byteLength,
|
|
4252
|
+
bytesBase64: bytes.toString("base64"),
|
|
4253
|
+
receiptDigest: validation.receiptDigest,
|
|
4254
|
+
schema: EVOLUTION_LEDGER_AUDIT_ARTIFACT_SCHEMA,
|
|
4255
|
+
tenantId: artifactTenantId,
|
|
4256
|
+
});
|
|
4257
|
+
}
|
|
3767
4258
|
} catch (cause) {
|
|
3768
4259
|
if (cause?.code === "CC_EVOLUTION_LEDGER_ARTIFACT_INVALID") {
|
|
3769
4260
|
throw cause;
|
|
@@ -3775,7 +4266,14 @@ export class EvolutionLedger {
|
|
|
3775
4266
|
);
|
|
3776
4267
|
}
|
|
3777
4268
|
}
|
|
3778
|
-
return
|
|
4269
|
+
return {
|
|
4270
|
+
artifacts,
|
|
4271
|
+
digest: domainDigest(ARTIFACT_VALIDATION_DOMAIN, validations),
|
|
4272
|
+
};
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
#resolveArtifacts(input, identity) {
|
|
4276
|
+
return this.#resolveArtifactEvidence(input, identity).digest;
|
|
3779
4277
|
}
|
|
3780
4278
|
|
|
3781
4279
|
#buildEvent(input, previous, identity, artifactValidationDigest) {
|
|
@@ -4078,17 +4576,135 @@ export class EvolutionLedger {
|
|
|
4078
4576
|
}
|
|
4079
4577
|
|
|
4080
4578
|
#queryState(state, selector) {
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
(selector.
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4579
|
+
const indexes = STATE_QUERY_INDEXES.get(state);
|
|
4580
|
+
const event =
|
|
4581
|
+
selector.sequence !== undefined
|
|
4582
|
+
? state.events[selector.sequence - 1] || null
|
|
4583
|
+
: selector.eventId !== undefined
|
|
4584
|
+
? indexes?.byEventId.get(selector.eventId) || null
|
|
4585
|
+
: indexes?.byEventDigest.get(selector.eventDigest) || null;
|
|
4586
|
+
return event &&
|
|
4587
|
+
(selector.eventId === undefined || event.eventId === selector.eventId) &&
|
|
4588
|
+
(selector.eventDigest === undefined ||
|
|
4589
|
+
event.eventDigest === selector.eventDigest) &&
|
|
4590
|
+
(selector.sequence === undefined || event.sequence === selector.sequence)
|
|
4591
|
+
? event
|
|
4592
|
+
: null;
|
|
4593
|
+
}
|
|
4594
|
+
|
|
4595
|
+
#queryResult(state, event, issueReceipt) {
|
|
4596
|
+
if (!event) return null;
|
|
4597
|
+
const anchor = state.anchors[event.sequence];
|
|
4598
|
+
const receipt = issueReceipt
|
|
4599
|
+
? this.#issueReceipt(state, event, anchor, "verified-existing")
|
|
4600
|
+
: null;
|
|
4601
|
+
return deepFreeze({
|
|
4602
|
+
anchor,
|
|
4603
|
+
authenticated: true,
|
|
4604
|
+
authority: this.#authorityProjection(state),
|
|
4605
|
+
durable: true,
|
|
4606
|
+
event,
|
|
4607
|
+
receipt,
|
|
4608
|
+
schema: EVOLUTION_LEDGER_QUERY_SCHEMA,
|
|
4609
|
+
});
|
|
4610
|
+
}
|
|
4611
|
+
|
|
4612
|
+
checkpointState() {
|
|
4613
|
+
return this.#withLock(() => {
|
|
4614
|
+
const state = this.#loadState({
|
|
4615
|
+
allowInitialize: false,
|
|
4616
|
+
incremental: true,
|
|
4617
|
+
});
|
|
4618
|
+
const snapshotPath = path.join(
|
|
4619
|
+
this.#paths.authorityRootDir,
|
|
4620
|
+
stateSnapshotFileName(state.witness.witnessDigest),
|
|
4621
|
+
);
|
|
4622
|
+
const existing = this.#readCanonicalFile(
|
|
4623
|
+
snapshotPath,
|
|
4624
|
+
MAX_STATE_SNAPSHOT_BYTES,
|
|
4625
|
+
"state snapshot",
|
|
4626
|
+
{ allowMissing: true },
|
|
4627
|
+
);
|
|
4628
|
+
if (existing) {
|
|
4629
|
+
this.#verifyStateSnapshot(
|
|
4630
|
+
existing.record,
|
|
4631
|
+
state.identity,
|
|
4632
|
+
state.storeMarker,
|
|
4633
|
+
state.witness,
|
|
4634
|
+
state.head,
|
|
4635
|
+
this.#stateCache.anchorNames,
|
|
4636
|
+
this.#stateCache.segmentNames,
|
|
4637
|
+
);
|
|
4638
|
+
this.#pruneStateSnapshots(path.basename(snapshotPath));
|
|
4639
|
+
return deepFreeze(existing.record);
|
|
4640
|
+
}
|
|
4641
|
+
|
|
4642
|
+
const content = deepFreeze({
|
|
4643
|
+
anchorFiles: this.#stateCache.anchorNames.map((name) => ({
|
|
4644
|
+
contentDigest:
|
|
4645
|
+
this.#stateCache.anchorFingerprints[name].contentDigest,
|
|
4646
|
+
name,
|
|
4647
|
+
})),
|
|
4648
|
+
anchors: state.anchors,
|
|
4649
|
+
events: state.events,
|
|
4650
|
+
segmentFiles: this.#stateCache.segmentNames.map((name) => ({
|
|
4651
|
+
contentDigest:
|
|
4652
|
+
this.#stateCache.segmentFingerprints[name].contentDigest,
|
|
4653
|
+
name,
|
|
4654
|
+
})),
|
|
4655
|
+
});
|
|
4656
|
+
const core = normalizeStateSnapshotCore({
|
|
4657
|
+
algorithm: this.#trust.algorithm,
|
|
4658
|
+
anchorDigest: state.head.anchorDigest,
|
|
4659
|
+
createdAt: clockTimestamp(this.#clock),
|
|
4660
|
+
epoch: state.identity.epoch,
|
|
4661
|
+
headDigest: state.head.headDigest,
|
|
4662
|
+
identityDigest: state.identity.identityDigest,
|
|
4663
|
+
keyId: this.#trust.keyId,
|
|
4664
|
+
ledgerId: state.identity.ledgerId,
|
|
4665
|
+
schema: EVOLUTION_LEDGER_STATE_SNAPSHOT_SCHEMA,
|
|
4666
|
+
sequence: state.head.sequence,
|
|
4667
|
+
stateDigest: domainDigest(STATE_SNAPSHOT_CONTENT_DOMAIN, content),
|
|
4668
|
+
storeMarkerDigest: state.storeMarker.storeMarkerDigest,
|
|
4669
|
+
storeMarkerEntryDigest: state.identity.storeMarkerEntryDigest,
|
|
4670
|
+
trustPolicyDigest: this.#trust.trustPolicyDigest,
|
|
4671
|
+
witnessDigest: state.witness.witnessDigest,
|
|
4672
|
+
witnessGeneration: state.witness.generation,
|
|
4673
|
+
witnessId: state.witness.witnessId,
|
|
4674
|
+
});
|
|
4675
|
+
const signed = this.#signRecord(
|
|
4676
|
+
STATE_SNAPSHOT_DOMAIN,
|
|
4677
|
+
core,
|
|
4678
|
+
"snapshotDigest",
|
|
4679
|
+
"state-snapshot",
|
|
4680
|
+
);
|
|
4681
|
+
const snapshot = deepFreeze({
|
|
4682
|
+
...signed,
|
|
4683
|
+
state: content,
|
|
4684
|
+
});
|
|
4685
|
+
const bytes = serializeRecord(
|
|
4686
|
+
snapshot,
|
|
4687
|
+
MAX_STATE_SNAPSHOT_BYTES,
|
|
4688
|
+
"state snapshot",
|
|
4689
|
+
);
|
|
4690
|
+
this.#writeImmutable(snapshotPath, bytes, "state snapshot");
|
|
4691
|
+
const persisted = this.#readCanonicalFile(
|
|
4692
|
+
snapshotPath,
|
|
4693
|
+
MAX_STATE_SNAPSHOT_BYTES,
|
|
4694
|
+
"state snapshot",
|
|
4695
|
+
);
|
|
4696
|
+
this.#verifyStateSnapshot(
|
|
4697
|
+
persisted.record,
|
|
4698
|
+
state.identity,
|
|
4699
|
+
state.storeMarker,
|
|
4700
|
+
state.witness,
|
|
4701
|
+
state.head,
|
|
4702
|
+
this.#stateCache.anchorNames,
|
|
4703
|
+
this.#stateCache.segmentNames,
|
|
4704
|
+
);
|
|
4705
|
+
this.#pruneStateSnapshots(path.basename(snapshotPath));
|
|
4706
|
+
return deepFreeze(persisted.record);
|
|
4707
|
+
});
|
|
4092
4708
|
}
|
|
4093
4709
|
|
|
4094
4710
|
query(selector, options = {}) {
|
|
@@ -4098,22 +4714,49 @@ export class EvolutionLedger {
|
|
|
4098
4714
|
});
|
|
4099
4715
|
const issueReceipt = options.issueReceipt !== false;
|
|
4100
4716
|
return this.#withLock(() => {
|
|
4101
|
-
const state = this.#loadState({
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
const anchor = state.anchors[event.sequence];
|
|
4105
|
-
const receipt = issueReceipt
|
|
4106
|
-
? this.#issueReceipt(state, event, anchor, "verified-existing")
|
|
4107
|
-
: null;
|
|
4108
|
-
return deepFreeze({
|
|
4109
|
-
anchor,
|
|
4110
|
-
authenticated: true,
|
|
4111
|
-
authority: this.#authorityProjection(state),
|
|
4112
|
-
durable: true,
|
|
4113
|
-
event,
|
|
4114
|
-
receipt,
|
|
4115
|
-
schema: EVOLUTION_LEDGER_QUERY_SCHEMA,
|
|
4717
|
+
const state = this.#loadState({
|
|
4718
|
+
allowInitialize: false,
|
|
4719
|
+
incremental: true,
|
|
4116
4720
|
});
|
|
4721
|
+
return this.#queryResult(
|
|
4722
|
+
state,
|
|
4723
|
+
this.#queryState(state, safeSelector),
|
|
4724
|
+
issueReceipt,
|
|
4725
|
+
);
|
|
4726
|
+
});
|
|
4727
|
+
}
|
|
4728
|
+
|
|
4729
|
+
queryMany(selectors, options = {}) {
|
|
4730
|
+
const entries = readDenseDataArray(
|
|
4731
|
+
selectors,
|
|
4732
|
+
"query selectors",
|
|
4733
|
+
MAX_LEDGER_BATCH_QUERIES,
|
|
4734
|
+
);
|
|
4735
|
+
if (entries.length === 0) {
|
|
4736
|
+
throw ledgerError(
|
|
4737
|
+
"CC_EVOLUTION_LEDGER_SCHEMA_INVALID",
|
|
4738
|
+
"query selectors must not be empty",
|
|
4739
|
+
);
|
|
4740
|
+
}
|
|
4741
|
+
const safeSelectors = entries.map(normalizeQuerySelector);
|
|
4742
|
+
assertExactKeys(options, QUERY_OPTION_KEYS, "query options", {
|
|
4743
|
+
optional: [...QUERY_OPTION_KEYS],
|
|
4744
|
+
});
|
|
4745
|
+
const issueReceipt = options.issueReceipt === true;
|
|
4746
|
+
return this.#withLock(() => {
|
|
4747
|
+
const state = this.#loadState({
|
|
4748
|
+
allowInitialize: false,
|
|
4749
|
+
incremental: true,
|
|
4750
|
+
});
|
|
4751
|
+
return deepFreeze(
|
|
4752
|
+
safeSelectors.map((selector) =>
|
|
4753
|
+
this.#queryResult(
|
|
4754
|
+
state,
|
|
4755
|
+
this.#queryState(state, selector),
|
|
4756
|
+
issueReceipt,
|
|
4757
|
+
),
|
|
4758
|
+
),
|
|
4759
|
+
);
|
|
4117
4760
|
});
|
|
4118
4761
|
}
|
|
4119
4762
|
|
|
@@ -4142,7 +4785,10 @@ export class EvolutionLedger {
|
|
|
4142
4785
|
);
|
|
4143
4786
|
}
|
|
4144
4787
|
return this.#withLock(() => {
|
|
4145
|
-
const state = this.#loadState({
|
|
4788
|
+
const state = this.#loadState({
|
|
4789
|
+
allowInitialize: false,
|
|
4790
|
+
incremental: true,
|
|
4791
|
+
});
|
|
4146
4792
|
const event = this.#queryState(state, {
|
|
4147
4793
|
eventDigest: verifiedReceipt.eventDigest,
|
|
4148
4794
|
eventId: verifiedReceipt.eventId,
|
|
@@ -4213,6 +4859,50 @@ export class EvolutionLedger {
|
|
|
4213
4859
|
});
|
|
4214
4860
|
}
|
|
4215
4861
|
|
|
4862
|
+
exportAuditBundle() {
|
|
4863
|
+
return this.#withLock(() => {
|
|
4864
|
+
const state = this.#loadState({
|
|
4865
|
+
allowInitialize: false,
|
|
4866
|
+
incremental: true,
|
|
4867
|
+
});
|
|
4868
|
+
const artifacts = [];
|
|
4869
|
+
for (const event of state.events) {
|
|
4870
|
+
const evidence = this.#resolveArtifactEvidence(event, state.identity, {
|
|
4871
|
+
includeBytes: true,
|
|
4872
|
+
});
|
|
4873
|
+
if (evidence.digest !== event.artifactValidationDigest) {
|
|
4874
|
+
throw ledgerError(
|
|
4875
|
+
"CC_EVOLUTION_LEDGER_ARTIFACT_INVALID",
|
|
4876
|
+
`artifact evidence changed since event ${event.sequence} was committed`,
|
|
4877
|
+
);
|
|
4878
|
+
}
|
|
4879
|
+
for (const artifact of evidence.artifacts) {
|
|
4880
|
+
artifacts.push({ ...artifact, eventSequence: event.sequence });
|
|
4881
|
+
if (artifacts.length > MAX_AUDIT_ARTIFACT_RECORDS) {
|
|
4882
|
+
throw ledgerError(
|
|
4883
|
+
"CC_EVOLUTION_LEDGER_CAPACITY_EXCEEDED",
|
|
4884
|
+
"audit export artifact evidence exceeds its bounded capacity",
|
|
4885
|
+
);
|
|
4886
|
+
}
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4889
|
+
const core = {
|
|
4890
|
+
anchors: state.anchors,
|
|
4891
|
+
artifacts,
|
|
4892
|
+
events: state.events,
|
|
4893
|
+
exportedAt: clockTimestamp(this.#clock),
|
|
4894
|
+
identity: state.identity,
|
|
4895
|
+
schema: EVOLUTION_LEDGER_AUDIT_EXPORT_SCHEMA,
|
|
4896
|
+
storeMarker: state.storeMarker,
|
|
4897
|
+
witness: state.witness,
|
|
4898
|
+
};
|
|
4899
|
+
return deepFreeze({
|
|
4900
|
+
...core,
|
|
4901
|
+
exportDigest: domainDigest(AUDIT_EXPORT_DOMAIN, core),
|
|
4902
|
+
});
|
|
4903
|
+
});
|
|
4904
|
+
}
|
|
4905
|
+
|
|
4216
4906
|
read({ afterSequence = 0, limit = 10_000 } = {}) {
|
|
4217
4907
|
if (
|
|
4218
4908
|
!Number.isSafeInteger(afterSequence) ||
|
|
@@ -4227,11 +4917,12 @@ export class EvolutionLedger {
|
|
|
4227
4917
|
);
|
|
4228
4918
|
}
|
|
4229
4919
|
return this.#withLock(() => {
|
|
4230
|
-
const state = this.#loadState({
|
|
4920
|
+
const state = this.#loadState({
|
|
4921
|
+
allowInitialize: false,
|
|
4922
|
+
incremental: true,
|
|
4923
|
+
});
|
|
4231
4924
|
return deepFreeze(
|
|
4232
|
-
state.events
|
|
4233
|
-
.filter((event) => event.sequence > afterSequence)
|
|
4234
|
-
.slice(0, limit),
|
|
4925
|
+
state.events.slice(afterSequence, afterSequence + limit),
|
|
4235
4926
|
);
|
|
4236
4927
|
});
|
|
4237
4928
|
}
|
|
@@ -4267,7 +4958,9 @@ export class EvolutionLedger {
|
|
|
4267
4958
|
|
|
4268
4959
|
verify() {
|
|
4269
4960
|
return this.#withLock(() =>
|
|
4270
|
-
this.#authorityProjection(
|
|
4961
|
+
this.#authorityProjection(
|
|
4962
|
+
this.#loadState({ allowInitialize: false, incremental: true }),
|
|
4963
|
+
),
|
|
4271
4964
|
);
|
|
4272
4965
|
}
|
|
4273
4966
|
|
|
@@ -4276,5 +4969,613 @@ export class EvolutionLedger {
|
|
|
4276
4969
|
}
|
|
4277
4970
|
}
|
|
4278
4971
|
|
|
4972
|
+
function artifactRefsForAudit(input) {
|
|
4973
|
+
const refs = Object.hasOwn(input, "subjectRef")
|
|
4974
|
+
? [input.subjectRef, ...input.sourceRefs]
|
|
4975
|
+
: [
|
|
4976
|
+
...input.sourceRefs,
|
|
4977
|
+
input.parentRef,
|
|
4978
|
+
input.candidateRef,
|
|
4979
|
+
input.diffRef,
|
|
4980
|
+
input.evalRef,
|
|
4981
|
+
input.policyRef,
|
|
4982
|
+
input.actorRef,
|
|
4983
|
+
input.targetRef,
|
|
4984
|
+
].filter(Boolean);
|
|
4985
|
+
const byRef = new Map();
|
|
4986
|
+
for (const ref of refs) {
|
|
4987
|
+
const existing = byRef.get(ref.ref);
|
|
4988
|
+
if (existing && existing.digest !== ref.digest) {
|
|
4989
|
+
throw ledgerError(
|
|
4990
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
4991
|
+
`audit event binds one artifact ref to conflicting digests: ${ref.ref}`,
|
|
4992
|
+
);
|
|
4993
|
+
}
|
|
4994
|
+
byRef.set(ref.ref, ref);
|
|
4995
|
+
}
|
|
4996
|
+
return [...byRef.values()].sort((left, right) =>
|
|
4997
|
+
compareStrings(left.ref, right.ref),
|
|
4998
|
+
);
|
|
4999
|
+
}
|
|
5000
|
+
|
|
5001
|
+
function verifyAuditSignature({
|
|
5002
|
+
digest: recordDigest,
|
|
5003
|
+
message,
|
|
5004
|
+
port,
|
|
5005
|
+
purpose,
|
|
5006
|
+
signature,
|
|
5007
|
+
trust,
|
|
5008
|
+
witness = false,
|
|
5009
|
+
}) {
|
|
5010
|
+
let valid;
|
|
5011
|
+
try {
|
|
5012
|
+
valid = port({
|
|
5013
|
+
digest: recordDigest,
|
|
5014
|
+
message: Buffer.from(message),
|
|
5015
|
+
purpose,
|
|
5016
|
+
signature: frozenClone(signature),
|
|
5017
|
+
trust,
|
|
5018
|
+
});
|
|
5019
|
+
} catch (cause) {
|
|
5020
|
+
throw ledgerError(
|
|
5021
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5022
|
+
`offline ${purpose} signature verification failed`,
|
|
5023
|
+
{ cause },
|
|
5024
|
+
);
|
|
5025
|
+
}
|
|
5026
|
+
if (isUnsafeAsyncResult(valid) || valid !== true) {
|
|
5027
|
+
throw ledgerError(
|
|
5028
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5029
|
+
`offline ${purpose} signature was not accepted synchronously${witness ? " by the witness authority" : ""}`,
|
|
5030
|
+
);
|
|
5031
|
+
}
|
|
5032
|
+
}
|
|
5033
|
+
|
|
5034
|
+
function assertAuditTrustBinding(
|
|
5035
|
+
record,
|
|
5036
|
+
identity,
|
|
5037
|
+
trust,
|
|
5038
|
+
witnessTrust,
|
|
5039
|
+
witnessId,
|
|
5040
|
+
) {
|
|
5041
|
+
if (
|
|
5042
|
+
record.algorithm !== trust.algorithm ||
|
|
5043
|
+
record.keyId !== trust.keyId ||
|
|
5044
|
+
record.trustPolicyDigest !== trust.trustPolicyDigest ||
|
|
5045
|
+
record.witnessAlgorithm !== witnessTrust.algorithm ||
|
|
5046
|
+
record.witnessId !== witnessId ||
|
|
5047
|
+
record.witnessKeyId !== witnessTrust.keyId ||
|
|
5048
|
+
record.witnessTrustPolicyDigest !== witnessTrust.trustPolicyDigest ||
|
|
5049
|
+
(identity &&
|
|
5050
|
+
(record.ledgerId !== identity.ledgerId ||
|
|
5051
|
+
record.epoch !== identity.epoch ||
|
|
5052
|
+
record.identityDigest !== identity.identityDigest))
|
|
5053
|
+
) {
|
|
5054
|
+
throw ledgerError(
|
|
5055
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5056
|
+
"offline audit record is not bound to the exported identity and trust roots",
|
|
5057
|
+
);
|
|
5058
|
+
}
|
|
5059
|
+
}
|
|
5060
|
+
|
|
5061
|
+
function verifyAuditIdentity(record, trust, witnessTrust, verifySignature) {
|
|
5062
|
+
assertExactKeys(record, IDENTITY_RECORD_KEYS, "audit identity");
|
|
5063
|
+
const core = normalizeIdentityCore(
|
|
5064
|
+
Object.fromEntries(
|
|
5065
|
+
[...IDENTITY_CORE_KEYS].map((key) => [key, record[key]]),
|
|
5066
|
+
),
|
|
5067
|
+
);
|
|
5068
|
+
assertAuditTrustBinding(core, null, trust, witnessTrust, core.witnessId);
|
|
5069
|
+
const identityDigest = digest(record.identityDigest, "identityDigest");
|
|
5070
|
+
const signature = normalizeSignature(record.signature, trust);
|
|
5071
|
+
const message = signedMessage(IDENTITY_DOMAIN, core);
|
|
5072
|
+
if (identityDigest !== sha256(message)) {
|
|
5073
|
+
throw ledgerError(
|
|
5074
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5075
|
+
"offline audit identity digest is invalid",
|
|
5076
|
+
);
|
|
5077
|
+
}
|
|
5078
|
+
verifyAuditSignature({
|
|
5079
|
+
digest: identityDigest,
|
|
5080
|
+
message,
|
|
5081
|
+
port: verifySignature,
|
|
5082
|
+
purpose: "identity",
|
|
5083
|
+
signature,
|
|
5084
|
+
trust,
|
|
5085
|
+
});
|
|
5086
|
+
return deepFreeze({ ...core, identityDigest, signature });
|
|
5087
|
+
}
|
|
5088
|
+
|
|
5089
|
+
function verifyAuditStoreMarker(record, identity, trust, witnessTrust, port) {
|
|
5090
|
+
assertExactKeys(record, STORE_MARKER_RECORD_KEYS, "audit store marker");
|
|
5091
|
+
const core = normalizeStoreMarkerCore(
|
|
5092
|
+
Object.fromEntries(
|
|
5093
|
+
[...STORE_MARKER_CORE_KEYS].map((key) => [key, record[key]]),
|
|
5094
|
+
),
|
|
5095
|
+
);
|
|
5096
|
+
assertAuditTrustBinding(core, null, trust, witnessTrust, identity.witnessId);
|
|
5097
|
+
const storeMarkerDigest = digest(
|
|
5098
|
+
record.storeMarkerDigest,
|
|
5099
|
+
"storeMarkerDigest",
|
|
5100
|
+
);
|
|
5101
|
+
const signature = normalizeSignature(record.signature, trust);
|
|
5102
|
+
const message = signedMessage(STORE_MARKER_DOMAIN, core);
|
|
5103
|
+
if (
|
|
5104
|
+
storeMarkerDigest !== sha256(message) ||
|
|
5105
|
+
core.ledgerId !== identity.ledgerId ||
|
|
5106
|
+
core.epoch !== identity.epoch ||
|
|
5107
|
+
core.createdAt !== identity.createdAt ||
|
|
5108
|
+
core.storeBindingDigest !== identity.storeBindingDigest ||
|
|
5109
|
+
core.storeMarkerId !== identity.storeMarkerId ||
|
|
5110
|
+
storeMarkerDigest !== identity.storeMarkerDigest
|
|
5111
|
+
) {
|
|
5112
|
+
throw ledgerError(
|
|
5113
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5114
|
+
"offline audit store marker is not bound to the signed identity",
|
|
5115
|
+
);
|
|
5116
|
+
}
|
|
5117
|
+
verifyAuditSignature({
|
|
5118
|
+
digest: storeMarkerDigest,
|
|
5119
|
+
message,
|
|
5120
|
+
port,
|
|
5121
|
+
purpose: "store-marker",
|
|
5122
|
+
signature,
|
|
5123
|
+
trust,
|
|
5124
|
+
});
|
|
5125
|
+
return deepFreeze({ ...core, signature, storeMarkerDigest });
|
|
5126
|
+
}
|
|
5127
|
+
|
|
5128
|
+
function verifyAuditEvent(
|
|
5129
|
+
record,
|
|
5130
|
+
previous,
|
|
5131
|
+
identity,
|
|
5132
|
+
trust,
|
|
5133
|
+
witnessTrust,
|
|
5134
|
+
port,
|
|
5135
|
+
) {
|
|
5136
|
+
assertPlainDataObject(record, "audit event");
|
|
5137
|
+
const domainEvent = record.schema === EVOLUTION_LEDGER_DOMAIN_EVENT_SCHEMA;
|
|
5138
|
+
const recordKeys = domainEvent ? DOMAIN_EVENT_RECORD_KEYS : EVENT_RECORD_KEYS;
|
|
5139
|
+
const coreKeys = domainEvent ? DOMAIN_EVENT_CORE_KEYS : EVENT_CORE_KEYS;
|
|
5140
|
+
assertExactKeys(record, recordKeys, "audit event");
|
|
5141
|
+
const core = domainEvent
|
|
5142
|
+
? normalizeDomainEventCore(
|
|
5143
|
+
Object.fromEntries([...coreKeys].map((key) => [key, record[key]])),
|
|
5144
|
+
)
|
|
5145
|
+
: normalizeEventCore(
|
|
5146
|
+
Object.fromEntries([...coreKeys].map((key) => [key, record[key]])),
|
|
5147
|
+
);
|
|
5148
|
+
assertAuditTrustBinding(
|
|
5149
|
+
core,
|
|
5150
|
+
identity,
|
|
5151
|
+
trust,
|
|
5152
|
+
witnessTrust,
|
|
5153
|
+
identity.witnessId,
|
|
5154
|
+
);
|
|
5155
|
+
const eventDigest = digest(record.eventDigest, "eventDigest");
|
|
5156
|
+
const signature = normalizeSignature(record.signature, trust);
|
|
5157
|
+
const signatureDomain = domainEvent ? DOMAIN_EVENT_DOMAIN : EVENT_DOMAIN;
|
|
5158
|
+
const message = signedMessage(signatureDomain, core);
|
|
5159
|
+
if (
|
|
5160
|
+
core.sequence !== (previous?.sequence || 0) + 1 ||
|
|
5161
|
+
core.prevDigest !== (previous?.eventDigest || null) ||
|
|
5162
|
+
eventDigest !== sha256(message)
|
|
5163
|
+
) {
|
|
5164
|
+
throw ledgerError(
|
|
5165
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5166
|
+
`offline audit event chain failed at sequence ${core.sequence}`,
|
|
5167
|
+
);
|
|
5168
|
+
}
|
|
5169
|
+
verifyAuditSignature({
|
|
5170
|
+
digest: eventDigest,
|
|
5171
|
+
message,
|
|
5172
|
+
port,
|
|
5173
|
+
purpose: domainEvent ? "domain-event" : "event",
|
|
5174
|
+
signature,
|
|
5175
|
+
trust,
|
|
5176
|
+
});
|
|
5177
|
+
return deepFreeze({ ...core, eventDigest, signature });
|
|
5178
|
+
}
|
|
5179
|
+
|
|
5180
|
+
function verifyAuditAnchor(
|
|
5181
|
+
record,
|
|
5182
|
+
previous,
|
|
5183
|
+
identity,
|
|
5184
|
+
trust,
|
|
5185
|
+
witnessTrust,
|
|
5186
|
+
port,
|
|
5187
|
+
) {
|
|
5188
|
+
assertExactKeys(record, ANCHOR_RECORD_KEYS, "audit anchor");
|
|
5189
|
+
const core = normalizeAnchorCore(
|
|
5190
|
+
Object.fromEntries([...ANCHOR_CORE_KEYS].map((key) => [key, record[key]])),
|
|
5191
|
+
);
|
|
5192
|
+
assertAuditTrustBinding(
|
|
5193
|
+
core,
|
|
5194
|
+
identity,
|
|
5195
|
+
trust,
|
|
5196
|
+
witnessTrust,
|
|
5197
|
+
identity.witnessId,
|
|
5198
|
+
);
|
|
5199
|
+
const anchorDigest = digest(record.anchorDigest, "anchorDigest");
|
|
5200
|
+
const signature = normalizeSignature(record.signature, trust);
|
|
5201
|
+
const message = signedMessage(ANCHOR_DOMAIN, core);
|
|
5202
|
+
if (
|
|
5203
|
+
core.sequence !== (previous ? previous.sequence + 1 : 0) ||
|
|
5204
|
+
core.previousAnchorDigest !== (previous?.anchorDigest || null) ||
|
|
5205
|
+
anchorDigest !== sha256(message) ||
|
|
5206
|
+
(core.sequence === 0 &&
|
|
5207
|
+
(core.headDigest !== null || core.segmentDigest !== null)) ||
|
|
5208
|
+
(core.sequence > 0 &&
|
|
5209
|
+
(core.headDigest === null || core.segmentDigest === null))
|
|
5210
|
+
) {
|
|
5211
|
+
throw ledgerError(
|
|
5212
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5213
|
+
`offline audit anchor chain failed at sequence ${core.sequence}`,
|
|
5214
|
+
);
|
|
5215
|
+
}
|
|
5216
|
+
verifyAuditSignature({
|
|
5217
|
+
digest: anchorDigest,
|
|
5218
|
+
message,
|
|
5219
|
+
port,
|
|
5220
|
+
purpose: "anchor",
|
|
5221
|
+
signature,
|
|
5222
|
+
trust,
|
|
5223
|
+
});
|
|
5224
|
+
return deepFreeze({ ...core, anchorDigest, signature });
|
|
5225
|
+
}
|
|
5226
|
+
|
|
5227
|
+
function decodeAuditArtifactBytes(value, expectedLength) {
|
|
5228
|
+
if (typeof value !== "string" || value.length > 24 * 1024 * 1024) {
|
|
5229
|
+
throw ledgerError(
|
|
5230
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5231
|
+
"offline audit artifact encoding is invalid or oversized",
|
|
5232
|
+
);
|
|
5233
|
+
}
|
|
5234
|
+
const bytes = Buffer.from(value, "base64");
|
|
5235
|
+
if (
|
|
5236
|
+
bytes.length !== expectedLength ||
|
|
5237
|
+
bytes.length > EVOLUTION_LEDGER_MAX_ARTIFACT_BYTES ||
|
|
5238
|
+
bytes.toString("base64") !== value
|
|
5239
|
+
) {
|
|
5240
|
+
throw ledgerError(
|
|
5241
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5242
|
+
"offline audit artifact bytes are not canonical or complete",
|
|
5243
|
+
);
|
|
5244
|
+
}
|
|
5245
|
+
return bytes;
|
|
5246
|
+
}
|
|
5247
|
+
|
|
5248
|
+
function verifyAuditArtifacts(records, events) {
|
|
5249
|
+
const byEventAndRef = new Map();
|
|
5250
|
+
let previousOrder = null;
|
|
5251
|
+
for (const record of records) {
|
|
5252
|
+
assertExactKeys(record, AUDIT_ARTIFACT_KEYS, "audit artifact");
|
|
5253
|
+
if (record.schema !== EVOLUTION_LEDGER_AUDIT_ARTIFACT_SCHEMA) {
|
|
5254
|
+
throw ledgerError(
|
|
5255
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5256
|
+
"offline audit artifact schema is unsupported",
|
|
5257
|
+
);
|
|
5258
|
+
}
|
|
5259
|
+
if (
|
|
5260
|
+
!Number.isSafeInteger(record.eventSequence) ||
|
|
5261
|
+
record.eventSequence < 1 ||
|
|
5262
|
+
record.eventSequence > events.length ||
|
|
5263
|
+
!Number.isSafeInteger(record.byteLength) ||
|
|
5264
|
+
record.byteLength < 0
|
|
5265
|
+
) {
|
|
5266
|
+
throw ledgerError(
|
|
5267
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5268
|
+
"offline audit artifact bounds are invalid",
|
|
5269
|
+
);
|
|
5270
|
+
}
|
|
5271
|
+
const tenantId = identifier(record.tenantId, "audit artifact tenantId");
|
|
5272
|
+
const artifactRef = normalizeArtifactRef(
|
|
5273
|
+
record.artifactRef,
|
|
5274
|
+
"audit artifactRef",
|
|
5275
|
+
);
|
|
5276
|
+
const receiptDigest = digest(
|
|
5277
|
+
record.receiptDigest,
|
|
5278
|
+
"audit artifact receiptDigest",
|
|
5279
|
+
);
|
|
5280
|
+
const bytes = decodeAuditArtifactBytes(
|
|
5281
|
+
record.bytesBase64,
|
|
5282
|
+
record.byteLength,
|
|
5283
|
+
);
|
|
5284
|
+
if (sha256(bytes) !== artifactRef.digest) {
|
|
5285
|
+
throw ledgerError(
|
|
5286
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5287
|
+
`offline audit artifact digest is invalid: ${artifactRef.ref}`,
|
|
5288
|
+
);
|
|
5289
|
+
}
|
|
5290
|
+
const order = [record.eventSequence, tenantId, artifactRef.ref];
|
|
5291
|
+
if (
|
|
5292
|
+
previousOrder &&
|
|
5293
|
+
(order[0] < previousOrder[0] ||
|
|
5294
|
+
(order[0] === previousOrder[0] &&
|
|
5295
|
+
(compareStrings(order[1], previousOrder[1]) < 0 ||
|
|
5296
|
+
(order[1] === previousOrder[1] &&
|
|
5297
|
+
compareStrings(order[2], previousOrder[2]) <= 0))))
|
|
5298
|
+
) {
|
|
5299
|
+
throw ledgerError(
|
|
5300
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5301
|
+
"offline audit artifact evidence must be unique and canonically ordered",
|
|
5302
|
+
);
|
|
5303
|
+
}
|
|
5304
|
+
previousOrder = order;
|
|
5305
|
+
const key = `${record.eventSequence}\0${tenantId}\0${artifactRef.ref}`;
|
|
5306
|
+
byEventAndRef.set(key, {
|
|
5307
|
+
artifactRef,
|
|
5308
|
+
byteLength: bytes.length,
|
|
5309
|
+
receiptDigest,
|
|
5310
|
+
});
|
|
5311
|
+
}
|
|
5312
|
+
|
|
5313
|
+
const consumed = new Set();
|
|
5314
|
+
for (const event of events) {
|
|
5315
|
+
const tenantId = Object.hasOwn(event, "artifactTenantId")
|
|
5316
|
+
? event.artifactTenantId
|
|
5317
|
+
: event.tenantId;
|
|
5318
|
+
const validations = [];
|
|
5319
|
+
for (const ref of artifactRefsForAudit(event)) {
|
|
5320
|
+
const key = `${event.sequence}\0${tenantId}\0${ref.ref}`;
|
|
5321
|
+
const evidence = byEventAndRef.get(key);
|
|
5322
|
+
if (!evidence || evidence.artifactRef.digest !== ref.digest) {
|
|
5323
|
+
throw ledgerError(
|
|
5324
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5325
|
+
`offline audit artifact evidence is missing or substituted: ${ref.ref}`,
|
|
5326
|
+
);
|
|
5327
|
+
}
|
|
5328
|
+
consumed.add(key);
|
|
5329
|
+
validations.push({
|
|
5330
|
+
byteLength: evidence.byteLength,
|
|
5331
|
+
digest: ref.digest,
|
|
5332
|
+
receiptDigest: evidence.receiptDigest,
|
|
5333
|
+
ref: ref.ref,
|
|
5334
|
+
});
|
|
5335
|
+
}
|
|
5336
|
+
if (
|
|
5337
|
+
domainDigest(ARTIFACT_VALIDATION_DOMAIN, validations) !==
|
|
5338
|
+
event.artifactValidationDigest
|
|
5339
|
+
) {
|
|
5340
|
+
throw ledgerError(
|
|
5341
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5342
|
+
`offline artifact validation receipt changed at event ${event.sequence}`,
|
|
5343
|
+
);
|
|
5344
|
+
}
|
|
5345
|
+
}
|
|
5346
|
+
if (consumed.size !== records.length) {
|
|
5347
|
+
throw ledgerError(
|
|
5348
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5349
|
+
"offline audit contains unreferenced artifact evidence",
|
|
5350
|
+
);
|
|
5351
|
+
}
|
|
5352
|
+
}
|
|
5353
|
+
|
|
5354
|
+
function verifyAuditWitness(
|
|
5355
|
+
record,
|
|
5356
|
+
identity,
|
|
5357
|
+
storeMarker,
|
|
5358
|
+
anchor,
|
|
5359
|
+
event,
|
|
5360
|
+
witnessTrust,
|
|
5361
|
+
port,
|
|
5362
|
+
) {
|
|
5363
|
+
const witness = normalizeWitnessRecordShape(record, witnessTrust);
|
|
5364
|
+
if (
|
|
5365
|
+
witness.witnessId !== identity.witnessId ||
|
|
5366
|
+
witness.algorithm !== witnessTrust.algorithm ||
|
|
5367
|
+
witness.keyId !== witnessTrust.keyId ||
|
|
5368
|
+
witness.trustPolicyDigest !== witnessTrust.trustPolicyDigest
|
|
5369
|
+
) {
|
|
5370
|
+
throw ledgerError(
|
|
5371
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5372
|
+
"offline witness is not bound to the independent trust root",
|
|
5373
|
+
);
|
|
5374
|
+
}
|
|
5375
|
+
const core = Object.fromEntries(
|
|
5376
|
+
[...WITNESS_CORE_KEYS].map((key) => [key, witness[key]]),
|
|
5377
|
+
);
|
|
5378
|
+
const message = signedMessage(WITNESS_DOMAIN, core);
|
|
5379
|
+
if (witness.witnessDigest !== sha256(message)) {
|
|
5380
|
+
throw ledgerError(
|
|
5381
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5382
|
+
"offline witness digest is invalid",
|
|
5383
|
+
);
|
|
5384
|
+
}
|
|
5385
|
+
verifyAuditSignature({
|
|
5386
|
+
digest: witness.witnessDigest,
|
|
5387
|
+
message,
|
|
5388
|
+
port,
|
|
5389
|
+
purpose: "witness",
|
|
5390
|
+
signature: witness.signature,
|
|
5391
|
+
trust: witnessTrust,
|
|
5392
|
+
witness: true,
|
|
5393
|
+
});
|
|
5394
|
+
const expected = {
|
|
5395
|
+
algorithm: witnessTrust.algorithm,
|
|
5396
|
+
anchorDigest: anchor.anchorDigest,
|
|
5397
|
+
epoch: identity.epoch,
|
|
5398
|
+
headDigest: anchor.headDigest,
|
|
5399
|
+
identityDigest: identity.identityDigest,
|
|
5400
|
+
keyId: witnessTrust.keyId,
|
|
5401
|
+
ledgerId: identity.ledgerId,
|
|
5402
|
+
payloadDigest: domainDigest(WITNESS_PAYLOAD_DOMAIN, {
|
|
5403
|
+
anchor,
|
|
5404
|
+
event,
|
|
5405
|
+
identity,
|
|
5406
|
+
storeMarker,
|
|
5407
|
+
}),
|
|
5408
|
+
segmentDigest: anchor.segmentDigest,
|
|
5409
|
+
sequence: anchor.sequence,
|
|
5410
|
+
storeMarkerDigest: identity.storeMarkerDigest,
|
|
5411
|
+
storeMarkerEntryDigest: identity.storeMarkerEntryDigest,
|
|
5412
|
+
storeMarkerId: identity.storeMarkerId,
|
|
5413
|
+
trustPolicyDigest: witnessTrust.trustPolicyDigest,
|
|
5414
|
+
witnessId: identity.witnessId,
|
|
5415
|
+
};
|
|
5416
|
+
if (
|
|
5417
|
+
witness.status !== "committed" ||
|
|
5418
|
+
witness.authenticated !== true ||
|
|
5419
|
+
witness.durable !== true ||
|
|
5420
|
+
Object.entries(expected).some(([key, value]) => witness[key] !== value)
|
|
5421
|
+
) {
|
|
5422
|
+
throw ledgerError(
|
|
5423
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5424
|
+
"offline witness does not authenticate the exported ledger head",
|
|
5425
|
+
);
|
|
5426
|
+
}
|
|
5427
|
+
return witness;
|
|
5428
|
+
}
|
|
5429
|
+
|
|
5430
|
+
export function verifyEvolutionLedgerAuditBundle(bundle, ports) {
|
|
5431
|
+
try {
|
|
5432
|
+
assertExactKeys(ports, AUDIT_VERIFY_PORT_KEYS, "audit verifier ports");
|
|
5433
|
+
if (
|
|
5434
|
+
typeof ports.verifySignature !== "function" ||
|
|
5435
|
+
typeof ports.verifyWitnessSignature !== "function"
|
|
5436
|
+
) {
|
|
5437
|
+
throw ledgerError(
|
|
5438
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5439
|
+
"offline audit requires synchronous ledger and witness verifiers",
|
|
5440
|
+
);
|
|
5441
|
+
}
|
|
5442
|
+
const verifySignatureImplementation = ports.verifySignature;
|
|
5443
|
+
const verifyWitnessSignatureImplementation = ports.verifyWitnessSignature;
|
|
5444
|
+
const verifySignature = Object.freeze((...args) =>
|
|
5445
|
+
Reflect.apply(verifySignatureImplementation, undefined, args),
|
|
5446
|
+
);
|
|
5447
|
+
const verifyWitnessSignature = Object.freeze((...args) =>
|
|
5448
|
+
Reflect.apply(verifyWitnessSignatureImplementation, undefined, args),
|
|
5449
|
+
);
|
|
5450
|
+
const trust = normalizeTrust(ports.trust);
|
|
5451
|
+
const witnessTrust = normalizeTrust(ports.witnessTrust);
|
|
5452
|
+
assertExactKeys(bundle, AUDIT_EXPORT_RECORD_KEYS, "audit export");
|
|
5453
|
+
if (bundle.schema !== EVOLUTION_LEDGER_AUDIT_EXPORT_SCHEMA) {
|
|
5454
|
+
throw ledgerError(
|
|
5455
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5456
|
+
"offline audit export schema is unsupported",
|
|
5457
|
+
);
|
|
5458
|
+
}
|
|
5459
|
+
canonicalTimestamp(bundle.exportedAt, "audit exportedAt");
|
|
5460
|
+
const core = Object.fromEntries(
|
|
5461
|
+
[...AUDIT_EXPORT_CORE_KEYS].map((key) => [key, bundle[key]]),
|
|
5462
|
+
);
|
|
5463
|
+
const exportDigest = digest(bundle.exportDigest, "audit exportDigest");
|
|
5464
|
+
if (exportDigest !== domainDigest(AUDIT_EXPORT_DOMAIN, core)) {
|
|
5465
|
+
throw ledgerError(
|
|
5466
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5467
|
+
"offline audit export digest is invalid",
|
|
5468
|
+
);
|
|
5469
|
+
}
|
|
5470
|
+
// Capture the fully traversed plain-data graph before invoking either
|
|
5471
|
+
// external trust port. A verifier closure must not be able to mutate the
|
|
5472
|
+
// caller's bundle between signature checks.
|
|
5473
|
+
const capturedBundle = JSON.parse(canonicalJson(bundle));
|
|
5474
|
+
const rawEvents = readDenseDataArray(
|
|
5475
|
+
capturedBundle.events,
|
|
5476
|
+
"audit events",
|
|
5477
|
+
EVOLUTION_LEDGER_MAX_EVENTS,
|
|
5478
|
+
);
|
|
5479
|
+
const rawAnchors = readDenseDataArray(
|
|
5480
|
+
capturedBundle.anchors,
|
|
5481
|
+
"audit anchors",
|
|
5482
|
+
EVOLUTION_LEDGER_MAX_EVENTS + 1,
|
|
5483
|
+
);
|
|
5484
|
+
const rawArtifacts = readDenseDataArray(
|
|
5485
|
+
capturedBundle.artifacts,
|
|
5486
|
+
"audit artifacts",
|
|
5487
|
+
MAX_AUDIT_ARTIFACT_RECORDS,
|
|
5488
|
+
);
|
|
5489
|
+
|
|
5490
|
+
const identity = verifyAuditIdentity(
|
|
5491
|
+
capturedBundle.identity,
|
|
5492
|
+
trust,
|
|
5493
|
+
witnessTrust,
|
|
5494
|
+
verifySignature,
|
|
5495
|
+
);
|
|
5496
|
+
const storeMarker = verifyAuditStoreMarker(
|
|
5497
|
+
capturedBundle.storeMarker,
|
|
5498
|
+
identity,
|
|
5499
|
+
trust,
|
|
5500
|
+
witnessTrust,
|
|
5501
|
+
verifySignature,
|
|
5502
|
+
);
|
|
5503
|
+
const events = [];
|
|
5504
|
+
for (const rawEvent of rawEvents) {
|
|
5505
|
+
events.push(
|
|
5506
|
+
verifyAuditEvent(
|
|
5507
|
+
rawEvent,
|
|
5508
|
+
events.at(-1) || null,
|
|
5509
|
+
identity,
|
|
5510
|
+
trust,
|
|
5511
|
+
witnessTrust,
|
|
5512
|
+
verifySignature,
|
|
5513
|
+
),
|
|
5514
|
+
);
|
|
5515
|
+
}
|
|
5516
|
+
if (rawAnchors.length !== events.length + 1) {
|
|
5517
|
+
throw ledgerError(
|
|
5518
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5519
|
+
"offline audit anchor coverage is incomplete",
|
|
5520
|
+
);
|
|
5521
|
+
}
|
|
5522
|
+
const anchors = [];
|
|
5523
|
+
for (const rawAnchor of rawAnchors) {
|
|
5524
|
+
const anchor = verifyAuditAnchor(
|
|
5525
|
+
rawAnchor,
|
|
5526
|
+
anchors.at(-1) || null,
|
|
5527
|
+
identity,
|
|
5528
|
+
trust,
|
|
5529
|
+
witnessTrust,
|
|
5530
|
+
verifySignature,
|
|
5531
|
+
);
|
|
5532
|
+
if (anchor.sequence > 0) {
|
|
5533
|
+
const event = events[anchor.sequence - 1];
|
|
5534
|
+
if (
|
|
5535
|
+
!event ||
|
|
5536
|
+
anchor.headDigest !== event.eventDigest ||
|
|
5537
|
+
anchor.segmentDigest !== domainDigest(SEGMENT_DOMAIN, event)
|
|
5538
|
+
) {
|
|
5539
|
+
throw ledgerError(
|
|
5540
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5541
|
+
`offline audit anchor is not bound to event ${anchor.sequence}`,
|
|
5542
|
+
);
|
|
5543
|
+
}
|
|
5544
|
+
}
|
|
5545
|
+
anchors.push(anchor);
|
|
5546
|
+
}
|
|
5547
|
+
verifyAuditArtifacts(rawArtifacts, events);
|
|
5548
|
+
const head = anchors.at(-1);
|
|
5549
|
+
const witness = verifyAuditWitness(
|
|
5550
|
+
capturedBundle.witness,
|
|
5551
|
+
identity,
|
|
5552
|
+
storeMarker,
|
|
5553
|
+
head,
|
|
5554
|
+
events.at(-1) || null,
|
|
5555
|
+
witnessTrust,
|
|
5556
|
+
verifyWitnessSignature,
|
|
5557
|
+
);
|
|
5558
|
+
return deepFreeze({
|
|
5559
|
+
artifactCount: rawArtifacts.length,
|
|
5560
|
+
eventCount: events.length,
|
|
5561
|
+
exportDigest,
|
|
5562
|
+
headDigest: head.headDigest,
|
|
5563
|
+
identityDigest: identity.identityDigest,
|
|
5564
|
+
ledgerId: identity.ledgerId,
|
|
5565
|
+
schema: EVOLUTION_LEDGER_AUDIT_VERIFICATION_SCHEMA,
|
|
5566
|
+
sequence: head.sequence,
|
|
5567
|
+
valid: true,
|
|
5568
|
+
witnessDigest: witness.witnessDigest,
|
|
5569
|
+
});
|
|
5570
|
+
} catch (cause) {
|
|
5571
|
+
if (cause?.code === "CC_EVOLUTION_LEDGER_AUDIT_INVALID") throw cause;
|
|
5572
|
+
throw ledgerError(
|
|
5573
|
+
"CC_EVOLUTION_LEDGER_AUDIT_INVALID",
|
|
5574
|
+
"offline evolution ledger audit verification failed closed",
|
|
5575
|
+
{ cause },
|
|
5576
|
+
);
|
|
5577
|
+
}
|
|
5578
|
+
}
|
|
5579
|
+
|
|
4279
5580
|
Object.freeze(EvolutionLedger.prototype);
|
|
4280
5581
|
Object.freeze(EvolutionLedger);
|