chainlesschain 0.166.21 → 0.166.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -9
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-BjMnsUci.js → AIOps-oJ7USzMC.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DdYFP03A.js → ActionButton-BlgnZe_C.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Bb8TuuK_.js → Analytics-D2rRbTLu.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-Kn3TIOtk.js → AppLayout-FwS3f0B2.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-4IxpqI_b.js → Artifacts-iF-_08Hi.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BOV2caqU.js → Audit-D24p0zQw.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-B86k-ay4.js → BackgroundAgents-Cg3mRVi9.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-C5_ie1e3.js → Backup-EOqht3H-.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-xpwg6QCW.js → BaseInput-9XWYq2hj.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CVZOcZau.js → Chat-Dq5yardA.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-D8fPYhUu.js → ChatBubbleRenderer-Ds3pA-ET.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-MKTq3zdP.js → Checkbox-CuyCBoBf.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-JUk3wzep.js → Codegen-DjWtYHNe.js} +1 -1
- package/src/assets/web-panel/assets/{Col-JPvTA0kC.js → Col-CrVr_iuG.js} +1 -1
- package/src/assets/web-panel/assets/{Community-Tqgl87NQ.js → Community-C6QxpfGc.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BuAFYBmN.js → Compact-B-v5r6Pa.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CXS9WIPZ.js → Compliance-zNiLiFgD.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Ds2bFHrr.js → Cowork-D9K7lMOu.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-C_dV_OeW.js → Cron-DAjO36z4.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BsO6tIRo.js → Crosschain-Bxqn0aqB.js} +1 -1
- package/src/assets/web-panel/assets/{DID-rNOFqBac.js → DID-DfRmLUe-.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-C2fcKKVY.js → Dashboard-Cz8KF3fr.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Y7Ndr0X_.js → Dropdown-C4oVfkP0.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DcP_uPmL.js → EmailListRenderer-ivyZKN8k.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Cwn3gDzk.js → FamilyGuardDashboard-DoxY1CPW.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-ClQuOsqb.js → Federation-C93gZxnb.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DfwtmlcK.js → FormItemContext-D_lEjYJE.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-zNZhtbC5.js → GenericCardRenderer-BNFN9YjN.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BZmdW-G5.js → Git-mUu2HcLI.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CqBk4lEn.js → Governance-CNuosrge.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Dbmt3ZXk.js → Inference-DwY5pbCB.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-hMTH6qf3.js → KnowledgeGraph-aRUxhACD.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CkiYgiW2.js → Logs-DFfsWYy_.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-BL1FElXg.js → MarkdownRenderer-DqTx7_u1.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-CbB6wfin.js → Marketplace-COI-ciM-.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BSwiFtte.js → McpTools-BJJwaIpe.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-B03PeHrz.js → Memory-CDO6SGDZ.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CRGrnd-X.js → MobileBridge-BlZ5tFf4.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CE_5v2kO.js → MobileProjects-DuoxbgWz.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-Dp11QVx1.js → Mtc-6ec9j70b.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-CYu-3JBu.js → MtcAudit-BuL7HkC7.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-CGyJ0Xl3.js → Multisig-CJxR85lk.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CoWi_ov1.js → NLProgramming-B71O1S4l.js} +1 -1
- package/src/assets/web-panel/assets/Notes-Bsw5zbpV.js +7 -0
- package/src/assets/web-panel/assets/{NotificationSettings-DX4I2b4W.js → NotificationSettings-BnAhXuK_.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BJ0R5GGz.js +1 -0
- package/src/assets/web-panel/assets/{Organization-oopLO_tk.js → Organization-B7wwNxoP.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BFVvjMyf.js → Overflow-CfAGr0gM.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CBy5t-Tk.js → P2P-KhMg22Fz.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser--YWJNsu6.js → PdhVaultBrowser-NdAXI9ft.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-C2dZ8scb.js → Permissions-CIYjqyT7.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Bs3l7Hlx.js → PersonalDataHub-D2ht3P_R.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-DEBWbZuD.js → Pipeline-i6flZW1m.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CjHBb6NU.js → Privacy-DE0lhYVc.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-D2WipUz5.js → ProjectInit-BL3YbyxS.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-B6WfnrGB.js → ProjectSettings-CEtdg7ni.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-FF_thRi6.js → Projects-CvtU5peJ.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CCv5M_Dv.js → Providers-DKt_Cg-j.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CGthxU54.js → QrScannerModal-DA61jNSx.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CnPmXPFb.js → QuickAsk-fS9ryUmq.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Df_tGEa9.js → Recommend-CR9dZU5Y.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-Z58N0Loe.js → RemoteSession-BV0BR2Dz.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CZTBQ2Vt.js → Reputation-BIeGUvYD.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CdK3YpCQ.js → Row-DHDT1aro.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CFMVT5sb.js → RssFeed-Cq6YKb1h.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DrF8RIvD.js → Search-DEF6vsy6.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Ddk2f2DM.js → Security-CHtYiKY9.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CCuK-VOo.js → Services-u16lvBJN.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DxCpHzeM.js → Skeleton-CGbizyDj.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-t2oTDgcv.js → Skills-BZMQcAa7.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Df63M9_R.js → Sla-Bm-CycVi.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-l7ox6KuX.js → SpeechSettings-BudpA5kr.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-COd_tgPp.js → SyncSettings-B8q1C1eC.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CzDCcrik.js → Tasks-SU1pBWc-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-D-vfxTGA.js → Templates-CyOfJzmt.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BlpT_Gxc.js → Tenant-xqWSm2GP.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-D5rq52Hg.js → Terminal-D1Ttgjsi.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DdoMndkK.js → TimelineRenderer-Da2lYs5D.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BhgBsI6g.js → Tokens-B34-SduO.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DwwzoikZ.js → Trigger-C8zyYm4R.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-HkTpzXFb.js → Trust-D1Pvqems.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-D9nk6Ehe.js → UkeySign-Do3VVnAy.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BKk0rfoy.js → VideoEditing-Dz7egVIA.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-IVWIU_Lq.js → Wallet-Dcuw-OxR.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CFTCpfsG.js → WebAuthn-DPlw-QsB.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-Dnf4Obif.js → WorkflowEditor-h4OsnU5q.js} +1 -1
- package/src/assets/web-panel/assets/{chat-D2snERdj.js → chat-eO9wEwrW.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DKEjK1N1.js → colors-34lSmOXw.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BEDLwQdt.js → compact-item-CK4kEWJ4.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DA9iW1a0.js → createContext-E0A57xed.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CF3xafX2.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-5l2xEcY9.js → hasIn-BD9ZMGAl.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7sKbM5s.js → index--N0u6kTG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DU2TXJu-.js → index-8qMA1DWQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CxEtQYdi.js → index-B0rkHxKK.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7_W_rYd.js → index-B_G_ElKO.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4QYBliR.js → index-BbX_Dted.js} +1 -1
- package/src/assets/web-panel/assets/{index-DLoalqRX.js → index-Bjs90Eh9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dbkghqn_.js → index-BmNbU2Z6.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgfImyZI.js → index-Brqc9Emx.js} +1 -1
- package/src/assets/web-panel/assets/{index-5pW4t1iD.js → index-BsGmo6v1.js} +1 -1
- package/src/assets/web-panel/assets/index-C907rPwd.js +1 -0
- package/src/assets/web-panel/assets/{index-CRPrHE7h.js → index-CT1_eRL-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvV03Ayk.js → index-CZBaqe3d.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7z4zB_a.js → index-CZkFMeWL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmruhzFi.js → index-Ch175NUw.js} +1 -1
- package/src/assets/web-panel/assets/{index-e-e40id6.js → index-CsrKJJcX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGlotREA.js → index-CuSau5ya.js} +1 -1
- package/src/assets/web-panel/assets/{index-CjWltbi_.js → index-Cx3Jn1r0.js} +1 -1
- package/src/assets/web-panel/assets/{index-fccZmC_h.js → index-Cz3unNfL.js} +1 -1
- package/src/assets/web-panel/assets/{index-NJieh3T1.js → index-D4bB_x_v.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkMKuqUs.js → index-DCn-ECvK.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZ5lY2zx.js → index-DFLE2qO9.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_eCoZgW.js → index-DGRxt5wj.js} +1 -1
- package/src/assets/web-panel/assets/{index-7P1b8HZo.js → index-DONOUtlz.js} +1 -1
- package/src/assets/web-panel/assets/{index-yYEQXYaU.js → index-DP-UZAQN.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dbd7eEex.js → index-Da5fmOHB.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bqc8tdIF.js → index-DcE1BspQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DXet1xol.js → index-DcbgVLOH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BO9dQjpt.js → index-DeXNCyQJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CX1x4AFn.js → index-DiT_REby.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bq5P3piS.js → index-DpfisY1g.js} +1 -1
- package/src/assets/web-panel/assets/{index-Csfb-7hw.js → index-DrOalGut.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpqtbXdE.js → index-DrSAAuJj.js} +1 -1
- package/src/assets/web-panel/assets/{index-B00GeekA.js → index-DrsDA_md.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dg2a9owQ.js → index-DvKcroix.js} +1 -1
- package/src/assets/web-panel/assets/{index-fgYk0QXO.js → index-DzXjP44T.js} +1 -1
- package/src/assets/web-panel/assets/{index-Td8ZwyoC.js → index-MIg5Z8qe.js} +1 -1
- package/src/assets/web-panel/assets/{index-DqwPFbOb.js → index-o8pYehRq.js} +3 -3
- package/src/assets/web-panel/assets/index-wueRSK2d.js +1 -0
- package/src/assets/web-panel/assets/{index-CbTPvp2_.js → index-yAGBu2E0.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CPrcB1TC.js → initDefaultProps-DMTkAomF.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BQ7mWRh_.js → motion-Cnti1F5g.js} +1 -1
- package/src/assets/web-panel/assets/{move-CzOEq99p.js → move-CGxmHUga.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BlQI5jrw.js → mtc-parser-CuwwLoq7.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DlW4gxmv.js → omit-BpdlTkPM.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BBYms5nP.js → pickAttrs-D7nWrThp.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-az2MPJ_7.js → placementArrow-Bhx0TV8F.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-D1-ibEol.js → responsiveObserve-CZ1KesZh.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DHGqa4GA.js → slide-CnldF8J6.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CYGDo6lD.js → statusUtils-CZfo_GOA.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Cl2EM9K6.js → styleChecker-B2QCcAv5.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DdYhzuv1.js → useFlexGapSupport-DzzjIS4J.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DWumPuBW.js → useFs-B_atg9M0.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DxAO-Aqi.js → usePersonalDataHub-DhZs3QYN.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CplsQf_N.js → vnode-DcePTYAm.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-Cw5HXNSG.js → zoom-BYmlnjRk.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/serve.js +31 -0
- package/src/data/changelog.json +13 -0
- package/src/index.js +11 -3
- package/src/lazy-dispatch.js +9 -1
- package/src/lib/app-server/websocket-transport.js +21 -0
- package/src/lib/evolution/evolution-artifact-ports.js +9 -0
- package/src/lib/evolution/evolution-deployment-loader.js +208 -0
- package/src/lib/evolution/evolution-release-train-ledger-adapter.js +335 -0
- package/src/lib/evolution/evolution-release-train.js +538 -0
- package/src/lib/evolution/evolution-workbench-projection.js +20 -1
- package/src/lib/evolution/evolvable-artifact-ledger-adapter.js +688 -0
- package/src/lib/evolution/governed-knowledge-artifact-lifecycle.js +364 -0
- package/src/lib/evolution/governed-knowledge-merge-ledger-executor.js +29 -2
- package/src/lib/evolution/governed-knowledge-merge-publisher-authority.js +31 -3
- package/src/lib/evolution/governed-knowledge-sync-ledger-adapter.js +92 -6
- package/src/lib/evolution/governed-knowledge-sync-merge-publisher.js +57 -4
- package/src/lib/evolution/governed-knowledge-sync.js +317 -7
- package/src/lib/evolution/skill-writer-inventory-manifest.js +7 -6
- package/src/lib/evolution/statistical-progressive-canary.js +683 -0
- package/src/lib/evolution/wikiskill-benchmark.js +510 -0
- package/src/lib/session-host-lease.js +11 -16
- package/src/runtime/agent-core.js +61 -7
- package/src/runtime/headless-stream.js +3 -0
- package/src/assets/web-panel/assets/Notes-CgU9DDo9.js +0 -7
- package/src/assets/web-panel/assets/OrderTableRenderer-CwHc15Zs.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DyhgpXPf.js +0 -1
- package/src/assets/web-panel/assets/index-Ck1jhFE0.js +0 -1
- package/src/assets/web-panel/assets/index-c8jJEHVs.js +0 -1
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const EVOLUTION_PLAN_SCHEMA = "chainlesschain.evolution-plan/v1";
|
|
4
|
+
export const EVOLUTION_TRAIN_RECEIPT_SCHEMA =
|
|
5
|
+
"chainlesschain.evolution-train-stage-receipt/v1";
|
|
6
|
+
export const EVOLUTION_TRAIN_STATE_SCHEMA =
|
|
7
|
+
"chainlesschain.evolution-release-train-state/v1";
|
|
8
|
+
|
|
9
|
+
export const EVOLUTION_RELEASE_TRAIN_STAGES = Object.freeze([
|
|
10
|
+
"wiki-maintain",
|
|
11
|
+
"propose",
|
|
12
|
+
"candidate",
|
|
13
|
+
"eval",
|
|
14
|
+
"review",
|
|
15
|
+
"pilot",
|
|
16
|
+
"promotion",
|
|
17
|
+
"wiki-impact",
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
21
|
+
const COMMIT = /^[a-f0-9]{40}$/u;
|
|
22
|
+
const NOISE_FAILURES = new Set([
|
|
23
|
+
"provider",
|
|
24
|
+
"mcp",
|
|
25
|
+
"sandbox",
|
|
26
|
+
"permission",
|
|
27
|
+
"policy",
|
|
28
|
+
"infrastructure",
|
|
29
|
+
]);
|
|
30
|
+
const TRIGGER_KINDS = new Set([
|
|
31
|
+
"procedure-failure",
|
|
32
|
+
"success-pattern",
|
|
33
|
+
"user-correction",
|
|
34
|
+
]);
|
|
35
|
+
const STORE_BRAND = new WeakSet();
|
|
36
|
+
|
|
37
|
+
function canonical(value) {
|
|
38
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
39
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
40
|
+
return `{${Object.keys(value)
|
|
41
|
+
.sort()
|
|
42
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
|
|
43
|
+
.join(",")}}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function hash(domain, value) {
|
|
47
|
+
return `sha256:${createHash("sha256")
|
|
48
|
+
.update(domain)
|
|
49
|
+
.update("\0")
|
|
50
|
+
.update(canonical(value))
|
|
51
|
+
.digest("hex")}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function exactKeys(value, keys, name) {
|
|
55
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
56
|
+
throw new TypeError(`${name} must be an object`);
|
|
57
|
+
if (canonical(Object.keys(value).sort()) !== canonical([...keys].sort()))
|
|
58
|
+
throw new TypeError(`${name} has unexpected or missing fields`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function text(value, name) {
|
|
62
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
63
|
+
throw new TypeError(`${name} must be a non-empty string`);
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function sha(value, name) {
|
|
68
|
+
if (!DIGEST.test(value ?? ""))
|
|
69
|
+
throw new TypeError(`${name} must be a sha256 digest`);
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function bounded(value, name) {
|
|
74
|
+
const number = Number(value);
|
|
75
|
+
if (!Number.isFinite(number) || number < 0)
|
|
76
|
+
throw new TypeError(`${name} must be a finite non-negative number`);
|
|
77
|
+
return number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function normalizeBudget(value, name = "rootBudget") {
|
|
81
|
+
exactKeys(value, ["tokens", "cost", "timeMs", "turns"], name);
|
|
82
|
+
return Object.freeze({
|
|
83
|
+
tokens: bounded(value.tokens, `${name}.tokens`),
|
|
84
|
+
cost: bounded(value.cost, `${name}.cost`),
|
|
85
|
+
timeMs: bounded(value.timeMs, `${name}.timeMs`),
|
|
86
|
+
turns: bounded(value.turns, `${name}.turns`),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function validatePlan(plan) {
|
|
91
|
+
if (!plan || plan.schema !== EVOLUTION_PLAN_SCHEMA)
|
|
92
|
+
throw new TypeError("a canonical EvolutionPlan is required");
|
|
93
|
+
const core = Object.fromEntries(
|
|
94
|
+
Object.entries(plan).filter(([key]) => key !== "planDigest"),
|
|
95
|
+
);
|
|
96
|
+
if (hash(EVOLUTION_PLAN_SCHEMA, core) !== plan.planDigest)
|
|
97
|
+
throw new Error("EvolutionPlan digest mismatch");
|
|
98
|
+
return plan;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function createEvolutionPlan(input = {}) {
|
|
102
|
+
exactKeys(
|
|
103
|
+
input,
|
|
104
|
+
[
|
|
105
|
+
"tenantId",
|
|
106
|
+
"skillId",
|
|
107
|
+
"gitCommit",
|
|
108
|
+
"baselineReleaseDigest",
|
|
109
|
+
"candidateDigest",
|
|
110
|
+
"wikiRevisionDigest",
|
|
111
|
+
"evalSuiteDigest",
|
|
112
|
+
"targetMatrixDigest",
|
|
113
|
+
"riskTier",
|
|
114
|
+
"rolloutPolicyDigest",
|
|
115
|
+
"metricPolicyDigest",
|
|
116
|
+
"permissionManifestDigest",
|
|
117
|
+
"policyDigest",
|
|
118
|
+
"requestedCapabilityDigests",
|
|
119
|
+
"baselineCapabilityDigests",
|
|
120
|
+
"rootBudget",
|
|
121
|
+
"expiresAt",
|
|
122
|
+
"triggerDigest",
|
|
123
|
+
],
|
|
124
|
+
"EvolutionPlan",
|
|
125
|
+
);
|
|
126
|
+
const requestedCapabilityDigests = input.requestedCapabilityDigests.map(
|
|
127
|
+
(value, index) => sha(value, `requestedCapabilityDigests ${index}`),
|
|
128
|
+
);
|
|
129
|
+
const baselineCapabilityDigests = input.baselineCapabilityDigests.map(
|
|
130
|
+
(value, index) => sha(value, `baselineCapabilityDigests ${index}`),
|
|
131
|
+
);
|
|
132
|
+
if (
|
|
133
|
+
new Set(requestedCapabilityDigests).size !==
|
|
134
|
+
requestedCapabilityDigests.length ||
|
|
135
|
+
new Set(baselineCapabilityDigests).size !== baselineCapabilityDigests.length
|
|
136
|
+
)
|
|
137
|
+
throw new TypeError("EvolutionPlan capability digests must be unique");
|
|
138
|
+
const baseline = new Set(baselineCapabilityDigests);
|
|
139
|
+
if (requestedCapabilityDigests.some((value) => !baseline.has(value)))
|
|
140
|
+
throw new Error("EvolutionPlan cannot increase Skill capabilities");
|
|
141
|
+
const expiresAt = Date.parse(input.expiresAt);
|
|
142
|
+
if (!Number.isFinite(expiresAt))
|
|
143
|
+
throw new TypeError("expiresAt must be an ISO timestamp");
|
|
144
|
+
const gitCommit = text(input.gitCommit, "gitCommit");
|
|
145
|
+
if (!COMMIT.test(gitCommit))
|
|
146
|
+
throw new TypeError("gitCommit must be a full commit SHA");
|
|
147
|
+
const core = {
|
|
148
|
+
schema: EVOLUTION_PLAN_SCHEMA,
|
|
149
|
+
tenantId: text(input.tenantId, "tenantId"),
|
|
150
|
+
skillId: text(input.skillId, "skillId"),
|
|
151
|
+
gitCommit,
|
|
152
|
+
baselineReleaseDigest: sha(
|
|
153
|
+
input.baselineReleaseDigest,
|
|
154
|
+
"baselineReleaseDigest",
|
|
155
|
+
),
|
|
156
|
+
candidateDigest: sha(input.candidateDigest, "candidateDigest"),
|
|
157
|
+
wikiRevisionDigest: sha(input.wikiRevisionDigest, "wikiRevisionDigest"),
|
|
158
|
+
evalSuiteDigest: sha(input.evalSuiteDigest, "evalSuiteDigest"),
|
|
159
|
+
targetMatrixDigest: sha(input.targetMatrixDigest, "targetMatrixDigest"),
|
|
160
|
+
riskTier: text(input.riskTier, "riskTier"),
|
|
161
|
+
rolloutPolicyDigest: sha(input.rolloutPolicyDigest, "rolloutPolicyDigest"),
|
|
162
|
+
metricPolicyDigest: sha(input.metricPolicyDigest, "metricPolicyDigest"),
|
|
163
|
+
permissionManifestDigest: sha(
|
|
164
|
+
input.permissionManifestDigest,
|
|
165
|
+
"permissionManifestDigest",
|
|
166
|
+
),
|
|
167
|
+
policyDigest: sha(input.policyDigest, "policyDigest"),
|
|
168
|
+
requestedCapabilityDigests: Object.freeze(requestedCapabilityDigests),
|
|
169
|
+
baselineCapabilityDigests: Object.freeze(baselineCapabilityDigests),
|
|
170
|
+
rootBudget: normalizeBudget(input.rootBudget),
|
|
171
|
+
expiresAt: new Date(expiresAt).toISOString(),
|
|
172
|
+
triggerDigest: sha(input.triggerDigest, "triggerDigest"),
|
|
173
|
+
};
|
|
174
|
+
return Object.freeze({
|
|
175
|
+
...core,
|
|
176
|
+
planDigest: hash(EVOLUTION_PLAN_SCHEMA, core),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function assessEvolutionCandidateTrigger({
|
|
181
|
+
events,
|
|
182
|
+
minimums = { procedureFailure: 3, successPattern: 3, userCorrection: 1 },
|
|
183
|
+
} = {}) {
|
|
184
|
+
exactKeys(
|
|
185
|
+
minimums,
|
|
186
|
+
["procedureFailure", "successPattern", "userCorrection"],
|
|
187
|
+
"trigger minimums",
|
|
188
|
+
);
|
|
189
|
+
const thresholds = {
|
|
190
|
+
"procedure-failure": bounded(
|
|
191
|
+
minimums.procedureFailure,
|
|
192
|
+
"procedureFailure minimum",
|
|
193
|
+
),
|
|
194
|
+
"success-pattern": bounded(
|
|
195
|
+
minimums.successPattern,
|
|
196
|
+
"successPattern minimum",
|
|
197
|
+
),
|
|
198
|
+
"user-correction": bounded(
|
|
199
|
+
minimums.userCorrection,
|
|
200
|
+
"userCorrection minimum",
|
|
201
|
+
),
|
|
202
|
+
};
|
|
203
|
+
if (
|
|
204
|
+
Object.values(thresholds).some(
|
|
205
|
+
(value) => !Number.isSafeInteger(value) || value < 1,
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
throw new TypeError("trigger minimums must be positive safe integers");
|
|
209
|
+
if (!Array.isArray(events) || events.length === 0)
|
|
210
|
+
return Object.freeze({
|
|
211
|
+
eligible: false,
|
|
212
|
+
reason: "needs-evidence",
|
|
213
|
+
skillId: null,
|
|
214
|
+
evidenceDigest: null,
|
|
215
|
+
evidenceIds: Object.freeze([]),
|
|
216
|
+
});
|
|
217
|
+
const ids = new Set();
|
|
218
|
+
const normalized = events.map((event, index) => {
|
|
219
|
+
exactKeys(
|
|
220
|
+
event,
|
|
221
|
+
[
|
|
222
|
+
"eventId",
|
|
223
|
+
"skillId",
|
|
224
|
+
"kind",
|
|
225
|
+
"failureClass",
|
|
226
|
+
"attributionReceiptDigest",
|
|
227
|
+
],
|
|
228
|
+
`trigger event ${index}`,
|
|
229
|
+
);
|
|
230
|
+
const eventId = text(event.eventId, `trigger event ${index} eventId`);
|
|
231
|
+
if (ids.has(eventId))
|
|
232
|
+
throw new TypeError(`duplicate trigger event: ${eventId}`);
|
|
233
|
+
ids.add(eventId);
|
|
234
|
+
if (!TRIGGER_KINDS.has(event.kind))
|
|
235
|
+
throw new TypeError(`trigger event ${index} kind is invalid`);
|
|
236
|
+
return Object.freeze({
|
|
237
|
+
eventId,
|
|
238
|
+
skillId: text(event.skillId, `trigger event ${index} skillId`),
|
|
239
|
+
kind: event.kind,
|
|
240
|
+
failureClass:
|
|
241
|
+
event.failureClass === null
|
|
242
|
+
? null
|
|
243
|
+
: text(event.failureClass, `trigger event ${index} failureClass`),
|
|
244
|
+
attributionReceiptDigest: sha(
|
|
245
|
+
event.attributionReceiptDigest,
|
|
246
|
+
`trigger event ${index} attributionReceiptDigest`,
|
|
247
|
+
),
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
const skillIds = new Set(normalized.map((event) => event.skillId));
|
|
251
|
+
if (skillIds.size !== 1)
|
|
252
|
+
return Object.freeze({
|
|
253
|
+
eligible: false,
|
|
254
|
+
reason: "multiple-skills",
|
|
255
|
+
skillId: null,
|
|
256
|
+
evidenceDigest: null,
|
|
257
|
+
evidenceIds: Object.freeze([]),
|
|
258
|
+
});
|
|
259
|
+
const attributable = normalized.filter(
|
|
260
|
+
(event) =>
|
|
261
|
+
event.kind !== "procedure-failure" ||
|
|
262
|
+
!NOISE_FAILURES.has(event.failureClass),
|
|
263
|
+
);
|
|
264
|
+
const acceptedKind = [...TRIGGER_KINDS].find(
|
|
265
|
+
(kind) =>
|
|
266
|
+
attributable.filter((event) => event.kind === kind).length >=
|
|
267
|
+
thresholds[kind],
|
|
268
|
+
);
|
|
269
|
+
if (!acceptedKind)
|
|
270
|
+
return Object.freeze({
|
|
271
|
+
eligible: false,
|
|
272
|
+
reason: "needs-evidence",
|
|
273
|
+
skillId: [...skillIds][0],
|
|
274
|
+
evidenceDigest: null,
|
|
275
|
+
evidenceIds: Object.freeze([]),
|
|
276
|
+
});
|
|
277
|
+
const accepted = attributable
|
|
278
|
+
.filter((event) => event.kind === acceptedKind)
|
|
279
|
+
.sort((left, right) => left.eventId.localeCompare(right.eventId));
|
|
280
|
+
const evidenceCore = accepted.map((event) => ({
|
|
281
|
+
eventId: event.eventId,
|
|
282
|
+
attributionReceiptDigest: event.attributionReceiptDigest,
|
|
283
|
+
}));
|
|
284
|
+
return Object.freeze({
|
|
285
|
+
eligible: true,
|
|
286
|
+
reason: acceptedKind,
|
|
287
|
+
skillId: [...skillIds][0],
|
|
288
|
+
evidenceDigest: hash(EVOLUTION_PLAN_SCHEMA, evidenceCore),
|
|
289
|
+
evidenceIds: Object.freeze(accepted.map((event) => event.eventId)),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function createEvolutionTrainStageReceipt(input = {}) {
|
|
294
|
+
exactKeys(
|
|
295
|
+
input,
|
|
296
|
+
[
|
|
297
|
+
"planDigest",
|
|
298
|
+
"stage",
|
|
299
|
+
"operationKey",
|
|
300
|
+
"inputDigest",
|
|
301
|
+
"outputDigest",
|
|
302
|
+
"accepted",
|
|
303
|
+
"durable",
|
|
304
|
+
"usage",
|
|
305
|
+
],
|
|
306
|
+
"stage receipt",
|
|
307
|
+
);
|
|
308
|
+
if (!EVOLUTION_RELEASE_TRAIN_STAGES.includes(input.stage))
|
|
309
|
+
throw new TypeError("stage receipt stage is invalid");
|
|
310
|
+
if (input.accepted !== true || input.durable !== true)
|
|
311
|
+
throw new Error("stage receipt must be accepted and durable");
|
|
312
|
+
const core = {
|
|
313
|
+
schema: EVOLUTION_TRAIN_RECEIPT_SCHEMA,
|
|
314
|
+
planDigest: sha(input.planDigest, "stage receipt planDigest"),
|
|
315
|
+
stage: input.stage,
|
|
316
|
+
operationKey: sha(input.operationKey, "stage receipt operationKey"),
|
|
317
|
+
inputDigest: sha(input.inputDigest, "stage receipt inputDigest"),
|
|
318
|
+
outputDigest: sha(input.outputDigest, "stage receipt outputDigest"),
|
|
319
|
+
accepted: true,
|
|
320
|
+
durable: true,
|
|
321
|
+
usage: normalizeBudget(input.usage, "stage receipt usage"),
|
|
322
|
+
};
|
|
323
|
+
return Object.freeze({
|
|
324
|
+
...core,
|
|
325
|
+
receiptDigest: hash(EVOLUTION_TRAIN_RECEIPT_SCHEMA, core),
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function verifyEvolutionTrainStageReceipt(receipt) {
|
|
330
|
+
if (!receipt || receipt.schema !== EVOLUTION_TRAIN_RECEIPT_SCHEMA)
|
|
331
|
+
throw new Error("value is not a canonical release train stage receipt");
|
|
332
|
+
const core = Object.fromEntries(
|
|
333
|
+
Object.entries(receipt).filter(([key]) => key !== "receiptDigest"),
|
|
334
|
+
);
|
|
335
|
+
if (hash(EVOLUTION_TRAIN_RECEIPT_SCHEMA, core) !== receipt.receiptDigest)
|
|
336
|
+
throw new Error("release train stage receipt digest mismatch");
|
|
337
|
+
if (!EVOLUTION_RELEASE_TRAIN_STAGES.includes(receipt.stage))
|
|
338
|
+
throw new Error("release train stage receipt stage is invalid");
|
|
339
|
+
if (receipt.accepted !== true || receipt.durable !== true)
|
|
340
|
+
throw new Error("release train stage receipt is not accepted and durable");
|
|
341
|
+
normalizeBudget(receipt.usage, "release train stage receipt usage");
|
|
342
|
+
return receipt;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function validateReceipt(receipt, expected) {
|
|
346
|
+
try {
|
|
347
|
+
verifyEvolutionTrainStageReceipt(receipt);
|
|
348
|
+
} catch (error) {
|
|
349
|
+
throw new Error(`${expected.stage} did not return a canonical receipt`, {
|
|
350
|
+
cause: error,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
for (const field of ["planDigest", "stage", "operationKey", "inputDigest"]) {
|
|
354
|
+
if (receipt[field] !== expected[field])
|
|
355
|
+
throw new Error(`${expected.stage} receipt ${field} mismatch`);
|
|
356
|
+
}
|
|
357
|
+
if (receipt.accepted !== true || receipt.durable !== true)
|
|
358
|
+
throw new Error(`${expected.stage} receipt is not accepted and durable`);
|
|
359
|
+
return receipt;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function makeState({ planDigest, receipts }) {
|
|
363
|
+
const usage = receipts.reduce(
|
|
364
|
+
(total, receipt) => ({
|
|
365
|
+
tokens: total.tokens + receipt.usage.tokens,
|
|
366
|
+
cost: total.cost + receipt.usage.cost,
|
|
367
|
+
timeMs: total.timeMs + receipt.usage.timeMs,
|
|
368
|
+
turns: total.turns + receipt.usage.turns,
|
|
369
|
+
}),
|
|
370
|
+
{ tokens: 0, cost: 0, timeMs: 0, turns: 0 },
|
|
371
|
+
);
|
|
372
|
+
const core = {
|
|
373
|
+
schema: EVOLUTION_TRAIN_STATE_SCHEMA,
|
|
374
|
+
planDigest,
|
|
375
|
+
stageIndex: receipts.length,
|
|
376
|
+
status:
|
|
377
|
+
receipts.length === EVOLUTION_RELEASE_TRAIN_STAGES.length
|
|
378
|
+
? "complete"
|
|
379
|
+
: "running",
|
|
380
|
+
receiptDigests: Object.freeze(
|
|
381
|
+
receipts.map((receipt) => receipt.receiptDigest),
|
|
382
|
+
),
|
|
383
|
+
outputDigests: Object.freeze(
|
|
384
|
+
receipts.map((receipt) => receipt.outputDigest),
|
|
385
|
+
),
|
|
386
|
+
usage: Object.freeze(usage),
|
|
387
|
+
};
|
|
388
|
+
return Object.freeze({
|
|
389
|
+
...core,
|
|
390
|
+
stateDigest: hash(EVOLUTION_TRAIN_STATE_SCHEMA, core),
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function verifyEvolutionReleaseTrainState(
|
|
395
|
+
state,
|
|
396
|
+
{ planDigest = null, allowNull = false } = {},
|
|
397
|
+
) {
|
|
398
|
+
if (state === null && allowNull) return null;
|
|
399
|
+
if (state === null) throw new Error("release train state is invalid");
|
|
400
|
+
if (
|
|
401
|
+
!state ||
|
|
402
|
+
state.schema !== EVOLUTION_TRAIN_STATE_SCHEMA ||
|
|
403
|
+
!DIGEST.test(state.planDigest ?? "") ||
|
|
404
|
+
(planDigest !== null && state.planDigest !== planDigest)
|
|
405
|
+
)
|
|
406
|
+
throw new Error("release train state is invalid");
|
|
407
|
+
const core = Object.fromEntries(
|
|
408
|
+
Object.entries(state).filter(([key]) => key !== "stateDigest"),
|
|
409
|
+
);
|
|
410
|
+
if (hash(EVOLUTION_TRAIN_STATE_SCHEMA, core) !== state.stateDigest)
|
|
411
|
+
throw new Error("release train state digest mismatch");
|
|
412
|
+
if (
|
|
413
|
+
!Number.isSafeInteger(state.stageIndex) ||
|
|
414
|
+
state.stageIndex < 0 ||
|
|
415
|
+
state.stageIndex > EVOLUTION_RELEASE_TRAIN_STAGES.length ||
|
|
416
|
+
state.receiptDigests.length !== state.stageIndex ||
|
|
417
|
+
state.outputDigests.length !== state.stageIndex
|
|
418
|
+
)
|
|
419
|
+
throw new Error("release train state progression is invalid");
|
|
420
|
+
normalizeBudget(state.usage, "release train state usage");
|
|
421
|
+
return state;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function assertBudget(plan, usage) {
|
|
425
|
+
for (const field of ["tokens", "cost", "timeMs", "turns"]) {
|
|
426
|
+
if (usage[field] > plan.rootBudget[field])
|
|
427
|
+
throw new Error(`EvolutionPlan root budget exceeded: ${field}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export function createEvolutionReleaseTrainStateStore({
|
|
432
|
+
load,
|
|
433
|
+
loadReceipt,
|
|
434
|
+
compareAndSet,
|
|
435
|
+
} = {}) {
|
|
436
|
+
if (
|
|
437
|
+
typeof load !== "function" ||
|
|
438
|
+
typeof loadReceipt !== "function" ||
|
|
439
|
+
typeof compareAndSet !== "function"
|
|
440
|
+
)
|
|
441
|
+
throw new TypeError(
|
|
442
|
+
"release train state store requires load, loadReceipt and compareAndSet",
|
|
443
|
+
);
|
|
444
|
+
const store = Object.freeze({ load, loadReceipt, compareAndSet });
|
|
445
|
+
STORE_BRAND.add(store);
|
|
446
|
+
return store;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export function createEvolutionReleaseTrain({
|
|
450
|
+
plan,
|
|
451
|
+
stateStore,
|
|
452
|
+
stages,
|
|
453
|
+
clock = Date.now,
|
|
454
|
+
} = {}) {
|
|
455
|
+
validatePlan(plan);
|
|
456
|
+
if (!STORE_BRAND.has(stateStore))
|
|
457
|
+
throw new TypeError("a branded release train state store is required");
|
|
458
|
+
exactKeys(stages, EVOLUTION_RELEASE_TRAIN_STAGES, "release train stages");
|
|
459
|
+
for (const stage of EVOLUTION_RELEASE_TRAIN_STAGES) {
|
|
460
|
+
if (typeof stages[stage] !== "function")
|
|
461
|
+
throw new TypeError(`release train stage ${stage} must be a function`);
|
|
462
|
+
}
|
|
463
|
+
if (typeof clock !== "function")
|
|
464
|
+
throw new TypeError("clock must be a function");
|
|
465
|
+
|
|
466
|
+
return Object.freeze({
|
|
467
|
+
planDigest: plan.planDigest,
|
|
468
|
+
async run() {
|
|
469
|
+
if (Number(clock()) >= Date.parse(plan.expiresAt))
|
|
470
|
+
throw new Error("EvolutionPlan has expired");
|
|
471
|
+
let state = verifyEvolutionReleaseTrainState(
|
|
472
|
+
await stateStore.load(plan.planDigest),
|
|
473
|
+
{ planDigest: plan.planDigest, allowNull: true },
|
|
474
|
+
);
|
|
475
|
+
const receipts = [];
|
|
476
|
+
if (state) {
|
|
477
|
+
for (let index = 0; index < state.stageIndex; index += 1) {
|
|
478
|
+
const receipt = await stateStore.loadReceipt(
|
|
479
|
+
state.receiptDigests[index],
|
|
480
|
+
);
|
|
481
|
+
receipts.push(
|
|
482
|
+
validateReceipt(receipt, {
|
|
483
|
+
planDigest: plan.planDigest,
|
|
484
|
+
stage: EVOLUTION_RELEASE_TRAIN_STAGES[index],
|
|
485
|
+
operationKey: hash(EVOLUTION_PLAN_SCHEMA, {
|
|
486
|
+
planDigest: plan.planDigest,
|
|
487
|
+
stage: EVOLUTION_RELEASE_TRAIN_STAGES[index],
|
|
488
|
+
}),
|
|
489
|
+
inputDigest:
|
|
490
|
+
index === 0
|
|
491
|
+
? plan.planDigest
|
|
492
|
+
: receipts[index - 1].outputDigest,
|
|
493
|
+
}),
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
assertBudget(plan, state.usage);
|
|
497
|
+
}
|
|
498
|
+
while (receipts.length < EVOLUTION_RELEASE_TRAIN_STAGES.length) {
|
|
499
|
+
const stage = EVOLUTION_RELEASE_TRAIN_STAGES[receipts.length];
|
|
500
|
+
const operationKey = hash(EVOLUTION_PLAN_SCHEMA, {
|
|
501
|
+
planDigest: plan.planDigest,
|
|
502
|
+
stage,
|
|
503
|
+
});
|
|
504
|
+
const inputDigest =
|
|
505
|
+
receipts.length === 0
|
|
506
|
+
? plan.planDigest
|
|
507
|
+
: receipts[receipts.length - 1].outputDigest;
|
|
508
|
+
const receipt = validateReceipt(
|
|
509
|
+
await stages[stage](
|
|
510
|
+
Object.freeze({ plan, stage, operationKey, inputDigest }),
|
|
511
|
+
),
|
|
512
|
+
{ planDigest: plan.planDigest, stage, operationKey, inputDigest },
|
|
513
|
+
);
|
|
514
|
+
const nextReceipts = [...receipts, receipt];
|
|
515
|
+
const nextState = makeState({
|
|
516
|
+
planDigest: plan.planDigest,
|
|
517
|
+
receipts: nextReceipts,
|
|
518
|
+
});
|
|
519
|
+
assertBudget(plan, nextState.usage);
|
|
520
|
+
const acknowledgement = await stateStore.compareAndSet({
|
|
521
|
+
planDigest: plan.planDigest,
|
|
522
|
+
expectedStateDigest: state?.stateDigest ?? null,
|
|
523
|
+
receipt,
|
|
524
|
+
nextState,
|
|
525
|
+
});
|
|
526
|
+
if (
|
|
527
|
+
acknowledgement?.durable !== true ||
|
|
528
|
+
acknowledgement?.stateDigest !== nextState.stateDigest ||
|
|
529
|
+
acknowledgement?.receiptDigest !== receipt.receiptDigest
|
|
530
|
+
)
|
|
531
|
+
throw new Error(`${stage} state was not durably acknowledged`);
|
|
532
|
+
receipts.push(receipt);
|
|
533
|
+
state = nextState;
|
|
534
|
+
}
|
|
535
|
+
return Object.freeze({ state, receipts: Object.freeze(receipts) });
|
|
536
|
+
},
|
|
537
|
+
});
|
|
538
|
+
}
|
|
@@ -211,8 +211,11 @@ function normalizePilot(value, descriptor) {
|
|
|
211
211
|
) ||
|
|
212
212
|
!Number.isSafeInteger(value.revision) ||
|
|
213
213
|
value.revision < 0 ||
|
|
214
|
+
typeof value.killSwitch !== "boolean" ||
|
|
214
215
|
typeof value.reconciliationRequired !== "boolean" ||
|
|
215
|
-
value.cohort?.id
|
|
216
|
+
typeof value.cohort?.id !== "string" ||
|
|
217
|
+
value.cohort.id.length < 1 ||
|
|
218
|
+
value.cohort.id.length > 256
|
|
216
219
|
) {
|
|
217
220
|
throw new Error("pilot source result is invalid");
|
|
218
221
|
}
|
|
@@ -674,8 +677,24 @@ export function filterEvolutionWorkbenchProjection(
|
|
|
674
677
|
.toLowerCase();
|
|
675
678
|
return searchable.includes(needle);
|
|
676
679
|
});
|
|
680
|
+
const governance = {
|
|
681
|
+
runStatus: projection.run.status,
|
|
682
|
+
activeReleaseId: projection.run.activeReleaseId,
|
|
683
|
+
lastKnownGoodReleaseId: projection.run.lastKnownGoodReleaseId,
|
|
684
|
+
conflictCount: projection.summary.conflictCount,
|
|
685
|
+
pilot:
|
|
686
|
+
projection.pilot === null
|
|
687
|
+
? null
|
|
688
|
+
: {
|
|
689
|
+
stage: projection.pilot.stage,
|
|
690
|
+
revision: projection.pilot.revision,
|
|
691
|
+
killSwitch: projection.pilot.killSwitch,
|
|
692
|
+
reconciliationRequired: projection.pilot.reconciliationRequired,
|
|
693
|
+
},
|
|
694
|
+
};
|
|
677
695
|
return deepFreeze({
|
|
678
696
|
projectionDigest: projection.projectionDigest,
|
|
697
|
+
governance: clone(governance),
|
|
679
698
|
total: matches.length,
|
|
680
699
|
offset,
|
|
681
700
|
limit,
|