chainlesschain 0.166.15 → 0.166.16
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/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-To-6rimH.js → AIOps-CTxa2Wbq.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CpdCnTv3.js → ActionButton-DHrQyYhy.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DK7VNES_.js → Analytics-DQiUqcNl.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-NZ5FvhCx.js → AppLayout-_Iz4r6wt.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-NXceQPmN.js → Artifacts-BNs63YHo.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-B2d64DH2.js → Audit-BgHIqRLn.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-ByYgvEVY.js → BackgroundAgents-DUQ5axBB.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BXvQLDvN.js → Backup-CJ51E9Vw.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-C4GZk-As.js → BaseInput-o-5toA_R.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CvPuohL8.js → Chat-Cu7a-x1L.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DTftcclX.js → ChatBubbleRenderer-BUyYkAXr.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-C8dQGIqD.js → Checkbox-DwdmZ71L.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DRM6S1W4.js → Codegen-BrH2eYiP.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BDM8l-r5.js → Col-BVJQEP2j.js} +1 -1
- package/src/assets/web-panel/assets/{Community-Bvmfi1fX.js → Community-FGO3vZPq.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DGQTCima.js → Compact-DYGAFVnT.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-D_0C-2P6.js → Compliance-CRvc8Bdt.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-3C_q99ok.js → Cowork-D6Ah2m7A.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-CIiMQ1Hl.js → Cron-AXbU2ifR.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-peebGnES.js → Crosschain-C5DXgEFd.js} +1 -1
- package/src/assets/web-panel/assets/{DID-OkX4KB9T.js → DID-Bxh9QV9v.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-D9lm0Wxa.js → Dashboard-B-ZHEta-.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CitActXx.js → Dropdown-66Ik1jqa.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DSmFkNKV.js → EmailListRenderer-CCWk1BIM.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CBWAi8XL.js → FamilyGuardDashboard--9faIULK.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-afj8jDcp.js → Federation-CejW9CgX.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Dabt59KY.js → FormItemContext-acHgcXki.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BQpXA9kj.js → GenericCardRenderer-D2ghd_K4.js} +1 -1
- package/src/assets/web-panel/assets/{Git-D2p2Moo1.js → Git-BOe1YJrV.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DkFfPykv.js → Governance-BjgzJsof.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-C1DKpeN2.js → Inference-CXCD5rnP.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CjfVg4oQ.js → KnowledgeGraph-CHA8QgJT.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-D-zMvdSf.js → Logs-DORPMcL6.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-jzEpGkbc.js → MarkdownRenderer-CPPHz01z.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-DfAjJLyS.js → Marketplace-BCFlo1Yx.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BGDHQES1.js → McpTools-Dl_uZAKh.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-BYqrGAPS.js → Memory-DupHamwz.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CQ6FEtty.js → MobileBridge-BRSMPJxW.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BpMvh5JW.js → MobileProjects-BHpuzsDl.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CWH25SrU.js → Mtc-Ch0jOQfZ.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-D6DiLmei.js → MtcAudit-qzwQrDi7.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BSxRN4jD.js → Multisig-B5wHzUcm.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-C0FDPKT4.js → NLProgramming-Hy9uAiA0.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-VKR78bH-.js → Notes-DkuchMC7.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DLSklSZB.js → NotificationSettings-CYZhD7Pi.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Dpj7D6tC.js → OrderTableRenderer-DKOoqajt.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BB3pB27e.js → Organization-D9AXn6j8.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Cr7bzwcs.js → Overflow-Bg15J6hX.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-D1fkMMzI.js → P2P--B54HoQn.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BiDEbLNA.js → PdhVaultBrowser-Cz9Kk2Nb.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-c3Ijv0v4.js → Permissions-DHx38eS1.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BJwbh3P2.js → PersonalDataHub-Dw8i2hAs.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-C4fhXafB.js → Pipeline-B2o6Ur2G.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DzSx3CD-.js → Privacy-8g-h2fnu.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-D9fL5fED.js → ProjectInit-DuVTcINx.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CB5FVrOR.js → ProjectSettings-C8njvdZX.js} +2 -2
- package/src/assets/web-panel/assets/Projects-Ca9BAfu7.js +1 -0
- package/src/assets/web-panel/assets/{Providers-B9ZH4faS.js → Providers-wFqgod_W.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BLKDHT8_.js → QrScannerModal-9NCXrpmA.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-D1oYUcSY.js → QuickAsk-WUTNvFkY.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CC2nmrOD.js → Recommend-xT4nYmju.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-qW9QF4-r.js → RemoteSession-fak1PBGC.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BDWqfzcr.js → Reputation-D1AkQvD-.js} +1 -1
- package/src/assets/web-panel/assets/{Row-BBV2iCjT.js → Row-DVfm6xGt.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CcwrNwZB.js → RssFeed-BDunoLSE.js} +3 -3
- package/src/assets/web-panel/assets/{Search-Dr6fZ0D_.js → Search-OPycRoU3.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BPzaoCU6.js → Security-C8-Fm3Dt.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BoKUU1I_.js → Services-qM_sMpMl.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C8lT8GUg.js → Skeleton-D3HBJjVF.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DPpWeicz.js → Skills-jr67X-tP.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-UiijzpMU.js → Sla-CS3ggyCg.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DoofrLwS.js → SpeechSettings-CTOCYxOi.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DXnyfTDw.js → SyncSettings-D3s3cLT5.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-g_WHjKeg.js → Tasks-CdfoJ25m.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DRuCh9g2.js → Templates-Drb4AI8X.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-QHIq1pH1.js → Tenant-DzwR3Qkz.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-G0fuI216.js → Terminal-D-ESq7xp.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-4MT7SpGk.js → TimelineRenderer-BK-AUuws.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-B5xT5umA.js → Tokens-xGTouuNp.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-D7HPbNgc.js → Trigger-BvZj-v2H.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CxH69dU5.js → Trust-BLC9CGmc.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CPPosveH.js → UkeySign-BSuCXpmE.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Du5lGwAt.js → VideoEditing-DT3A-_Ql.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BBIGNESK.js → Wallet-c1OXao85.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Cc3HSGmv.js → WebAuthn-CuA-F7KJ.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DBoVPNCh.js → WorkflowEditor-MANZrf82.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DHcjgiem.js → chat-D4Y4Ey0O.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DrRrvJIV.js → colors-DDH06hKO.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BuY_sAoN.js → compact-item-Cym756Xj.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CepH1Fha.js → createContext-By4Nb1k-.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-KO3pAGeO.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-Dh80Ot6o.js → hasIn-Ch7OY258.js} +1 -1
- package/src/assets/web-panel/assets/{index-BW9Pycxh.js → index-6-2eYBXr.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7lstbTh.js → index-6CpOV3Pn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUnd5hyp.js → index-ArMrnx0v.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0rvJjId.js → index-B51otVoZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6tOVlq-.js → index-BBBUn5v7.js} +1 -1
- package/src/assets/web-panel/assets/{index-NS9dW1F1.js → index-BFydNU6a.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiUx-tkV.js → index-BIrRX-ra.js} +1 -1
- package/src/assets/web-panel/assets/{index-By0qqC3S.js → index-BZB9q-Lp.js} +1 -1
- package/src/assets/web-panel/assets/index-Bh4Rgkg2.js +1 -0
- package/src/assets/web-panel/assets/{index-CNQK9DIr.js → index-Bo20BfdS.js} +1 -1
- package/src/assets/web-panel/assets/{index-C10033eO.js → index-CF3Lhyns.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRy3_beb.js → index-CGsvEbbq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWANFMlq.js → index-CJPks33Y.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnRK8_Dd.js → index-CT-3FYwd.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPDEOOlA.js → index-CUSUzaB8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYN49m7k.js → index-CV1UC4zo.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5vhNRqS.js → index-CV8qXTvb.js} +1 -1
- package/src/assets/web-panel/assets/{index-9crgJ3Id.js → index-CZn6dmZe.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkZP_QaB.js → index-CjNUTBwe.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoRy2yup.js → index-CpudmDhf.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ca5d64IH.js → index-CykROEJA.js} +1 -1
- package/src/assets/web-panel/assets/{index-A5lVwYVq.js → index-D5sADPGY.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnVIvc15.js → index-D9le0-dc.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cx21_p3m.js → index-DAkk1M7x.js} +1 -1
- package/src/assets/web-panel/assets/{index-0TYLnd11.js → index-DNEJzc0r.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9Gh-SOn.js → index-DNlxihs8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtqigRzf.js → index-DiewptTL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CW_23-gU.js → index-DjwgJ5Jd.js} +1 -1
- package/src/assets/web-panel/assets/index-Dox_coTY.js +1 -0
- package/src/assets/web-panel/assets/{index-LBkSOwky.js → index-DtGValNi.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5_K3GKu.js → index-DvnywwUj.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiZQooWK.js → index-OU8DZ9BL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CYydOga6.js → index-XGeQMvWK.js} +1 -1
- package/src/assets/web-panel/assets/{index-hYaigdGm.js → index-ZY2Qs_Sn.js} +1 -1
- package/src/assets/web-panel/assets/{index-zGH9zH3w.js → index-jx7kr6mB.js} +3 -3
- package/src/assets/web-panel/assets/{index-DuRYBWrj.js → index-mL3nOcH6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuSUCH7h.js → index-oBsfUbyH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEfVkKIY.js → index-q-s04OKn.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUNyTmkS.js → index-tZ5BUdad.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-nI_X3wKl.js → initDefaultProps-DGcX9iPP.js} +1 -1
- package/src/assets/web-panel/assets/{motion-LUHbQu_R.js → motion-2SeL1D8A.js} +1 -1
- package/src/assets/web-panel/assets/{move-DHI_KvJt.js → move-3cYbQCpS.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-AYdL_7Mo.js → mtc-parser-IDdxfw0z.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CtV3HRKa.js → omit-BNswdKO6.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CcJLTOu4.js → pickAttrs-BwCnem2I.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DHbiLUps.js → placementArrow-B9PtZ5rq.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BC2DGkWP.js → responsiveObserve-CPB6iSzV.js} +1 -1
- package/src/assets/web-panel/assets/{slide-B4ga4pPY.js → slide-DZgyblZa.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-hzphgKfH.js → statusUtils-D82IBdsx.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-IfN9Bxoi.js → styleChecker-qSHtosM-.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-pcGKJ4pT.js → useFlexGapSupport-CmWO7h0I.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-COXhQpTG.js → useFs-yLrs_ub4.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-xp9F9szv.js → usePersonalDataHub-CJ1VW6rh.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BdSQy1Ve.js → vnode-CzY_moQ4.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CHCt5P55.js → zoom-DAa7rEY5.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/learning.js +71 -1
- package/src/commands/team.js +5 -0
- package/src/data/changelog.json +15 -2
- package/src/lib/agent-team/task-lease.js +12 -1
- package/src/lib/capability-manifest.js +212 -1
- package/src/lib/evolution/evolution-artifact-ports.js +3277 -0
- package/src/lib/evolution/evolution-eval-gate.js +6442 -0
- package/src/lib/evolution/evolution-evidence-projector.js +3786 -0
- package/src/lib/evolution/evolution-ledger-ports.js +2689 -0
- package/src/lib/evolution/evolution-ledger.js +4280 -0
- package/src/lib/evolution/skill-candidate-registry.js +2258 -0
- package/src/lib/evolution/skill-execution-manifest.js +942 -0
- package/src/lib/evolution/skill-mutation-authority.js +1677 -0
- package/src/lib/evolution/skill-promotion-controller.js +506 -0
- package/src/lib/evolution/skill-release-registry.js +3277 -0
- package/src/lib/evolution/skill-target-matrix-eval.js +3233 -0
- package/src/lib/evolution/skill-writer-inventory-manifest.js +998 -0
- package/src/lib/evolution/skill-writer-inventory-validator.js +816 -0
- package/src/lib/formal-quality-eval-runtime.js +100 -0
- package/src/lib/graph-kernel/cutover-contract.js +7 -0
- package/src/lib/graph-kernel/cutover-ledger.js +8 -7
- package/src/lib/graph-kernel/evidence-digest.js +47 -0
- package/src/lib/graph-kernel/graph-runtime-surfaces.json +8 -1
- package/src/lib/graph-kernel/production-cutover-evidence.js +342 -15
- package/src/lib/graph-kernel/production-source-evidence.js +1599 -0
- package/src/lib/graph-kernel/retirement-evidence.js +15 -5
- package/src/lib/graph-kernel/runtime-surface-manifest.js +67 -2
- package/src/lib/headless-manifest.js +3 -1
- package/src/lib/learning/skill-improver.js +311 -43
- package/src/lib/learning/skill-synthesizer.js +236 -16
- package/src/lib/secure-fs.js +20 -0
- package/src/assets/web-panel/assets/Projects-DWhwdgfY.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DIb8j04q.js +0 -1
- package/src/assets/web-panel/assets/index-BJc3t4PC.js +0 -1
- package/src/assets/web-panel/assets/index-CkksSUnI.js +0 -1
|
@@ -0,0 +1,2689 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict domain adapters that retain Skill release and mutation evidence in
|
|
3
|
+
* one caller-owned EvolutionLedger. This module never constructs or reopens a
|
|
4
|
+
* ledger and never projects release evidence into the legacy proposal schema.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import crypto from "node:crypto";
|
|
8
|
+
import { types as utilTypes } from "node:util";
|
|
9
|
+
import {
|
|
10
|
+
EVOLUTION_ARTIFACT_MAX_CANONICAL_BYTES,
|
|
11
|
+
EVOLUTION_ARTIFACT_PERSISTENCE_RECEIPT_SCHEMA,
|
|
12
|
+
EVOLUTION_ARTIFACT_STORE_FAILED_CODE,
|
|
13
|
+
EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA,
|
|
14
|
+
EvolutionArtifactPorts,
|
|
15
|
+
} from "./evolution-artifact-ports.js";
|
|
16
|
+
import {
|
|
17
|
+
EVOLUTION_ARTIFACT_REF_SCHEMA,
|
|
18
|
+
EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA,
|
|
19
|
+
EVOLUTION_LEDGER_DOMAIN_EVENT_SCHEMA,
|
|
20
|
+
EVOLUTION_LEDGER_MAX_EVENTS,
|
|
21
|
+
EVOLUTION_LEDGER_QUERY_SCHEMA,
|
|
22
|
+
EvolutionLedger,
|
|
23
|
+
} from "./evolution-ledger.js";
|
|
24
|
+
import {
|
|
25
|
+
SKILL_MUTATION_NONCE_ACK_SCHEMA,
|
|
26
|
+
SKILL_MUTATION_OPERATIONS,
|
|
27
|
+
SKILL_MUTATION_ROLES,
|
|
28
|
+
SKILL_MUTATION_TARGET_SCOPES,
|
|
29
|
+
verifySkillMutationAuditEvent,
|
|
30
|
+
verifySkillMutationConsumptionReceipt,
|
|
31
|
+
verifySkillMutationNonceClaim,
|
|
32
|
+
verifySkillMutationRequest,
|
|
33
|
+
} from "./skill-mutation-authority.js";
|
|
34
|
+
import { SKILL_RELEASE_LEDGER_PROJECTION_SCHEMA } from "./skill-release-registry.js";
|
|
35
|
+
|
|
36
|
+
export const EVOLUTION_LEDGER_PORTS_INVALID_CODE =
|
|
37
|
+
"CC_EVOLUTION_LEDGER_PORTS_INVALID";
|
|
38
|
+
export const EVOLUTION_LEDGER_PORTS_COLLISION_CODE =
|
|
39
|
+
"CC_EVOLUTION_LEDGER_PORTS_COLLISION";
|
|
40
|
+
export const EVOLUTION_LEDGER_PORTS_CORRUPT_CODE =
|
|
41
|
+
"CC_EVOLUTION_LEDGER_PORTS_CORRUPT";
|
|
42
|
+
export const EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE =
|
|
43
|
+
"CC_EVOLUTION_LEDGER_PORTS_UNAVAILABLE";
|
|
44
|
+
export const EVOLUTION_ARTIFACT_DURABILITY_BINDING_SCHEMA =
|
|
45
|
+
"chainlesschain.evolution-artifact-durability-binding/v1";
|
|
46
|
+
export const EVOLUTION_ARTIFACT_DURABILITY_RETAIN_REQUEST_SCHEMA =
|
|
47
|
+
"chainlesschain.evolution-artifact-durability-retain-request/v1";
|
|
48
|
+
export const EVOLUTION_ARTIFACT_DURABILITY_RESOLVE_REQUEST_SCHEMA =
|
|
49
|
+
"chainlesschain.evolution-artifact-durability-resolve-request/v1";
|
|
50
|
+
export const EVOLUTION_ARTIFACT_DURABILITY_RECEIPT_SCHEMA =
|
|
51
|
+
"chainlesschain.evolution-artifact-durability-receipt/v1";
|
|
52
|
+
export const EVOLUTION_ARTIFACT_DURABILITY_RESOLUTION_SCHEMA =
|
|
53
|
+
"chainlesschain.evolution-artifact-durability-resolution/v1";
|
|
54
|
+
|
|
55
|
+
const RELEASE_INTENT_SCHEMA =
|
|
56
|
+
"chainlesschain.skill-release-transition-intent/v2";
|
|
57
|
+
const RELEASE_INTENT_DOMAIN = `${RELEASE_INTENT_SCHEMA}\0`;
|
|
58
|
+
const RELEASE_FINALIZATION_DOMAIN =
|
|
59
|
+
"chainlesschain.evolution-release-finalization/v1\0";
|
|
60
|
+
const PROJECTION_PREPARE_RECEIPT_DOMAIN =
|
|
61
|
+
"chainlesschain.evolution-ledger-release-prepare-projection/v1\0";
|
|
62
|
+
const PROJECTION_FINALIZE_RECEIPT_DOMAIN =
|
|
63
|
+
"chainlesschain.evolution-ledger-release-finalize-projection/v1\0";
|
|
64
|
+
const EVENT_ID_DOMAINS = Object.freeze({
|
|
65
|
+
audit: "chainlesschain.evolution-ledger-event-id/mutation.audit/v1\0",
|
|
66
|
+
finalize: "chainlesschain.evolution-ledger-event-id/release.finalize/v1\0",
|
|
67
|
+
nonce: "chainlesschain.evolution-ledger-event-id/mutation.nonce/v1\0",
|
|
68
|
+
prepare: "chainlesschain.evolution-ledger-event-id/release.prepare/v1\0",
|
|
69
|
+
});
|
|
70
|
+
const EVENT_PREFIXES = Object.freeze({
|
|
71
|
+
audit: "mutation.audit",
|
|
72
|
+
finalize: "release.finalize",
|
|
73
|
+
nonce: "mutation.nonce",
|
|
74
|
+
prepare: "release.prepare",
|
|
75
|
+
});
|
|
76
|
+
const EVENT_TYPES = Object.freeze({
|
|
77
|
+
audit: "skill.mutation.audit",
|
|
78
|
+
finalize: "skill.release.finalize",
|
|
79
|
+
nonce: "skill.mutation.nonce",
|
|
80
|
+
prepare: "skill.release.prepare",
|
|
81
|
+
});
|
|
82
|
+
const ARTIFACT_TYPES = Object.freeze({
|
|
83
|
+
audit: "skill-mutation-audit",
|
|
84
|
+
finalize: "skill-release-finalization",
|
|
85
|
+
nonce: "skill-mutation-nonce-claim",
|
|
86
|
+
prepare: "skill-release-transition-intent",
|
|
87
|
+
});
|
|
88
|
+
const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/u;
|
|
89
|
+
const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/u;
|
|
90
|
+
const SKILL_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
|
|
91
|
+
const NONCE_PATTERN = /^[A-Za-z0-9_-]{16,128}$/u;
|
|
92
|
+
const MAX_CANONICAL_DEPTH = 32;
|
|
93
|
+
const MAX_CANONICAL_NODES = 100_000;
|
|
94
|
+
const MAX_ARRAY_ENTRIES = 65_536;
|
|
95
|
+
const MAX_OBJECT_FIELDS = 4096;
|
|
96
|
+
const MAX_KEY_CHARS = 512;
|
|
97
|
+
const MAX_STRING_CHARS = EVOLUTION_ARTIFACT_MAX_CANONICAL_BYTES;
|
|
98
|
+
const MAX_FINALIZE_RETRIES = 8;
|
|
99
|
+
|
|
100
|
+
const FACTORY_OPTION_KEYS = new Set([
|
|
101
|
+
"artifactDurabilityAuthority",
|
|
102
|
+
"artifactPorts",
|
|
103
|
+
"artifactTenantId",
|
|
104
|
+
"audience",
|
|
105
|
+
"ledger",
|
|
106
|
+
"purpose",
|
|
107
|
+
]);
|
|
108
|
+
const FACTORY_REQUIRED_KEYS = new Set([
|
|
109
|
+
"artifactDurabilityAuthority",
|
|
110
|
+
"artifactPorts",
|
|
111
|
+
"artifactTenantId",
|
|
112
|
+
"ledger",
|
|
113
|
+
]);
|
|
114
|
+
const DURABLE_RESOLVER_OPTION_KEYS = new Set([
|
|
115
|
+
"artifactDurabilityAuthority",
|
|
116
|
+
"artifactPorts",
|
|
117
|
+
"artifactTenantId",
|
|
118
|
+
"purpose",
|
|
119
|
+
]);
|
|
120
|
+
const ARTIFACT_REF_KEYS = new Set(["digest", "ref", "schema"]);
|
|
121
|
+
const ARTIFACT_PUT_RESULT_KEYS = new Set([
|
|
122
|
+
"digest",
|
|
123
|
+
"envelope",
|
|
124
|
+
"receipt",
|
|
125
|
+
"ref",
|
|
126
|
+
]);
|
|
127
|
+
const ARTIFACT_RECORD_KEYS = new Set([
|
|
128
|
+
"audience",
|
|
129
|
+
"purpose",
|
|
130
|
+
"retention",
|
|
131
|
+
"schema",
|
|
132
|
+
"tenantId",
|
|
133
|
+
"type",
|
|
134
|
+
"value",
|
|
135
|
+
]);
|
|
136
|
+
const LEDGER_RESOLUTION_KEYS = new Set([
|
|
137
|
+
"authenticated",
|
|
138
|
+
"bytes",
|
|
139
|
+
"digest",
|
|
140
|
+
"found",
|
|
141
|
+
"receiptDigest",
|
|
142
|
+
"ref",
|
|
143
|
+
"schema",
|
|
144
|
+
]);
|
|
145
|
+
const DURABILITY_AUTHORITY_KEYS = new Set(["id", "resolve", "retain"]);
|
|
146
|
+
const DURABILITY_RECEIPT_KEYS = new Set([
|
|
147
|
+
"artifactTenantId",
|
|
148
|
+
"authenticated",
|
|
149
|
+
"authorityId",
|
|
150
|
+
"digest",
|
|
151
|
+
"durable",
|
|
152
|
+
"purpose",
|
|
153
|
+
"receiptDigest",
|
|
154
|
+
"ref",
|
|
155
|
+
"retention",
|
|
156
|
+
"schema",
|
|
157
|
+
"type",
|
|
158
|
+
]);
|
|
159
|
+
const DURABILITY_RESOLVE_REQUEST_KEYS = new Set([
|
|
160
|
+
"artifactTenantId",
|
|
161
|
+
"digest",
|
|
162
|
+
"purpose",
|
|
163
|
+
"ref",
|
|
164
|
+
"retention",
|
|
165
|
+
"schema",
|
|
166
|
+
]);
|
|
167
|
+
const DURABILITY_RESOLUTION_KEYS = new Set([
|
|
168
|
+
...DURABILITY_RECEIPT_KEYS,
|
|
169
|
+
"bytes",
|
|
170
|
+
]);
|
|
171
|
+
const LEDGER_ARTIFACT_REQUEST_KEYS = new Set([
|
|
172
|
+
"epoch",
|
|
173
|
+
"ledgerId",
|
|
174
|
+
"ref",
|
|
175
|
+
"tenantId",
|
|
176
|
+
]);
|
|
177
|
+
const DURABLE_ARTIFACT_TYPE_SET = new Set(Object.values(ARTIFACT_TYPES));
|
|
178
|
+
const QUERY_RESULT_KEYS = new Set([
|
|
179
|
+
"anchor",
|
|
180
|
+
"authenticated",
|
|
181
|
+
"authority",
|
|
182
|
+
"durable",
|
|
183
|
+
"event",
|
|
184
|
+
"receipt",
|
|
185
|
+
"schema",
|
|
186
|
+
]);
|
|
187
|
+
const INTENT_KEYS = new Set([
|
|
188
|
+
"authorityReceipt",
|
|
189
|
+
"authorityReceiptDigest",
|
|
190
|
+
"candidateId",
|
|
191
|
+
"dependencyLockDigest",
|
|
192
|
+
"expectedParentDigest",
|
|
193
|
+
"expectedRevision",
|
|
194
|
+
"intentDigest",
|
|
195
|
+
"mutationRequest",
|
|
196
|
+
"nextStateDigest",
|
|
197
|
+
"operation",
|
|
198
|
+
"operationId",
|
|
199
|
+
"pointerDigest",
|
|
200
|
+
"previousStateDigest",
|
|
201
|
+
"receiptDigests",
|
|
202
|
+
"requestDigest",
|
|
203
|
+
"schema",
|
|
204
|
+
"skillName",
|
|
205
|
+
"targetReleaseDigest",
|
|
206
|
+
"transactionId",
|
|
207
|
+
"transitionSubjectDigest",
|
|
208
|
+
]);
|
|
209
|
+
const FINALIZE_KEYS = new Set([
|
|
210
|
+
"authorityReceiptDigest",
|
|
211
|
+
"expectedPrepareReceiptDigest",
|
|
212
|
+
"intentDigest",
|
|
213
|
+
"pointerDigest",
|
|
214
|
+
"revision",
|
|
215
|
+
"skillName",
|
|
216
|
+
"stateDigest",
|
|
217
|
+
"transactionId",
|
|
218
|
+
]);
|
|
219
|
+
const RECEIPT_DIGEST_KEYS = new Set([
|
|
220
|
+
"actor",
|
|
221
|
+
"candidate",
|
|
222
|
+
"eval",
|
|
223
|
+
"parent",
|
|
224
|
+
"policy",
|
|
225
|
+
"target",
|
|
226
|
+
]);
|
|
227
|
+
const TYPED_ARRAY_PROTOTYPE = Object.getPrototypeOf(Uint8Array.prototype);
|
|
228
|
+
const TYPED_ARRAY_BUFFER_GETTER = Object.getOwnPropertyDescriptor(
|
|
229
|
+
TYPED_ARRAY_PROTOTYPE,
|
|
230
|
+
"buffer",
|
|
231
|
+
).get;
|
|
232
|
+
const TYPED_ARRAY_BYTE_LENGTH_GETTER = Object.getOwnPropertyDescriptor(
|
|
233
|
+
TYPED_ARRAY_PROTOTYPE,
|
|
234
|
+
"byteLength",
|
|
235
|
+
).get;
|
|
236
|
+
const TYPED_ARRAY_BYTE_OFFSET_GETTER = Object.getOwnPropertyDescriptor(
|
|
237
|
+
TYPED_ARRAY_PROTOTYPE,
|
|
238
|
+
"byteOffset",
|
|
239
|
+
).get;
|
|
240
|
+
const bufferToString = Function.prototype.call.bind(Buffer.prototype.toString);
|
|
241
|
+
|
|
242
|
+
export class EvolutionLedgerPortsError extends Error {
|
|
243
|
+
constructor(code, message, details = {}) {
|
|
244
|
+
super(message, details.cause ? { cause: details.cause } : undefined);
|
|
245
|
+
this.name = "EvolutionLedgerPortsError";
|
|
246
|
+
this.code = code;
|
|
247
|
+
for (const [key, value] of Object.entries(details)) {
|
|
248
|
+
if (key !== "cause") this[key] = value;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function portsError(code, message, details = {}) {
|
|
254
|
+
return new EvolutionLedgerPortsError(code, message, details);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function rejectProxy(value, label, code = EVOLUTION_LEDGER_PORTS_INVALID_CODE) {
|
|
258
|
+
if (
|
|
259
|
+
value !== null &&
|
|
260
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
261
|
+
utilTypes.isProxy(value)
|
|
262
|
+
) {
|
|
263
|
+
throw portsError(code, `${label} must not be a Proxy`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function isPlainRecord(value, label, code) {
|
|
268
|
+
if (!value || typeof value !== "object") return false;
|
|
269
|
+
rejectProxy(value, label, code);
|
|
270
|
+
if (Array.isArray(value)) return false;
|
|
271
|
+
const prototype = Object.getPrototypeOf(value);
|
|
272
|
+
rejectProxy(prototype, `${label} prototype`, code);
|
|
273
|
+
return prototype === Object.prototype || prototype === null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function ownData(value, key, label, code) {
|
|
277
|
+
rejectProxy(value, label, code);
|
|
278
|
+
let descriptor;
|
|
279
|
+
try {
|
|
280
|
+
descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
281
|
+
} catch (cause) {
|
|
282
|
+
throw portsError(code, `${label}.${String(key)} could not be inspected`, {
|
|
283
|
+
cause,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
if (
|
|
287
|
+
!descriptor ||
|
|
288
|
+
!("value" in descriptor) ||
|
|
289
|
+
descriptor.enumerable !== true
|
|
290
|
+
) {
|
|
291
|
+
throw portsError(
|
|
292
|
+
code,
|
|
293
|
+
`${label}.${String(key)} must be an enumerable own data property`,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
return descriptor.value;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function assertExactRecord(
|
|
300
|
+
value,
|
|
301
|
+
allowed,
|
|
302
|
+
required,
|
|
303
|
+
label,
|
|
304
|
+
code = EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
305
|
+
) {
|
|
306
|
+
if (!isPlainRecord(value, label, code)) {
|
|
307
|
+
throw portsError(code, `${label} must be a plain object`);
|
|
308
|
+
}
|
|
309
|
+
let keys;
|
|
310
|
+
try {
|
|
311
|
+
keys = Reflect.ownKeys(value);
|
|
312
|
+
} catch (cause) {
|
|
313
|
+
throw portsError(code, `${label} keys could not be inspected`, { cause });
|
|
314
|
+
}
|
|
315
|
+
if (
|
|
316
|
+
keys.some((key) => typeof key !== "string" || !allowed.has(key)) ||
|
|
317
|
+
[...required].some((key) => !keys.includes(key))
|
|
318
|
+
) {
|
|
319
|
+
throw portsError(code, `${label} contains missing or unsupported fields`);
|
|
320
|
+
}
|
|
321
|
+
for (const key of keys) ownData(value, key, label, code);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function assertAllExactRecord(
|
|
325
|
+
value,
|
|
326
|
+
keys,
|
|
327
|
+
label,
|
|
328
|
+
code = EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
329
|
+
) {
|
|
330
|
+
assertExactRecord(value, keys, keys, label, code);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function readDenseDataArray(
|
|
334
|
+
value,
|
|
335
|
+
label,
|
|
336
|
+
maximum = MAX_ARRAY_ENTRIES,
|
|
337
|
+
code = EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
338
|
+
) {
|
|
339
|
+
rejectProxy(value, label, code);
|
|
340
|
+
if (!Array.isArray(value)) {
|
|
341
|
+
throw portsError(code, `${label} must be an array`);
|
|
342
|
+
}
|
|
343
|
+
const prototype = Object.getPrototypeOf(value);
|
|
344
|
+
rejectProxy(prototype, `${label} prototype`, code);
|
|
345
|
+
if (prototype !== Array.prototype && prototype !== null) {
|
|
346
|
+
throw portsError(code, `${label} must use a safe array prototype`);
|
|
347
|
+
}
|
|
348
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
349
|
+
const length =
|
|
350
|
+
lengthDescriptor && "value" in lengthDescriptor
|
|
351
|
+
? lengthDescriptor.value
|
|
352
|
+
: null;
|
|
353
|
+
if (!Number.isSafeInteger(length) || length < 0 || length > maximum) {
|
|
354
|
+
throw portsError(code, `${label} exceeds its bounded length`);
|
|
355
|
+
}
|
|
356
|
+
const allowed = new Set(["length"]);
|
|
357
|
+
for (let index = 0; index < length; index += 1) {
|
|
358
|
+
allowed.add(String(index));
|
|
359
|
+
}
|
|
360
|
+
const keys = Reflect.ownKeys(value);
|
|
361
|
+
if (
|
|
362
|
+
keys.length !== length + 1 ||
|
|
363
|
+
keys.some((key) => typeof key !== "string" || !allowed.has(key))
|
|
364
|
+
) {
|
|
365
|
+
throw portsError(code, `${label} must be dense and contain only indexes`);
|
|
366
|
+
}
|
|
367
|
+
const entries = new Array(length);
|
|
368
|
+
for (let index = 0; index < length; index += 1) {
|
|
369
|
+
entries[index] = ownData(value, String(index), label, code);
|
|
370
|
+
}
|
|
371
|
+
return entries;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function appendCanonical(state, fragment) {
|
|
375
|
+
state.byteLength += Buffer.byteLength(fragment, "utf8");
|
|
376
|
+
if (state.byteLength > state.maximumBytes) {
|
|
377
|
+
throw portsError(
|
|
378
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
379
|
+
"canonical JSON exceeds its byte limit",
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
state.fragments.push(fragment);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function canonicalWalk(value, state, depth) {
|
|
386
|
+
state.nodes += 1;
|
|
387
|
+
if (state.nodes > MAX_CANONICAL_NODES || depth > MAX_CANONICAL_DEPTH) {
|
|
388
|
+
throw portsError(
|
|
389
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
390
|
+
"canonical JSON exceeds its node or depth limit",
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
if (value === null || typeof value === "boolean") {
|
|
394
|
+
appendCanonical(state, JSON.stringify(value));
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (typeof value === "number") {
|
|
398
|
+
if (!Number.isFinite(value) || Object.is(value, -0)) {
|
|
399
|
+
throw portsError(
|
|
400
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
401
|
+
"canonical JSON numbers must be finite and not negative zero",
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
appendCanonical(state, JSON.stringify(value));
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (typeof value === "string") {
|
|
408
|
+
if (value.length > MAX_STRING_CHARS) {
|
|
409
|
+
throw portsError(
|
|
410
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
411
|
+
"canonical JSON string exceeds its character limit",
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
appendCanonical(state, JSON.stringify(value));
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (!value || typeof value !== "object") {
|
|
418
|
+
throw portsError(
|
|
419
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
420
|
+
"canonical JSON contains an unsupported value",
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
rejectProxy(value, "canonical JSON value");
|
|
424
|
+
if (state.seen.has(value)) {
|
|
425
|
+
throw portsError(
|
|
426
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
427
|
+
"canonical JSON must not contain cycles",
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
state.seen.add(value);
|
|
431
|
+
try {
|
|
432
|
+
if (Array.isArray(value)) {
|
|
433
|
+
const entries = readDenseDataArray(value, "canonical JSON array");
|
|
434
|
+
appendCanonical(state, "[");
|
|
435
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
436
|
+
if (index > 0) appendCanonical(state, ",");
|
|
437
|
+
canonicalWalk(entries[index], state, depth + 1);
|
|
438
|
+
}
|
|
439
|
+
appendCanonical(state, "]");
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if (!isPlainRecord(value, "canonical JSON object")) {
|
|
443
|
+
throw portsError(
|
|
444
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
445
|
+
"canonical JSON objects must use Object or null prototypes",
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
const keys = Reflect.ownKeys(value);
|
|
449
|
+
if (
|
|
450
|
+
keys.length > MAX_OBJECT_FIELDS ||
|
|
451
|
+
keys.some((key) => typeof key !== "string" || key.length > MAX_KEY_CHARS)
|
|
452
|
+
) {
|
|
453
|
+
throw portsError(
|
|
454
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
455
|
+
"canonical JSON object keys exceed their limits",
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
keys.sort(compareStrings);
|
|
459
|
+
appendCanonical(state, "{");
|
|
460
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
461
|
+
if (index > 0) appendCanonical(state, ",");
|
|
462
|
+
const key = keys[index];
|
|
463
|
+
appendCanonical(state, JSON.stringify(key));
|
|
464
|
+
appendCanonical(state, ":");
|
|
465
|
+
canonicalWalk(
|
|
466
|
+
ownData(
|
|
467
|
+
value,
|
|
468
|
+
key,
|
|
469
|
+
"canonical JSON object",
|
|
470
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
471
|
+
),
|
|
472
|
+
state,
|
|
473
|
+
depth + 1,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
appendCanonical(state, "}");
|
|
477
|
+
} finally {
|
|
478
|
+
state.seen.delete(value);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function canonicalJson(value) {
|
|
483
|
+
const state = {
|
|
484
|
+
byteLength: 0,
|
|
485
|
+
fragments: [],
|
|
486
|
+
maximumBytes: EVOLUTION_ARTIFACT_MAX_CANONICAL_BYTES,
|
|
487
|
+
nodes: 0,
|
|
488
|
+
seen: new Set(),
|
|
489
|
+
};
|
|
490
|
+
canonicalWalk(value, state, 0);
|
|
491
|
+
return state.fragments.join("");
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function deepFreeze(value, seen = new WeakSet()) {
|
|
495
|
+
if (!value || typeof value !== "object" || seen.has(value)) return value;
|
|
496
|
+
rejectProxy(value, "value being frozen");
|
|
497
|
+
seen.add(value);
|
|
498
|
+
const keys = Reflect.ownKeys(value);
|
|
499
|
+
for (const key of keys) {
|
|
500
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
501
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
502
|
+
throw portsError(
|
|
503
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
504
|
+
"value being frozen must contain only data properties",
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
deepFreeze(descriptor.value, seen);
|
|
508
|
+
}
|
|
509
|
+
return Object.freeze(value);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function frozenCanonicalClone(value) {
|
|
513
|
+
return deepFreeze(JSON.parse(canonicalJson(value)));
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function compareStrings(left, right) {
|
|
517
|
+
if (left < right) return -1;
|
|
518
|
+
if (left > right) return 1;
|
|
519
|
+
return 0;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function sha256(value) {
|
|
523
|
+
return `sha256:${crypto.createHash("sha256").update(value).digest("hex")}`;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function domainDigest(domain, value) {
|
|
527
|
+
return sha256(
|
|
528
|
+
Buffer.concat([
|
|
529
|
+
Buffer.from(domain, "utf8"),
|
|
530
|
+
Buffer.from(canonicalJson(value), "utf8"),
|
|
531
|
+
]),
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function digest(value, label, code = EVOLUTION_LEDGER_PORTS_INVALID_CODE) {
|
|
536
|
+
if (typeof value !== "string" || !DIGEST_PATTERN.test(value)) {
|
|
537
|
+
throw portsError(code, `${label} must be a lowercase SHA-256 digest`);
|
|
538
|
+
}
|
|
539
|
+
return value;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function nullableDigest(value, label) {
|
|
543
|
+
return value === null ? null : digest(value, label);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function identifier(value, label, maximum = 256) {
|
|
547
|
+
if (
|
|
548
|
+
typeof value !== "string" ||
|
|
549
|
+
value.length < 1 ||
|
|
550
|
+
value.length > maximum ||
|
|
551
|
+
value.trim() !== value ||
|
|
552
|
+
!IDENTIFIER_PATTERN.test(value)
|
|
553
|
+
) {
|
|
554
|
+
throw portsError(
|
|
555
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
556
|
+
`${label} must be a bounded identifier`,
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
return value;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function nullableEventIdentifier(value) {
|
|
563
|
+
return typeof value === "string" &&
|
|
564
|
+
value.length <= 160 &&
|
|
565
|
+
IDENTIFIER_PATTERN.test(value)
|
|
566
|
+
? value
|
|
567
|
+
: null;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function skillName(value, label = "skillName") {
|
|
571
|
+
if (
|
|
572
|
+
typeof value !== "string" ||
|
|
573
|
+
value.length > 128 ||
|
|
574
|
+
!SKILL_NAME_PATTERN.test(value)
|
|
575
|
+
) {
|
|
576
|
+
throw portsError(
|
|
577
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
578
|
+
`${label} must use kebab-case`,
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
return value;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function nullableEventSkillName(value) {
|
|
585
|
+
return typeof value === "string" &&
|
|
586
|
+
value.length <= 128 &&
|
|
587
|
+
SKILL_NAME_PATTERN.test(value)
|
|
588
|
+
? value
|
|
589
|
+
: null;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function revision(value, label, { minimum = 0 } = {}) {
|
|
593
|
+
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
594
|
+
throw portsError(
|
|
595
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
596
|
+
`${label} must be a bounded safe integer`,
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
return value;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function assertSame(
|
|
603
|
+
left,
|
|
604
|
+
right,
|
|
605
|
+
label,
|
|
606
|
+
code = EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
607
|
+
) {
|
|
608
|
+
if (left !== right) {
|
|
609
|
+
throw portsError(code, `${label} binding differs`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function refsEqual(left, right) {
|
|
614
|
+
return (
|
|
615
|
+
left.schema === right.schema &&
|
|
616
|
+
left.ref === right.ref &&
|
|
617
|
+
left.digest === right.digest
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function normalizeArtifactRef(value, label, code) {
|
|
622
|
+
assertAllExactRecord(value, ARTIFACT_REF_KEYS, label, code);
|
|
623
|
+
if (value.schema !== EVOLUTION_ARTIFACT_REF_SCHEMA) {
|
|
624
|
+
throw portsError(code, `${label} schema is invalid`);
|
|
625
|
+
}
|
|
626
|
+
return deepFreeze({
|
|
627
|
+
digest: digest(value.digest, `${label}.digest`, code),
|
|
628
|
+
ref: identifier(value.ref, `${label}.ref`, 2048),
|
|
629
|
+
schema: EVOLUTION_ARTIFACT_REF_SCHEMA,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function requireStableInstance(value, prototype, label) {
|
|
634
|
+
if (!value || typeof value !== "object") {
|
|
635
|
+
throw new TypeError(`${label} instance is required`);
|
|
636
|
+
}
|
|
637
|
+
rejectProxy(value, label);
|
|
638
|
+
let cursor = Object.getPrototypeOf(value);
|
|
639
|
+
while (cursor !== null) {
|
|
640
|
+
rejectProxy(cursor, `${label} prototype chain`);
|
|
641
|
+
if (cursor === prototype) return value;
|
|
642
|
+
cursor = Object.getPrototypeOf(cursor);
|
|
643
|
+
}
|
|
644
|
+
throw new TypeError(`${label} instance is required`);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function captureDurabilityAuthority(value) {
|
|
648
|
+
assertAllExactRecord(
|
|
649
|
+
value,
|
|
650
|
+
DURABILITY_AUTHORITY_KEYS,
|
|
651
|
+
"artifactDurabilityAuthority",
|
|
652
|
+
);
|
|
653
|
+
const id = identifier(
|
|
654
|
+
ownData(
|
|
655
|
+
value,
|
|
656
|
+
"id",
|
|
657
|
+
"artifactDurabilityAuthority",
|
|
658
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
659
|
+
),
|
|
660
|
+
"artifactDurabilityAuthority.id",
|
|
661
|
+
);
|
|
662
|
+
const retain = ownData(
|
|
663
|
+
value,
|
|
664
|
+
"retain",
|
|
665
|
+
"artifactDurabilityAuthority",
|
|
666
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
667
|
+
);
|
|
668
|
+
const resolve = ownData(
|
|
669
|
+
value,
|
|
670
|
+
"resolve",
|
|
671
|
+
"artifactDurabilityAuthority",
|
|
672
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
673
|
+
);
|
|
674
|
+
rejectProxy(retain, "artifactDurabilityAuthority.retain");
|
|
675
|
+
rejectProxy(resolve, "artifactDurabilityAuthority.resolve");
|
|
676
|
+
if (typeof retain !== "function" || typeof resolve !== "function") {
|
|
677
|
+
throw new TypeError(
|
|
678
|
+
"artifactDurabilityAuthority requires synchronous retain and resolve methods",
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
return Object.freeze({
|
|
682
|
+
id,
|
|
683
|
+
retain: Object.freeze((request) => Reflect.apply(retain, value, [request])),
|
|
684
|
+
resolve: Object.freeze((request) =>
|
|
685
|
+
Reflect.apply(resolve, value, [request]),
|
|
686
|
+
),
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function deterministicEventId(kind, identity) {
|
|
691
|
+
const digestValue = domainDigest(EVENT_ID_DOMAINS[kind], identity);
|
|
692
|
+
return `${EVENT_PREFIXES[kind]}:${digestValue.slice("sha256:".length)}`;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function copyBytes(value) {
|
|
696
|
+
rejectProxy(
|
|
697
|
+
value,
|
|
698
|
+
"artifact resolution bytes",
|
|
699
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
700
|
+
);
|
|
701
|
+
if (!value || typeof value !== "object") {
|
|
702
|
+
throw portsError(
|
|
703
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
704
|
+
"artifact resolution bytes are invalid",
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
const prototype = Object.getPrototypeOf(value);
|
|
708
|
+
const safeBuffer = prototype === Buffer.prototype && Buffer.isBuffer(value);
|
|
709
|
+
const safeUint8 =
|
|
710
|
+
prototype === Uint8Array.prototype && utilTypes.isUint8Array(value);
|
|
711
|
+
if (!safeBuffer && !safeUint8) {
|
|
712
|
+
throw portsError(
|
|
713
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
714
|
+
"artifact resolution bytes must use a safe byte-array prototype",
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
try {
|
|
718
|
+
const arrayBuffer = Reflect.apply(TYPED_ARRAY_BUFFER_GETTER, value, []);
|
|
719
|
+
const byteLength = Reflect.apply(TYPED_ARRAY_BYTE_LENGTH_GETTER, value, []);
|
|
720
|
+
const byteOffset = Reflect.apply(TYPED_ARRAY_BYTE_OFFSET_GETTER, value, []);
|
|
721
|
+
if (
|
|
722
|
+
!Number.isSafeInteger(byteLength) ||
|
|
723
|
+
byteLength < 1 ||
|
|
724
|
+
byteLength > EVOLUTION_ARTIFACT_MAX_CANONICAL_BYTES ||
|
|
725
|
+
!Number.isSafeInteger(byteOffset) ||
|
|
726
|
+
byteOffset < 0
|
|
727
|
+
) {
|
|
728
|
+
throw new TypeError("artifact byte view bounds are invalid");
|
|
729
|
+
}
|
|
730
|
+
return Buffer.from(Buffer.from(arrayBuffer, byteOffset, byteLength));
|
|
731
|
+
} catch (cause) {
|
|
732
|
+
throw portsError(
|
|
733
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
734
|
+
"artifact bytes could not be copied safely",
|
|
735
|
+
{ cause },
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function isCommitUnknown(error) {
|
|
741
|
+
return error?.code === "CC_EVOLUTION_LEDGER_COMMIT_UNKNOWN";
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function isEventConflict(error) {
|
|
745
|
+
return error?.code === "CC_EVOLUTION_LEDGER_EVENT_CONFLICT";
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function isHeadConflict(error) {
|
|
749
|
+
return error?.code === "CC_EVOLUTION_LEDGER_HEAD_CONFLICT";
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function assertSynchronous(value, label) {
|
|
753
|
+
rejectProxy(value, label, EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE);
|
|
754
|
+
if (utilTypes.isPromise(value)) {
|
|
755
|
+
throw portsError(
|
|
756
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
757
|
+
`${label} must be synchronous`,
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
return value;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function durabilityResolveRequest({
|
|
764
|
+
artifactTenantId,
|
|
765
|
+
digest: digestValue,
|
|
766
|
+
purpose,
|
|
767
|
+
ref,
|
|
768
|
+
}) {
|
|
769
|
+
return deepFreeze({
|
|
770
|
+
artifactTenantId,
|
|
771
|
+
digest: digestValue,
|
|
772
|
+
purpose,
|
|
773
|
+
ref,
|
|
774
|
+
retention: "ledger",
|
|
775
|
+
schema: EVOLUTION_ARTIFACT_DURABILITY_RESOLVE_REQUEST_SCHEMA,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function resolveDurableArtifact(authority, request, expectedType = null) {
|
|
780
|
+
assertAllExactRecord(
|
|
781
|
+
request,
|
|
782
|
+
DURABILITY_RESOLVE_REQUEST_KEYS,
|
|
783
|
+
"artifact durability resolve request",
|
|
784
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
785
|
+
);
|
|
786
|
+
let result;
|
|
787
|
+
try {
|
|
788
|
+
result = assertSynchronous(
|
|
789
|
+
authority.resolve(request),
|
|
790
|
+
"artifact durability resolve result",
|
|
791
|
+
);
|
|
792
|
+
} catch (cause) {
|
|
793
|
+
if (cause instanceof EvolutionLedgerPortsError) throw cause;
|
|
794
|
+
throw portsError(
|
|
795
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
796
|
+
"artifact durability authority could not resolve canonical bytes",
|
|
797
|
+
{ cause, ref: request.ref },
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
assertAllExactRecord(
|
|
801
|
+
result,
|
|
802
|
+
DURABILITY_RESOLUTION_KEYS,
|
|
803
|
+
"artifact durability resolution",
|
|
804
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
805
|
+
);
|
|
806
|
+
if (
|
|
807
|
+
result.schema !== EVOLUTION_ARTIFACT_DURABILITY_RESOLUTION_SCHEMA ||
|
|
808
|
+
result.authenticated !== true ||
|
|
809
|
+
result.durable !== true ||
|
|
810
|
+
result.authorityId !== authority.id ||
|
|
811
|
+
result.artifactTenantId !== request.artifactTenantId ||
|
|
812
|
+
result.digest !== request.digest ||
|
|
813
|
+
result.purpose !== request.purpose ||
|
|
814
|
+
result.ref !== request.ref ||
|
|
815
|
+
result.retention !== request.retention ||
|
|
816
|
+
(expectedType !== null && result.type !== expectedType)
|
|
817
|
+
) {
|
|
818
|
+
throw portsError(
|
|
819
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
820
|
+
"artifact durability resolution is not authenticated and exactly bound",
|
|
821
|
+
{ ref: request.ref },
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
digest(
|
|
825
|
+
result.receiptDigest,
|
|
826
|
+
"artifact durability resolution receiptDigest",
|
|
827
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
828
|
+
);
|
|
829
|
+
const bytes = copyBytes(result.bytes);
|
|
830
|
+
if (sha256(bytes) !== request.digest) {
|
|
831
|
+
throw portsError(
|
|
832
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
833
|
+
"artifact durability resolution bytes differ from the bound digest",
|
|
834
|
+
{ ref: request.ref },
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
return { bytes, result };
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function normalizeReceiptDigests(value) {
|
|
841
|
+
assertAllExactRecord(value, RECEIPT_DIGEST_KEYS, "intent receiptDigests");
|
|
842
|
+
const result = {};
|
|
843
|
+
for (const key of [...RECEIPT_DIGEST_KEYS].sort(compareStrings)) {
|
|
844
|
+
result[key] = digest(value[key], `receiptDigests.${key}`);
|
|
845
|
+
}
|
|
846
|
+
return deepFreeze(result);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function verifyReceiptRequestBinding(receipt, request) {
|
|
850
|
+
for (const field of [
|
|
851
|
+
"tenantId",
|
|
852
|
+
"audience",
|
|
853
|
+
"operationId",
|
|
854
|
+
"operation",
|
|
855
|
+
"transitionSubjectDigest",
|
|
856
|
+
"skillName",
|
|
857
|
+
"targetScope",
|
|
858
|
+
"expectedTargetDigest",
|
|
859
|
+
"expectedTargetRevision",
|
|
860
|
+
"expiresAt",
|
|
861
|
+
"nonce",
|
|
862
|
+
"requestDigest",
|
|
863
|
+
]) {
|
|
864
|
+
assertSame(
|
|
865
|
+
receipt[field],
|
|
866
|
+
request[field],
|
|
867
|
+
`consumption receipt ${field}`,
|
|
868
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
if (
|
|
872
|
+
receipt.role !== SKILL_MUTATION_ROLES.PROMOTION_CONTROLLER ||
|
|
873
|
+
request.targetScope !== SKILL_MUTATION_TARGET_SCOPES.ACTIVE ||
|
|
874
|
+
![
|
|
875
|
+
SKILL_MUTATION_OPERATIONS.PROMOTE,
|
|
876
|
+
SKILL_MUTATION_OPERATIONS.ROLLBACK,
|
|
877
|
+
].includes(request.operation)
|
|
878
|
+
) {
|
|
879
|
+
throw portsError(
|
|
880
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
881
|
+
"release intent requires a consumed active promotion-controller request",
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function normalizeIntent(input) {
|
|
887
|
+
const value = frozenCanonicalClone(input);
|
|
888
|
+
assertAllExactRecord(value, INTENT_KEYS, "release prepare intent");
|
|
889
|
+
if (value.schema !== RELEASE_INTENT_SCHEMA) {
|
|
890
|
+
throw portsError(
|
|
891
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
892
|
+
"release intent schema is unsupported",
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
const receipt = verifySkillMutationConsumptionReceipt(value.authorityReceipt);
|
|
896
|
+
const request = verifySkillMutationRequest(value.mutationRequest);
|
|
897
|
+
verifyReceiptRequestBinding(receipt, request);
|
|
898
|
+
const core = { ...value };
|
|
899
|
+
delete core.intentDigest;
|
|
900
|
+
const expectedIntentDigest = domainDigest(RELEASE_INTENT_DOMAIN, core);
|
|
901
|
+
const normalized = {
|
|
902
|
+
...value,
|
|
903
|
+
authorityReceiptDigest: digest(
|
|
904
|
+
value.authorityReceiptDigest,
|
|
905
|
+
"intent authorityReceiptDigest",
|
|
906
|
+
),
|
|
907
|
+
candidateId: nullableDigest(value.candidateId, "intent candidateId"),
|
|
908
|
+
dependencyLockDigest: digest(
|
|
909
|
+
value.dependencyLockDigest,
|
|
910
|
+
"intent dependencyLockDigest",
|
|
911
|
+
),
|
|
912
|
+
expectedParentDigest: digest(
|
|
913
|
+
value.expectedParentDigest,
|
|
914
|
+
"intent expectedParentDigest",
|
|
915
|
+
),
|
|
916
|
+
expectedRevision: revision(
|
|
917
|
+
value.expectedRevision,
|
|
918
|
+
"intent expectedRevision",
|
|
919
|
+
),
|
|
920
|
+
intentDigest: digest(value.intentDigest, "intent intentDigest"),
|
|
921
|
+
nextStateDigest: digest(value.nextStateDigest, "intent nextStateDigest"),
|
|
922
|
+
operationId: identifier(value.operationId, "intent operationId"),
|
|
923
|
+
pointerDigest: digest(value.pointerDigest, "intent pointerDigest"),
|
|
924
|
+
previousStateDigest: nullableDigest(
|
|
925
|
+
value.previousStateDigest,
|
|
926
|
+
"intent previousStateDigest",
|
|
927
|
+
),
|
|
928
|
+
receiptDigests: normalizeReceiptDigests(value.receiptDigests),
|
|
929
|
+
requestDigest: digest(value.requestDigest, "intent requestDigest"),
|
|
930
|
+
skillName: skillName(value.skillName),
|
|
931
|
+
targetReleaseDigest: digest(
|
|
932
|
+
value.targetReleaseDigest,
|
|
933
|
+
"intent targetReleaseDigest",
|
|
934
|
+
),
|
|
935
|
+
transactionId: digest(value.transactionId, "intent transactionId"),
|
|
936
|
+
transitionSubjectDigest: digest(
|
|
937
|
+
value.transitionSubjectDigest,
|
|
938
|
+
"intent transitionSubjectDigest",
|
|
939
|
+
),
|
|
940
|
+
};
|
|
941
|
+
if (
|
|
942
|
+
![
|
|
943
|
+
SKILL_MUTATION_OPERATIONS.PROMOTE,
|
|
944
|
+
SKILL_MUTATION_OPERATIONS.ROLLBACK,
|
|
945
|
+
].includes(value.operation) ||
|
|
946
|
+
value.intentDigest !== expectedIntentDigest ||
|
|
947
|
+
value.authorityReceiptDigest !== receipt.receiptDigest ||
|
|
948
|
+
value.requestDigest !== request.requestDigest ||
|
|
949
|
+
value.operation !== request.operation ||
|
|
950
|
+
value.operationId !== request.operationId ||
|
|
951
|
+
value.skillName !== request.skillName ||
|
|
952
|
+
value.transitionSubjectDigest !== request.transitionSubjectDigest ||
|
|
953
|
+
value.expectedParentDigest !== request.expectedTargetDigest ||
|
|
954
|
+
value.expectedRevision !== request.expectedTargetRevision ||
|
|
955
|
+
value.nextStateDigest !== value.pointerDigest ||
|
|
956
|
+
(value.expectedRevision === 0) !== (value.previousStateDigest === null) ||
|
|
957
|
+
(value.operation === SKILL_MUTATION_OPERATIONS.PROMOTE &&
|
|
958
|
+
value.candidateId === null) ||
|
|
959
|
+
(value.operation === SKILL_MUTATION_OPERATIONS.ROLLBACK &&
|
|
960
|
+
value.candidateId !== null)
|
|
961
|
+
) {
|
|
962
|
+
throw portsError(
|
|
963
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
964
|
+
"release intent digest or authority bindings are invalid",
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
return deepFreeze(normalized);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function normalizeFinalize(input) {
|
|
971
|
+
const value = frozenCanonicalClone(input);
|
|
972
|
+
assertAllExactRecord(value, FINALIZE_KEYS, "release finalize request");
|
|
973
|
+
const normalized = deepFreeze({
|
|
974
|
+
authorityReceiptDigest: digest(
|
|
975
|
+
value.authorityReceiptDigest,
|
|
976
|
+
"finalize authorityReceiptDigest",
|
|
977
|
+
),
|
|
978
|
+
expectedPrepareReceiptDigest: digest(
|
|
979
|
+
value.expectedPrepareReceiptDigest,
|
|
980
|
+
"finalize expectedPrepareReceiptDigest",
|
|
981
|
+
),
|
|
982
|
+
intentDigest: digest(value.intentDigest, "finalize intentDigest"),
|
|
983
|
+
pointerDigest: digest(value.pointerDigest, "finalize pointerDigest"),
|
|
984
|
+
revision: revision(value.revision, "finalize revision", { minimum: 1 }),
|
|
985
|
+
skillName: skillName(value.skillName),
|
|
986
|
+
stateDigest: digest(value.stateDigest, "finalize stateDigest"),
|
|
987
|
+
transactionId: digest(value.transactionId, "finalize transactionId"),
|
|
988
|
+
});
|
|
989
|
+
if (normalized.pointerDigest !== normalized.stateDigest) {
|
|
990
|
+
throw portsError(
|
|
991
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
992
|
+
"finalize pointerDigest must equal stateDigest",
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
return normalized;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
function logicalFinalizeDigest(value) {
|
|
999
|
+
return domainDigest(RELEASE_FINALIZATION_DOMAIN, value);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function nonceKey(claim) {
|
|
1003
|
+
return deepFreeze({
|
|
1004
|
+
audience: claim.audience,
|
|
1005
|
+
nonce: claim.nonce,
|
|
1006
|
+
tenantId: claim.tenantId,
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function projectionReceiptDigest(kind, projection) {
|
|
1011
|
+
const core = { ...projection };
|
|
1012
|
+
delete core.current;
|
|
1013
|
+
delete core.receiptDigest;
|
|
1014
|
+
return domainDigest(
|
|
1015
|
+
kind === "prepare"
|
|
1016
|
+
? PROJECTION_PREPARE_RECEIPT_DOMAIN
|
|
1017
|
+
: PROJECTION_FINALIZE_RECEIPT_DOMAIN,
|
|
1018
|
+
core,
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
class EvolutionLedgerDomainPorts {
|
|
1023
|
+
#appendDomainEvent;
|
|
1024
|
+
#artifactPut;
|
|
1025
|
+
#artifactResolve;
|
|
1026
|
+
#artifactTenantId;
|
|
1027
|
+
#audience;
|
|
1028
|
+
#durabilityAuthority;
|
|
1029
|
+
#durabilityAuthorityId;
|
|
1030
|
+
#durabilityRetain;
|
|
1031
|
+
#ledgerQuery;
|
|
1032
|
+
#ledgerRead;
|
|
1033
|
+
#ledgerVerifyReceipt;
|
|
1034
|
+
#purpose;
|
|
1035
|
+
|
|
1036
|
+
constructor({
|
|
1037
|
+
artifactDurabilityAuthority,
|
|
1038
|
+
artifactPorts,
|
|
1039
|
+
artifactTenantId,
|
|
1040
|
+
audience,
|
|
1041
|
+
ledger,
|
|
1042
|
+
purpose,
|
|
1043
|
+
}) {
|
|
1044
|
+
this.#artifactTenantId = identifier(artifactTenantId, "artifactTenantId");
|
|
1045
|
+
this.#audience =
|
|
1046
|
+
audience === undefined ? null : identifier(audience, "audience");
|
|
1047
|
+
this.#purpose = identifier(purpose, "purpose");
|
|
1048
|
+
this.#durabilityAuthority = artifactDurabilityAuthority;
|
|
1049
|
+
this.#durabilityAuthorityId = artifactDurabilityAuthority.id;
|
|
1050
|
+
this.#durabilityRetain = artifactDurabilityAuthority.retain;
|
|
1051
|
+
this.#appendDomainEvent = Object.freeze(
|
|
1052
|
+
EvolutionLedger.prototype.appendDomainEvent.bind(ledger),
|
|
1053
|
+
);
|
|
1054
|
+
this.#ledgerQuery = Object.freeze(
|
|
1055
|
+
EvolutionLedger.prototype.query.bind(ledger),
|
|
1056
|
+
);
|
|
1057
|
+
this.#ledgerRead = Object.freeze(
|
|
1058
|
+
EvolutionLedger.prototype.read.bind(ledger),
|
|
1059
|
+
);
|
|
1060
|
+
this.#ledgerVerifyReceipt = Object.freeze(
|
|
1061
|
+
EvolutionLedger.prototype.verifyReceipt.bind(ledger),
|
|
1062
|
+
);
|
|
1063
|
+
this.#artifactPut = Object.freeze(
|
|
1064
|
+
EvolutionArtifactPorts.prototype.putCanonical.bind(artifactPorts),
|
|
1065
|
+
);
|
|
1066
|
+
this.#artifactResolve =
|
|
1067
|
+
EvolutionArtifactPorts.prototype.createEvolutionLedgerArtifactResolver.call(
|
|
1068
|
+
artifactPorts,
|
|
1069
|
+
Object.freeze({ purpose: this.#purpose }),
|
|
1070
|
+
);
|
|
1071
|
+
Object.freeze(this);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
#assertAudience(value, label, { nullable = false } = {}) {
|
|
1075
|
+
if (value === null && nullable) return null;
|
|
1076
|
+
const normalized = identifier(value, label);
|
|
1077
|
+
if (this.#audience !== null && normalized !== this.#audience) {
|
|
1078
|
+
throw portsError(
|
|
1079
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
1080
|
+
`${label} differs from the captured audience`,
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
return normalized;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
#durabilityBinding(ref, type) {
|
|
1087
|
+
return deepFreeze({
|
|
1088
|
+
artifactTenantId: this.#artifactTenantId,
|
|
1089
|
+
digest: ref.digest,
|
|
1090
|
+
purpose: this.#purpose,
|
|
1091
|
+
ref: ref.ref,
|
|
1092
|
+
retention: "ledger",
|
|
1093
|
+
schema: EVOLUTION_ARTIFACT_DURABILITY_BINDING_SCHEMA,
|
|
1094
|
+
type,
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
#validateDurabilityReceipt(receipt, binding, label) {
|
|
1099
|
+
assertAllExactRecord(
|
|
1100
|
+
receipt,
|
|
1101
|
+
DURABILITY_RECEIPT_KEYS,
|
|
1102
|
+
label,
|
|
1103
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
1104
|
+
);
|
|
1105
|
+
if (
|
|
1106
|
+
receipt.schema !== EVOLUTION_ARTIFACT_DURABILITY_RECEIPT_SCHEMA ||
|
|
1107
|
+
receipt.authenticated !== true ||
|
|
1108
|
+
receipt.durable !== true ||
|
|
1109
|
+
receipt.authorityId !== this.#durabilityAuthorityId
|
|
1110
|
+
) {
|
|
1111
|
+
throw portsError(
|
|
1112
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
1113
|
+
`${label} is not authenticated durable evidence`,
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
for (const field of [
|
|
1117
|
+
"artifactTenantId",
|
|
1118
|
+
"digest",
|
|
1119
|
+
"purpose",
|
|
1120
|
+
"ref",
|
|
1121
|
+
"retention",
|
|
1122
|
+
"type",
|
|
1123
|
+
]) {
|
|
1124
|
+
assertSame(
|
|
1125
|
+
receipt[field],
|
|
1126
|
+
binding[field],
|
|
1127
|
+
`${label} ${field}`,
|
|
1128
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
digest(
|
|
1132
|
+
receipt.receiptDigest,
|
|
1133
|
+
`${label} receiptDigest`,
|
|
1134
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
1135
|
+
);
|
|
1136
|
+
return receipt;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
#retainDurably(binding, bytes) {
|
|
1140
|
+
let receipt;
|
|
1141
|
+
try {
|
|
1142
|
+
receipt = assertSynchronous(
|
|
1143
|
+
this.#durabilityRetain(
|
|
1144
|
+
Object.freeze({
|
|
1145
|
+
binding,
|
|
1146
|
+
bytes: Buffer.from(bytes),
|
|
1147
|
+
schema: EVOLUTION_ARTIFACT_DURABILITY_RETAIN_REQUEST_SCHEMA,
|
|
1148
|
+
}),
|
|
1149
|
+
),
|
|
1150
|
+
"artifact durability retain result",
|
|
1151
|
+
);
|
|
1152
|
+
} catch (cause) {
|
|
1153
|
+
if (cause instanceof EvolutionLedgerPortsError) throw cause;
|
|
1154
|
+
throw portsError(
|
|
1155
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
1156
|
+
"artifact durability authority failed to retain canonical bytes",
|
|
1157
|
+
{ cause },
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
return this.#validateDurabilityReceipt(
|
|
1161
|
+
receipt,
|
|
1162
|
+
binding,
|
|
1163
|
+
"artifact durability retain receipt",
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
#resolveDurably(ref, type) {
|
|
1168
|
+
return resolveDurableArtifact(
|
|
1169
|
+
this.#durabilityAuthority,
|
|
1170
|
+
durabilityResolveRequest({
|
|
1171
|
+
artifactTenantId: this.#artifactTenantId,
|
|
1172
|
+
digest: ref.digest,
|
|
1173
|
+
purpose: this.#purpose,
|
|
1174
|
+
ref: ref.ref,
|
|
1175
|
+
}),
|
|
1176
|
+
type,
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
#putSubject(type, value, logicalDigest, audience) {
|
|
1181
|
+
const context = {
|
|
1182
|
+
purpose: this.#purpose,
|
|
1183
|
+
retention: "ledger",
|
|
1184
|
+
...(audience === null || audience === undefined ? {} : { audience }),
|
|
1185
|
+
};
|
|
1186
|
+
let result;
|
|
1187
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
1188
|
+
try {
|
|
1189
|
+
result = assertSynchronous(
|
|
1190
|
+
this.#artifactPut(type, value, deepFreeze(context)),
|
|
1191
|
+
"EvolutionArtifactPorts.putCanonical result",
|
|
1192
|
+
);
|
|
1193
|
+
break;
|
|
1194
|
+
} catch (error) {
|
|
1195
|
+
if (
|
|
1196
|
+
attempt === 0 &&
|
|
1197
|
+
error?.code === EVOLUTION_ARTIFACT_STORE_FAILED_CODE
|
|
1198
|
+
) {
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
throw error;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
assertAllExactRecord(
|
|
1205
|
+
result,
|
|
1206
|
+
ARTIFACT_PUT_RESULT_KEYS,
|
|
1207
|
+
"artifact put result",
|
|
1208
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1209
|
+
);
|
|
1210
|
+
const ref = normalizeArtifactRef(
|
|
1211
|
+
result.ref,
|
|
1212
|
+
"artifact put result ref",
|
|
1213
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1214
|
+
);
|
|
1215
|
+
assertSame(
|
|
1216
|
+
result.digest,
|
|
1217
|
+
ref.digest,
|
|
1218
|
+
"artifact put digest",
|
|
1219
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1220
|
+
);
|
|
1221
|
+
if (logicalDigest === ref.digest) {
|
|
1222
|
+
throw portsError(
|
|
1223
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1224
|
+
"logical payload digest must remain distinct from artifact record digest",
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
if (typeof result.envelope !== "string") {
|
|
1228
|
+
throw portsError(
|
|
1229
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1230
|
+
"artifact put result envelope is invalid",
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
const receipt = result.receipt;
|
|
1234
|
+
if (
|
|
1235
|
+
!isPlainRecord(
|
|
1236
|
+
receipt,
|
|
1237
|
+
"artifact persistence receipt",
|
|
1238
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1239
|
+
)
|
|
1240
|
+
) {
|
|
1241
|
+
throw portsError(
|
|
1242
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1243
|
+
"artifact persistence receipt is invalid",
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
if (
|
|
1247
|
+
ownData(
|
|
1248
|
+
receipt,
|
|
1249
|
+
"schema",
|
|
1250
|
+
"artifact persistence receipt",
|
|
1251
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1252
|
+
) !== EVOLUTION_ARTIFACT_PERSISTENCE_RECEIPT_SCHEMA ||
|
|
1253
|
+
ownData(
|
|
1254
|
+
receipt,
|
|
1255
|
+
"tenantId",
|
|
1256
|
+
"artifact persistence receipt",
|
|
1257
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1258
|
+
) !== this.#artifactTenantId ||
|
|
1259
|
+
ownData(
|
|
1260
|
+
receipt,
|
|
1261
|
+
"type",
|
|
1262
|
+
"artifact persistence receipt",
|
|
1263
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1264
|
+
) !== type ||
|
|
1265
|
+
ownData(
|
|
1266
|
+
receipt,
|
|
1267
|
+
"retention",
|
|
1268
|
+
"artifact persistence receipt",
|
|
1269
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1270
|
+
) !== "ledger" ||
|
|
1271
|
+
ownData(
|
|
1272
|
+
receipt,
|
|
1273
|
+
"purpose",
|
|
1274
|
+
"artifact persistence receipt",
|
|
1275
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1276
|
+
) !== this.#purpose
|
|
1277
|
+
) {
|
|
1278
|
+
throw portsError(
|
|
1279
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1280
|
+
"artifact persistence receipt is not bound to this adapter",
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
const receiptAudience = ownData(
|
|
1284
|
+
receipt,
|
|
1285
|
+
"audience",
|
|
1286
|
+
"artifact persistence receipt",
|
|
1287
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1288
|
+
);
|
|
1289
|
+
if (
|
|
1290
|
+
typeof receiptAudience !== "string" ||
|
|
1291
|
+
(this.#audience !== null && receiptAudience !== this.#audience) ||
|
|
1292
|
+
(audience !== null &&
|
|
1293
|
+
audience !== undefined &&
|
|
1294
|
+
receiptAudience !== audience) ||
|
|
1295
|
+
ownData(
|
|
1296
|
+
receipt,
|
|
1297
|
+
"digest",
|
|
1298
|
+
"artifact persistence receipt",
|
|
1299
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1300
|
+
) !== ref.digest ||
|
|
1301
|
+
ownData(
|
|
1302
|
+
receipt,
|
|
1303
|
+
"ref",
|
|
1304
|
+
"artifact persistence receipt",
|
|
1305
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1306
|
+
) !== ref.ref ||
|
|
1307
|
+
ownData(
|
|
1308
|
+
receipt,
|
|
1309
|
+
"persisted",
|
|
1310
|
+
"artifact persistence receipt",
|
|
1311
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1312
|
+
) !== true ||
|
|
1313
|
+
ownData(
|
|
1314
|
+
receipt,
|
|
1315
|
+
"readbackVerified",
|
|
1316
|
+
"artifact persistence receipt",
|
|
1317
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1318
|
+
) !== true ||
|
|
1319
|
+
ownData(
|
|
1320
|
+
receipt,
|
|
1321
|
+
"integrityVerified",
|
|
1322
|
+
"artifact persistence receipt",
|
|
1323
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1324
|
+
) !== true
|
|
1325
|
+
) {
|
|
1326
|
+
throw portsError(
|
|
1327
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1328
|
+
"artifact persistence receipt does not bind its exact readback",
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
const durableRecord = deepFreeze({
|
|
1332
|
+
audience: receiptAudience,
|
|
1333
|
+
purpose: this.#purpose,
|
|
1334
|
+
retention: "ledger",
|
|
1335
|
+
schema: EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA,
|
|
1336
|
+
tenantId: this.#artifactTenantId,
|
|
1337
|
+
type,
|
|
1338
|
+
value,
|
|
1339
|
+
});
|
|
1340
|
+
const durableBytes = Buffer.from(canonicalJson(durableRecord), "utf8");
|
|
1341
|
+
if (sha256(durableBytes) !== ref.digest) {
|
|
1342
|
+
throw portsError(
|
|
1343
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1344
|
+
"artifact persistence ref does not identify the exact canonical record",
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
this.#retainDurably(this.#durabilityBinding(ref, type), durableBytes);
|
|
1348
|
+
return ref;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
#resolveSubject(event, expectedType, expectedAudience = null) {
|
|
1352
|
+
const ref = normalizeArtifactRef(
|
|
1353
|
+
event.subjectRef,
|
|
1354
|
+
"domain event subjectRef",
|
|
1355
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1356
|
+
);
|
|
1357
|
+
const durable = this.#resolveDurably(ref, expectedType);
|
|
1358
|
+
let resolution = null;
|
|
1359
|
+
try {
|
|
1360
|
+
resolution = assertSynchronous(
|
|
1361
|
+
this.#artifactResolve(
|
|
1362
|
+
deepFreeze({
|
|
1363
|
+
epoch: event.epoch,
|
|
1364
|
+
ledgerId: event.ledgerId,
|
|
1365
|
+
ref,
|
|
1366
|
+
tenantId: this.#artifactTenantId,
|
|
1367
|
+
}),
|
|
1368
|
+
),
|
|
1369
|
+
"artifact resolver result",
|
|
1370
|
+
);
|
|
1371
|
+
} catch {
|
|
1372
|
+
// A removed/corrupt local cache is recoverable only through the trusted
|
|
1373
|
+
// durability authority's exact canonical-byte replica above.
|
|
1374
|
+
}
|
|
1375
|
+
if (resolution !== null) {
|
|
1376
|
+
assertAllExactRecord(
|
|
1377
|
+
resolution,
|
|
1378
|
+
LEDGER_RESOLUTION_KEYS,
|
|
1379
|
+
"artifact resolver result",
|
|
1380
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1381
|
+
);
|
|
1382
|
+
if (
|
|
1383
|
+
resolution.schema !== EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA ||
|
|
1384
|
+
resolution.authenticated !== true ||
|
|
1385
|
+
resolution.found !== true ||
|
|
1386
|
+
resolution.ref !== ref.ref ||
|
|
1387
|
+
resolution.digest !== ref.digest
|
|
1388
|
+
) {
|
|
1389
|
+
throw portsError(
|
|
1390
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1391
|
+
"artifact resolver result is not authenticated and exactly bound",
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
digest(
|
|
1395
|
+
resolution.receiptDigest,
|
|
1396
|
+
"artifact resolution receiptDigest",
|
|
1397
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1398
|
+
);
|
|
1399
|
+
const localBytes = copyBytes(resolution.bytes);
|
|
1400
|
+
if (
|
|
1401
|
+
sha256(localBytes) !== ref.digest ||
|
|
1402
|
+
!localBytes.equals(durable.bytes)
|
|
1403
|
+
) {
|
|
1404
|
+
throw portsError(
|
|
1405
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1406
|
+
"local artifact bytes differ from the authoritative durable replica",
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
const bytes = durable.bytes;
|
|
1411
|
+
let record;
|
|
1412
|
+
const json = bufferToString(bytes, "utf8");
|
|
1413
|
+
try {
|
|
1414
|
+
record = JSON.parse(json);
|
|
1415
|
+
} catch (cause) {
|
|
1416
|
+
throw portsError(
|
|
1417
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1418
|
+
"artifact record is not canonical JSON",
|
|
1419
|
+
{ cause },
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1422
|
+
assertAllExactRecord(
|
|
1423
|
+
record,
|
|
1424
|
+
ARTIFACT_RECORD_KEYS,
|
|
1425
|
+
"artifact record",
|
|
1426
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1427
|
+
);
|
|
1428
|
+
if (
|
|
1429
|
+
canonicalJson(record) !== json ||
|
|
1430
|
+
record.schema !== EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA ||
|
|
1431
|
+
record.tenantId !== this.#artifactTenantId ||
|
|
1432
|
+
record.type !== expectedType ||
|
|
1433
|
+
record.retention !== "ledger" ||
|
|
1434
|
+
record.purpose !== this.#purpose ||
|
|
1435
|
+
(expectedAudience !== null && record.audience !== expectedAudience) ||
|
|
1436
|
+
(this.#audience !== null && record.audience !== this.#audience)
|
|
1437
|
+
) {
|
|
1438
|
+
throw portsError(
|
|
1439
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1440
|
+
"artifact record is not bound to the expected tenant, audience, type, retention, and purpose",
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
return deepFreeze(record);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
#queryEvent(eventId) {
|
|
1447
|
+
let result;
|
|
1448
|
+
try {
|
|
1449
|
+
result = assertSynchronous(
|
|
1450
|
+
this.#ledgerQuery(
|
|
1451
|
+
Object.freeze({ eventId }),
|
|
1452
|
+
Object.freeze({ issueReceipt: false }),
|
|
1453
|
+
),
|
|
1454
|
+
"EvolutionLedger.query result",
|
|
1455
|
+
);
|
|
1456
|
+
} catch (cause) {
|
|
1457
|
+
throw portsError(
|
|
1458
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
1459
|
+
"EvolutionLedger query failed closed",
|
|
1460
|
+
{ cause, eventId },
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
if (result === null) return null;
|
|
1464
|
+
assertAllExactRecord(
|
|
1465
|
+
result,
|
|
1466
|
+
QUERY_RESULT_KEYS,
|
|
1467
|
+
"EvolutionLedger query result",
|
|
1468
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1469
|
+
);
|
|
1470
|
+
if (
|
|
1471
|
+
result.schema !== EVOLUTION_LEDGER_QUERY_SCHEMA ||
|
|
1472
|
+
result.authenticated !== true ||
|
|
1473
|
+
result.durable !== true ||
|
|
1474
|
+
result.receipt !== null ||
|
|
1475
|
+
result.event.eventId !== eventId
|
|
1476
|
+
) {
|
|
1477
|
+
throw portsError(
|
|
1478
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1479
|
+
"EvolutionLedger query result is not authenticated and exactly bound",
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1482
|
+
return result;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
#verifiedAppend(input, options = {}) {
|
|
1486
|
+
const receipt = assertSynchronous(
|
|
1487
|
+
this.#appendDomainEvent(deepFreeze(input), deepFreeze(options)),
|
|
1488
|
+
"EvolutionLedger append receipt",
|
|
1489
|
+
);
|
|
1490
|
+
const verification = assertSynchronous(
|
|
1491
|
+
this.#ledgerVerifyReceipt(receipt),
|
|
1492
|
+
"EvolutionLedger receipt verification",
|
|
1493
|
+
);
|
|
1494
|
+
if (
|
|
1495
|
+
verification.valid !== true ||
|
|
1496
|
+
verification.authenticated !== true ||
|
|
1497
|
+
verification.durable !== true ||
|
|
1498
|
+
verification.event.eventId !== input.eventId
|
|
1499
|
+
) {
|
|
1500
|
+
throw portsError(
|
|
1501
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1502
|
+
"EvolutionLedger append receipt is not durably bound",
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
this.#assertEvidenceAnchor(verification, input.eventId);
|
|
1506
|
+
return verification;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
#assertEvidenceAnchor(evidence, eventId = null) {
|
|
1510
|
+
const event = evidence?.event;
|
|
1511
|
+
const anchor = evidence?.anchor;
|
|
1512
|
+
const authority = evidence?.authority;
|
|
1513
|
+
if (
|
|
1514
|
+
evidence?.authenticated !== true ||
|
|
1515
|
+
evidence?.durable !== true ||
|
|
1516
|
+
!event ||
|
|
1517
|
+
!anchor ||
|
|
1518
|
+
!authority ||
|
|
1519
|
+
authority.authenticated !== true ||
|
|
1520
|
+
authority.durable !== true ||
|
|
1521
|
+
!Number.isSafeInteger(event.sequence) ||
|
|
1522
|
+
event.sequence < 1 ||
|
|
1523
|
+
anchor.sequence !== event.sequence ||
|
|
1524
|
+
anchor.headDigest !== event.eventDigest ||
|
|
1525
|
+
anchor.ledgerId !== event.ledgerId ||
|
|
1526
|
+
anchor.epoch !== event.epoch ||
|
|
1527
|
+
anchor.identityDigest !== event.identityDigest ||
|
|
1528
|
+
authority.ledgerId !== event.ledgerId ||
|
|
1529
|
+
authority.epoch !== event.epoch ||
|
|
1530
|
+
authority.identityDigest !== event.identityDigest ||
|
|
1531
|
+
!Number.isSafeInteger(authority.sequence) ||
|
|
1532
|
+
authority.sequence < event.sequence ||
|
|
1533
|
+
(eventId !== null && event.eventId !== eventId)
|
|
1534
|
+
) {
|
|
1535
|
+
throw portsError(
|
|
1536
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1537
|
+
"EvolutionLedger evidence anchor is not exactly bound to its event",
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
return event;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
#events() {
|
|
1544
|
+
let events;
|
|
1545
|
+
try {
|
|
1546
|
+
events = assertSynchronous(
|
|
1547
|
+
this.#ledgerRead(
|
|
1548
|
+
Object.freeze({
|
|
1549
|
+
afterSequence: 0,
|
|
1550
|
+
limit: EVOLUTION_LEDGER_MAX_EVENTS,
|
|
1551
|
+
}),
|
|
1552
|
+
),
|
|
1553
|
+
"EvolutionLedger.read result",
|
|
1554
|
+
);
|
|
1555
|
+
} catch (cause) {
|
|
1556
|
+
throw portsError(
|
|
1557
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
1558
|
+
"EvolutionLedger bounded read failed closed",
|
|
1559
|
+
{ cause },
|
|
1560
|
+
);
|
|
1561
|
+
}
|
|
1562
|
+
if (!Array.isArray(events) || events.length > EVOLUTION_LEDGER_MAX_EVENTS) {
|
|
1563
|
+
throw portsError(
|
|
1564
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1565
|
+
"EvolutionLedger returned an invalid bounded event snapshot",
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
return events;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
#indexSnapshot(events, subjects = new Map()) {
|
|
1572
|
+
const byEventId = new Map();
|
|
1573
|
+
const bySequence = new Map();
|
|
1574
|
+
for (const event of events) {
|
|
1575
|
+
if (
|
|
1576
|
+
byEventId.has(event.eventId) ||
|
|
1577
|
+
bySequence.has(event.sequence) ||
|
|
1578
|
+
event.sequence !== bySequence.size + 1
|
|
1579
|
+
) {
|
|
1580
|
+
throw portsError(
|
|
1581
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1582
|
+
"EvolutionLedger returned a non-unique or discontinuous authenticated event snapshot",
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
byEventId.set(event.eventId, event);
|
|
1586
|
+
bySequence.set(event.sequence, event);
|
|
1587
|
+
}
|
|
1588
|
+
return {
|
|
1589
|
+
byEventId,
|
|
1590
|
+
bySequence,
|
|
1591
|
+
events,
|
|
1592
|
+
subjects,
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
#snapshot() {
|
|
1597
|
+
return this.#indexSnapshot(this.#events());
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
#snapshotWithVerifiedEvent(snapshot, verification) {
|
|
1601
|
+
const event = this.#assertEvidenceAnchor(verification);
|
|
1602
|
+
const authority = verification.authority;
|
|
1603
|
+
const previous = snapshot.events.at(-1) || null;
|
|
1604
|
+
const expectedSequence = (previous?.sequence ?? 0) + 1;
|
|
1605
|
+
if (
|
|
1606
|
+
event.sequence > expectedSequence ||
|
|
1607
|
+
authority.sequence > event.sequence
|
|
1608
|
+
) {
|
|
1609
|
+
// Another writer committed between the authenticated read and this
|
|
1610
|
+
// verification. A complete reread is required because events are absent.
|
|
1611
|
+
return null;
|
|
1612
|
+
}
|
|
1613
|
+
if (
|
|
1614
|
+
event.sequence !== expectedSequence ||
|
|
1615
|
+
event.prevDigest !== (previous?.eventDigest ?? null) ||
|
|
1616
|
+
authority.sequence !== event.sequence ||
|
|
1617
|
+
authority.headDigest !== event.eventDigest ||
|
|
1618
|
+
authority.anchorDigest !== verification.anchor.anchorDigest ||
|
|
1619
|
+
snapshot.byEventId.has(event.eventId) ||
|
|
1620
|
+
snapshot.bySequence.has(event.sequence)
|
|
1621
|
+
) {
|
|
1622
|
+
throw portsError(
|
|
1623
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1624
|
+
"verified append event is not the exact continuation of its authenticated snapshot",
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
return this.#indexSnapshot(
|
|
1628
|
+
[...snapshot.events, event],
|
|
1629
|
+
new Map(snapshot.subjects),
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
#snapshotSubject(snapshot, event, expectedType, expectedAudience = null) {
|
|
1634
|
+
const ref = normalizeArtifactRef(
|
|
1635
|
+
event.subjectRef,
|
|
1636
|
+
"domain event subjectRef",
|
|
1637
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1638
|
+
);
|
|
1639
|
+
const cacheKey = `${expectedType}\0${expectedAudience ?? ""}\0${ref.ref}\0${ref.digest}`;
|
|
1640
|
+
let record = snapshot.subjects.get(cacheKey);
|
|
1641
|
+
if (!record) {
|
|
1642
|
+
record = this.#resolveSubject(event, expectedType, expectedAudience);
|
|
1643
|
+
snapshot.subjects.set(cacheKey, record);
|
|
1644
|
+
}
|
|
1645
|
+
return record;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
#assertDomainEvent(event, kind, eventId, logicalDigest) {
|
|
1649
|
+
if (
|
|
1650
|
+
event.schema !== EVOLUTION_LEDGER_DOMAIN_EVENT_SCHEMA ||
|
|
1651
|
+
event.eventId !== eventId ||
|
|
1652
|
+
event.type !== EVENT_TYPES[kind] ||
|
|
1653
|
+
event.reason !== logicalDigest ||
|
|
1654
|
+
event.artifactTenantId !== this.#artifactTenantId ||
|
|
1655
|
+
event.subjectRef.digest === logicalDigest
|
|
1656
|
+
) {
|
|
1657
|
+
throw portsError(
|
|
1658
|
+
EVOLUTION_LEDGER_PORTS_COLLISION_CODE,
|
|
1659
|
+
`deterministic ${kind} event id is bound to another subject`,
|
|
1660
|
+
{ eventId },
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
const sourceRefs = readDenseDataArray(
|
|
1664
|
+
event.sourceRefs,
|
|
1665
|
+
`${kind} event sourceRefs`,
|
|
1666
|
+
2,
|
|
1667
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1668
|
+
);
|
|
1669
|
+
for (const [index, ref] of sourceRefs.entries()) {
|
|
1670
|
+
normalizeArtifactRef(
|
|
1671
|
+
ref,
|
|
1672
|
+
`${kind} event sourceRefs[${index}]`,
|
|
1673
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
return sourceRefs;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
#auditFromEvent(
|
|
1680
|
+
event,
|
|
1681
|
+
expectedAuditDigest = null,
|
|
1682
|
+
record = this.#resolveSubject(event, ARTIFACT_TYPES.audit),
|
|
1683
|
+
) {
|
|
1684
|
+
const audit = verifySkillMutationAuditEvent(record.value);
|
|
1685
|
+
const eventId = deterministicEventId("audit", {
|
|
1686
|
+
auditDigest: audit.auditDigest,
|
|
1687
|
+
});
|
|
1688
|
+
const sourceRefs = this.#assertDomainEvent(
|
|
1689
|
+
event,
|
|
1690
|
+
"audit",
|
|
1691
|
+
eventId,
|
|
1692
|
+
audit.auditDigest,
|
|
1693
|
+
);
|
|
1694
|
+
if (
|
|
1695
|
+
sourceRefs.length !== 0 ||
|
|
1696
|
+
event.decision !==
|
|
1697
|
+
(audit.decision === "allow" ? "accepted" : "rejected") ||
|
|
1698
|
+
event.correlationId !== nullableEventIdentifier(audit.operationId) ||
|
|
1699
|
+
event.tenantId !== nullableEventIdentifier(audit.tenantId) ||
|
|
1700
|
+
event.skillName !== nullableEventSkillName(audit.skillName) ||
|
|
1701
|
+
event.timestamp !== audit.occurredAt ||
|
|
1702
|
+
(expectedAuditDigest !== null &&
|
|
1703
|
+
audit.auditDigest !== expectedAuditDigest)
|
|
1704
|
+
) {
|
|
1705
|
+
throw portsError(
|
|
1706
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1707
|
+
"mutation audit ledger event differs from its exact subject",
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
return { audit, event, record };
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
#assertConsumptionAudit(resolved, receipt) {
|
|
1714
|
+
const audit = resolved.audit;
|
|
1715
|
+
if (
|
|
1716
|
+
audit.phase !== "consume" ||
|
|
1717
|
+
audit.decision !== "allow" ||
|
|
1718
|
+
audit.code !== "CC_SKILL_MUTATION_CONSUMED" ||
|
|
1719
|
+
resolved.event.eventDigest !== receipt.headDigest ||
|
|
1720
|
+
resolved.event.sequence !== receipt.sequence ||
|
|
1721
|
+
resolved.record.audience !== receipt.audience
|
|
1722
|
+
) {
|
|
1723
|
+
throw portsError(
|
|
1724
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1725
|
+
"consumption receipt is not bound to a consume-allow audit head",
|
|
1726
|
+
);
|
|
1727
|
+
}
|
|
1728
|
+
for (const field of [
|
|
1729
|
+
"tenantId",
|
|
1730
|
+
"audience",
|
|
1731
|
+
"operationId",
|
|
1732
|
+
"operation",
|
|
1733
|
+
"transitionSubjectDigest",
|
|
1734
|
+
"skillName",
|
|
1735
|
+
"targetScope",
|
|
1736
|
+
"expectedTargetDigest",
|
|
1737
|
+
"expectedTargetRevision",
|
|
1738
|
+
"expiresAt",
|
|
1739
|
+
"nonce",
|
|
1740
|
+
"principalId",
|
|
1741
|
+
"role",
|
|
1742
|
+
"requestDigest",
|
|
1743
|
+
]) {
|
|
1744
|
+
assertSame(
|
|
1745
|
+
audit[field],
|
|
1746
|
+
receipt[field],
|
|
1747
|
+
`consume audit ${field}`,
|
|
1748
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
return resolved;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
#auditFromSnapshot(snapshot, receipt) {
|
|
1755
|
+
const event = snapshot.bySequence.get(receipt.sequence);
|
|
1756
|
+
if (!event) {
|
|
1757
|
+
throw portsError(
|
|
1758
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1759
|
+
"consumption receipt audit sequence is absent from the authenticated snapshot",
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
const record = this.#snapshotSubject(snapshot, event, ARTIFACT_TYPES.audit);
|
|
1763
|
+
// EvolutionLedger.read authenticates the complete persisted chain before
|
|
1764
|
+
// returning it, so the indexed event digest is the signed head at this
|
|
1765
|
+
// exact receipt sequence.
|
|
1766
|
+
return this.#assertConsumptionAudit(
|
|
1767
|
+
this.#auditFromEvent(event, receipt.auditDigest, record),
|
|
1768
|
+
receipt,
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
#prepareFromEvent(event, record, consumeAudit, expected = null) {
|
|
1773
|
+
const intent = normalizeIntent(record.value);
|
|
1774
|
+
const eventId = deterministicEventId("prepare", {
|
|
1775
|
+
transactionId: intent.transactionId,
|
|
1776
|
+
});
|
|
1777
|
+
const sourceRefs = this.#assertDomainEvent(
|
|
1778
|
+
event,
|
|
1779
|
+
"prepare",
|
|
1780
|
+
eventId,
|
|
1781
|
+
intent.intentDigest,
|
|
1782
|
+
);
|
|
1783
|
+
if (
|
|
1784
|
+
sourceRefs.length !== 1 ||
|
|
1785
|
+
!refsEqual(sourceRefs[0], consumeAudit.event.subjectRef) ||
|
|
1786
|
+
event.decision !== "prepared" ||
|
|
1787
|
+
event.correlationId !== intent.transactionId ||
|
|
1788
|
+
event.tenantId !== intent.mutationRequest.tenantId ||
|
|
1789
|
+
event.skillName !== intent.skillName ||
|
|
1790
|
+
record.audience !== intent.mutationRequest.audience
|
|
1791
|
+
) {
|
|
1792
|
+
throw portsError(
|
|
1793
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1794
|
+
"release prepare event differs from its intent or authority audit",
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1797
|
+
if (
|
|
1798
|
+
expected !== null &&
|
|
1799
|
+
(expected.transactionId !== intent.transactionId ||
|
|
1800
|
+
(expected.intentDigest !== undefined &&
|
|
1801
|
+
expected.intentDigest !== intent.intentDigest) ||
|
|
1802
|
+
(expected.authorityReceiptDigest !== undefined &&
|
|
1803
|
+
expected.authorityReceiptDigest !== intent.authorityReceiptDigest))
|
|
1804
|
+
) {
|
|
1805
|
+
throw portsError(
|
|
1806
|
+
EVOLUTION_LEDGER_PORTS_COLLISION_CODE,
|
|
1807
|
+
"release prepare event collides with another transaction subject",
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
return { consumeAudit, event, intent, record };
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
#prepareFromSnapshot(snapshot, event, expected = null) {
|
|
1814
|
+
const record = this.#snapshotSubject(
|
|
1815
|
+
snapshot,
|
|
1816
|
+
event,
|
|
1817
|
+
ARTIFACT_TYPES.prepare,
|
|
1818
|
+
);
|
|
1819
|
+
const intent = normalizeIntent(record.value);
|
|
1820
|
+
return this.#prepareFromEvent(
|
|
1821
|
+
event,
|
|
1822
|
+
record,
|
|
1823
|
+
this.#auditFromSnapshot(snapshot, intent.authorityReceipt),
|
|
1824
|
+
expected,
|
|
1825
|
+
);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
#prepareProjection(prepared) {
|
|
1829
|
+
const core = {
|
|
1830
|
+
authenticated: true,
|
|
1831
|
+
authorityReceiptDigest: prepared.intent.authorityReceiptDigest,
|
|
1832
|
+
durable: true,
|
|
1833
|
+
epoch: prepared.event.epoch,
|
|
1834
|
+
headDigest: prepared.event.eventDigest,
|
|
1835
|
+
intentDigest: prepared.intent.intentDigest,
|
|
1836
|
+
ledgerId: prepared.event.ledgerId,
|
|
1837
|
+
schema: SKILL_RELEASE_LEDGER_PROJECTION_SCHEMA,
|
|
1838
|
+
sequence: prepared.event.sequence,
|
|
1839
|
+
status: "prepared",
|
|
1840
|
+
transactionId: prepared.intent.transactionId,
|
|
1841
|
+
};
|
|
1842
|
+
return deepFreeze({
|
|
1843
|
+
...core,
|
|
1844
|
+
receiptDigest: projectionReceiptDigest("prepare", core),
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
#finalizeFromEvent(snapshot, event, prepareCache = new Map()) {
|
|
1849
|
+
const record = this.#snapshotSubject(
|
|
1850
|
+
snapshot,
|
|
1851
|
+
event,
|
|
1852
|
+
ARTIFACT_TYPES.finalize,
|
|
1853
|
+
);
|
|
1854
|
+
const finalization = normalizeFinalize(record.value);
|
|
1855
|
+
const logicalDigest = logicalFinalizeDigest(finalization);
|
|
1856
|
+
const eventId = deterministicEventId("finalize", {
|
|
1857
|
+
transactionId: finalization.transactionId,
|
|
1858
|
+
});
|
|
1859
|
+
const sourceRefs = this.#assertDomainEvent(
|
|
1860
|
+
event,
|
|
1861
|
+
"finalize",
|
|
1862
|
+
eventId,
|
|
1863
|
+
logicalDigest,
|
|
1864
|
+
);
|
|
1865
|
+
const prepareEventId = deterministicEventId("prepare", {
|
|
1866
|
+
transactionId: finalization.transactionId,
|
|
1867
|
+
});
|
|
1868
|
+
const prepareEvent = snapshot.byEventId.get(prepareEventId);
|
|
1869
|
+
if (!prepareEvent) {
|
|
1870
|
+
throw portsError(
|
|
1871
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1872
|
+
"release finalize event has no verified prepare event",
|
|
1873
|
+
);
|
|
1874
|
+
}
|
|
1875
|
+
let prepared = prepareCache.get(prepareEventId);
|
|
1876
|
+
if (!prepared) {
|
|
1877
|
+
prepared = this.#prepareFromSnapshot(snapshot, prepareEvent, {
|
|
1878
|
+
transactionId: finalization.transactionId,
|
|
1879
|
+
});
|
|
1880
|
+
prepareCache.set(prepareEventId, prepared);
|
|
1881
|
+
}
|
|
1882
|
+
const prepareProjection = this.#prepareProjection(prepared);
|
|
1883
|
+
if (
|
|
1884
|
+
sourceRefs.length !== 1 ||
|
|
1885
|
+
!refsEqual(sourceRefs[0], prepared.event.subjectRef) ||
|
|
1886
|
+
event.decision !== "committed" ||
|
|
1887
|
+
event.correlationId !== finalization.transactionId ||
|
|
1888
|
+
event.tenantId !== prepared.intent.mutationRequest.tenantId ||
|
|
1889
|
+
event.skillName !== finalization.skillName ||
|
|
1890
|
+
record.audience !== prepared.intent.mutationRequest.audience ||
|
|
1891
|
+
finalization.intentDigest !== prepared.intent.intentDigest ||
|
|
1892
|
+
finalization.authorityReceiptDigest !==
|
|
1893
|
+
prepared.intent.authorityReceiptDigest ||
|
|
1894
|
+
finalization.expectedPrepareReceiptDigest !==
|
|
1895
|
+
prepareProjection.receiptDigest ||
|
|
1896
|
+
finalization.skillName !== prepared.intent.skillName ||
|
|
1897
|
+
finalization.revision !== prepared.intent.expectedRevision + 1 ||
|
|
1898
|
+
finalization.stateDigest !== prepared.intent.nextStateDigest ||
|
|
1899
|
+
finalization.pointerDigest !== prepared.intent.pointerDigest
|
|
1900
|
+
) {
|
|
1901
|
+
throw portsError(
|
|
1902
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1903
|
+
"release finalize event is not exactly bound to its prepare lineage",
|
|
1904
|
+
);
|
|
1905
|
+
}
|
|
1906
|
+
return {
|
|
1907
|
+
event,
|
|
1908
|
+
finalization,
|
|
1909
|
+
logicalDigest,
|
|
1910
|
+
prepared,
|
|
1911
|
+
prepareProjection,
|
|
1912
|
+
record,
|
|
1913
|
+
tenantId: prepared.intent.mutationRequest.tenantId,
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
#lineages(snapshot, prepareCache = new Map()) {
|
|
1918
|
+
const groups = new Map();
|
|
1919
|
+
const byTransaction = new Map();
|
|
1920
|
+
for (const event of snapshot.events) {
|
|
1921
|
+
if (
|
|
1922
|
+
event.schema !== EVOLUTION_LEDGER_DOMAIN_EVENT_SCHEMA ||
|
|
1923
|
+
event.type !== EVENT_TYPES.finalize
|
|
1924
|
+
) {
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1927
|
+
const finalized = this.#finalizeFromEvent(snapshot, event, prepareCache);
|
|
1928
|
+
if (byTransaction.has(finalized.finalization.transactionId)) {
|
|
1929
|
+
throw portsError(
|
|
1930
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1931
|
+
"release finalize transaction appears more than once",
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
byTransaction.set(finalized.finalization.transactionId, finalized);
|
|
1935
|
+
const groupKey = `${finalized.tenantId}\0${finalized.finalization.skillName}`;
|
|
1936
|
+
const group = groups.get(groupKey) || [];
|
|
1937
|
+
group.push(finalized);
|
|
1938
|
+
groups.set(groupKey, group);
|
|
1939
|
+
}
|
|
1940
|
+
for (const group of groups.values()) {
|
|
1941
|
+
group.sort(
|
|
1942
|
+
(left, right) =>
|
|
1943
|
+
left.finalization.revision - right.finalization.revision ||
|
|
1944
|
+
left.event.sequence - right.event.sequence,
|
|
1945
|
+
);
|
|
1946
|
+
let previous = null;
|
|
1947
|
+
for (let index = 0; index < group.length; index += 1) {
|
|
1948
|
+
const entry = group[index];
|
|
1949
|
+
if (
|
|
1950
|
+
entry.finalization.revision !== index + 1 ||
|
|
1951
|
+
entry.prepared.intent.expectedRevision !== index ||
|
|
1952
|
+
entry.prepared.intent.previousStateDigest !==
|
|
1953
|
+
(previous?.finalization.stateDigest ?? null) ||
|
|
1954
|
+
(previous !== null && entry.event.sequence <= previous.event.sequence)
|
|
1955
|
+
) {
|
|
1956
|
+
throw portsError(
|
|
1957
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
1958
|
+
"release finalize lineage contains a revision fork or gap",
|
|
1959
|
+
);
|
|
1960
|
+
}
|
|
1961
|
+
previous = entry;
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
return { byTransaction, groups };
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
#committedProjection(finalized, lineages) {
|
|
1968
|
+
const groupKey = `${finalized.tenantId}\0${finalized.finalization.skillName}`;
|
|
1969
|
+
const group = lineages.groups.get(groupKey);
|
|
1970
|
+
const current = group?.at(-1) === finalized;
|
|
1971
|
+
const core = {
|
|
1972
|
+
authenticated: true,
|
|
1973
|
+
authorityReceiptDigest: finalized.finalization.authorityReceiptDigest,
|
|
1974
|
+
durable: true,
|
|
1975
|
+
epoch: finalized.event.epoch,
|
|
1976
|
+
headDigest: finalized.event.eventDigest,
|
|
1977
|
+
intentDigest: finalized.finalization.intentDigest,
|
|
1978
|
+
ledgerId: finalized.event.ledgerId,
|
|
1979
|
+
pointerDigest: finalized.finalization.pointerDigest,
|
|
1980
|
+
prepareReceiptDigest: finalized.finalization.expectedPrepareReceiptDigest,
|
|
1981
|
+
revision: finalized.finalization.revision,
|
|
1982
|
+
schema: SKILL_RELEASE_LEDGER_PROJECTION_SCHEMA,
|
|
1983
|
+
sequence: finalized.event.sequence,
|
|
1984
|
+
skillName: finalized.finalization.skillName,
|
|
1985
|
+
stateDigest: finalized.finalization.stateDigest,
|
|
1986
|
+
status: "committed",
|
|
1987
|
+
transactionId: finalized.finalization.transactionId,
|
|
1988
|
+
};
|
|
1989
|
+
return deepFreeze({
|
|
1990
|
+
...core,
|
|
1991
|
+
current,
|
|
1992
|
+
receiptDigest: projectionReceiptDigest("finalize", core),
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
#finalizeState(snapshot, finalization, logicalDigest) {
|
|
1997
|
+
const prepareEventId = deterministicEventId("prepare", {
|
|
1998
|
+
transactionId: finalization.transactionId,
|
|
1999
|
+
});
|
|
2000
|
+
const prepareEvent = snapshot.byEventId.get(prepareEventId);
|
|
2001
|
+
if (!prepareEvent) {
|
|
2002
|
+
throw portsError(
|
|
2003
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2004
|
+
"release finalize requires a verified prepare event",
|
|
2005
|
+
);
|
|
2006
|
+
}
|
|
2007
|
+
const prepared = this.#prepareFromSnapshot(snapshot, prepareEvent, {
|
|
2008
|
+
authorityReceiptDigest: finalization.authorityReceiptDigest,
|
|
2009
|
+
intentDigest: finalization.intentDigest,
|
|
2010
|
+
transactionId: finalization.transactionId,
|
|
2011
|
+
});
|
|
2012
|
+
const prepareProjection = this.#prepareProjection(prepared);
|
|
2013
|
+
if (
|
|
2014
|
+
finalization.expectedPrepareReceiptDigest !==
|
|
2015
|
+
prepareProjection.receiptDigest ||
|
|
2016
|
+
finalization.skillName !== prepared.intent.skillName ||
|
|
2017
|
+
finalization.revision !== prepared.intent.expectedRevision + 1 ||
|
|
2018
|
+
finalization.stateDigest !== prepared.intent.nextStateDigest ||
|
|
2019
|
+
finalization.pointerDigest !== prepared.intent.pointerDigest
|
|
2020
|
+
) {
|
|
2021
|
+
throw portsError(
|
|
2022
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2023
|
+
"release finalize request differs from its verified prepare event",
|
|
2024
|
+
);
|
|
2025
|
+
}
|
|
2026
|
+
const lineages = this.#lineages(
|
|
2027
|
+
snapshot,
|
|
2028
|
+
new Map([[prepareEventId, prepared]]),
|
|
2029
|
+
);
|
|
2030
|
+
const existing = lineages.byTransaction.get(finalization.transactionId);
|
|
2031
|
+
if (existing) {
|
|
2032
|
+
if (existing.logicalDigest !== logicalDigest) {
|
|
2033
|
+
throw portsError(
|
|
2034
|
+
EVOLUTION_LEDGER_PORTS_COLLISION_CODE,
|
|
2035
|
+
"release finalize event id is bound to another subject",
|
|
2036
|
+
);
|
|
2037
|
+
}
|
|
2038
|
+
return { existing, lineages, prepared };
|
|
2039
|
+
}
|
|
2040
|
+
const groupKey = `${prepared.intent.mutationRequest.tenantId}\0${finalization.skillName}`;
|
|
2041
|
+
const current = lineages.groups.get(groupKey)?.at(-1) || null;
|
|
2042
|
+
if (
|
|
2043
|
+
finalization.revision !== (current?.finalization.revision ?? 0) + 1 ||
|
|
2044
|
+
prepared.intent.previousStateDigest !==
|
|
2045
|
+
(current?.finalization.stateDigest ?? null)
|
|
2046
|
+
) {
|
|
2047
|
+
throw portsError(
|
|
2048
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
2049
|
+
"release finalize would create a revision fork or gap",
|
|
2050
|
+
);
|
|
2051
|
+
}
|
|
2052
|
+
return { existing: null, lineages, prepared };
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
prepare(input) {
|
|
2056
|
+
const intent = normalizeIntent(input);
|
|
2057
|
+
this.#assertAudience(intent.mutationRequest.audience, "intent audience");
|
|
2058
|
+
const eventId = deterministicEventId("prepare", {
|
|
2059
|
+
transactionId: intent.transactionId,
|
|
2060
|
+
});
|
|
2061
|
+
const snapshot = this.#snapshot();
|
|
2062
|
+
const existing = snapshot.byEventId.get(eventId);
|
|
2063
|
+
if (existing) {
|
|
2064
|
+
return this.#prepareProjection(
|
|
2065
|
+
this.#prepareFromSnapshot(snapshot, existing, {
|
|
2066
|
+
authorityReceiptDigest: intent.authorityReceiptDigest,
|
|
2067
|
+
intentDigest: intent.intentDigest,
|
|
2068
|
+
transactionId: intent.transactionId,
|
|
2069
|
+
}),
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2072
|
+
const audit = this.#auditFromSnapshot(snapshot, intent.authorityReceipt);
|
|
2073
|
+
const subjectRef = this.#putSubject(
|
|
2074
|
+
ARTIFACT_TYPES.prepare,
|
|
2075
|
+
intent,
|
|
2076
|
+
intent.intentDigest,
|
|
2077
|
+
intent.mutationRequest.audience,
|
|
2078
|
+
);
|
|
2079
|
+
const eventInput = {
|
|
2080
|
+
artifactTenantId: this.#artifactTenantId,
|
|
2081
|
+
correlationId: intent.transactionId,
|
|
2082
|
+
decision: "prepared",
|
|
2083
|
+
eventId,
|
|
2084
|
+
reason: intent.intentDigest,
|
|
2085
|
+
skillName: intent.skillName,
|
|
2086
|
+
sourceRefs: [audit.event.subjectRef],
|
|
2087
|
+
subjectRef,
|
|
2088
|
+
tenantId: intent.mutationRequest.tenantId,
|
|
2089
|
+
type: EVENT_TYPES.prepare,
|
|
2090
|
+
};
|
|
2091
|
+
let persistedSnapshot;
|
|
2092
|
+
try {
|
|
2093
|
+
const verification = this.#verifiedAppend(eventInput);
|
|
2094
|
+
persistedSnapshot =
|
|
2095
|
+
this.#snapshotWithVerifiedEvent(snapshot, verification) ||
|
|
2096
|
+
this.#snapshot();
|
|
2097
|
+
} catch (cause) {
|
|
2098
|
+
if (!isCommitUnknown(cause) && !isEventConflict(cause)) throw cause;
|
|
2099
|
+
persistedSnapshot = this.#snapshot();
|
|
2100
|
+
}
|
|
2101
|
+
const persisted = persistedSnapshot.byEventId.get(eventId);
|
|
2102
|
+
if (!persisted) {
|
|
2103
|
+
throw portsError(
|
|
2104
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
2105
|
+
"release prepare append did not converge to a queryable event",
|
|
2106
|
+
);
|
|
2107
|
+
}
|
|
2108
|
+
return this.#prepareProjection(
|
|
2109
|
+
this.#prepareFromSnapshot(persistedSnapshot, persisted, {
|
|
2110
|
+
authorityReceiptDigest: intent.authorityReceiptDigest,
|
|
2111
|
+
intentDigest: intent.intentDigest,
|
|
2112
|
+
transactionId: intent.transactionId,
|
|
2113
|
+
}),
|
|
2114
|
+
);
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
finalize(input) {
|
|
2118
|
+
const finalization = normalizeFinalize(input);
|
|
2119
|
+
const logicalDigest = logicalFinalizeDigest(finalization);
|
|
2120
|
+
const eventId = deterministicEventId("finalize", {
|
|
2121
|
+
transactionId: finalization.transactionId,
|
|
2122
|
+
});
|
|
2123
|
+
let snapshot = this.#snapshot();
|
|
2124
|
+
let state = this.#finalizeState(snapshot, finalization, logicalDigest);
|
|
2125
|
+
if (state.existing) {
|
|
2126
|
+
return this.#committedProjection(state.existing, state.lineages);
|
|
2127
|
+
}
|
|
2128
|
+
const subjectRef = this.#putSubject(
|
|
2129
|
+
ARTIFACT_TYPES.finalize,
|
|
2130
|
+
finalization,
|
|
2131
|
+
logicalDigest,
|
|
2132
|
+
state.prepared.intent.mutationRequest.audience,
|
|
2133
|
+
);
|
|
2134
|
+
for (let attempt = 0; attempt < MAX_FINALIZE_RETRIES; attempt += 1) {
|
|
2135
|
+
const previous = snapshot.events.at(-1) || null;
|
|
2136
|
+
const eventInput = {
|
|
2137
|
+
artifactTenantId: this.#artifactTenantId,
|
|
2138
|
+
correlationId: finalization.transactionId,
|
|
2139
|
+
decision: "committed",
|
|
2140
|
+
eventId,
|
|
2141
|
+
reason: logicalDigest,
|
|
2142
|
+
skillName: finalization.skillName,
|
|
2143
|
+
sourceRefs: [state.prepared.event.subjectRef],
|
|
2144
|
+
subjectRef,
|
|
2145
|
+
tenantId: state.prepared.intent.mutationRequest.tenantId,
|
|
2146
|
+
type: EVENT_TYPES.finalize,
|
|
2147
|
+
};
|
|
2148
|
+
try {
|
|
2149
|
+
this.#verifiedAppend(eventInput, {
|
|
2150
|
+
expectedHeadDigest: previous?.eventDigest ?? null,
|
|
2151
|
+
expectedSequence: previous?.sequence ?? 0,
|
|
2152
|
+
});
|
|
2153
|
+
} catch (cause) {
|
|
2154
|
+
if (isHeadConflict(cause)) {
|
|
2155
|
+
snapshot = this.#snapshot();
|
|
2156
|
+
state = this.#finalizeState(snapshot, finalization, logicalDigest);
|
|
2157
|
+
if (state.existing) {
|
|
2158
|
+
return this.#committedProjection(state.existing, state.lineages);
|
|
2159
|
+
}
|
|
2160
|
+
continue;
|
|
2161
|
+
}
|
|
2162
|
+
if (!isCommitUnknown(cause) && !isEventConflict(cause)) throw cause;
|
|
2163
|
+
}
|
|
2164
|
+
const recoveredSnapshot = this.#snapshot();
|
|
2165
|
+
const recoveredState = this.#finalizeState(
|
|
2166
|
+
recoveredSnapshot,
|
|
2167
|
+
finalization,
|
|
2168
|
+
logicalDigest,
|
|
2169
|
+
);
|
|
2170
|
+
if (!recoveredState.existing) {
|
|
2171
|
+
throw portsError(
|
|
2172
|
+
EVOLUTION_LEDGER_PORTS_COLLISION_CODE,
|
|
2173
|
+
"release finalize append did not converge to its exact subject",
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
return this.#committedProjection(
|
|
2177
|
+
recoveredState.existing,
|
|
2178
|
+
recoveredState.lineages,
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
throw portsError(
|
|
2182
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
2183
|
+
"release finalize could not acquire a stable ledger head",
|
|
2184
|
+
);
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
query(transactionIdValue) {
|
|
2188
|
+
const transactionId = digest(transactionIdValue, "transactionId");
|
|
2189
|
+
const prepareEventId = deterministicEventId("prepare", { transactionId });
|
|
2190
|
+
const finalizeEventId = deterministicEventId("finalize", { transactionId });
|
|
2191
|
+
const snapshot = this.#snapshot();
|
|
2192
|
+
const prepareEvent = snapshot.byEventId.get(prepareEventId);
|
|
2193
|
+
const finalizeEvent = snapshot.byEventId.get(finalizeEventId);
|
|
2194
|
+
if (!prepareEvent) {
|
|
2195
|
+
if (finalizeEvent) {
|
|
2196
|
+
throw portsError(
|
|
2197
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
2198
|
+
"release transaction has finalize evidence without prepare evidence",
|
|
2199
|
+
);
|
|
2200
|
+
}
|
|
2201
|
+
return deepFreeze({
|
|
2202
|
+
authenticated: true,
|
|
2203
|
+
durable: true,
|
|
2204
|
+
schema: SKILL_RELEASE_LEDGER_PROJECTION_SCHEMA,
|
|
2205
|
+
status: "absent",
|
|
2206
|
+
transactionId,
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
const prepared = this.#prepareFromSnapshot(snapshot, prepareEvent, {
|
|
2210
|
+
transactionId,
|
|
2211
|
+
});
|
|
2212
|
+
if (!finalizeEvent) return this.#prepareProjection(prepared);
|
|
2213
|
+
const lineages = this.#lineages(
|
|
2214
|
+
snapshot,
|
|
2215
|
+
new Map([[prepareEventId, prepared]]),
|
|
2216
|
+
);
|
|
2217
|
+
const finalized = lineages.byTransaction.get(transactionId);
|
|
2218
|
+
if (!finalized || finalized.event !== finalizeEvent) {
|
|
2219
|
+
throw portsError(
|
|
2220
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
2221
|
+
"release finalize query differs from verified lineage",
|
|
2222
|
+
);
|
|
2223
|
+
}
|
|
2224
|
+
return this.#committedProjection(finalized, lineages);
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
appendAudit(input) {
|
|
2228
|
+
const value = frozenCanonicalClone(input);
|
|
2229
|
+
const audit = verifySkillMutationAuditEvent(value);
|
|
2230
|
+
if (audit.audience !== null) {
|
|
2231
|
+
this.#assertAudience(audit.audience, "audit audience");
|
|
2232
|
+
}
|
|
2233
|
+
const eventId = deterministicEventId("audit", {
|
|
2234
|
+
auditDigest: audit.auditDigest,
|
|
2235
|
+
});
|
|
2236
|
+
const existing = this.#queryEvent(eventId);
|
|
2237
|
+
if (existing)
|
|
2238
|
+
return this.#auditAcknowledgement(existing, audit.auditDigest);
|
|
2239
|
+
const subjectRef = this.#putSubject(
|
|
2240
|
+
ARTIFACT_TYPES.audit,
|
|
2241
|
+
audit,
|
|
2242
|
+
audit.auditDigest,
|
|
2243
|
+
audit.audience,
|
|
2244
|
+
);
|
|
2245
|
+
const eventInput = {
|
|
2246
|
+
artifactTenantId: this.#artifactTenantId,
|
|
2247
|
+
correlationId: nullableEventIdentifier(audit.operationId),
|
|
2248
|
+
decision: audit.decision === "allow" ? "accepted" : "rejected",
|
|
2249
|
+
eventId,
|
|
2250
|
+
reason: audit.auditDigest,
|
|
2251
|
+
skillName: nullableEventSkillName(audit.skillName),
|
|
2252
|
+
sourceRefs: [],
|
|
2253
|
+
subjectRef,
|
|
2254
|
+
tenantId: nullableEventIdentifier(audit.tenantId),
|
|
2255
|
+
timestamp: audit.occurredAt,
|
|
2256
|
+
type: EVENT_TYPES.audit,
|
|
2257
|
+
};
|
|
2258
|
+
let verification = null;
|
|
2259
|
+
try {
|
|
2260
|
+
verification = this.#verifiedAppend(eventInput);
|
|
2261
|
+
} catch (cause) {
|
|
2262
|
+
if (!isCommitUnknown(cause) && !isEventConflict(cause)) throw cause;
|
|
2263
|
+
}
|
|
2264
|
+
if (verification) {
|
|
2265
|
+
return this.#auditAcknowledgement(verification, audit.auditDigest);
|
|
2266
|
+
}
|
|
2267
|
+
const persisted = this.#queryEvent(eventId);
|
|
2268
|
+
if (!persisted) {
|
|
2269
|
+
throw portsError(
|
|
2270
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
2271
|
+
"mutation audit append did not converge to a queryable event",
|
|
2272
|
+
);
|
|
2273
|
+
}
|
|
2274
|
+
return this.#auditAcknowledgement(persisted, audit.auditDigest);
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
#auditAcknowledgement(evidence, auditDigest) {
|
|
2278
|
+
const event = this.#assertEvidenceAnchor(evidence);
|
|
2279
|
+
const resolved = this.#auditFromEvent(event, auditDigest);
|
|
2280
|
+
return deepFreeze({
|
|
2281
|
+
auditDigest,
|
|
2282
|
+
headDigest: evidence.anchor.headDigest,
|
|
2283
|
+
persisted: true,
|
|
2284
|
+
sequence: resolved.event.sequence,
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
#nonceFromEvidence(evidence) {
|
|
2289
|
+
const event = this.#assertEvidenceAnchor(evidence);
|
|
2290
|
+
const record = this.#resolveSubject(event, ARTIFACT_TYPES.nonce);
|
|
2291
|
+
const claim = verifySkillMutationNonceClaim(record.value);
|
|
2292
|
+
const key = nonceKey(claim);
|
|
2293
|
+
const eventId = deterministicEventId("nonce", key);
|
|
2294
|
+
const sourceRefs = this.#assertDomainEvent(
|
|
2295
|
+
event,
|
|
2296
|
+
"nonce",
|
|
2297
|
+
eventId,
|
|
2298
|
+
claim.claimDigest,
|
|
2299
|
+
);
|
|
2300
|
+
if (
|
|
2301
|
+
sourceRefs.length !== 0 ||
|
|
2302
|
+
event.decision !== "committed" ||
|
|
2303
|
+
event.correlationId !== claim.operationId ||
|
|
2304
|
+
event.tenantId !== claim.tenantId ||
|
|
2305
|
+
event.skillName !== null ||
|
|
2306
|
+
event.timestamp !== claim.claimedAt ||
|
|
2307
|
+
record.audience !== claim.audience
|
|
2308
|
+
) {
|
|
2309
|
+
throw portsError(
|
|
2310
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
2311
|
+
"mutation nonce event differs from its exact claim subject",
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2314
|
+
return { claim, event, evidence, key, record };
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
claimNonce(input) {
|
|
2318
|
+
const value = frozenCanonicalClone(input);
|
|
2319
|
+
const claim = verifySkillMutationNonceClaim(value);
|
|
2320
|
+
this.#assertAudience(claim.audience, "nonce audience");
|
|
2321
|
+
if (!NONCE_PATTERN.test(claim.nonce)) {
|
|
2322
|
+
throw portsError(
|
|
2323
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2324
|
+
"nonce claim nonce is invalid",
|
|
2325
|
+
);
|
|
2326
|
+
}
|
|
2327
|
+
const key = nonceKey(claim);
|
|
2328
|
+
const eventId = deterministicEventId("nonce", key);
|
|
2329
|
+
const existing = this.#queryEvent(eventId);
|
|
2330
|
+
if (existing) {
|
|
2331
|
+
const winner = this.#nonceFromEvidence(existing);
|
|
2332
|
+
return this.#nonceAcknowledgement(claim, winner, false);
|
|
2333
|
+
}
|
|
2334
|
+
const subjectRef = this.#putSubject(
|
|
2335
|
+
ARTIFACT_TYPES.nonce,
|
|
2336
|
+
claim,
|
|
2337
|
+
claim.claimDigest,
|
|
2338
|
+
claim.audience,
|
|
2339
|
+
);
|
|
2340
|
+
const eventInput = {
|
|
2341
|
+
artifactTenantId: this.#artifactTenantId,
|
|
2342
|
+
correlationId: claim.operationId,
|
|
2343
|
+
decision: "committed",
|
|
2344
|
+
eventId,
|
|
2345
|
+
reason: claim.claimDigest,
|
|
2346
|
+
skillName: null,
|
|
2347
|
+
sourceRefs: [],
|
|
2348
|
+
subjectRef,
|
|
2349
|
+
tenantId: claim.tenantId,
|
|
2350
|
+
timestamp: claim.claimedAt,
|
|
2351
|
+
type: EVENT_TYPES.nonce,
|
|
2352
|
+
};
|
|
2353
|
+
let verification = null;
|
|
2354
|
+
let appendFailure = null;
|
|
2355
|
+
try {
|
|
2356
|
+
verification = this.#verifiedAppend(eventInput);
|
|
2357
|
+
} catch (cause) {
|
|
2358
|
+
if (!isCommitUnknown(cause) && !isEventConflict(cause)) throw cause;
|
|
2359
|
+
appendFailure = cause;
|
|
2360
|
+
}
|
|
2361
|
+
if (verification) {
|
|
2362
|
+
const winner = this.#nonceFromEvidence(verification);
|
|
2363
|
+
if (winner.claim.claimDigest !== claim.claimDigest) {
|
|
2364
|
+
throw portsError(
|
|
2365
|
+
EVOLUTION_LEDGER_PORTS_COLLISION_CODE,
|
|
2366
|
+
"mutation nonce winner differs after a successful append",
|
|
2367
|
+
);
|
|
2368
|
+
}
|
|
2369
|
+
return this.#nonceAcknowledgement(claim, winner, true);
|
|
2370
|
+
}
|
|
2371
|
+
const persisted = this.#queryEvent(eventId);
|
|
2372
|
+
if (!persisted) {
|
|
2373
|
+
throw portsError(
|
|
2374
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
2375
|
+
"mutation nonce commit outcome remains unknown",
|
|
2376
|
+
{ cause: appendFailure },
|
|
2377
|
+
);
|
|
2378
|
+
}
|
|
2379
|
+
const winner = this.#nonceFromEvidence(persisted);
|
|
2380
|
+
if (isCommitUnknown(appendFailure)) {
|
|
2381
|
+
if (winner.claim.claimDigest !== claim.claimDigest) {
|
|
2382
|
+
throw portsError(
|
|
2383
|
+
EVOLUTION_LEDGER_PORTS_COLLISION_CODE,
|
|
2384
|
+
"commit-unknown nonce append did not publish its exact subject",
|
|
2385
|
+
{ cause: appendFailure },
|
|
2386
|
+
);
|
|
2387
|
+
}
|
|
2388
|
+
return this.#nonceAcknowledgement(claim, winner, true);
|
|
2389
|
+
}
|
|
2390
|
+
return this.#nonceAcknowledgement(claim, winner, false);
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
#nonceAcknowledgement(claim, winner, claimed) {
|
|
2394
|
+
for (const field of ["tenantId", "audience", "nonce"]) {
|
|
2395
|
+
assertSame(
|
|
2396
|
+
winner.claim[field],
|
|
2397
|
+
claim[field],
|
|
2398
|
+
`nonce winner ${field}`,
|
|
2399
|
+
EVOLUTION_LEDGER_PORTS_COLLISION_CODE,
|
|
2400
|
+
);
|
|
2401
|
+
}
|
|
2402
|
+
return deepFreeze({
|
|
2403
|
+
claimDigest: claim.claimDigest,
|
|
2404
|
+
claimed,
|
|
2405
|
+
expiresAt: claim.expiresAt,
|
|
2406
|
+
headDigest: winner.evidence.anchor.headDigest,
|
|
2407
|
+
persisted: true,
|
|
2408
|
+
schema: SKILL_MUTATION_NONCE_ACK_SCHEMA,
|
|
2409
|
+
sequence: winner.event.sequence,
|
|
2410
|
+
});
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
/**
|
|
2415
|
+
* Build the resolver that must be captured by the one EvolutionLedger used by
|
|
2416
|
+
* these ports. Ledger-retained domain subjects always resolve from the trusted
|
|
2417
|
+
* durable replica; the local ArtifactPorts copy is an optional cross-check and
|
|
2418
|
+
* cache. Other artifact classes continue to use the local resolver.
|
|
2419
|
+
*/
|
|
2420
|
+
export function createEvolutionLedgerDurableArtifactResolver(options = {}) {
|
|
2421
|
+
assertAllExactRecord(
|
|
2422
|
+
options,
|
|
2423
|
+
DURABLE_RESOLVER_OPTION_KEYS,
|
|
2424
|
+
"durable ledger artifact resolver options",
|
|
2425
|
+
);
|
|
2426
|
+
const artifactPorts = requireStableInstance(
|
|
2427
|
+
ownData(
|
|
2428
|
+
options,
|
|
2429
|
+
"artifactPorts",
|
|
2430
|
+
"durable ledger artifact resolver options",
|
|
2431
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2432
|
+
),
|
|
2433
|
+
EvolutionArtifactPorts.prototype,
|
|
2434
|
+
"EvolutionArtifactPorts",
|
|
2435
|
+
);
|
|
2436
|
+
const authority = captureDurabilityAuthority(
|
|
2437
|
+
ownData(
|
|
2438
|
+
options,
|
|
2439
|
+
"artifactDurabilityAuthority",
|
|
2440
|
+
"durable ledger artifact resolver options",
|
|
2441
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2442
|
+
),
|
|
2443
|
+
);
|
|
2444
|
+
const artifactTenantId = identifier(
|
|
2445
|
+
ownData(
|
|
2446
|
+
options,
|
|
2447
|
+
"artifactTenantId",
|
|
2448
|
+
"durable ledger artifact resolver options",
|
|
2449
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2450
|
+
),
|
|
2451
|
+
"artifactTenantId",
|
|
2452
|
+
);
|
|
2453
|
+
const purpose = identifier(
|
|
2454
|
+
ownData(
|
|
2455
|
+
options,
|
|
2456
|
+
"purpose",
|
|
2457
|
+
"durable ledger artifact resolver options",
|
|
2458
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2459
|
+
),
|
|
2460
|
+
"purpose",
|
|
2461
|
+
);
|
|
2462
|
+
const localResolve =
|
|
2463
|
+
EvolutionArtifactPorts.prototype.createEvolutionLedgerArtifactResolver.call(
|
|
2464
|
+
artifactPorts,
|
|
2465
|
+
Object.freeze({ purpose }),
|
|
2466
|
+
);
|
|
2467
|
+
|
|
2468
|
+
const resolve = (input) => {
|
|
2469
|
+
assertAllExactRecord(
|
|
2470
|
+
input,
|
|
2471
|
+
LEDGER_ARTIFACT_REQUEST_KEYS,
|
|
2472
|
+
"EvolutionLedger artifact request",
|
|
2473
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2474
|
+
);
|
|
2475
|
+
assertSame(
|
|
2476
|
+
input.tenantId,
|
|
2477
|
+
artifactTenantId,
|
|
2478
|
+
"EvolutionLedger artifact tenantId",
|
|
2479
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2480
|
+
);
|
|
2481
|
+
identifier(input.ledgerId, "EvolutionLedger ledgerId");
|
|
2482
|
+
identifier(input.epoch, "EvolutionLedger epoch");
|
|
2483
|
+
const ref = normalizeArtifactRef(
|
|
2484
|
+
input.ref,
|
|
2485
|
+
"EvolutionLedger artifact ref",
|
|
2486
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2487
|
+
);
|
|
2488
|
+
let durable = null;
|
|
2489
|
+
let durableError = null;
|
|
2490
|
+
try {
|
|
2491
|
+
durable = resolveDurableArtifact(
|
|
2492
|
+
authority,
|
|
2493
|
+
durabilityResolveRequest({
|
|
2494
|
+
artifactTenantId,
|
|
2495
|
+
digest: ref.digest,
|
|
2496
|
+
purpose,
|
|
2497
|
+
ref: ref.ref,
|
|
2498
|
+
}),
|
|
2499
|
+
);
|
|
2500
|
+
} catch (cause) {
|
|
2501
|
+
durableError = cause;
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
let local = null;
|
|
2505
|
+
try {
|
|
2506
|
+
local = assertSynchronous(
|
|
2507
|
+
localResolve(input),
|
|
2508
|
+
"local EvolutionLedger artifact resolution",
|
|
2509
|
+
);
|
|
2510
|
+
} catch {
|
|
2511
|
+
// Local ArtifactPorts is a cache for retained subjects. Its absence is
|
|
2512
|
+
// recoverable only when the authoritative replica above is valid.
|
|
2513
|
+
}
|
|
2514
|
+
let localBytes = null;
|
|
2515
|
+
if (local !== null) {
|
|
2516
|
+
assertAllExactRecord(
|
|
2517
|
+
local,
|
|
2518
|
+
LEDGER_RESOLUTION_KEYS,
|
|
2519
|
+
"local EvolutionLedger artifact resolution",
|
|
2520
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
2521
|
+
);
|
|
2522
|
+
if (
|
|
2523
|
+
local.schema !== EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA ||
|
|
2524
|
+
local.authenticated !== true ||
|
|
2525
|
+
local.found !== true ||
|
|
2526
|
+
local.ref !== ref.ref ||
|
|
2527
|
+
local.digest !== ref.digest
|
|
2528
|
+
) {
|
|
2529
|
+
throw portsError(
|
|
2530
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
2531
|
+
"local EvolutionLedger artifact resolution is not exactly bound",
|
|
2532
|
+
);
|
|
2533
|
+
}
|
|
2534
|
+
localBytes = copyBytes(local.bytes);
|
|
2535
|
+
if (sha256(localBytes) !== ref.digest) {
|
|
2536
|
+
throw portsError(
|
|
2537
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
2538
|
+
"local EvolutionLedger artifact bytes differ from their digest",
|
|
2539
|
+
);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
const selectedBytes = durable?.bytes || localBytes;
|
|
2544
|
+
if (!selectedBytes) {
|
|
2545
|
+
throw portsError(
|
|
2546
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
2547
|
+
"artifact is absent from both local and authoritative durable stores",
|
|
2548
|
+
{ cause: durableError, ref: ref.ref },
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
2551
|
+
let record;
|
|
2552
|
+
const json = bufferToString(selectedBytes, "utf8");
|
|
2553
|
+
try {
|
|
2554
|
+
record = JSON.parse(json);
|
|
2555
|
+
} catch (cause) {
|
|
2556
|
+
throw portsError(
|
|
2557
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
2558
|
+
"resolved artifact is not canonical JSON",
|
|
2559
|
+
{ cause },
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
assertAllExactRecord(
|
|
2563
|
+
record,
|
|
2564
|
+
ARTIFACT_RECORD_KEYS,
|
|
2565
|
+
"resolved durable artifact record",
|
|
2566
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
2567
|
+
);
|
|
2568
|
+
const retainedSubject =
|
|
2569
|
+
record.schema === EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA &&
|
|
2570
|
+
record.tenantId === artifactTenantId &&
|
|
2571
|
+
record.purpose === purpose &&
|
|
2572
|
+
record.retention === "ledger" &&
|
|
2573
|
+
DURABLE_ARTIFACT_TYPE_SET.has(record.type);
|
|
2574
|
+
if (
|
|
2575
|
+
canonicalJson(record) !== json ||
|
|
2576
|
+
(durable !== null && durable.result.type !== record.type) ||
|
|
2577
|
+
(retainedSubject && durable === null)
|
|
2578
|
+
) {
|
|
2579
|
+
throw portsError(
|
|
2580
|
+
EVOLUTION_LEDGER_PORTS_UNAVAILABLE_CODE,
|
|
2581
|
+
"ledger-retained subject lacks an exact authoritative replica",
|
|
2582
|
+
{ cause: durableError, ref: ref.ref },
|
|
2583
|
+
);
|
|
2584
|
+
}
|
|
2585
|
+
if (
|
|
2586
|
+
durable !== null &&
|
|
2587
|
+
localBytes !== null &&
|
|
2588
|
+
!localBytes.equals(durable.bytes)
|
|
2589
|
+
) {
|
|
2590
|
+
throw portsError(
|
|
2591
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
2592
|
+
"local artifact differs from the authoritative durable replica",
|
|
2593
|
+
);
|
|
2594
|
+
}
|
|
2595
|
+
return Object.freeze({
|
|
2596
|
+
authenticated: true,
|
|
2597
|
+
bytes: Buffer.from(selectedBytes),
|
|
2598
|
+
digest: ref.digest,
|
|
2599
|
+
found: true,
|
|
2600
|
+
receiptDigest:
|
|
2601
|
+
durable?.result.receiptDigest ||
|
|
2602
|
+
digest(
|
|
2603
|
+
local.receiptDigest,
|
|
2604
|
+
"local artifact resolution receiptDigest",
|
|
2605
|
+
EVOLUTION_LEDGER_PORTS_CORRUPT_CODE,
|
|
2606
|
+
),
|
|
2607
|
+
ref: ref.ref,
|
|
2608
|
+
schema: EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA,
|
|
2609
|
+
});
|
|
2610
|
+
};
|
|
2611
|
+
return Object.freeze(resolve);
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
export function createEvolutionLedgerPorts(options = {}) {
|
|
2615
|
+
assertExactRecord(
|
|
2616
|
+
options,
|
|
2617
|
+
FACTORY_OPTION_KEYS,
|
|
2618
|
+
FACTORY_REQUIRED_KEYS,
|
|
2619
|
+
"evolution ledger ports options",
|
|
2620
|
+
);
|
|
2621
|
+
const ledger = requireStableInstance(
|
|
2622
|
+
ownData(
|
|
2623
|
+
options,
|
|
2624
|
+
"ledger",
|
|
2625
|
+
"evolution ledger ports options",
|
|
2626
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2627
|
+
),
|
|
2628
|
+
EvolutionLedger.prototype,
|
|
2629
|
+
"EvolutionLedger",
|
|
2630
|
+
);
|
|
2631
|
+
const artifactPorts = requireStableInstance(
|
|
2632
|
+
ownData(
|
|
2633
|
+
options,
|
|
2634
|
+
"artifactPorts",
|
|
2635
|
+
"evolution ledger ports options",
|
|
2636
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2637
|
+
),
|
|
2638
|
+
EvolutionArtifactPorts.prototype,
|
|
2639
|
+
"EvolutionArtifactPorts",
|
|
2640
|
+
);
|
|
2641
|
+
const artifactDurabilityAuthority = captureDurabilityAuthority(
|
|
2642
|
+
ownData(
|
|
2643
|
+
options,
|
|
2644
|
+
"artifactDurabilityAuthority",
|
|
2645
|
+
"evolution ledger ports options",
|
|
2646
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2647
|
+
),
|
|
2648
|
+
);
|
|
2649
|
+
const adapter = new EvolutionLedgerDomainPorts({
|
|
2650
|
+
artifactDurabilityAuthority,
|
|
2651
|
+
artifactPorts,
|
|
2652
|
+
artifactTenantId: ownData(
|
|
2653
|
+
options,
|
|
2654
|
+
"artifactTenantId",
|
|
2655
|
+
"evolution ledger ports options",
|
|
2656
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2657
|
+
),
|
|
2658
|
+
audience: Object.hasOwn(options, "audience")
|
|
2659
|
+
? ownData(
|
|
2660
|
+
options,
|
|
2661
|
+
"audience",
|
|
2662
|
+
"evolution ledger ports options",
|
|
2663
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2664
|
+
)
|
|
2665
|
+
: undefined,
|
|
2666
|
+
ledger,
|
|
2667
|
+
purpose: Object.hasOwn(options, "purpose")
|
|
2668
|
+
? ownData(
|
|
2669
|
+
options,
|
|
2670
|
+
"purpose",
|
|
2671
|
+
"evolution ledger ports options",
|
|
2672
|
+
EVOLUTION_LEDGER_PORTS_INVALID_CODE,
|
|
2673
|
+
)
|
|
2674
|
+
: "evolution-ledger",
|
|
2675
|
+
});
|
|
2676
|
+
const prepare = Object.freeze((input) => adapter.prepare(input));
|
|
2677
|
+
const finalize = Object.freeze((input) => adapter.finalize(input));
|
|
2678
|
+
const query = Object.freeze((transactionId) => adapter.query(transactionId));
|
|
2679
|
+
const append = Object.freeze((event) => adapter.appendAudit(event));
|
|
2680
|
+
const claim = Object.freeze((nonce) => adapter.claimNonce(nonce));
|
|
2681
|
+
return Object.freeze({
|
|
2682
|
+
auditSink: Object.freeze({ append }),
|
|
2683
|
+
nonceStore: Object.freeze({ claim }),
|
|
2684
|
+
transactionLedger: Object.freeze({ finalize, prepare, query }),
|
|
2685
|
+
});
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
Object.freeze(EvolutionLedgerPortsError.prototype);
|
|
2689
|
+
Object.freeze(EvolutionLedgerPortsError);
|