chainlesschain 0.166.20 → 0.166.21
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 +2 -2
- package/src/assets/web-panel/assets/{AIOps-CcJst5Zs.js → AIOps-BjMnsUci.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BpHPFKd3.js → ActionButton-DdYFP03A.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DHAsASuS.js → Analytics-Bb8TuuK_.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BTKwo5RC.js → AppLayout-Kn3TIOtk.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BcCBTggA.js → Artifacts-4IxpqI_b.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-z1JvJYbT.js → Audit-BOV2caqU.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CJ7DCMDR.js → BackgroundAgents-B86k-ay4.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CKpxw4uw.js → Backup-C5_ie1e3.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-2jI_IlUB.js → BaseInput-xpwg6QCW.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-C_4RfBeN.js → Chat-CVZOcZau.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-D8fPYhUu.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-BnF4z4N1.js → Checkbox-MKTq3zdP.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-m_LMamhM.js → Codegen-JUk3wzep.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DzMbA760.js → Col-JPvTA0kC.js} +1 -1
- package/src/assets/web-panel/assets/{Community-SScuZUcW.js → Community-Tqgl87NQ.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CiOHRvwQ.js → Compact-BuAFYBmN.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-97FzYz-6.js → Compliance-CXS9WIPZ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-C0ss-SRl.js → Cowork-Ds2bFHrr.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CCbdc9fp.js → Cron-C_dV_OeW.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BPnabXej.js → Crosschain-BsO6tIRo.js} +1 -1
- package/src/assets/web-panel/assets/{DID-B65hFnHs.js → DID-rNOFqBac.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-B6qR_TF2.js → Dashboard-C2fcKKVY.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-6c-2Fxm-.js → Dropdown-Y7Ndr0X_.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-GD57mfiz.js → EmailListRenderer-DcP_uPmL.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-B2mxG6LN.js → FamilyGuardDashboard-Cwn3gDzk.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-cZS-uvAo.js → Federation-ClQuOsqb.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-UbSfBIe1.js → FormItemContext-DfwtmlcK.js} +1 -1
- package/src/assets/web-panel/assets/GenericCardRenderer-zNZhtbC5.js +1 -0
- package/src/assets/web-panel/assets/{Git-DAlhOiDA.js → Git-BZmdW-G5.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-B-RRVaP8.js → Governance-CqBk4lEn.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DBl-pXYR.js → Inference-Dbmt3ZXk.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DjqOkvgY.js → KnowledgeGraph-hMTH6qf3.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CEV7AEAD.js → Logs-CkiYgiW2.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-BDCxa4d1.js → MarkdownRenderer-BL1FElXg.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-oRjzfmYJ.js → Marketplace-CbB6wfin.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BiTnznLw.js → McpTools-BSwiFtte.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-DR0C5K4N.js → Memory-B03PeHrz.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge--oR8wEcp.js → MobileBridge-CRGrnd-X.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DKqpYMuG.js → MobileProjects-CE_5v2kO.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-Dg0D6fDQ.js → Mtc-Dp11QVx1.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-D4_-yWTK.js → MtcAudit-CYu-3JBu.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-Coz55FxA.js → Multisig-CGyJ0Xl3.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BWVjgW9Y.js → NLProgramming-CoWi_ov1.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BflVuGwr.js → Notes-CgU9DDo9.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-CT0F_dJ7.js → NotificationSettings-DX4I2b4W.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CwHc15Zs.js +1 -0
- package/src/assets/web-panel/assets/{Organization-CMDTM2z5.js → Organization-oopLO_tk.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-acKvvYpC.js → Overflow-BFVvjMyf.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CRq5j9Ax.js → P2P-CBy5t-Tk.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DmtzN6Kn.js → PdhVaultBrowser--YWJNsu6.js} +4 -4
- package/src/assets/web-panel/assets/{Permissions-SZ-kjJDa.js → Permissions-C2dZ8scb.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-DXh-ZfgJ.js → PersonalDataHub-Bs3l7Hlx.js} +6 -6
- package/src/assets/web-panel/assets/{Pipeline-DhBU9ftP.js → Pipeline-DEBWbZuD.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BgEXpO_V.js → Privacy-CjHBb6NU.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-B2DJ-dmK.js → ProjectInit-D2WipUz5.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-ve3SocWc.js → ProjectSettings-B6WfnrGB.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-5ZvFasSJ.js → Projects-FF_thRi6.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-C8fcF3BK.js → Providers-CCv5M_Dv.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-unjO8lNO.js → QrScannerModal-CGthxU54.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BT1QtT7D.js → QuickAsk-CnPmXPFb.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BSjFrY1w.js → Recommend-Df_tGEa9.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-CWV0bICg.js → RemoteSession-Z58N0Loe.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BMjiMsk2.js → Reputation-CZTBQ2Vt.js} +1 -1
- package/src/assets/web-panel/assets/{Row-9XCY22PM.js → Row-CdK3YpCQ.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DQ9PDy7Q.js → RssFeed-CFMVT5sb.js} +3 -3
- package/src/assets/web-panel/assets/{Search-Boym2HwG.js → Search-DrF8RIvD.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Gbq3lGQb.js → Security-Ddk2f2DM.js} +4 -4
- package/src/assets/web-panel/assets/{Services-OcUfLdnu.js → Services-CCuK-VOo.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BjTog6cA.js → Skeleton-DxCpHzeM.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-Dny4ft_1.js → Skills-t2oTDgcv.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Cy_VnhpZ.js → Sla-Df63M9_R.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CrFuL_lp.js → SpeechSettings-l7ox6KuX.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-k-FnjuOR.js → SyncSettings-COd_tgPp.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-ZEqDSo0_.js → Tasks-CzDCcrik.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CYYksYV5.js → Templates-D-vfxTGA.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BvTTRTHS.js → Tenant-BlpT_Gxc.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DMOQOtkj.js → Terminal-D5rq52Hg.js} +2 -2
- package/src/assets/web-panel/assets/TimelineRenderer-DdoMndkK.js +1 -0
- package/src/assets/web-panel/assets/{Tokens-VW6Wocdp.js → Tokens-BhgBsI6g.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BtOxZqP6.js → Trigger-DwwzoikZ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-XcGJoTQx.js → Trust-HkTpzXFb.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-aASNCs5j.js → UkeySign-D9nk6Ehe.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Bz89M52u.js → VideoEditing-BKk0rfoy.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-uU4dSS_N.js → Wallet-IVWIU_Lq.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Brrroxgr.js → WebAuthn-CFTCpfsG.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-Cny-evI9.js → WorkflowEditor-Dnf4Obif.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CJ5iSwWq.js → chat-D2snERdj.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DWa-2flG.js → colors-DKEjK1N1.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DFwUfptU.js → compact-item-BEDLwQdt.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-FHWWcyeB.js → createContext-DA9iW1a0.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DyhgpXPf.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-QY_TElrN.js → hasIn-5l2xEcY9.js} +1 -1
- package/src/assets/web-panel/assets/{index-hw1J_Oxs.js → index-5pW4t1iD.js} +1 -1
- package/src/assets/web-panel/assets/{index-BR4RMPnB.js → index-7P1b8HZo.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUNwuu5v.js → index-B00GeekA.js} +1 -1
- package/src/assets/web-panel/assets/{index-B18zERdP.js → index-B4QYBliR.js} +1 -1
- package/src/assets/web-panel/assets/{index-wqiOz655.js → index-B7_W_rYd.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQCtBurK.js → index-BO9dQjpt.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUfILsc-.js → index-BZ5lY2zx.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUepuy7O.js → index-B_eCoZgW.js} +1 -1
- package/src/assets/web-panel/assets/{index-uRUZdL7I.js → index-BgfImyZI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUoLd1dt.js → index-Bq5P3piS.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiHRihC0.js → index-Bqc8tdIF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcVr-LOk.js → index-C7sKbM5s.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDpXDi4J.js → index-CGlotREA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CjejB-z9.js → index-CRPrHE7h.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2rPJ6Sv.js → index-CX1x4AFn.js} +1 -1
- package/src/assets/web-panel/assets/{index-w3SdYwaC.js → index-CbTPvp2_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-Mhq8K6.js → index-CjWltbi_.js} +1 -1
- package/src/assets/web-panel/assets/index-Ck1jhFE0.js +1 -0
- package/src/assets/web-panel/assets/{index-Bd7McYpJ.js → index-CkMKuqUs.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYbwQ7SP.js → index-CmruhzFi.js} +1 -1
- package/src/assets/web-panel/assets/{index-2ufsNuVn.js → index-Csfb-7hw.js} +1 -1
- package/src/assets/web-panel/assets/{index-0umhind6.js → index-CxEtQYdi.js} +1 -1
- package/src/assets/web-panel/assets/{index-B33frlaD.js → index-D7z4zB_a.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dy2PnKZx.js → index-DLoalqRX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUiTugu3.js → index-DU2TXJu-.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGv5In_T.js → index-DXet1xol.js} +1 -1
- package/src/assets/web-panel/assets/{index-DCHMVYG1.js → index-Dbd7eEex.js} +1 -1
- package/src/assets/web-panel/assets/{index-th467RN6.js → index-Dbkghqn_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkmgbT7h.js → index-Dg2a9owQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-aTiI7lOQ.js → index-DpqtbXdE.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5F1vwec.js → index-DqwPFbOb.js} +3 -3
- package/src/assets/web-panel/assets/{index-DaHvj4jA.js → index-DvV03Ayk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRsGZBHc.js → index-NJieh3T1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeGn0A4W.js → index-Td8ZwyoC.js} +1 -1
- package/src/assets/web-panel/assets/index-c8jJEHVs.js +1 -0
- package/src/assets/web-panel/assets/{index-va4Rt-yM.js → index-e-e40id6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKS7n6mE.js → index-fccZmC_h.js} +1 -1
- package/src/assets/web-panel/assets/{index-BEjwv2x6.js → index-fgYk0QXO.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dlq-u9E4.js → index-yYEQXYaU.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BeSPUFDz.js → initDefaultProps-CPrcB1TC.js} +1 -1
- package/src/assets/web-panel/assets/{motion-Dcajvjm9.js → motion-BQ7mWRh_.js} +1 -1
- package/src/assets/web-panel/assets/{move-B76orau-.js → move-CzOEq99p.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-zB6yFwcX.js → mtc-parser-BlQI5jrw.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BsbNjz8w.js → omit-DlW4gxmv.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BiJ9tssQ.js → pickAttrs-BBYms5nP.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BdfMjMeN.js → placementArrow-az2MPJ_7.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-rV4LGpbU.js → responsiveObserve-D1-ibEol.js} +1 -1
- package/src/assets/web-panel/assets/{slide-HsVw1qFQ.js → slide-DHGqa4GA.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-D1ry8XPE.js → statusUtils-CYGDo6lD.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-B1t6aQQ_.js → styleChecker-Cl2EM9K6.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-C8qbl-9w.js → useFlexGapSupport-DdYhzuv1.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-R7g_mYTC.js → useFs-DWumPuBW.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Cd-kCe7Z.js → usePersonalDataHub-DxAO-Aqi.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-9WhK5UWA.js → vnode-CplsQf_N.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CUqppPco.js → zoom-Cw5HXNSG.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +2 -2
- package/src/commands/agent.js +19 -1
- package/src/commands/evolution-knowledge.js +54 -0
- package/src/commands/evolution-workbench.js +83 -0
- package/src/commands/evolution.js +6 -1
- package/src/commands/serve.js +16 -0
- package/src/commands/skill.js +158 -16
- package/src/data/changelog.json +13 -0
- package/src/gateways/ws/session-protocol.js +2 -0
- package/src/gateways/ws/ws-agent-handler.js +124 -69
- package/src/gateways/ws/ws-server.js +19 -0
- package/src/generated/cc-agent-protocol.schema.json +7 -1
- package/src/harness/jsonl-session-store.js +25 -1
- package/src/index.js +1 -1
- package/src/lib/app-server/cli-agent-kernel-adapter.js +8 -0
- package/src/lib/app-server/graph-runtime.js +40 -1
- package/src/lib/app-server/server.js +181 -3
- package/src/lib/app-server/websocket-transport.js +2 -0
- package/src/lib/evolution/agent-evolution-runtime-composition-brand.js +17 -0
- package/src/lib/evolution/agent-evolution-runtime-composition.js +165 -1
- package/src/lib/evolution/controlled-skill-production-pilot.js +912 -0
- package/src/lib/evolution/evolution-artifact-ports.js +21 -0
- package/src/lib/evolution/evolution-raw-crypto-shred.js +185 -0
- package/src/lib/evolution/evolution-workbench-batch-executor.js +195 -0
- package/src/lib/evolution/evolution-workbench-cli-host.js +169 -0
- package/src/lib/evolution/evolution-workbench-metrics-ledger-adapter.js +648 -0
- package/src/lib/evolution/evolution-workbench-metrics.js +943 -0
- package/src/lib/evolution/evolution-workbench-projection.js +734 -0
- package/src/lib/evolution/evolution-workbench-version-control.js +272 -0
- package/src/lib/evolution/governed-knowledge-conflict-merge.js +352 -0
- package/src/lib/evolution/governed-knowledge-dependency-authority.js +273 -0
- package/src/lib/evolution/governed-knowledge-dependency-ledger-executor.js +646 -0
- package/src/lib/evolution/governed-knowledge-merge-ledger-executor.js +644 -0
- package/src/lib/evolution/governed-knowledge-merge-publisher-authority.js +282 -0
- package/src/lib/evolution/governed-knowledge-node-crypto-authority.js +348 -0
- package/src/lib/evolution/governed-knowledge-rbac-approval-authority.js +621 -0
- package/src/lib/evolution/governed-knowledge-review-host.js +249 -0
- package/src/lib/evolution/governed-knowledge-sync-ledger-adapter.js +643 -0
- package/src/lib/evolution/governed-knowledge-sync-merge-publisher.js +259 -0
- package/src/lib/evolution/governed-knowledge-sync.js +485 -0
- package/src/lib/evolution/governed-knowledge-trust-ledger.js +707 -0
- package/src/lib/evolution/governed-skill-marketplace.js +430 -0
- package/src/lib/evolution/governed-wiki-pruning.js +435 -0
- package/src/lib/evolution/skill-outcome-index-authority.js +268 -0
- package/src/lib/evolution/skill-outcome-source-catalog-authority.js +237 -0
- package/src/lib/public-ide-capability-manifest.js +4 -0
- package/src/lib/skill-outcome-authority.js +43 -0
- package/src/lib/skill-outcome-transcript-authority.js +252 -0
- package/src/lib/skill-retrieval-router-benchmark.js +135 -0
- package/src/lib/skill-retrieval-router.js +235 -0
- package/src/lib/skill-vector-authority.js +325 -0
- package/src/lib/skill-vector-process-authority.js +386 -0
- package/src/repl/agent-repl.js +7 -1
- package/src/runtime/agent-core.js +72 -6
- package/src/runtime/agent-runtime.js +42 -0
- package/src/runtime/headless-runner.js +32 -0
- package/src/runtime/headless-stream.js +36 -1
- package/src/runtime/runtime-factory.js +62 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-eMmw1eZF.js +0 -1
- package/src/assets/web-panel/assets/GenericCardRenderer-BMAtvGt1.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Dyrnqy_o.js +0 -1
- package/src/assets/web-panel/assets/TimelineRenderer-CnudkJxk.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DxufPwXC.js +0 -1
- package/src/assets/web-panel/assets/index-ZMTB9p5z.js +0 -1
- package/src/assets/web-panel/assets/index-_PvIDelE.js +0 -1
|
@@ -0,0 +1,943 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { types as utilTypes } from "node:util";
|
|
3
|
+
|
|
4
|
+
import skillInvocationReceipt from "@chainlesschain/session-core/skill-invocation-receipt";
|
|
5
|
+
|
|
6
|
+
const { verifySkillInvocationReceipt } = skillInvocationReceipt;
|
|
7
|
+
|
|
8
|
+
export const EVOLUTION_WORKBENCH_METRICS_SNAPSHOT_SCHEMA =
|
|
9
|
+
"chainlesschain.evolution-workbench-metrics-snapshot/v1";
|
|
10
|
+
export const EVOLUTION_WORKBENCH_METRICS_MAX_RECEIPTS = 100_000;
|
|
11
|
+
export const EVOLUTION_WORKBENCH_METRICS_MAX_DELTA = 10_000;
|
|
12
|
+
export const EVOLUTION_WORKBENCH_METRICS_MAX_HOT_RECEIPTS = 10_000;
|
|
13
|
+
export const EVOLUTION_WORKBENCH_METRICS_HISTORY_SCHEMA =
|
|
14
|
+
"chainlesschain.evolution-workbench-metrics-history/v1";
|
|
15
|
+
|
|
16
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
17
|
+
const RETENTION_SNAPSHOT_KEYS = Object.freeze(
|
|
18
|
+
[
|
|
19
|
+
"evolutionRunId",
|
|
20
|
+
"priorSnapshotDigest",
|
|
21
|
+
"receiptDigests",
|
|
22
|
+
"retainedReceiptCount",
|
|
23
|
+
"retentionRootDigest",
|
|
24
|
+
"revision",
|
|
25
|
+
"schema",
|
|
26
|
+
"skillName",
|
|
27
|
+
"snapshotDigest",
|
|
28
|
+
"sourceDigest",
|
|
29
|
+
"tenantId",
|
|
30
|
+
"throughAt",
|
|
31
|
+
"versions",
|
|
32
|
+
].sort(),
|
|
33
|
+
);
|
|
34
|
+
const LEGACY_SNAPSHOT_KEYS = Object.freeze(
|
|
35
|
+
RETENTION_SNAPSHOT_KEYS.filter(
|
|
36
|
+
(key) => key !== "retainedReceiptCount" && key !== "retentionRootDigest",
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
const OUTCOME_SNAPSHOT_KEYS = Object.freeze(
|
|
40
|
+
[...RETENTION_SNAPSHOT_KEYS, "outcomeHistoryComplete"].sort(),
|
|
41
|
+
);
|
|
42
|
+
const LEGACY_VERSION_KEYS = Object.freeze(
|
|
43
|
+
[
|
|
44
|
+
"blocked",
|
|
45
|
+
"completed",
|
|
46
|
+
"contentDigest",
|
|
47
|
+
"costUsd",
|
|
48
|
+
"failed",
|
|
49
|
+
"latencyMs",
|
|
50
|
+
"maxLatencyMs",
|
|
51
|
+
"receiptCount",
|
|
52
|
+
"tokensInput",
|
|
53
|
+
"tokensOutput",
|
|
54
|
+
].sort(),
|
|
55
|
+
);
|
|
56
|
+
const OUTCOME_VERSION_KEYS = Object.freeze(
|
|
57
|
+
[
|
|
58
|
+
...LEGACY_VERSION_KEYS,
|
|
59
|
+
"outcomeCompleted",
|
|
60
|
+
"outcomeReceiptCount",
|
|
61
|
+
"userCorrectionCount",
|
|
62
|
+
].sort(),
|
|
63
|
+
);
|
|
64
|
+
const HISTORY_KEYS = Object.freeze(
|
|
65
|
+
[
|
|
66
|
+
"authenticated",
|
|
67
|
+
"durable",
|
|
68
|
+
"evolutionRunId",
|
|
69
|
+
"historyDigest",
|
|
70
|
+
"receipts",
|
|
71
|
+
"schema",
|
|
72
|
+
"skillName",
|
|
73
|
+
"snapshotDigest",
|
|
74
|
+
"sourceDigest",
|
|
75
|
+
"tenantId",
|
|
76
|
+
"throughAt",
|
|
77
|
+
].sort(),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
function canonical(value) {
|
|
81
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
82
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
83
|
+
return `{${Object.keys(value)
|
|
84
|
+
.sort()
|
|
85
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
|
|
86
|
+
.join(",")}}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function hash(domain, value) {
|
|
90
|
+
return `sha256:${createHash("sha256")
|
|
91
|
+
.update(domain)
|
|
92
|
+
.update("\0")
|
|
93
|
+
.update(canonical(value))
|
|
94
|
+
.digest("hex")}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function freeze(value) {
|
|
98
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
99
|
+
Object.freeze(value);
|
|
100
|
+
for (const child of Object.values(value)) freeze(child);
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function string(value, label) {
|
|
106
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
107
|
+
throw new TypeError(`${label} is required`);
|
|
108
|
+
}
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function digest(value, label) {
|
|
113
|
+
if (!DIGEST.test(value ?? "")) throw new TypeError(`${label} is invalid`);
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function exactRecord(value, keys) {
|
|
118
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
const prototype = Object.getPrototypeOf(value);
|
|
122
|
+
if (prototype !== Object.prototype && prototype !== null) return false;
|
|
123
|
+
const actual = Object.keys(value).sort();
|
|
124
|
+
return (
|
|
125
|
+
actual.length === keys.length &&
|
|
126
|
+
actual.every((key, index) => key === keys[index])
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function boundedString(value) {
|
|
131
|
+
return (
|
|
132
|
+
typeof value === "string" &&
|
|
133
|
+
value.length > 0 &&
|
|
134
|
+
value.length <= 256 &&
|
|
135
|
+
value.trim() === value
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function safeCount(value) {
|
|
140
|
+
return Number.isSafeInteger(value) && value >= 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function nonNegativeNumber(value) {
|
|
144
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function createEmptyEvolutionWorkbenchMetricsSnapshot(
|
|
148
|
+
tenantId,
|
|
149
|
+
evolutionRunId,
|
|
150
|
+
skillName,
|
|
151
|
+
) {
|
|
152
|
+
const core = {
|
|
153
|
+
schema: EVOLUTION_WORKBENCH_METRICS_SNAPSHOT_SCHEMA,
|
|
154
|
+
tenantId,
|
|
155
|
+
evolutionRunId,
|
|
156
|
+
skillName,
|
|
157
|
+
revision: 0,
|
|
158
|
+
priorSnapshotDigest: null,
|
|
159
|
+
sourceDigest: null,
|
|
160
|
+
throughAt: null,
|
|
161
|
+
retainedReceiptCount: 0,
|
|
162
|
+
retentionRootDigest: null,
|
|
163
|
+
outcomeHistoryComplete: true,
|
|
164
|
+
receiptDigests: [],
|
|
165
|
+
versions: [],
|
|
166
|
+
};
|
|
167
|
+
return freeze({
|
|
168
|
+
...core,
|
|
169
|
+
snapshotDigest: hash(EVOLUTION_WORKBENCH_METRICS_SNAPSHOT_SCHEMA, core),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function digestEvolutionWorkbenchMetricsDelta({
|
|
174
|
+
tenantId,
|
|
175
|
+
evolutionRunId,
|
|
176
|
+
priorSourceDigest,
|
|
177
|
+
throughAt,
|
|
178
|
+
receipts,
|
|
179
|
+
}) {
|
|
180
|
+
return hash("chainlesschain.evolution-workbench-metrics-delta/v1", {
|
|
181
|
+
tenantId,
|
|
182
|
+
evolutionRunId,
|
|
183
|
+
priorSourceDigest,
|
|
184
|
+
throughAt,
|
|
185
|
+
receiptDigests: receipts.map(({ receiptDigest }) => receiptDigest),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function digestEvolutionWorkbenchMetricsHistory({
|
|
190
|
+
tenantId,
|
|
191
|
+
evolutionRunId,
|
|
192
|
+
skillName,
|
|
193
|
+
snapshotDigest,
|
|
194
|
+
sourceDigest,
|
|
195
|
+
throughAt,
|
|
196
|
+
receipts,
|
|
197
|
+
}) {
|
|
198
|
+
return hash(EVOLUTION_WORKBENCH_METRICS_HISTORY_SCHEMA, {
|
|
199
|
+
tenantId,
|
|
200
|
+
evolutionRunId,
|
|
201
|
+
skillName,
|
|
202
|
+
snapshotDigest,
|
|
203
|
+
sourceDigest,
|
|
204
|
+
throughAt,
|
|
205
|
+
receiptDigests: receipts.map(({ receiptDigest }) => receiptDigest),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function digestEvolutionWorkbenchMetricsRetentionBatch({
|
|
210
|
+
tenantId,
|
|
211
|
+
evolutionRunId,
|
|
212
|
+
skillName,
|
|
213
|
+
priorRetentionRootDigest,
|
|
214
|
+
priorRetainedReceiptCount,
|
|
215
|
+
throughAt,
|
|
216
|
+
receiptDigests,
|
|
217
|
+
}) {
|
|
218
|
+
return hash("chainlesschain.evolution-workbench-metrics-retention-batch/v1", {
|
|
219
|
+
tenantId,
|
|
220
|
+
evolutionRunId,
|
|
221
|
+
skillName,
|
|
222
|
+
priorRetentionRootDigest,
|
|
223
|
+
priorRetainedReceiptCount,
|
|
224
|
+
throughAt,
|
|
225
|
+
receiptDigests: [...receiptDigests].sort(),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function digestEvolutionWorkbenchMetricsRetentionQuery({
|
|
230
|
+
tenantId,
|
|
231
|
+
evolutionRunId,
|
|
232
|
+
skillName,
|
|
233
|
+
retentionRootDigest,
|
|
234
|
+
receiptDigests,
|
|
235
|
+
}) {
|
|
236
|
+
return hash("chainlesschain.evolution-workbench-metrics-retention-query/v1", {
|
|
237
|
+
tenantId,
|
|
238
|
+
evolutionRunId,
|
|
239
|
+
skillName,
|
|
240
|
+
retentionRootDigest,
|
|
241
|
+
receiptDigests,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function verifyEvolutionWorkbenchMetricsSnapshot(
|
|
246
|
+
value,
|
|
247
|
+
{ tenantId, evolutionRunId, skillName },
|
|
248
|
+
) {
|
|
249
|
+
const exactOutcome = exactRecord(value, OUTCOME_SNAPSHOT_KEYS);
|
|
250
|
+
const exactRetention = exactRecord(value, RETENTION_SNAPSHOT_KEYS);
|
|
251
|
+
const exactLegacy = exactRecord(value, LEGACY_SNAPSHOT_KEYS);
|
|
252
|
+
if (
|
|
253
|
+
(!exactOutcome && !exactRetention && !exactLegacy) ||
|
|
254
|
+
value.schema !== EVOLUTION_WORKBENCH_METRICS_SNAPSHOT_SCHEMA ||
|
|
255
|
+
!boundedString(value.tenantId) ||
|
|
256
|
+
!boundedString(value.evolutionRunId) ||
|
|
257
|
+
!boundedString(value.skillName) ||
|
|
258
|
+
value.tenantId !== tenantId ||
|
|
259
|
+
value.evolutionRunId !== evolutionRunId ||
|
|
260
|
+
value.skillName !== skillName ||
|
|
261
|
+
!safeCount(value.revision) ||
|
|
262
|
+
(value.revision === 0
|
|
263
|
+
? value.priorSnapshotDigest !== null ||
|
|
264
|
+
value.sourceDigest !== null ||
|
|
265
|
+
value.throughAt !== null
|
|
266
|
+
: !DIGEST.test(value.priorSnapshotDigest ?? "") ||
|
|
267
|
+
!DIGEST.test(value.sourceDigest ?? "") ||
|
|
268
|
+
typeof value.throughAt !== "string" ||
|
|
269
|
+
!Number.isFinite(Date.parse(value.throughAt))) ||
|
|
270
|
+
!DIGEST.test(value.snapshotDigest ?? "") ||
|
|
271
|
+
!Array.isArray(value.receiptDigests) ||
|
|
272
|
+
value.receiptDigests.length > EVOLUTION_WORKBENCH_METRICS_MAX_RECEIPTS ||
|
|
273
|
+
new Set(value.receiptDigests).size !== value.receiptDigests.length ||
|
|
274
|
+
value.receiptDigests.join("\n") !==
|
|
275
|
+
[...value.receiptDigests].sort().join("\n") ||
|
|
276
|
+
!Array.isArray(value.versions) ||
|
|
277
|
+
value.versions.length > EVOLUTION_WORKBENCH_METRICS_MAX_RECEIPTS
|
|
278
|
+
) {
|
|
279
|
+
throw new TypeError("Workbench metrics snapshot is invalid");
|
|
280
|
+
}
|
|
281
|
+
const retainedReceiptCount = value.retainedReceiptCount ?? 0;
|
|
282
|
+
const retentionRootDigest = value.retentionRootDigest ?? null;
|
|
283
|
+
if (
|
|
284
|
+
!safeCount(retainedReceiptCount) ||
|
|
285
|
+
(retainedReceiptCount === 0 && retentionRootDigest !== null) ||
|
|
286
|
+
(retainedReceiptCount > 0 && !DIGEST.test(retentionRootDigest ?? ""))
|
|
287
|
+
) {
|
|
288
|
+
throw new TypeError("Workbench metrics retention state is invalid");
|
|
289
|
+
}
|
|
290
|
+
if (
|
|
291
|
+
value.revision === 0 &&
|
|
292
|
+
(retainedReceiptCount !== 0 ||
|
|
293
|
+
value.receiptDigests.length !== 0 ||
|
|
294
|
+
value.versions.length !== 0)
|
|
295
|
+
) {
|
|
296
|
+
throw new TypeError("Workbench metrics genesis snapshot is invalid");
|
|
297
|
+
}
|
|
298
|
+
if (
|
|
299
|
+
exactOutcome &&
|
|
300
|
+
(typeof value.outcomeHistoryComplete !== "boolean" ||
|
|
301
|
+
(value.revision === 0 && value.outcomeHistoryComplete !== true))
|
|
302
|
+
) {
|
|
303
|
+
throw new TypeError("Workbench metrics outcome coverage is invalid");
|
|
304
|
+
}
|
|
305
|
+
for (const receiptDigest of value.receiptDigests)
|
|
306
|
+
digest(receiptDigest, "receipt digest");
|
|
307
|
+
const versionDigests = new Set();
|
|
308
|
+
let projectedReceiptCount = 0;
|
|
309
|
+
let priorVersionDigest = null;
|
|
310
|
+
let versionLayout = null;
|
|
311
|
+
for (const version of value.versions) {
|
|
312
|
+
const outcomeVersion = exactRecord(version, OUTCOME_VERSION_KEYS);
|
|
313
|
+
const legacyVersion = exactRecord(version, LEGACY_VERSION_KEYS);
|
|
314
|
+
if (
|
|
315
|
+
(!outcomeVersion && !legacyVersion) ||
|
|
316
|
+
versionDigests.has(version.contentDigest)
|
|
317
|
+
) {
|
|
318
|
+
throw new TypeError("Workbench metrics version is invalid");
|
|
319
|
+
}
|
|
320
|
+
const layout = outcomeVersion ? "outcome" : "legacy";
|
|
321
|
+
if (versionLayout !== null && versionLayout !== layout) {
|
|
322
|
+
throw new TypeError("Workbench metrics version layouts are mixed");
|
|
323
|
+
}
|
|
324
|
+
versionLayout = layout;
|
|
325
|
+
digest(version.contentDigest, "version content digest");
|
|
326
|
+
if (
|
|
327
|
+
priorVersionDigest !== null &&
|
|
328
|
+
version.contentDigest.localeCompare(priorVersionDigest) <= 0
|
|
329
|
+
) {
|
|
330
|
+
throw new TypeError("Workbench metrics versions are not canonical");
|
|
331
|
+
}
|
|
332
|
+
priorVersionDigest = version.contentDigest;
|
|
333
|
+
versionDigests.add(version.contentDigest);
|
|
334
|
+
for (const field of ["receiptCount", "completed", "failed", "blocked"]) {
|
|
335
|
+
if (!safeCount(version[field])) {
|
|
336
|
+
throw new TypeError(`Workbench metrics ${field} is invalid`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (outcomeVersion) {
|
|
340
|
+
for (const field of [
|
|
341
|
+
"outcomeReceiptCount",
|
|
342
|
+
"outcomeCompleted",
|
|
343
|
+
"userCorrectionCount",
|
|
344
|
+
]) {
|
|
345
|
+
if (!safeCount(version[field])) {
|
|
346
|
+
throw new TypeError(`Workbench metrics ${field} is invalid`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (
|
|
350
|
+
version.outcomeReceiptCount > version.receiptCount ||
|
|
351
|
+
version.outcomeCompleted > version.outcomeReceiptCount ||
|
|
352
|
+
version.userCorrectionCount > version.outcomeReceiptCount
|
|
353
|
+
) {
|
|
354
|
+
throw new Error("Workbench metrics outcome counts are inconsistent");
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
for (const field of [
|
|
358
|
+
"tokensInput",
|
|
359
|
+
"tokensOutput",
|
|
360
|
+
"costUsd",
|
|
361
|
+
"latencyMs",
|
|
362
|
+
"maxLatencyMs",
|
|
363
|
+
]) {
|
|
364
|
+
if (!nonNegativeNumber(version[field])) {
|
|
365
|
+
throw new TypeError(`Workbench metrics ${field} is invalid`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (
|
|
369
|
+
version.receiptCount < 1 ||
|
|
370
|
+
version.completed + version.failed + version.blocked !==
|
|
371
|
+
version.receiptCount
|
|
372
|
+
) {
|
|
373
|
+
throw new Error("Workbench metrics outcomes do not sum to receiptCount");
|
|
374
|
+
}
|
|
375
|
+
projectedReceiptCount += version.receiptCount;
|
|
376
|
+
if (!Number.isSafeInteger(projectedReceiptCount)) {
|
|
377
|
+
throw new TypeError("Workbench metrics receipt total is invalid");
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (
|
|
381
|
+
projectedReceiptCount !==
|
|
382
|
+
retainedReceiptCount + value.receiptDigests.length
|
|
383
|
+
) {
|
|
384
|
+
throw new Error("Workbench metrics receipt retention total is invalid");
|
|
385
|
+
}
|
|
386
|
+
if (
|
|
387
|
+
(exactOutcome && versionLayout === "legacy") ||
|
|
388
|
+
(!exactOutcome && versionLayout === "outcome")
|
|
389
|
+
) {
|
|
390
|
+
throw new TypeError("Workbench metrics outcome layout is invalid");
|
|
391
|
+
}
|
|
392
|
+
const core = structuredClone(value);
|
|
393
|
+
delete core.snapshotDigest;
|
|
394
|
+
if (
|
|
395
|
+
hash(EVOLUTION_WORKBENCH_METRICS_SNAPSHOT_SCHEMA, core) !==
|
|
396
|
+
value.snapshotDigest
|
|
397
|
+
) {
|
|
398
|
+
throw new Error("Workbench metrics snapshot digest is invalid");
|
|
399
|
+
}
|
|
400
|
+
return freeze(structuredClone(value));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function nextSnapshot(previous, source, receipts, retention) {
|
|
404
|
+
if (source.priorSourceDigest !== previous.sourceDigest) {
|
|
405
|
+
throw new Error("Workbench metrics source lineage is discontinuous");
|
|
406
|
+
}
|
|
407
|
+
const seen = new Set(retention.hotReceiptDigests);
|
|
408
|
+
const versions = new Map(
|
|
409
|
+
previous.versions.map((entry) => [
|
|
410
|
+
entry.contentDigest,
|
|
411
|
+
{
|
|
412
|
+
...structuredClone(entry),
|
|
413
|
+
outcomeReceiptCount: entry.outcomeReceiptCount ?? 0,
|
|
414
|
+
outcomeCompleted: entry.outcomeCompleted ?? 0,
|
|
415
|
+
userCorrectionCount: entry.userCorrectionCount ?? 0,
|
|
416
|
+
},
|
|
417
|
+
]),
|
|
418
|
+
);
|
|
419
|
+
const previousReceiptCount = previous.versions.reduce(
|
|
420
|
+
(total, version) => total + version.receiptCount,
|
|
421
|
+
0,
|
|
422
|
+
);
|
|
423
|
+
const outcomeHistoryComplete =
|
|
424
|
+
previous.outcomeHistoryComplete === true || previousReceiptCount === 0;
|
|
425
|
+
for (const receipt of receipts) {
|
|
426
|
+
if (seen.has(receipt.receiptDigest)) {
|
|
427
|
+
throw new Error("Workbench metrics source replayed a receipt");
|
|
428
|
+
}
|
|
429
|
+
seen.add(receipt.receiptDigest);
|
|
430
|
+
for (const contentDigest of receipt.selectedSkillDigests) {
|
|
431
|
+
const current = versions.get(contentDigest) ?? {
|
|
432
|
+
contentDigest,
|
|
433
|
+
receiptCount: 0,
|
|
434
|
+
completed: 0,
|
|
435
|
+
failed: 0,
|
|
436
|
+
blocked: 0,
|
|
437
|
+
outcomeReceiptCount: 0,
|
|
438
|
+
outcomeCompleted: 0,
|
|
439
|
+
userCorrectionCount: 0,
|
|
440
|
+
tokensInput: 0,
|
|
441
|
+
tokensOutput: 0,
|
|
442
|
+
costUsd: 0,
|
|
443
|
+
latencyMs: 0,
|
|
444
|
+
maxLatencyMs: 0,
|
|
445
|
+
};
|
|
446
|
+
current.receiptCount += 1;
|
|
447
|
+
current[receipt.executionStatus] += 1;
|
|
448
|
+
current.tokensInput += receipt.tokenCostLatency.tokensInput;
|
|
449
|
+
current.tokensOutput += receipt.tokenCostLatency.tokensOutput;
|
|
450
|
+
current.costUsd += receipt.tokenCostLatency.costUsd;
|
|
451
|
+
current.latencyMs += receipt.tokenCostLatency.latencyMs;
|
|
452
|
+
current.maxLatencyMs = Math.max(
|
|
453
|
+
current.maxLatencyMs,
|
|
454
|
+
receipt.tokenCostLatency.latencyMs,
|
|
455
|
+
);
|
|
456
|
+
const hasOutcomeEvidence =
|
|
457
|
+
receipt.graderReceipts.length > 0 || receipt.userCorrectionRef !== null;
|
|
458
|
+
if (
|
|
459
|
+
hasOutcomeEvidence &&
|
|
460
|
+
["completed", "failed"].includes(receipt.executionStatus)
|
|
461
|
+
) {
|
|
462
|
+
current.outcomeReceiptCount += 1;
|
|
463
|
+
if (receipt.executionStatus === "completed") {
|
|
464
|
+
current.outcomeCompleted += 1;
|
|
465
|
+
}
|
|
466
|
+
if (receipt.userCorrectionRef !== null) {
|
|
467
|
+
current.userCorrectionCount += 1;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
versions.set(contentDigest, current);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (seen.size > EVOLUTION_WORKBENCH_METRICS_MAX_RECEIPTS) {
|
|
474
|
+
throw new Error("Workbench metrics receipt retention limit was reached");
|
|
475
|
+
}
|
|
476
|
+
const core = {
|
|
477
|
+
schema: EVOLUTION_WORKBENCH_METRICS_SNAPSHOT_SCHEMA,
|
|
478
|
+
tenantId: previous.tenantId,
|
|
479
|
+
evolutionRunId: previous.evolutionRunId,
|
|
480
|
+
skillName: previous.skillName,
|
|
481
|
+
revision: previous.revision + 1,
|
|
482
|
+
priorSnapshotDigest: previous.snapshotDigest,
|
|
483
|
+
sourceDigest: source.sourceDigest,
|
|
484
|
+
throughAt: source.throughAt,
|
|
485
|
+
retainedReceiptCount: retention.retainedReceiptCount,
|
|
486
|
+
retentionRootDigest: retention.retentionRootDigest,
|
|
487
|
+
outcomeHistoryComplete,
|
|
488
|
+
receiptDigests: [...seen].sort(),
|
|
489
|
+
versions: [...versions.values()].sort((left, right) =>
|
|
490
|
+
left.contentDigest.localeCompare(right.contentDigest),
|
|
491
|
+
),
|
|
492
|
+
};
|
|
493
|
+
return freeze({
|
|
494
|
+
...core,
|
|
495
|
+
snapshotDigest: hash(EVOLUTION_WORKBENCH_METRICS_SNAPSHOT_SCHEMA, core),
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function projectReceiptHistory(receipts) {
|
|
500
|
+
const versions = new Map();
|
|
501
|
+
for (const receipt of receipts) {
|
|
502
|
+
for (const contentDigest of receipt.selectedSkillDigests) {
|
|
503
|
+
const current = versions.get(contentDigest) ?? {
|
|
504
|
+
contentDigest,
|
|
505
|
+
receiptCount: 0,
|
|
506
|
+
completed: 0,
|
|
507
|
+
failed: 0,
|
|
508
|
+
blocked: 0,
|
|
509
|
+
outcomeReceiptCount: 0,
|
|
510
|
+
outcomeCompleted: 0,
|
|
511
|
+
userCorrectionCount: 0,
|
|
512
|
+
tokensInput: 0,
|
|
513
|
+
tokensOutput: 0,
|
|
514
|
+
costUsd: 0,
|
|
515
|
+
latencyMs: 0,
|
|
516
|
+
maxLatencyMs: 0,
|
|
517
|
+
};
|
|
518
|
+
current.receiptCount += 1;
|
|
519
|
+
current[receipt.executionStatus] += 1;
|
|
520
|
+
current.tokensInput += receipt.tokenCostLatency.tokensInput;
|
|
521
|
+
current.tokensOutput += receipt.tokenCostLatency.tokensOutput;
|
|
522
|
+
current.costUsd += receipt.tokenCostLatency.costUsd;
|
|
523
|
+
current.latencyMs += receipt.tokenCostLatency.latencyMs;
|
|
524
|
+
current.maxLatencyMs = Math.max(
|
|
525
|
+
current.maxLatencyMs,
|
|
526
|
+
receipt.tokenCostLatency.latencyMs,
|
|
527
|
+
);
|
|
528
|
+
const hasOutcomeEvidence =
|
|
529
|
+
receipt.graderReceipts.length > 0 || receipt.userCorrectionRef !== null;
|
|
530
|
+
if (
|
|
531
|
+
hasOutcomeEvidence &&
|
|
532
|
+
["completed", "failed"].includes(receipt.executionStatus)
|
|
533
|
+
) {
|
|
534
|
+
current.outcomeReceiptCount += 1;
|
|
535
|
+
if (receipt.executionStatus === "completed") {
|
|
536
|
+
current.outcomeCompleted += 1;
|
|
537
|
+
}
|
|
538
|
+
if (receipt.userCorrectionRef !== null) {
|
|
539
|
+
current.userCorrectionCount += 1;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
versions.set(contentDigest, current);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return [...versions.values()].sort((left, right) =>
|
|
546
|
+
left.contentDigest.localeCompare(right.contentDigest),
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function legacyProjection(versions) {
|
|
551
|
+
return versions.map((version) => {
|
|
552
|
+
const projected = {};
|
|
553
|
+
for (const key of LEGACY_VERSION_KEYS) projected[key] = version[key];
|
|
554
|
+
return projected;
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export class EvolutionWorkbenchMetricsOutcomeBackfiller {
|
|
559
|
+
constructor({ tenantId, evolutionRunId, skillName, ports } = {}) {
|
|
560
|
+
this.descriptor = freeze({
|
|
561
|
+
tenantId: string(tenantId, "tenantId"),
|
|
562
|
+
evolutionRunId: string(evolutionRunId, "evolutionRunId"),
|
|
563
|
+
skillName: string(skillName, "skillName"),
|
|
564
|
+
});
|
|
565
|
+
if (!ports || typeof ports !== "object" || utilTypes.isProxy(ports)) {
|
|
566
|
+
throw new TypeError("Workbench metrics backfill ports are required");
|
|
567
|
+
}
|
|
568
|
+
for (const name of [
|
|
569
|
+
"loadSnapshot",
|
|
570
|
+
"readReceiptHistory",
|
|
571
|
+
"commitSnapshot",
|
|
572
|
+
"queryRetainedReceiptDigests",
|
|
573
|
+
]) {
|
|
574
|
+
if (typeof ports[name] !== "function" || utilTypes.isProxy(ports[name])) {
|
|
575
|
+
throw new TypeError(
|
|
576
|
+
`Workbench metrics backfill port ${name} is required`,
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
this[`_${name}`] = ports[name].bind(ports);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
async backfill() {
|
|
584
|
+
const loaded = await this._loadSnapshot(this.descriptor);
|
|
585
|
+
if (
|
|
586
|
+
loaded?.found !== true ||
|
|
587
|
+
loaded.authenticated !== true ||
|
|
588
|
+
loaded.durable !== true
|
|
589
|
+
) {
|
|
590
|
+
throw new Error(
|
|
591
|
+
"Workbench metrics backfill snapshot is not authoritative",
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
const previous = verifyEvolutionWorkbenchMetricsSnapshot(
|
|
595
|
+
loaded.snapshot,
|
|
596
|
+
this.descriptor,
|
|
597
|
+
);
|
|
598
|
+
if (
|
|
599
|
+
previous.outcomeHistoryComplete === true ||
|
|
600
|
+
previous.versions.length === 0
|
|
601
|
+
) {
|
|
602
|
+
return freeze({ status: "already-complete", snapshot: previous });
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const expectedReceiptCount =
|
|
606
|
+
(previous.retainedReceiptCount ?? 0) + previous.receiptDigests.length;
|
|
607
|
+
const history = await this._readReceiptHistory({
|
|
608
|
+
...this.descriptor,
|
|
609
|
+
snapshotDigest: previous.snapshotDigest,
|
|
610
|
+
sourceDigest: previous.sourceDigest,
|
|
611
|
+
throughAt: previous.throughAt,
|
|
612
|
+
expectedReceiptCount,
|
|
613
|
+
});
|
|
614
|
+
if (
|
|
615
|
+
!history ||
|
|
616
|
+
typeof history !== "object" ||
|
|
617
|
+
utilTypes.isProxy(history) ||
|
|
618
|
+
!exactRecord(history, HISTORY_KEYS) ||
|
|
619
|
+
history.schema !== EVOLUTION_WORKBENCH_METRICS_HISTORY_SCHEMA ||
|
|
620
|
+
history.authenticated !== true ||
|
|
621
|
+
history.durable !== true ||
|
|
622
|
+
history.tenantId !== this.descriptor.tenantId ||
|
|
623
|
+
history.evolutionRunId !== this.descriptor.evolutionRunId ||
|
|
624
|
+
history.skillName !== this.descriptor.skillName ||
|
|
625
|
+
history.snapshotDigest !== previous.snapshotDigest ||
|
|
626
|
+
history.sourceDigest !== previous.sourceDigest ||
|
|
627
|
+
history.throughAt !== previous.throughAt ||
|
|
628
|
+
!Array.isArray(history.receipts) ||
|
|
629
|
+
utilTypes.isProxy(history.receipts) ||
|
|
630
|
+
history.receipts.length !== expectedReceiptCount ||
|
|
631
|
+
history.receipts.length > EVOLUTION_WORKBENCH_METRICS_MAX_RECEIPTS
|
|
632
|
+
) {
|
|
633
|
+
throw new Error("Workbench metrics receipt history is not authoritative");
|
|
634
|
+
}
|
|
635
|
+
const receipts = history.receipts.map((receipt) => {
|
|
636
|
+
if (
|
|
637
|
+
receipt &&
|
|
638
|
+
typeof receipt === "object" &&
|
|
639
|
+
utilTypes.isProxy(receipt)
|
|
640
|
+
) {
|
|
641
|
+
throw new Error("Workbench metrics receipt history content is invalid");
|
|
642
|
+
}
|
|
643
|
+
const verified = verifySkillInvocationReceipt(receipt);
|
|
644
|
+
if (
|
|
645
|
+
verified.evolutionRunId !== this.descriptor.evolutionRunId ||
|
|
646
|
+
verified.attributionEligible !== true ||
|
|
647
|
+
Date.parse(verified.completedAt) > Date.parse(previous.throughAt)
|
|
648
|
+
) {
|
|
649
|
+
throw new Error("Workbench metrics history lacks exact attribution");
|
|
650
|
+
}
|
|
651
|
+
return verified;
|
|
652
|
+
});
|
|
653
|
+
const receiptDigests = receipts.map(({ receiptDigest }) => receiptDigest);
|
|
654
|
+
if (
|
|
655
|
+
new Set(receiptDigests).size !== receiptDigests.length ||
|
|
656
|
+
receiptDigests.join("\n") !== [...receiptDigests].sort().join("\n") ||
|
|
657
|
+
history.historyDigest !==
|
|
658
|
+
digestEvolutionWorkbenchMetricsHistory({ ...history, receipts })
|
|
659
|
+
) {
|
|
660
|
+
throw new Error("Workbench metrics receipt history content is invalid");
|
|
661
|
+
}
|
|
662
|
+
const supplied = new Set(receiptDigests);
|
|
663
|
+
if (previous.receiptDigests.some((digest) => !supplied.has(digest))) {
|
|
664
|
+
throw new Error("Workbench metrics receipt history is incomplete");
|
|
665
|
+
}
|
|
666
|
+
const hot = new Set(previous.receiptDigests);
|
|
667
|
+
const retainedDigests = receiptDigests.filter((digest) => !hot.has(digest));
|
|
668
|
+
if (retainedDigests.length !== (previous.retainedReceiptCount ?? 0)) {
|
|
669
|
+
throw new Error(
|
|
670
|
+
"Workbench metrics retained receipt history is incomplete",
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
if (retainedDigests.length > 0) {
|
|
674
|
+
const query = {
|
|
675
|
+
...this.descriptor,
|
|
676
|
+
retentionRootDigest: previous.retentionRootDigest,
|
|
677
|
+
receiptDigests: retainedDigests,
|
|
678
|
+
};
|
|
679
|
+
const checked = await this._queryRetainedReceiptDigests(query);
|
|
680
|
+
if (
|
|
681
|
+
checked?.authenticated !== true ||
|
|
682
|
+
checked.durable !== true ||
|
|
683
|
+
checked.retentionRootDigest !== previous.retentionRootDigest ||
|
|
684
|
+
checked.queryDigest !==
|
|
685
|
+
digestEvolutionWorkbenchMetricsRetentionQuery(query) ||
|
|
686
|
+
!Array.isArray(checked.matches) ||
|
|
687
|
+
checked.matches.length !== retainedDigests.length ||
|
|
688
|
+
checked.matches.some((match) => match !== true)
|
|
689
|
+
) {
|
|
690
|
+
throw new Error(
|
|
691
|
+
"Workbench metrics retained receipt history is not authoritative",
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
const versions = projectReceiptHistory(receipts);
|
|
696
|
+
if (
|
|
697
|
+
canonical(legacyProjection(versions)) !==
|
|
698
|
+
canonical(legacyProjection(previous.versions))
|
|
699
|
+
) {
|
|
700
|
+
throw new Error("Workbench metrics history does not reconcile");
|
|
701
|
+
}
|
|
702
|
+
const core = {
|
|
703
|
+
schema: EVOLUTION_WORKBENCH_METRICS_SNAPSHOT_SCHEMA,
|
|
704
|
+
tenantId: previous.tenantId,
|
|
705
|
+
evolutionRunId: previous.evolutionRunId,
|
|
706
|
+
skillName: previous.skillName,
|
|
707
|
+
revision: previous.revision + 1,
|
|
708
|
+
priorSnapshotDigest: previous.snapshotDigest,
|
|
709
|
+
sourceDigest: previous.sourceDigest,
|
|
710
|
+
throughAt: previous.throughAt,
|
|
711
|
+
retainedReceiptCount: previous.retainedReceiptCount ?? 0,
|
|
712
|
+
retentionRootDigest: previous.retentionRootDigest ?? null,
|
|
713
|
+
outcomeHistoryComplete: true,
|
|
714
|
+
receiptDigests: previous.receiptDigests,
|
|
715
|
+
versions,
|
|
716
|
+
};
|
|
717
|
+
const snapshot = freeze({
|
|
718
|
+
...core,
|
|
719
|
+
snapshotDigest: hash(EVOLUTION_WORKBENCH_METRICS_SNAPSHOT_SCHEMA, core),
|
|
720
|
+
});
|
|
721
|
+
const committed = await this._commitSnapshot({
|
|
722
|
+
...this.descriptor,
|
|
723
|
+
expectedSnapshotDigest: previous.snapshotDigest,
|
|
724
|
+
snapshot,
|
|
725
|
+
});
|
|
726
|
+
if (
|
|
727
|
+
committed?.authenticated !== true ||
|
|
728
|
+
committed.durable !== true ||
|
|
729
|
+
committed.snapshotDigest !== snapshot.snapshotDigest
|
|
730
|
+
) {
|
|
731
|
+
throw new Error("Workbench metrics backfill was not durably committed");
|
|
732
|
+
}
|
|
733
|
+
const readback = await this._loadSnapshot(this.descriptor);
|
|
734
|
+
if (
|
|
735
|
+
readback?.found !== true ||
|
|
736
|
+
readback.authenticated !== true ||
|
|
737
|
+
readback.durable !== true ||
|
|
738
|
+
verifyEvolutionWorkbenchMetricsSnapshot(
|
|
739
|
+
readback.snapshot,
|
|
740
|
+
this.descriptor,
|
|
741
|
+
).snapshotDigest !== snapshot.snapshotDigest
|
|
742
|
+
) {
|
|
743
|
+
throw new Error("Workbench metrics backfill readback differs");
|
|
744
|
+
}
|
|
745
|
+
return freeze({
|
|
746
|
+
status: "reconciled",
|
|
747
|
+
receiptCount: receipts.length,
|
|
748
|
+
snapshot,
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
export class EvolutionWorkbenchMetricsAggregator {
|
|
754
|
+
constructor({
|
|
755
|
+
tenantId,
|
|
756
|
+
evolutionRunId,
|
|
757
|
+
skillName,
|
|
758
|
+
hotReceiptLimit = EVOLUTION_WORKBENCH_METRICS_MAX_HOT_RECEIPTS,
|
|
759
|
+
ports,
|
|
760
|
+
} = {}) {
|
|
761
|
+
this.descriptor = freeze({
|
|
762
|
+
tenantId: string(tenantId, "tenantId"),
|
|
763
|
+
evolutionRunId: string(evolutionRunId, "evolutionRunId"),
|
|
764
|
+
skillName: string(skillName, "skillName"),
|
|
765
|
+
});
|
|
766
|
+
for (const name of ["loadSnapshot", "readReceiptDelta", "commitSnapshot"]) {
|
|
767
|
+
if (typeof ports?.[name] !== "function") {
|
|
768
|
+
throw new TypeError(`Workbench metrics port ${name} is required`);
|
|
769
|
+
}
|
|
770
|
+
this[`_${name}`] = ports[name].bind(ports);
|
|
771
|
+
}
|
|
772
|
+
if (
|
|
773
|
+
!Number.isSafeInteger(hotReceiptLimit) ||
|
|
774
|
+
hotReceiptLimit < 1 ||
|
|
775
|
+
hotReceiptLimit > EVOLUTION_WORKBENCH_METRICS_MAX_HOT_RECEIPTS
|
|
776
|
+
) {
|
|
777
|
+
throw new TypeError("Workbench metrics hot receipt limit is invalid");
|
|
778
|
+
}
|
|
779
|
+
this.hotReceiptLimit = hotReceiptLimit;
|
|
780
|
+
this._retainReceiptDigests =
|
|
781
|
+
typeof ports?.retainReceiptDigests === "function"
|
|
782
|
+
? ports.retainReceiptDigests.bind(ports)
|
|
783
|
+
: null;
|
|
784
|
+
this._queryRetainedReceiptDigests =
|
|
785
|
+
typeof ports?.queryRetainedReceiptDigests === "function"
|
|
786
|
+
? ports.queryRetainedReceiptDigests.bind(ports)
|
|
787
|
+
: null;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
async aggregate() {
|
|
791
|
+
const loaded = await this._loadSnapshot(this.descriptor);
|
|
792
|
+
let previous;
|
|
793
|
+
if (
|
|
794
|
+
loaded?.found === false &&
|
|
795
|
+
loaded.authenticated === true &&
|
|
796
|
+
loaded.durable === true
|
|
797
|
+
) {
|
|
798
|
+
previous = createEmptyEvolutionWorkbenchMetricsSnapshot(
|
|
799
|
+
this.descriptor.tenantId,
|
|
800
|
+
this.descriptor.evolutionRunId,
|
|
801
|
+
this.descriptor.skillName,
|
|
802
|
+
);
|
|
803
|
+
} else if (
|
|
804
|
+
loaded?.found === true &&
|
|
805
|
+
loaded.authenticated === true &&
|
|
806
|
+
loaded.durable === true
|
|
807
|
+
) {
|
|
808
|
+
previous = verifyEvolutionWorkbenchMetricsSnapshot(
|
|
809
|
+
loaded.snapshot,
|
|
810
|
+
this.descriptor,
|
|
811
|
+
);
|
|
812
|
+
} else {
|
|
813
|
+
throw new Error("Workbench metrics snapshot load is not authoritative");
|
|
814
|
+
}
|
|
815
|
+
const source = await this._readReceiptDelta({
|
|
816
|
+
...this.descriptor,
|
|
817
|
+
fromSourceDigest: previous.sourceDigest,
|
|
818
|
+
});
|
|
819
|
+
if (
|
|
820
|
+
source?.authenticated !== true ||
|
|
821
|
+
source.durable !== true ||
|
|
822
|
+
source.tenantId !== this.descriptor.tenantId ||
|
|
823
|
+
source.evolutionRunId !== this.descriptor.evolutionRunId ||
|
|
824
|
+
source.priorSourceDigest !== previous.sourceDigest ||
|
|
825
|
+
!DIGEST.test(source.sourceDigest ?? "") ||
|
|
826
|
+
typeof source.throughAt !== "string" ||
|
|
827
|
+
!Number.isFinite(Date.parse(source.throughAt)) ||
|
|
828
|
+
!Array.isArray(source.receipts) ||
|
|
829
|
+
source.receipts.length > EVOLUTION_WORKBENCH_METRICS_MAX_DELTA
|
|
830
|
+
) {
|
|
831
|
+
throw new Error("Workbench metrics receipt delta is not authoritative");
|
|
832
|
+
}
|
|
833
|
+
const receipts = source.receipts.map((receipt) => {
|
|
834
|
+
const verified = verifySkillInvocationReceipt(receipt);
|
|
835
|
+
if (
|
|
836
|
+
verified.evolutionRunId !== this.descriptor.evolutionRunId ||
|
|
837
|
+
verified.attributionEligible !== true
|
|
838
|
+
) {
|
|
839
|
+
throw new Error("Workbench metrics receipt lacks exact attribution");
|
|
840
|
+
}
|
|
841
|
+
return verified;
|
|
842
|
+
});
|
|
843
|
+
if (
|
|
844
|
+
source.sourceDigest !==
|
|
845
|
+
digestEvolutionWorkbenchMetricsDelta({ ...source, receipts }) ||
|
|
846
|
+
(previous.throughAt !== null &&
|
|
847
|
+
Date.parse(source.throughAt) <= Date.parse(previous.throughAt)) ||
|
|
848
|
+
receipts.some(
|
|
849
|
+
(receipt) =>
|
|
850
|
+
Date.parse(receipt.completedAt) > Date.parse(source.throughAt),
|
|
851
|
+
)
|
|
852
|
+
) {
|
|
853
|
+
throw new Error("Workbench metrics delta content or window is invalid");
|
|
854
|
+
}
|
|
855
|
+
const receiptDigests = receipts.map(({ receiptDigest }) => receiptDigest);
|
|
856
|
+
const hotReceiptDigests = new Set(previous.receiptDigests);
|
|
857
|
+
if (receiptDigests.some((value) => hotReceiptDigests.has(value))) {
|
|
858
|
+
throw new Error("Workbench metrics source replayed a receipt");
|
|
859
|
+
}
|
|
860
|
+
const retainedReceiptCount = previous.retainedReceiptCount ?? 0;
|
|
861
|
+
const retentionRootDigest = previous.retentionRootDigest ?? null;
|
|
862
|
+
if (retainedReceiptCount > 0) {
|
|
863
|
+
if (!this._queryRetainedReceiptDigests) {
|
|
864
|
+
throw new Error("Workbench metrics retention query port is required");
|
|
865
|
+
}
|
|
866
|
+
const query = {
|
|
867
|
+
...this.descriptor,
|
|
868
|
+
retentionRootDigest,
|
|
869
|
+
receiptDigests,
|
|
870
|
+
};
|
|
871
|
+
const checked = await this._queryRetainedReceiptDigests(query);
|
|
872
|
+
if (
|
|
873
|
+
checked?.authenticated !== true ||
|
|
874
|
+
checked.durable !== true ||
|
|
875
|
+
checked.retentionRootDigest !== retentionRootDigest ||
|
|
876
|
+
checked.queryDigest !==
|
|
877
|
+
digestEvolutionWorkbenchMetricsRetentionQuery(query) ||
|
|
878
|
+
!Array.isArray(checked.matches) ||
|
|
879
|
+
checked.matches.length !== receiptDigests.length ||
|
|
880
|
+
checked.matches.some((match) => typeof match !== "boolean")
|
|
881
|
+
) {
|
|
882
|
+
throw new Error(
|
|
883
|
+
"Workbench metrics retention query is not authoritative",
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
if (checked.matches.some(Boolean)) {
|
|
887
|
+
throw new Error("Workbench metrics source replayed a retained receipt");
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
let retention = {
|
|
891
|
+
retainedReceiptCount,
|
|
892
|
+
retentionRootDigest,
|
|
893
|
+
hotReceiptDigests: previous.receiptDigests,
|
|
894
|
+
};
|
|
895
|
+
if (
|
|
896
|
+
previous.receiptDigests.length + receipts.length >
|
|
897
|
+
this.hotReceiptLimit
|
|
898
|
+
) {
|
|
899
|
+
if (!this._retainReceiptDigests) {
|
|
900
|
+
throw new Error("Workbench metrics retention commit port is required");
|
|
901
|
+
}
|
|
902
|
+
const batch = {
|
|
903
|
+
...this.descriptor,
|
|
904
|
+
priorRetentionRootDigest: retentionRootDigest,
|
|
905
|
+
priorRetainedReceiptCount: retainedReceiptCount,
|
|
906
|
+
throughAt: previous.throughAt,
|
|
907
|
+
receiptDigests: previous.receiptDigests,
|
|
908
|
+
};
|
|
909
|
+
const retained = await this._retainReceiptDigests(batch);
|
|
910
|
+
if (
|
|
911
|
+
retained?.authenticated !== true ||
|
|
912
|
+
retained.durable !== true ||
|
|
913
|
+
retained.priorRetentionRootDigest !== retentionRootDigest ||
|
|
914
|
+
retained.batchDigest !==
|
|
915
|
+
digestEvolutionWorkbenchMetricsRetentionBatch(batch) ||
|
|
916
|
+
!DIGEST.test(retained.retentionRootDigest ?? "") ||
|
|
917
|
+
retained.retainedReceiptCount !==
|
|
918
|
+
retainedReceiptCount + previous.receiptDigests.length
|
|
919
|
+
) {
|
|
920
|
+
throw new Error("Workbench metrics receipt retention was not durable");
|
|
921
|
+
}
|
|
922
|
+
retention = {
|
|
923
|
+
retainedReceiptCount: retained.retainedReceiptCount,
|
|
924
|
+
retentionRootDigest: retained.retentionRootDigest,
|
|
925
|
+
hotReceiptDigests: [],
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
const snapshot = nextSnapshot(previous, source, receipts, retention);
|
|
929
|
+
const committed = await this._commitSnapshot({
|
|
930
|
+
...this.descriptor,
|
|
931
|
+
expectedSnapshotDigest: previous.snapshotDigest,
|
|
932
|
+
snapshot,
|
|
933
|
+
});
|
|
934
|
+
if (
|
|
935
|
+
committed?.authenticated !== true ||
|
|
936
|
+
committed.durable !== true ||
|
|
937
|
+
committed.snapshotDigest !== snapshot.snapshotDigest
|
|
938
|
+
) {
|
|
939
|
+
throw new Error("Workbench metrics snapshot was not durably committed");
|
|
940
|
+
}
|
|
941
|
+
return snapshot;
|
|
942
|
+
}
|
|
943
|
+
}
|