chainlesschain 0.166.38 → 0.166.39
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 +6 -1
- package/scripts/governed-learning-local-attestor-service.mjs +222 -0
- package/scripts/governed-learning-local-attestor-trust-approval-service.mjs +338 -0
- package/scripts/governed-learning-local-attestor-trust-operations-service.mjs +575 -0
- package/scripts/governed-learning-windows-secure-pipe-broker.ps1 +375 -0
- package/src/assets/web-panel/assets/{AIOps-DBbcVPqB.js → AIOps-qWUdiFr5.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-D5vETPOk.js → ActionButton-B_f7go3E.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-WVhas3Fw.js → Analytics-hSGFlSkB.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CSTRI93Q.js → AppLayout-CCYGgj71.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BZT8FOoE.js → Artifacts-BdS1H9gq.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CT2-Tsby.js → Audit-Cck9IebC.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BWA5LNBe.js → BackgroundAgents-DZzoM_QS.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-B-zEbldA.js → Backup-D8H_QtGu.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BAEwtdNT.js → BaseInput-CTsNF_pu.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BT1-y6t1.js → Chat-CcEiTYY8.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BVijMHeO.js → ChatBubbleRenderer-9BY1DPuc.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-B8IJYZsD.js → Checkbox-BPB9NNJx.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DEt453nL.js → Codegen-CKKMf7jD.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DWuVSmZv.js → Col-DpSSB4Uu.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CcoNuU33.js → Community-CfR6ikoH.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BKtuIYWA.js → Compact-CqnkGGjK.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-bHzxGq3P.js → Compliance-9IzhdVQb.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BY2Kb15t.js → Cowork-eXsgH33N.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DC-j0jvp.js → Cron-DvkzHFRi.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CzEuAHQz.js → Crosschain--az9d9f7.js} +1 -1
- package/src/assets/web-panel/assets/{DID-7QUNq6Fz.js → DID-_Eb4kpnJ.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CT6RiEFl.js → Dashboard-aDVisdXj.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CW8IPY7r.js → Dropdown-oQ54XzS9.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Zt8WOLuN.js → EmailListRenderer-BUPdMau1.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-B6aymxi7.js → FamilyGuardDashboard-DtQTOCQI.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BiuFrRq0.js → Federation-Cpnb3Moe.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DaiOAkHa.js → FormItemContext-DmsrJkeU.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-D1KbDpXs.js → GenericCardRenderer-CpFDYnh3.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BILl0huA.js → Git-HFG17p7Q.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CLyhrPrh.js → Governance-D5Z5c7N8.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DyNhiIpK.js → Inference-DH159h74.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Bor_ye4m.js → KnowledgeGraph-DuL1pcJD.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-ZDqbu8kg.js → Logs-Gqn8hFsM.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-NHJaejHI.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-DbDi29dH.js → Marketplace-BArcWifg.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DfU8dWbV.js → McpTools-Q1BfBSnd.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-DeAz-2-6.js → Memory-Cz8nRL7N.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-ws3TJXal.js → MobileBridge-CRuxLRJw.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CGwVfeTv.js → MobileProjects-B1CQIAGp.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BtJjJJpl.js → Mtc-CFhXZbPk.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BNZwipXs.js → MtcAudit-C69LqcZ5.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-9mSOrKWA.js → Multisig-y7yZeTTG.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-gz1lEDED.js → NLProgramming-DgjhoWea.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BeufuOVX.js → Notes-DRc2WTDh.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-CbFyi9jZ.js → NotificationSettings-CLPx24yn.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CMSNA4CO.js +1 -0
- package/src/assets/web-panel/assets/{Organization-DKBKNViH.js → Organization-Dm6LIyhZ.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-CvPfKE1x.js → Overflow-DP4DOYd-.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DjL2q8og.js → P2P-HFwvtFI1.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-D9gPfKRM.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-BUoNho3w.js → Permissions-Bdtxniwh.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-C4Iu2Pic.js → PersonalDataHub-Dgl2AbES.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-BiDrIlwr.js → Pipeline-DpZrzYwK.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BnvU6vGp.js → Privacy-EQ551c0t.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DDzoyhnH.js → ProjectInit-xUSeczsM.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CV6b43H0.js → ProjectSettings-B66Sr_t8.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-B0k1907g.js → Projects-B4zikj95.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DaeuFo-x.js → Providers-DjLbqQPh.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-C03Bll1h.js → QrScannerModal-KVsAQCd_.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Cn9nLaen.js → QuickAsk-C4noJXu8.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BXfkC8Ts.js → Recommend-Cn15clqY.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DNj6D_LI.js → RemoteSession-DdonMFwH.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-DDDIwiUI.js → Reputation-Bqe3NwWN.js} +1 -1
- package/src/assets/web-panel/assets/{Row-JukNm-id.js → Row-CnxBgjkD.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-B1AhzL2z.js → RssFeed-CSRrgZyK.js} +2 -2
- package/src/assets/web-panel/assets/{Search-B5y6_DVm.js → Search-DTUYUfFx.js} +1 -1
- package/src/assets/web-panel/assets/{Security-C2pvv32x.js → Security-BHExHVIm.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DBka7ppY.js → Services-B_IWqPes.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DfPwNTSh.js → Skeleton-C_oAtzmX.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-Cs4ntp-l.js → Skills-maJNokSL.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CrUJ0guM.js → Sla-CMM_ZsLH.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BFbokLV1.js → SpeechSettings-D7w_oNwK.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CSnh1o3D.js → SyncSettings-D0Zl3NIr.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CQZppIPo.js → Tasks-Bs0H0ctR.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BqLeHq7Z.js → Templates-C6T6oCpR.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CkzPA-ri.js → Tenant-ByL-kAYO.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-D5IRhnu6.js → Terminal-6JXNhc3b.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-ICdiYzgz.js → TimelineRenderer-KiuUtCsl.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-89CaZBwe.js → Tokens-C0TWTIER.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Di251qR1.js → Trigger-DpC5aaJu.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-M4sC3Mv6.js → Trust-lqqflCwv.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BL7EUWxj.js → UkeySign-BYSlVfBc.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BV5Se5il.js → VideoEditing-DX08oo_R.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-IoY13rov.js → Wallet-CcIFPvzL.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DKFPq0uV.js → WebAuthn-D8DfxfVT.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-oizkYeQD.js → WorkflowEditor-Csadpaln.js} +1 -1
- package/src/assets/web-panel/assets/{chat-Du6ZthE5.js → chat-CewHWc_T.js} +1 -1
- package/src/assets/web-panel/assets/{colors-I2ie73HS.js → colors-B8at89r3.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-C-02XOOx.js → compact-item-v_gxtF5-.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DgP09zg2.js → createContext-BOwFpJqA.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DenB_kSS.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-kWDasRib.js → hasIn-DjHUzt7E.js} +1 -1
- package/src/assets/web-panel/assets/{index-HH3Zccc0.js → index-1Tfwr_GA.js} +1 -1
- package/src/assets/web-panel/assets/{index-M9mzJo6V.js → index-5qmLo1us.js} +1 -1
- package/src/assets/web-panel/assets/index-B8bNwM9U.js +1 -0
- package/src/assets/web-panel/assets/{index-CDM6OO4D.js → index-BC20EQ7h.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cokjvq67.js → index-BGrmPk8T.js} +1 -1
- package/src/assets/web-panel/assets/{index-5Ll5Fdqb.js → index-BPJ7uL2Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-H_31JkaZ.js → index-BRNyE3Kb.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9HwtJX5.js → index-BVPsegMN.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bpz5RnQy.js → index-B_egGFcR.js} +3 -3
- package/src/assets/web-panel/assets/{index-CjuJ1FYZ.js → index-BdZLA7k9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cx5dAdFu.js → index-Bk_Te9OR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CHSghc3j.js → index-Bz8C8VXx.js} +1 -1
- package/src/assets/web-panel/assets/{index-RwcHDSqP.js → index-C7MHbmwn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtbMRqD-.js → index-CFL1Hewx.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0EQSoEb.js → index-CV1SgaBd.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3D5NSjS.js → index-CXIPAeAB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZ6bWk4x.js → index-Ca8YZU2B.js} +1 -1
- package/src/assets/web-panel/assets/{index-AtAiEmjM.js → index-Caf9UjhG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKJ-d5F1.js → index-Cb1uNDOv.js} +1 -1
- package/src/assets/web-panel/assets/{index-mF8UmKqp.js → index-CquUCUzS.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpOQeTac.js → index-CwHoF-TP.js} +1 -1
- package/src/assets/web-panel/assets/{index-Na5QA1LE.js → index-Cxk0mCoQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUvpjPMv.js → index-D3o-pacB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZcdP3id.js → index-DA3n0Y28.js} +1 -1
- package/src/assets/web-panel/assets/index-DC0cpgIP.js +1 -0
- package/src/assets/web-panel/assets/{index-LLQQqj9B.js → index-DRRK_8Eg.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5LV04FO.js → index-DUMvVGyf.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkcCllZ7.js → index-DdHdbXpy.js} +1 -1
- package/src/assets/web-panel/assets/{index-OYyn8GoU.js → index-Ddw05Myq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgogPjKJ.js → index-Dfrb3Prv.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbHEQTU6.js → index-DiKaYIdo.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQCdk4dn.js → index-DmZLZsyC.js} +1 -1
- package/src/assets/web-panel/assets/{index-uHI8QHu4.js → index-FgUrhq_H.js} +1 -1
- package/src/assets/web-panel/assets/{index-I4A_y2Ee.js → index-Khw7wzVk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSlTGxUJ.js → index-San3zacc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYxia8nl.js → index-coOi37of.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkP2DW3a.js → index-hyeBuocp.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ryflob2W.js → index-uk59xr0W.js} +1 -1
- package/src/assets/web-panel/assets/{index-DcVj7YJB.js → index-wjvn8-9F.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BPHGo-UX.js → initDefaultProps-Id2dFdjh.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BBr8JvWc.js → motion-DRxtwxng.js} +1 -1
- package/src/assets/web-panel/assets/{move-CdKWToz7.js → move-BDYvdTb6.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-B8tkkKBs.js → mtc-parser-cz8LGI8Z.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DN4aP33H.js → omit-DHJeh6tH.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CoHRkRJq.js → pickAttrs-XJEQIp7e.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DpGGGAPa.js → placementArrow-AFcf8lEn.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BabRsZo5.js → responsiveObserve-CDEeBxs1.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DJjyrwqK.js → slide-CvbVfi6T.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BaRVkO5S.js → statusUtils-DtCyuEnq.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-HcMYUjTR.js → styleChecker--UgmTsGY.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-c3IBzp2l.js → useFlexGapSupport-D9i1as-Y.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DD7igwyF.js → useFs-CJx3zw01.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-GEF0hhnd.js → usePersonalDataHub-ZsIKYdZv.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-D51u7zqF.js → vnode-B2RGEQG4.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-tXG23Lc9.js → zoom-ob-0px2z.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/evolution-attestor-trust.js +113 -0
- package/src/commands/evolution.js +2 -0
- package/src/commands/learning.js +34 -5
- package/src/data/changelog.json +13 -0
- package/src/lib/evolution/evolution-artifact-ports.js +21 -0
- package/src/lib/evolution/evolution-deployment-loader.js +56 -0
- package/src/lib/evolution/evolution-immutable-ledger-segment-store.js +812 -0
- package/src/lib/evolution/evolution-ledger-file-manifest-catalog.js +218 -0
- package/src/lib/evolution/evolution-ledger-manifest-catalog.js +472 -0
- package/src/lib/evolution/evolution-ledger-manifest-chain.js +997 -0
- package/src/lib/evolution/evolution-ledger-manifest-head-store.js +408 -0
- package/src/lib/evolution/evolution-ledger-manifest-witness-adapter.js +554 -0
- package/src/lib/evolution/evolution-ledger-v2-manifest-backend.js +460 -0
- package/src/lib/evolution/governed-skill-synthesis-attestor-ipc-endpoint.js +94 -0
- package/src/lib/evolution/governed-skill-synthesis-attestor-trust-approval-client.js +485 -0
- package/src/lib/evolution/governed-skill-synthesis-attestor-trust-ipc-capability.js +200 -0
- package/src/lib/evolution/governed-skill-synthesis-attestor-trust-ledger.js +1222 -0
- package/src/lib/evolution/governed-skill-synthesis-attestor-trust-operations-cli-host.js +802 -0
- package/src/lib/evolution/governed-skill-synthesis-attestor-trust-operations-client.js +445 -0
- package/src/lib/evolution/governed-skill-synthesis-attestor-trust-operations.js +1082 -0
- package/src/lib/evolution/governed-skill-synthesis-attestor-trust-operator-registry.js +1453 -0
- package/src/lib/evolution/governed-skill-synthesis-candidate-evaluator.js +147 -0
- package/src/lib/evolution/governed-skill-synthesis-cli-host.js +115 -0
- package/src/lib/evolution/governed-skill-synthesis-evaluation-ledger-adapter.js +715 -0
- package/src/lib/evolution/governed-skill-synthesis-external-attestor.js +499 -0
- package/src/lib/evolution/governed-skill-synthesis-model-evaluator.js +514 -0
- package/src/lib/evolution/governed-skill-synthesis-process-attestor-worker.mjs +208 -0
- package/src/lib/evolution/governed-skill-synthesis-process-attestor.js +557 -0
- package/src/lib/evolution/governed-skill-synthesis-process-grader-worker.mjs +143 -0
- package/src/lib/evolution/governed-skill-synthesis-process-grader.js +444 -0
- package/src/lib/evolution/governed-skill-synthesis-provider-chat.js +220 -0
- package/src/lib/evolution/governed-skill-synthesis-windows-secure-pipe-host.js +236 -0
- package/src/lib/hook-audit-store.js +68 -2
- package/src/lib/hooks-v2-runtime.js +52 -26
- package/src/lib/learning/skill-synthesizer.js +66 -5
- package/src/lib/remote-read-loop-guard.js +30 -3
- package/src/assets/web-panel/assets/MarkdownRenderer-DnSfkf98.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CFbhBT44.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-Cv9PLMWJ.js +0 -7
- package/src/assets/web-panel/assets/devWarning-CxKkOIjh.js +0 -1
- package/src/assets/web-panel/assets/index-b5a6l3Uu.js +0 -1
- package/src/assets/web-panel/assets/index-xt-BOyp_.js +0 -1
|
@@ -0,0 +1,1453 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createHash,
|
|
3
|
+
createPrivateKey,
|
|
4
|
+
createPublicKey,
|
|
5
|
+
sign,
|
|
6
|
+
verify,
|
|
7
|
+
} from "node:crypto";
|
|
8
|
+
import { types as utilTypes } from "node:util";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA,
|
|
12
|
+
isEvolutionLedgerArtifactResolver,
|
|
13
|
+
} from "./evolution-artifact-ports.js";
|
|
14
|
+
import {
|
|
15
|
+
EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA,
|
|
16
|
+
EVOLUTION_LEDGER_DOMAIN_EVENT_SCHEMA,
|
|
17
|
+
} from "./evolution-ledger.js";
|
|
18
|
+
|
|
19
|
+
export const GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_SCHEMA =
|
|
20
|
+
"chainlesschain.governed-skill-synthesis-attestor-trust-operator-registry/v1";
|
|
21
|
+
export const GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_RECORD_SCHEMA =
|
|
22
|
+
"chainlesschain.governed-skill-synthesis-attestor-trust-operator-registry-record/v1";
|
|
23
|
+
export const GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_GENESIS_EVENT =
|
|
24
|
+
"learning.skill-synthesis-attestor-trust.operator-registry.genesis.committed";
|
|
25
|
+
export const GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_REQUEST_SCHEMA =
|
|
26
|
+
"chainlesschain.governed-skill-synthesis-attestor-trust-operator-registry-change-request/v1";
|
|
27
|
+
export const GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_APPROVAL_SCHEMA =
|
|
28
|
+
"chainlesschain.governed-skill-synthesis-attestor-trust-operator-registry-approval/v1";
|
|
29
|
+
export const GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_AUTHORIZATION_SCHEMA =
|
|
30
|
+
"chainlesschain.governed-skill-synthesis-attestor-trust-operator-registry-authorization/v1";
|
|
31
|
+
export const GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_RECORD_SCHEMA =
|
|
32
|
+
"chainlesschain.governed-skill-synthesis-attestor-trust-operator-registry-change-record/v1";
|
|
33
|
+
export const GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_EVENT =
|
|
34
|
+
"learning.skill-synthesis-attestor-trust.operator-registry.change.committed";
|
|
35
|
+
export const GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CORRUPT_CODE =
|
|
36
|
+
"CC_LEARNING_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CORRUPT";
|
|
37
|
+
|
|
38
|
+
const ARTIFACT_TYPE =
|
|
39
|
+
"governed-skill-synthesis-attestor-trust-operator-registry";
|
|
40
|
+
const REGISTRIES = new WeakSet();
|
|
41
|
+
const ISSUERS = new WeakSet();
|
|
42
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
43
|
+
const ID = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,255}$/u;
|
|
44
|
+
const KEY_ID = /^key:ed25519:[a-f0-9]{64}$/u;
|
|
45
|
+
const MAX_OPERATORS = 16;
|
|
46
|
+
const MAX_TTL_MS = 15 * 60 * 1000;
|
|
47
|
+
const FUTURE_SKEW_MS = 30 * 1000;
|
|
48
|
+
const RECORD_KEYS = new Set([
|
|
49
|
+
"createdAt",
|
|
50
|
+
"operatorCount",
|
|
51
|
+
"operators",
|
|
52
|
+
"policyDigest",
|
|
53
|
+
"policyId",
|
|
54
|
+
"recordDigest",
|
|
55
|
+
"requiredApprovals",
|
|
56
|
+
"revision",
|
|
57
|
+
"schema",
|
|
58
|
+
"tenantId",
|
|
59
|
+
]);
|
|
60
|
+
const OPERATOR_KEYS = new Set(["keyId", "operatorId", "publicKeySpki"]);
|
|
61
|
+
const CHANGE_REQUEST_KEYS = new Set([
|
|
62
|
+
"expiresAt",
|
|
63
|
+
"keyId",
|
|
64
|
+
"operation",
|
|
65
|
+
"operatorId",
|
|
66
|
+
"policyDigest",
|
|
67
|
+
"policyId",
|
|
68
|
+
"priorKeyId",
|
|
69
|
+
"publicKeySpki",
|
|
70
|
+
"reason",
|
|
71
|
+
"requestDigest",
|
|
72
|
+
"requestedAt",
|
|
73
|
+
"requiredApprovals",
|
|
74
|
+
"revision",
|
|
75
|
+
"schema",
|
|
76
|
+
"tenantId",
|
|
77
|
+
]);
|
|
78
|
+
const APPROVAL_KEYS = new Set([
|
|
79
|
+
"approvedAt",
|
|
80
|
+
"attestation",
|
|
81
|
+
"automated",
|
|
82
|
+
"expiresAt",
|
|
83
|
+
"operatorId",
|
|
84
|
+
"policyDigest",
|
|
85
|
+
"receiptDigest",
|
|
86
|
+
"requestDigest",
|
|
87
|
+
"schema",
|
|
88
|
+
"tenantId",
|
|
89
|
+
]);
|
|
90
|
+
const ATTESTATION_KEYS = new Set(["algorithm", "keyId", "value"]);
|
|
91
|
+
const AUTHORIZATION_KEYS = new Set([
|
|
92
|
+
"approvalReceiptDigests",
|
|
93
|
+
"authorizationDigest",
|
|
94
|
+
"authorizedAt",
|
|
95
|
+
"operatorIds",
|
|
96
|
+
"policyDigest",
|
|
97
|
+
"requestDigest",
|
|
98
|
+
"requiredApprovals",
|
|
99
|
+
"schema",
|
|
100
|
+
"tenantId",
|
|
101
|
+
]);
|
|
102
|
+
const CHANGE_RECORD_KEYS = new Set([
|
|
103
|
+
"approvals",
|
|
104
|
+
"authorization",
|
|
105
|
+
"effectiveAt",
|
|
106
|
+
"operatorCount",
|
|
107
|
+
"operators",
|
|
108
|
+
"policyDigest",
|
|
109
|
+
"policyId",
|
|
110
|
+
"priorRecordDigest",
|
|
111
|
+
"recordDigest",
|
|
112
|
+
"request",
|
|
113
|
+
"requiredApprovals",
|
|
114
|
+
"revision",
|
|
115
|
+
"schema",
|
|
116
|
+
"tenantId",
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
function corrupt(message) {
|
|
120
|
+
const error = new Error(message);
|
|
121
|
+
error.code =
|
|
122
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CORRUPT_CODE;
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function canonical(value) {
|
|
127
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
128
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
129
|
+
return `{${Object.keys(value)
|
|
130
|
+
.sort()
|
|
131
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
|
|
132
|
+
.join(",")}}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function hash(domain, value) {
|
|
136
|
+
return `sha256:${createHash("sha256")
|
|
137
|
+
.update(domain)
|
|
138
|
+
.update("\0")
|
|
139
|
+
.update(canonical(value))
|
|
140
|
+
.digest("hex")}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function exact(value, keys, label) {
|
|
144
|
+
if (
|
|
145
|
+
!value ||
|
|
146
|
+
typeof value !== "object" ||
|
|
147
|
+
Array.isArray(value) ||
|
|
148
|
+
utilTypes.isProxy(value) ||
|
|
149
|
+
Object.getPrototypeOf(value) !== Object.prototype ||
|
|
150
|
+
Reflect.ownKeys(value).length !== keys.size ||
|
|
151
|
+
Reflect.ownKeys(value).some((key) => {
|
|
152
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
153
|
+
return (
|
|
154
|
+
typeof key !== "string" ||
|
|
155
|
+
!keys.has(key) ||
|
|
156
|
+
!descriptor ||
|
|
157
|
+
!("value" in descriptor) ||
|
|
158
|
+
descriptor.enumerable !== true
|
|
159
|
+
);
|
|
160
|
+
})
|
|
161
|
+
) {
|
|
162
|
+
throw new TypeError(`${label} is invalid`);
|
|
163
|
+
}
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function identifier(value, label) {
|
|
168
|
+
if (typeof value !== "string" || !ID.test(value)) {
|
|
169
|
+
throw new TypeError(`${label} is invalid`);
|
|
170
|
+
}
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function capture(owner, method, label) {
|
|
175
|
+
if (
|
|
176
|
+
!owner ||
|
|
177
|
+
typeof owner !== "object" ||
|
|
178
|
+
utilTypes.isProxy(owner) ||
|
|
179
|
+
typeof owner[method] !== "function"
|
|
180
|
+
) {
|
|
181
|
+
throw new TypeError(`${label}.${method}() is required`);
|
|
182
|
+
}
|
|
183
|
+
return (...args) => Reflect.apply(owner[method], owner, args);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function freeze(value) {
|
|
187
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
188
|
+
for (const child of Object.values(value)) freeze(child);
|
|
189
|
+
Object.freeze(value);
|
|
190
|
+
}
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function publicKeyIdentity(value, label) {
|
|
195
|
+
let key;
|
|
196
|
+
try {
|
|
197
|
+
if (
|
|
198
|
+
(utilTypes.isKeyObject(value) && value.type !== "public") ||
|
|
199
|
+
(typeof value === "string" && /PRIVATE KEY/u.test(value)) ||
|
|
200
|
+
(value &&
|
|
201
|
+
typeof value === "object" &&
|
|
202
|
+
!Buffer.isBuffer(value) &&
|
|
203
|
+
Object.hasOwn(value, "d"))
|
|
204
|
+
) {
|
|
205
|
+
throw new Error();
|
|
206
|
+
}
|
|
207
|
+
key = utilTypes.isKeyObject(value) ? value : createPublicKey(value);
|
|
208
|
+
} catch {
|
|
209
|
+
throw new TypeError(`${label} must be an Ed25519 public key`);
|
|
210
|
+
}
|
|
211
|
+
if (key.asymmetricKeyType !== "ed25519") {
|
|
212
|
+
throw new TypeError(`${label} must be an Ed25519 public key`);
|
|
213
|
+
}
|
|
214
|
+
const bytes = key.export({ type: "spki", format: "der" });
|
|
215
|
+
return Object.freeze({
|
|
216
|
+
keyId: `key:ed25519:${createHash("sha256").update(bytes).digest("hex")}`,
|
|
217
|
+
publicKeySpki: bytes.toString("base64url"),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function decodePublicKey(value) {
|
|
222
|
+
if (
|
|
223
|
+
!KEY_ID.test(value.keyId ?? "") ||
|
|
224
|
+
typeof value.publicKeySpki !== "string"
|
|
225
|
+
) {
|
|
226
|
+
corrupt("operator registry public key identity is invalid");
|
|
227
|
+
}
|
|
228
|
+
const bytes = Buffer.from(value.publicKeySpki, "base64url");
|
|
229
|
+
if (bytes.toString("base64url") !== value.publicKeySpki) {
|
|
230
|
+
corrupt("operator registry public key is not canonical");
|
|
231
|
+
}
|
|
232
|
+
const identity = publicKeyIdentity(
|
|
233
|
+
{ key: bytes, type: "spki", format: "der" },
|
|
234
|
+
"stored operator public key",
|
|
235
|
+
);
|
|
236
|
+
if (
|
|
237
|
+
identity.keyId !== value.keyId ||
|
|
238
|
+
identity.publicKeySpki !== value.publicKeySpki
|
|
239
|
+
) {
|
|
240
|
+
corrupt("operator registry keyId does not bind its SPKI");
|
|
241
|
+
}
|
|
242
|
+
return createPublicKey({ key: bytes, type: "spki", format: "der" });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function normalizeOperators(value, tenantId) {
|
|
246
|
+
if (
|
|
247
|
+
!Array.isArray(value) ||
|
|
248
|
+
value.length < 1 ||
|
|
249
|
+
value.length > MAX_OPERATORS ||
|
|
250
|
+
utilTypes.isProxy(value) ||
|
|
251
|
+
Object.getPrototypeOf(value) !== Array.prototype ||
|
|
252
|
+
Reflect.ownKeys(value).length !== value.length + 1
|
|
253
|
+
) {
|
|
254
|
+
throw new TypeError("operator identities must be a bounded array");
|
|
255
|
+
}
|
|
256
|
+
const operatorIds = new Set();
|
|
257
|
+
const keyIds = new Set();
|
|
258
|
+
const operators = value.map((entry) => {
|
|
259
|
+
exact(
|
|
260
|
+
entry,
|
|
261
|
+
new Set(["operatorId", "publicKey", "tenantId"]),
|
|
262
|
+
"operator identity",
|
|
263
|
+
);
|
|
264
|
+
if (entry.tenantId !== tenantId) {
|
|
265
|
+
throw new Error("operator identity crossed its tenant boundary");
|
|
266
|
+
}
|
|
267
|
+
const operatorId = identifier(entry.operatorId, "operatorId");
|
|
268
|
+
const key = publicKeyIdentity(entry.publicKey, "operator publicKey");
|
|
269
|
+
if (operatorIds.has(operatorId) || keyIds.has(key.keyId)) {
|
|
270
|
+
throw new Error("operator identity or key is duplicated");
|
|
271
|
+
}
|
|
272
|
+
operatorIds.add(operatorId);
|
|
273
|
+
keyIds.add(key.keyId);
|
|
274
|
+
return { operatorId, ...key };
|
|
275
|
+
});
|
|
276
|
+
return operators.sort((left, right) =>
|
|
277
|
+
left.operatorId.localeCompare(right.operatorId),
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function policyCore({
|
|
282
|
+
tenantId,
|
|
283
|
+
policyId,
|
|
284
|
+
revision,
|
|
285
|
+
requiredApprovals,
|
|
286
|
+
operators,
|
|
287
|
+
}) {
|
|
288
|
+
return {
|
|
289
|
+
tenantId,
|
|
290
|
+
policyId,
|
|
291
|
+
revision,
|
|
292
|
+
requiredApprovals,
|
|
293
|
+
operators: operators.map(({ operatorId, keyId }) => ({
|
|
294
|
+
operatorId,
|
|
295
|
+
keyId,
|
|
296
|
+
})),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function recordCore(value) {
|
|
301
|
+
const core = structuredClone(value);
|
|
302
|
+
delete core.recordDigest;
|
|
303
|
+
return core;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function digestGovernedSkillSynthesisAttestorTrustOperatorRegistryRecord(
|
|
307
|
+
value,
|
|
308
|
+
) {
|
|
309
|
+
return hash(
|
|
310
|
+
value?.schema ===
|
|
311
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_RECORD_SCHEMA
|
|
312
|
+
? GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_RECORD_SCHEMA
|
|
313
|
+
: GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_RECORD_SCHEMA,
|
|
314
|
+
recordCore(value),
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function validateGenesisRecord(value, descriptor) {
|
|
319
|
+
try {
|
|
320
|
+
exact(value, RECORD_KEYS, "operator registry record");
|
|
321
|
+
} catch {
|
|
322
|
+
corrupt("operator registry record shape is invalid");
|
|
323
|
+
}
|
|
324
|
+
if (
|
|
325
|
+
value.schema !==
|
|
326
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_RECORD_SCHEMA ||
|
|
327
|
+
value.tenantId !== descriptor.tenantId ||
|
|
328
|
+
!ID.test(value.policyId ?? "") ||
|
|
329
|
+
!Number.isSafeInteger(value.revision) ||
|
|
330
|
+
value.revision < 1 ||
|
|
331
|
+
!Number.isSafeInteger(value.requiredApprovals) ||
|
|
332
|
+
value.requiredApprovals < 1 ||
|
|
333
|
+
!Number.isSafeInteger(value.operatorCount) ||
|
|
334
|
+
value.operatorCount < value.requiredApprovals ||
|
|
335
|
+
value.operatorCount > MAX_OPERATORS ||
|
|
336
|
+
!DIGEST.test(value.policyDigest ?? "") ||
|
|
337
|
+
!Number.isFinite(Date.parse(value.createdAt)) ||
|
|
338
|
+
new Date(Date.parse(value.createdAt)).toISOString() !== value.createdAt ||
|
|
339
|
+
!Array.isArray(value.operators) ||
|
|
340
|
+
value.operators.length !== value.operatorCount ||
|
|
341
|
+
value.recordDigest !==
|
|
342
|
+
digestGovernedSkillSynthesisAttestorTrustOperatorRegistryRecord(value)
|
|
343
|
+
) {
|
|
344
|
+
corrupt("operator registry record binding is invalid");
|
|
345
|
+
}
|
|
346
|
+
const ids = new Set();
|
|
347
|
+
const keys = new Set();
|
|
348
|
+
let previous = null;
|
|
349
|
+
for (const operator of value.operators) {
|
|
350
|
+
try {
|
|
351
|
+
exact(operator, OPERATOR_KEYS, "stored operator identity");
|
|
352
|
+
} catch {
|
|
353
|
+
corrupt("stored operator identity shape is invalid");
|
|
354
|
+
}
|
|
355
|
+
if (
|
|
356
|
+
!ID.test(operator.operatorId ?? "") ||
|
|
357
|
+
ids.has(operator.operatorId) ||
|
|
358
|
+
keys.has(operator.keyId) ||
|
|
359
|
+
(previous !== null && previous.localeCompare(operator.operatorId) >= 0)
|
|
360
|
+
) {
|
|
361
|
+
corrupt("stored operator identities are not unique and sorted");
|
|
362
|
+
}
|
|
363
|
+
decodePublicKey(operator);
|
|
364
|
+
ids.add(operator.operatorId);
|
|
365
|
+
keys.add(operator.keyId);
|
|
366
|
+
previous = operator.operatorId;
|
|
367
|
+
}
|
|
368
|
+
const expectedPolicy = hash(
|
|
369
|
+
"chainlesschain.attestor-trust-operations-policy/v1",
|
|
370
|
+
policyCore(value),
|
|
371
|
+
);
|
|
372
|
+
if (value.policyDigest !== expectedPolicy) {
|
|
373
|
+
corrupt("operator registry policy digest is invalid");
|
|
374
|
+
}
|
|
375
|
+
return freeze(structuredClone(value));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function timestamp(value, label) {
|
|
379
|
+
const milliseconds = Date.parse(value);
|
|
380
|
+
if (
|
|
381
|
+
typeof value !== "string" ||
|
|
382
|
+
!Number.isFinite(milliseconds) ||
|
|
383
|
+
new Date(milliseconds).toISOString() !== value
|
|
384
|
+
) {
|
|
385
|
+
throw new TypeError(`${label} is invalid`);
|
|
386
|
+
}
|
|
387
|
+
return milliseconds;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function dense(value, label, maximum = MAX_OPERATORS) {
|
|
391
|
+
if (
|
|
392
|
+
!Array.isArray(value) ||
|
|
393
|
+
utilTypes.isProxy(value) ||
|
|
394
|
+
Object.getPrototypeOf(value) !== Array.prototype ||
|
|
395
|
+
value.length < 1 ||
|
|
396
|
+
value.length > maximum ||
|
|
397
|
+
Reflect.ownKeys(value).length !== value.length + 1
|
|
398
|
+
) {
|
|
399
|
+
throw new TypeError(`${label} must be a bounded dense array`);
|
|
400
|
+
}
|
|
401
|
+
return value;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function changeRequestCore(value) {
|
|
405
|
+
const core = structuredClone(value);
|
|
406
|
+
delete core.requestDigest;
|
|
407
|
+
return core;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export function digestGovernedSkillSynthesisAttestorTrustOperatorRegistryChangeRequest(
|
|
411
|
+
value,
|
|
412
|
+
) {
|
|
413
|
+
return hash(
|
|
414
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_REQUEST_SCHEMA,
|
|
415
|
+
changeRequestCore(value),
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function approvalCore(value) {
|
|
420
|
+
const core = structuredClone(value);
|
|
421
|
+
delete core.receiptDigest;
|
|
422
|
+
delete core.attestation;
|
|
423
|
+
return core;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export function digestGovernedSkillSynthesisAttestorTrustOperatorRegistryApproval(
|
|
427
|
+
value,
|
|
428
|
+
) {
|
|
429
|
+
return hash(
|
|
430
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_APPROVAL_SCHEMA,
|
|
431
|
+
approvalCore(value),
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function governedSkillSynthesisAttestorTrustOperatorRegistryApprovalMessage(
|
|
436
|
+
receiptDigest,
|
|
437
|
+
) {
|
|
438
|
+
if (!DIGEST.test(receiptDigest ?? "")) {
|
|
439
|
+
throw new TypeError("operator registry approval receiptDigest is invalid");
|
|
440
|
+
}
|
|
441
|
+
return Buffer.from(
|
|
442
|
+
`chainlesschain.governed-skill-synthesis-attestor-trust-operator-registry-approval-signature/v1\0${receiptDigest}`,
|
|
443
|
+
"utf8",
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function validateChangeRequest(value, current, currentTime) {
|
|
448
|
+
exact(value, CHANGE_REQUEST_KEYS, "operator registry change request");
|
|
449
|
+
const requestedAt = timestamp(value.requestedAt, "change requestedAt");
|
|
450
|
+
const expiresAt = timestamp(value.expiresAt, "change expiresAt");
|
|
451
|
+
if (
|
|
452
|
+
value.schema !==
|
|
453
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_REQUEST_SCHEMA ||
|
|
454
|
+
value.tenantId !== current.tenantId ||
|
|
455
|
+
value.policyId !== current.policyId ||
|
|
456
|
+
value.revision !== current.revision ||
|
|
457
|
+
value.policyDigest !== current.policyDigest ||
|
|
458
|
+
value.requiredApprovals !== current.requiredApprovals ||
|
|
459
|
+
!["register", "rotate", "revoke"].includes(value.operation) ||
|
|
460
|
+
!ID.test(value.operatorId ?? "") ||
|
|
461
|
+
!KEY_ID.test(value.keyId ?? "") ||
|
|
462
|
+
requestedAt > currentTime + FUTURE_SKEW_MS ||
|
|
463
|
+
expiresAt <= currentTime ||
|
|
464
|
+
expiresAt <= requestedAt ||
|
|
465
|
+
expiresAt > requestedAt + MAX_TTL_MS ||
|
|
466
|
+
value.requestDigest !==
|
|
467
|
+
digestGovernedSkillSynthesisAttestorTrustOperatorRegistryChangeRequest(
|
|
468
|
+
value,
|
|
469
|
+
)
|
|
470
|
+
) {
|
|
471
|
+
throw new Error("operator registry change request is not policy-bound");
|
|
472
|
+
}
|
|
473
|
+
if (value.operation === "register") {
|
|
474
|
+
if (
|
|
475
|
+
value.priorKeyId !== null ||
|
|
476
|
+
value.reason !== null ||
|
|
477
|
+
typeof value.publicKeySpki !== "string"
|
|
478
|
+
) {
|
|
479
|
+
throw new Error("operator registration request is invalid");
|
|
480
|
+
}
|
|
481
|
+
decodePublicKey(value);
|
|
482
|
+
} else if (value.operation === "rotate") {
|
|
483
|
+
if (
|
|
484
|
+
!KEY_ID.test(value.priorKeyId ?? "") ||
|
|
485
|
+
value.priorKeyId === value.keyId ||
|
|
486
|
+
typeof value.publicKeySpki !== "string" ||
|
|
487
|
+
typeof value.reason !== "string" ||
|
|
488
|
+
value.reason.trim() !== value.reason ||
|
|
489
|
+
value.reason.length < 1 ||
|
|
490
|
+
value.reason.length > 2048
|
|
491
|
+
) {
|
|
492
|
+
throw new Error("operator rotation request is invalid");
|
|
493
|
+
}
|
|
494
|
+
decodePublicKey(value);
|
|
495
|
+
} else if (
|
|
496
|
+
value.priorKeyId !== null ||
|
|
497
|
+
value.publicKeySpki !== null ||
|
|
498
|
+
typeof value.reason !== "string" ||
|
|
499
|
+
value.reason.trim() !== value.reason ||
|
|
500
|
+
value.reason.length < 1 ||
|
|
501
|
+
value.reason.length > 2048
|
|
502
|
+
) {
|
|
503
|
+
throw new Error("operator revocation request is invalid");
|
|
504
|
+
}
|
|
505
|
+
return value;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export function validateGovernedSkillSynthesisAttestorTrustOperatorRegistryChangeRequest(
|
|
509
|
+
value,
|
|
510
|
+
current,
|
|
511
|
+
currentTime = Date.now(),
|
|
512
|
+
) {
|
|
513
|
+
const now = Number(currentTime);
|
|
514
|
+
if (!Number.isFinite(now)) {
|
|
515
|
+
throw new TypeError("operator registry validation clock is invalid");
|
|
516
|
+
}
|
|
517
|
+
return freeze(structuredClone(validateChangeRequest(value, current, now)));
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function normalizeApproval(receipt, request, operator, currentTime) {
|
|
521
|
+
exact(receipt, APPROVAL_KEYS, "operator registry approval");
|
|
522
|
+
const attestation = exact(
|
|
523
|
+
receipt.attestation,
|
|
524
|
+
ATTESTATION_KEYS,
|
|
525
|
+
"operator registry approval attestation",
|
|
526
|
+
);
|
|
527
|
+
const approvedAt = timestamp(receipt.approvedAt, "approval approvedAt");
|
|
528
|
+
const expiresAt = timestamp(receipt.expiresAt, "approval expiresAt");
|
|
529
|
+
if (
|
|
530
|
+
receipt.schema !==
|
|
531
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_APPROVAL_SCHEMA ||
|
|
532
|
+
receipt.tenantId !== request.tenantId ||
|
|
533
|
+
receipt.operatorId !== operator.operatorId ||
|
|
534
|
+
receipt.automated !== false ||
|
|
535
|
+
receipt.requestDigest !== request.requestDigest ||
|
|
536
|
+
receipt.policyDigest !== request.policyDigest ||
|
|
537
|
+
approvedAt < Date.parse(request.requestedAt) - FUTURE_SKEW_MS ||
|
|
538
|
+
approvedAt > currentTime + FUTURE_SKEW_MS ||
|
|
539
|
+
approvedAt >= expiresAt ||
|
|
540
|
+
expiresAt !== Date.parse(request.expiresAt) ||
|
|
541
|
+
expiresAt <= currentTime ||
|
|
542
|
+
receipt.receiptDigest !==
|
|
543
|
+
digestGovernedSkillSynthesisAttestorTrustOperatorRegistryApproval(
|
|
544
|
+
receipt,
|
|
545
|
+
) ||
|
|
546
|
+
attestation.algorithm !== "Ed25519" ||
|
|
547
|
+
attestation.keyId !== operator.keyId ||
|
|
548
|
+
typeof attestation.value !== "string"
|
|
549
|
+
) {
|
|
550
|
+
throw new Error("operator registry approval is not exactly bound");
|
|
551
|
+
}
|
|
552
|
+
const signature = Buffer.from(attestation.value, "base64url");
|
|
553
|
+
if (
|
|
554
|
+
signature.length !== 64 ||
|
|
555
|
+
signature.toString("base64url") !== attestation.value ||
|
|
556
|
+
!verify(
|
|
557
|
+
null,
|
|
558
|
+
governedSkillSynthesisAttestorTrustOperatorRegistryApprovalMessage(
|
|
559
|
+
receipt.receiptDigest,
|
|
560
|
+
),
|
|
561
|
+
operator.publicKey,
|
|
562
|
+
signature,
|
|
563
|
+
)
|
|
564
|
+
) {
|
|
565
|
+
throw new Error("operator registry approval signature is invalid");
|
|
566
|
+
}
|
|
567
|
+
return receipt;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function buildAuthorization(request, approvals) {
|
|
571
|
+
const accepted = [...approvals].sort((left, right) =>
|
|
572
|
+
left.operatorId.localeCompare(right.operatorId),
|
|
573
|
+
);
|
|
574
|
+
const core = {
|
|
575
|
+
schema:
|
|
576
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_AUTHORIZATION_SCHEMA,
|
|
577
|
+
tenantId: request.tenantId,
|
|
578
|
+
requestDigest: request.requestDigest,
|
|
579
|
+
policyDigest: request.policyDigest,
|
|
580
|
+
requiredApprovals: request.requiredApprovals,
|
|
581
|
+
operatorIds: accepted.map((approval) => approval.operatorId),
|
|
582
|
+
approvalReceiptDigests: accepted.map((approval) => approval.receiptDigest),
|
|
583
|
+
authorizedAt: accepted
|
|
584
|
+
.map((approval) => approval.approvedAt)
|
|
585
|
+
.sort()
|
|
586
|
+
.at(-1),
|
|
587
|
+
};
|
|
588
|
+
return freeze({
|
|
589
|
+
...core,
|
|
590
|
+
authorizationDigest: hash(
|
|
591
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_AUTHORIZATION_SCHEMA,
|
|
592
|
+
core,
|
|
593
|
+
),
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function operatorMap(record) {
|
|
598
|
+
return new Map(
|
|
599
|
+
record.operators.map((operator) => [
|
|
600
|
+
operator.operatorId,
|
|
601
|
+
{
|
|
602
|
+
...operator,
|
|
603
|
+
publicKey: decodePublicKey(operator),
|
|
604
|
+
},
|
|
605
|
+
]),
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function applyChange(request, current, history) {
|
|
610
|
+
const operators = new Map(
|
|
611
|
+
current.operators.map((operator) => [operator.operatorId, operator]),
|
|
612
|
+
);
|
|
613
|
+
if (request.operation === "register") {
|
|
614
|
+
if (
|
|
615
|
+
history.operatorIds.has(request.operatorId) ||
|
|
616
|
+
history.keyIds.has(request.keyId) ||
|
|
617
|
+
operators.size >= MAX_OPERATORS
|
|
618
|
+
) {
|
|
619
|
+
throw new Error(
|
|
620
|
+
"operator registration would reuse an identity or exceed capacity",
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
operators.set(request.operatorId, {
|
|
624
|
+
operatorId: request.operatorId,
|
|
625
|
+
keyId: request.keyId,
|
|
626
|
+
publicKeySpki: request.publicKeySpki,
|
|
627
|
+
});
|
|
628
|
+
} else if (request.operation === "rotate") {
|
|
629
|
+
const active = operators.get(request.operatorId);
|
|
630
|
+
if (
|
|
631
|
+
!active ||
|
|
632
|
+
active.keyId !== request.priorKeyId ||
|
|
633
|
+
history.keyIds.has(request.keyId)
|
|
634
|
+
) {
|
|
635
|
+
throw new Error("operator rotation does not match the active identity");
|
|
636
|
+
}
|
|
637
|
+
operators.set(request.operatorId, {
|
|
638
|
+
operatorId: request.operatorId,
|
|
639
|
+
keyId: request.keyId,
|
|
640
|
+
publicKeySpki: request.publicKeySpki,
|
|
641
|
+
});
|
|
642
|
+
} else {
|
|
643
|
+
const active = operators.get(request.operatorId);
|
|
644
|
+
if (!active || active.keyId !== request.keyId) {
|
|
645
|
+
throw new Error("operator revocation does not match the active identity");
|
|
646
|
+
}
|
|
647
|
+
if (operators.size - 1 < current.requiredApprovals) {
|
|
648
|
+
throw new Error(
|
|
649
|
+
"operator revocation would reduce the active set below quorum",
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
operators.delete(request.operatorId);
|
|
653
|
+
}
|
|
654
|
+
return [...operators.values()].sort((left, right) =>
|
|
655
|
+
left.operatorId.localeCompare(right.operatorId),
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function validateAuthorization(value, request, approvals) {
|
|
660
|
+
exact(value, AUTHORIZATION_KEYS, "operator registry authorization");
|
|
661
|
+
const expected = buildAuthorization(request, approvals);
|
|
662
|
+
if (canonical(value) !== canonical(expected)) {
|
|
663
|
+
corrupt("operator registry authorization is not exactly bound");
|
|
664
|
+
}
|
|
665
|
+
return value;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function validateChangeRecord(value, descriptor) {
|
|
669
|
+
try {
|
|
670
|
+
exact(value, CHANGE_RECORD_KEYS, "operator registry change record");
|
|
671
|
+
} catch {
|
|
672
|
+
corrupt("operator registry change record shape is invalid");
|
|
673
|
+
}
|
|
674
|
+
if (
|
|
675
|
+
value.schema !==
|
|
676
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_RECORD_SCHEMA ||
|
|
677
|
+
value.tenantId !== descriptor.tenantId ||
|
|
678
|
+
!DIGEST.test(value.priorRecordDigest ?? "") ||
|
|
679
|
+
!DIGEST.test(value.policyDigest ?? "") ||
|
|
680
|
+
!Number.isSafeInteger(value.revision) ||
|
|
681
|
+
value.revision < 2 ||
|
|
682
|
+
!Number.isSafeInteger(value.requiredApprovals) ||
|
|
683
|
+
value.requiredApprovals < 1 ||
|
|
684
|
+
!Number.isSafeInteger(value.operatorCount) ||
|
|
685
|
+
value.operatorCount < value.requiredApprovals ||
|
|
686
|
+
value.operatorCount > MAX_OPERATORS ||
|
|
687
|
+
value.effectiveAt !== value.authorization?.authorizedAt ||
|
|
688
|
+
value.recordDigest !==
|
|
689
|
+
digestGovernedSkillSynthesisAttestorTrustOperatorRegistryRecord(value)
|
|
690
|
+
) {
|
|
691
|
+
corrupt("operator registry change record binding is invalid");
|
|
692
|
+
}
|
|
693
|
+
dense(value.operators, "stored active operators");
|
|
694
|
+
dense(value.approvals, "stored operator approvals");
|
|
695
|
+
for (const operator of value.operators) {
|
|
696
|
+
try {
|
|
697
|
+
exact(operator, OPERATOR_KEYS, "stored active operator");
|
|
698
|
+
} catch {
|
|
699
|
+
corrupt("stored active operator shape is invalid");
|
|
700
|
+
}
|
|
701
|
+
decodePublicKey(operator);
|
|
702
|
+
}
|
|
703
|
+
return freeze(structuredClone(value));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function normalizeChangeInput(value, current, history, currentTime) {
|
|
707
|
+
if (
|
|
708
|
+
!value ||
|
|
709
|
+
typeof value !== "object" ||
|
|
710
|
+
Array.isArray(value) ||
|
|
711
|
+
utilTypes.isProxy(value) ||
|
|
712
|
+
Object.getPrototypeOf(value) !== Object.prototype ||
|
|
713
|
+
!["register", "rotate", "revoke"].includes(value.operation) ||
|
|
714
|
+
!ID.test(value.operatorId ?? "")
|
|
715
|
+
) {
|
|
716
|
+
throw new TypeError("operator registry change input is invalid");
|
|
717
|
+
}
|
|
718
|
+
let keyId;
|
|
719
|
+
let publicKeySpki;
|
|
720
|
+
let priorKeyId = null;
|
|
721
|
+
let reason = null;
|
|
722
|
+
if (value.operation === "register" || value.operation === "rotate") {
|
|
723
|
+
exact(
|
|
724
|
+
value,
|
|
725
|
+
value.operation === "register"
|
|
726
|
+
? new Set(["operation", "operatorId", "publicKey"])
|
|
727
|
+
: new Set([
|
|
728
|
+
"operation",
|
|
729
|
+
"operatorId",
|
|
730
|
+
"priorKeyId",
|
|
731
|
+
"publicKey",
|
|
732
|
+
"reason",
|
|
733
|
+
]),
|
|
734
|
+
"operator registry change input",
|
|
735
|
+
);
|
|
736
|
+
const key = publicKeyIdentity(value.publicKey, "new operator publicKey");
|
|
737
|
+
keyId = key.keyId;
|
|
738
|
+
publicKeySpki = key.publicKeySpki;
|
|
739
|
+
if (value.operation === "rotate") {
|
|
740
|
+
priorKeyId = value.priorKeyId;
|
|
741
|
+
reason = value.reason;
|
|
742
|
+
}
|
|
743
|
+
} else {
|
|
744
|
+
exact(
|
|
745
|
+
value,
|
|
746
|
+
new Set(["keyId", "operation", "operatorId", "reason"]),
|
|
747
|
+
"operator registry change input",
|
|
748
|
+
);
|
|
749
|
+
keyId = value.keyId;
|
|
750
|
+
publicKeySpki = null;
|
|
751
|
+
reason = value.reason;
|
|
752
|
+
}
|
|
753
|
+
const requestedAt = new Date(currentTime).toISOString();
|
|
754
|
+
const core = {
|
|
755
|
+
schema:
|
|
756
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_REQUEST_SCHEMA,
|
|
757
|
+
tenantId: current.tenantId,
|
|
758
|
+
policyId: current.policyId,
|
|
759
|
+
revision: current.revision,
|
|
760
|
+
policyDigest: current.policyDigest,
|
|
761
|
+
requiredApprovals: current.requiredApprovals,
|
|
762
|
+
operation: value.operation,
|
|
763
|
+
operatorId: value.operatorId,
|
|
764
|
+
keyId,
|
|
765
|
+
publicKeySpki,
|
|
766
|
+
priorKeyId,
|
|
767
|
+
reason,
|
|
768
|
+
requestedAt,
|
|
769
|
+
expiresAt: new Date(currentTime + 5 * 60 * 1000).toISOString(),
|
|
770
|
+
};
|
|
771
|
+
const request = freeze({
|
|
772
|
+
...core,
|
|
773
|
+
requestDigest:
|
|
774
|
+
digestGovernedSkillSynthesisAttestorTrustOperatorRegistryChangeRequest(
|
|
775
|
+
core,
|
|
776
|
+
),
|
|
777
|
+
});
|
|
778
|
+
validateChangeRequest(request, current, currentTime);
|
|
779
|
+
applyChange(request, current, history);
|
|
780
|
+
return request;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function changeResult(record, event, recovered) {
|
|
784
|
+
return freeze({
|
|
785
|
+
authorization: structuredClone(record.authorization),
|
|
786
|
+
persistence: {
|
|
787
|
+
authenticated: true,
|
|
788
|
+
durable: true,
|
|
789
|
+
recovered,
|
|
790
|
+
recordDigest: record.recordDigest,
|
|
791
|
+
eventSequence: event.sequence,
|
|
792
|
+
ledgerEventDigest: event.eventDigest,
|
|
793
|
+
},
|
|
794
|
+
registry: {
|
|
795
|
+
tenantId: record.tenantId,
|
|
796
|
+
policyId: record.policyId,
|
|
797
|
+
revision: record.revision,
|
|
798
|
+
requiredApprovals: record.requiredApprovals,
|
|
799
|
+
operatorCount: record.operatorCount,
|
|
800
|
+
policyDigest: record.policyDigest,
|
|
801
|
+
recordDigest: record.recordDigest,
|
|
802
|
+
operators: record.operators.map(({ operatorId, keyId }) => ({
|
|
803
|
+
operatorId,
|
|
804
|
+
keyId,
|
|
805
|
+
})),
|
|
806
|
+
},
|
|
807
|
+
rebindRequired: true,
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
export function createGovernedSkillSynthesisAttestorTrustOperatorRegistryApprovalIssuer({
|
|
812
|
+
tenantId: tenantIdInput,
|
|
813
|
+
operatorId: operatorIdInput,
|
|
814
|
+
privateKey: privateKeyInput,
|
|
815
|
+
now = Date.now,
|
|
816
|
+
} = {}) {
|
|
817
|
+
const tenantId = identifier(tenantIdInput, "tenantId");
|
|
818
|
+
const operatorId = identifier(operatorIdInput, "operatorId");
|
|
819
|
+
if (typeof now !== "function" || utilTypes.isProxy(now)) {
|
|
820
|
+
throw new TypeError("operator registry approval issuer clock is invalid");
|
|
821
|
+
}
|
|
822
|
+
let privateKey;
|
|
823
|
+
try {
|
|
824
|
+
privateKey = utilTypes.isKeyObject(privateKeyInput)
|
|
825
|
+
? privateKeyInput
|
|
826
|
+
: createPrivateKey(privateKeyInput);
|
|
827
|
+
} catch {
|
|
828
|
+
throw new TypeError(
|
|
829
|
+
"operator registry approval privateKey must be Ed25519",
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
if (
|
|
833
|
+
privateKey.type !== "private" ||
|
|
834
|
+
privateKey.asymmetricKeyType !== "ed25519"
|
|
835
|
+
) {
|
|
836
|
+
throw new TypeError(
|
|
837
|
+
"operator registry approval privateKey must be Ed25519",
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
const publicIdentity = publicKeyIdentity(
|
|
841
|
+
createPublicKey(privateKey),
|
|
842
|
+
"operator approval publicKey",
|
|
843
|
+
);
|
|
844
|
+
const issuer = Object.freeze({
|
|
845
|
+
tenantId,
|
|
846
|
+
operatorId,
|
|
847
|
+
keyId: publicIdentity.keyId,
|
|
848
|
+
issue(request) {
|
|
849
|
+
const currentTime = Number(now());
|
|
850
|
+
if (!Number.isFinite(currentTime)) {
|
|
851
|
+
throw new TypeError(
|
|
852
|
+
"operator registry approval issuer clock is invalid",
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
validateChangeRequest(
|
|
856
|
+
request,
|
|
857
|
+
{
|
|
858
|
+
tenantId,
|
|
859
|
+
policyId: request?.policyId,
|
|
860
|
+
revision: request?.revision,
|
|
861
|
+
policyDigest: request?.policyDigest,
|
|
862
|
+
requiredApprovals: request?.requiredApprovals,
|
|
863
|
+
},
|
|
864
|
+
currentTime,
|
|
865
|
+
);
|
|
866
|
+
const core = {
|
|
867
|
+
schema:
|
|
868
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_APPROVAL_SCHEMA,
|
|
869
|
+
tenantId,
|
|
870
|
+
operatorId,
|
|
871
|
+
automated: false,
|
|
872
|
+
requestDigest: request.requestDigest,
|
|
873
|
+
policyDigest: request.policyDigest,
|
|
874
|
+
approvedAt: new Date(currentTime).toISOString(),
|
|
875
|
+
expiresAt: request.expiresAt,
|
|
876
|
+
};
|
|
877
|
+
const receiptDigest =
|
|
878
|
+
digestGovernedSkillSynthesisAttestorTrustOperatorRegistryApproval(core);
|
|
879
|
+
return freeze({
|
|
880
|
+
...core,
|
|
881
|
+
receiptDigest,
|
|
882
|
+
attestation: {
|
|
883
|
+
algorithm: "Ed25519",
|
|
884
|
+
keyId: publicIdentity.keyId,
|
|
885
|
+
value: sign(
|
|
886
|
+
null,
|
|
887
|
+
governedSkillSynthesisAttestorTrustOperatorRegistryApprovalMessage(
|
|
888
|
+
receiptDigest,
|
|
889
|
+
),
|
|
890
|
+
privateKey,
|
|
891
|
+
).toString("base64url"),
|
|
892
|
+
},
|
|
893
|
+
});
|
|
894
|
+
},
|
|
895
|
+
});
|
|
896
|
+
ISSUERS.add(issuer);
|
|
897
|
+
return issuer;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
export function isGovernedSkillSynthesisAttestorTrustOperatorRegistryApprovalIssuer(
|
|
901
|
+
value,
|
|
902
|
+
) {
|
|
903
|
+
return ISSUERS.has(value);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function parseArtifact(resolution, descriptor) {
|
|
907
|
+
if (
|
|
908
|
+
resolution?.schema !== EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA ||
|
|
909
|
+
resolution.authenticated !== true ||
|
|
910
|
+
resolution.found !== true ||
|
|
911
|
+
!DIGEST.test(resolution.digest ?? "") ||
|
|
912
|
+
!DIGEST.test(resolution.receiptDigest ?? "") ||
|
|
913
|
+
!Buffer.isBuffer(resolution.bytes)
|
|
914
|
+
) {
|
|
915
|
+
corrupt("operator registry artifact resolution is incomplete");
|
|
916
|
+
}
|
|
917
|
+
let artifact;
|
|
918
|
+
try {
|
|
919
|
+
artifact = JSON.parse(resolution.bytes.toString("utf8"));
|
|
920
|
+
} catch {
|
|
921
|
+
corrupt("operator registry artifact is not JSON");
|
|
922
|
+
}
|
|
923
|
+
if (
|
|
924
|
+
artifact?.schema !== EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA ||
|
|
925
|
+
artifact.tenantId !== descriptor.artifactTenantId ||
|
|
926
|
+
artifact.audience !== descriptor.audience ||
|
|
927
|
+
artifact.purpose !== descriptor.purpose ||
|
|
928
|
+
artifact.retention !== "ledger" ||
|
|
929
|
+
artifact.type !== ARTIFACT_TYPE
|
|
930
|
+
) {
|
|
931
|
+
corrupt("operator registry durable artifact binding is invalid");
|
|
932
|
+
}
|
|
933
|
+
return artifact.value?.schema ===
|
|
934
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_RECORD_SCHEMA
|
|
935
|
+
? validateChangeRecord(artifact.value, descriptor)
|
|
936
|
+
: validateGenesisRecord(artifact.value, descriptor);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function sameConfiguration(record, expected) {
|
|
940
|
+
return (
|
|
941
|
+
canonical({
|
|
942
|
+
tenantId: record.tenantId,
|
|
943
|
+
policyId: record.policyId,
|
|
944
|
+
revision: record.revision,
|
|
945
|
+
requiredApprovals: record.requiredApprovals,
|
|
946
|
+
operatorCount: record.operatorCount,
|
|
947
|
+
operators: record.operators,
|
|
948
|
+
policyDigest: record.policyDigest,
|
|
949
|
+
}) === canonical(expected)
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function snapshot(record, recovered) {
|
|
954
|
+
return freeze({
|
|
955
|
+
authenticated: true,
|
|
956
|
+
durable: true,
|
|
957
|
+
recovered,
|
|
958
|
+
recordDigest: record.recordDigest,
|
|
959
|
+
policyDigest: record.policyDigest,
|
|
960
|
+
policyId: record.policyId,
|
|
961
|
+
revision: record.revision,
|
|
962
|
+
requiredApprovals: record.requiredApprovals,
|
|
963
|
+
operatorCount: record.operatorCount,
|
|
964
|
+
operatorIdentities: record.operators.map((operator) => ({
|
|
965
|
+
tenantId: record.tenantId,
|
|
966
|
+
operatorId: operator.operatorId,
|
|
967
|
+
publicKey: decodePublicKey(operator).export({
|
|
968
|
+
type: "spki",
|
|
969
|
+
format: "pem",
|
|
970
|
+
}),
|
|
971
|
+
})),
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
export function createGovernedSkillSynthesisAttestorTrustOperatorRegistry({
|
|
976
|
+
descriptor: input,
|
|
977
|
+
artifactPorts,
|
|
978
|
+
ledger,
|
|
979
|
+
ledgerArtifactResolver,
|
|
980
|
+
now = Date.now,
|
|
981
|
+
} = {}) {
|
|
982
|
+
const descriptor = Object.freeze({
|
|
983
|
+
schema: GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_SCHEMA,
|
|
984
|
+
tenantId: identifier(input?.tenantId, "tenantId"),
|
|
985
|
+
artifactTenantId: identifier(input?.artifactTenantId, "artifactTenantId"),
|
|
986
|
+
streamId: identifier(input?.streamId, "streamId"),
|
|
987
|
+
audience: identifier(input?.audience, "audience"),
|
|
988
|
+
purpose: identifier(input?.purpose, "purpose"),
|
|
989
|
+
});
|
|
990
|
+
const put = capture(artifactPorts, "putCanonical", "artifactPorts");
|
|
991
|
+
const readLedger = capture(ledger, "read", "ledger");
|
|
992
|
+
const verifyLedger = capture(ledger, "verify", "ledger");
|
|
993
|
+
const appendLedger = capture(ledger, "appendDomainEvent", "ledger");
|
|
994
|
+
if (!isEvolutionLedgerArtifactResolver(ledgerArtifactResolver)) {
|
|
995
|
+
throw new TypeError(
|
|
996
|
+
"a branded EvolutionArtifactPorts ledger resolver is required",
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
if (typeof now !== "function" || utilTypes.isProxy(now)) {
|
|
1000
|
+
throw new TypeError("operator registry clock is invalid");
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
const resolveEvent = async (event) => {
|
|
1004
|
+
const authority = verifyLedger();
|
|
1005
|
+
const resolution = await ledgerArtifactResolver({
|
|
1006
|
+
epoch: authority.epoch,
|
|
1007
|
+
ledgerId: authority.ledgerId,
|
|
1008
|
+
ref: event.subjectRef,
|
|
1009
|
+
tenantId: descriptor.artifactTenantId,
|
|
1010
|
+
});
|
|
1011
|
+
if (
|
|
1012
|
+
resolution?.ref !== event.subjectRef.ref ||
|
|
1013
|
+
resolution?.digest !== event.subjectRef.digest
|
|
1014
|
+
) {
|
|
1015
|
+
corrupt("operator registry resolved a substituted artifact");
|
|
1016
|
+
}
|
|
1017
|
+
return parseArtifact(resolution, descriptor);
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
const load = async () => {
|
|
1021
|
+
const ledgerEvents = readLedger();
|
|
1022
|
+
if (!Array.isArray(ledgerEvents)) {
|
|
1023
|
+
corrupt("EvolutionLedger returned no operator registry events");
|
|
1024
|
+
}
|
|
1025
|
+
const events = ledgerEvents
|
|
1026
|
+
.filter(
|
|
1027
|
+
(event) =>
|
|
1028
|
+
event.schema === EVOLUTION_LEDGER_DOMAIN_EVENT_SCHEMA &&
|
|
1029
|
+
event.tenantId === descriptor.tenantId &&
|
|
1030
|
+
event.correlationId === descriptor.streamId &&
|
|
1031
|
+
[
|
|
1032
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_GENESIS_EVENT,
|
|
1033
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_EVENT,
|
|
1034
|
+
].includes(event.type),
|
|
1035
|
+
)
|
|
1036
|
+
.sort((left, right) => left.sequence - right.sequence);
|
|
1037
|
+
if (events.length === 0) return null;
|
|
1038
|
+
const genesisEvents = events.filter(
|
|
1039
|
+
(event) =>
|
|
1040
|
+
event.type ===
|
|
1041
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_GENESIS_EVENT,
|
|
1042
|
+
);
|
|
1043
|
+
if (genesisEvents.length !== 1 || events[0] !== genesisEvents[0]) {
|
|
1044
|
+
corrupt("operator registry genesis is missing or ambiguous");
|
|
1045
|
+
}
|
|
1046
|
+
let event = events[0];
|
|
1047
|
+
let record = await resolveEvent(event);
|
|
1048
|
+
if (
|
|
1049
|
+
record.schema !==
|
|
1050
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_RECORD_SCHEMA ||
|
|
1051
|
+
event.eventId !==
|
|
1052
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_GENESIS_EVENT ||
|
|
1053
|
+
event.decision !== "committed" ||
|
|
1054
|
+
event.timestamp !== record.createdAt ||
|
|
1055
|
+
!Array.isArray(event.sourceRefs) ||
|
|
1056
|
+
event.sourceRefs.length !== 0
|
|
1057
|
+
) {
|
|
1058
|
+
corrupt("operator registry genesis event binding is invalid");
|
|
1059
|
+
}
|
|
1060
|
+
const history = {
|
|
1061
|
+
operatorIds: new Set(record.operators.map((entry) => entry.operatorId)),
|
|
1062
|
+
keyIds: new Set(record.operators.map((entry) => entry.keyId)),
|
|
1063
|
+
};
|
|
1064
|
+
const changes = [];
|
|
1065
|
+
for (const nextEvent of events.slice(1)) {
|
|
1066
|
+
const next = await resolveEvent(nextEvent);
|
|
1067
|
+
if (
|
|
1068
|
+
next.schema !==
|
|
1069
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_RECORD_SCHEMA ||
|
|
1070
|
+
next.priorRecordDigest !== record.recordDigest ||
|
|
1071
|
+
nextEvent.eventId !==
|
|
1072
|
+
`${GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_EVENT}.${next.request.requestDigest.slice(7)}` ||
|
|
1073
|
+
nextEvent.decision !== "committed" ||
|
|
1074
|
+
nextEvent.timestamp !== next.effectiveAt ||
|
|
1075
|
+
!Array.isArray(nextEvent.sourceRefs) ||
|
|
1076
|
+
nextEvent.sourceRefs.length !== 1 ||
|
|
1077
|
+
canonical(nextEvent.sourceRefs[0]) !== canonical(event.subjectRef)
|
|
1078
|
+
) {
|
|
1079
|
+
corrupt("operator registry change event lineage is invalid");
|
|
1080
|
+
}
|
|
1081
|
+
const authorizedAt = timestamp(
|
|
1082
|
+
next.authorization?.authorizedAt,
|
|
1083
|
+
"stored authorization authorizedAt",
|
|
1084
|
+
);
|
|
1085
|
+
try {
|
|
1086
|
+
validateChangeRequest(next.request, record, authorizedAt);
|
|
1087
|
+
} catch {
|
|
1088
|
+
corrupt("stored operator registry request is invalid");
|
|
1089
|
+
}
|
|
1090
|
+
let approvals;
|
|
1091
|
+
try {
|
|
1092
|
+
dense(next.approvals, "stored operator approvals");
|
|
1093
|
+
if (next.approvals.length !== record.requiredApprovals) {
|
|
1094
|
+
throw new Error();
|
|
1095
|
+
}
|
|
1096
|
+
const currentOperators = operatorMap(record);
|
|
1097
|
+
const seenOperators = new Set();
|
|
1098
|
+
const seenKeys = new Set();
|
|
1099
|
+
approvals = next.approvals.map((approval) => {
|
|
1100
|
+
const operator = currentOperators.get(approval?.operatorId);
|
|
1101
|
+
if (
|
|
1102
|
+
!operator ||
|
|
1103
|
+
seenOperators.has(operator.operatorId) ||
|
|
1104
|
+
seenKeys.has(operator.keyId)
|
|
1105
|
+
) {
|
|
1106
|
+
throw new Error();
|
|
1107
|
+
}
|
|
1108
|
+
normalizeApproval(approval, next.request, operator, authorizedAt);
|
|
1109
|
+
seenOperators.add(operator.operatorId);
|
|
1110
|
+
seenKeys.add(operator.keyId);
|
|
1111
|
+
return approval;
|
|
1112
|
+
});
|
|
1113
|
+
validateAuthorization(next.authorization, next.request, approvals);
|
|
1114
|
+
} catch {
|
|
1115
|
+
corrupt("stored operator registry approvals are invalid");
|
|
1116
|
+
}
|
|
1117
|
+
let expectedOperators;
|
|
1118
|
+
try {
|
|
1119
|
+
expectedOperators = applyChange(next.request, record, history);
|
|
1120
|
+
} catch {
|
|
1121
|
+
corrupt("stored operator registry transition is invalid");
|
|
1122
|
+
}
|
|
1123
|
+
const expectedRevision = record.revision + 1;
|
|
1124
|
+
const expectedPolicy = policyCore({
|
|
1125
|
+
tenantId: descriptor.tenantId,
|
|
1126
|
+
policyId: record.policyId,
|
|
1127
|
+
revision: expectedRevision,
|
|
1128
|
+
requiredApprovals: record.requiredApprovals,
|
|
1129
|
+
operators: expectedOperators,
|
|
1130
|
+
});
|
|
1131
|
+
const expectedPolicyDigest = hash(
|
|
1132
|
+
"chainlesschain.attestor-trust-operations-policy/v1",
|
|
1133
|
+
expectedPolicy,
|
|
1134
|
+
);
|
|
1135
|
+
if (
|
|
1136
|
+
next.policyId !== record.policyId ||
|
|
1137
|
+
next.revision !== expectedRevision ||
|
|
1138
|
+
next.requiredApprovals !== record.requiredApprovals ||
|
|
1139
|
+
next.operatorCount !== expectedOperators.length ||
|
|
1140
|
+
next.policyDigest !== expectedPolicyDigest ||
|
|
1141
|
+
canonical(next.operators) !== canonical(expectedOperators)
|
|
1142
|
+
) {
|
|
1143
|
+
corrupt("operator registry transition result is invalid");
|
|
1144
|
+
}
|
|
1145
|
+
history.operatorIds.add(next.request.operatorId);
|
|
1146
|
+
history.keyIds.add(next.request.keyId);
|
|
1147
|
+
changes.push({ event: nextEvent, record: next, prior: record });
|
|
1148
|
+
event = nextEvent;
|
|
1149
|
+
record = next;
|
|
1150
|
+
}
|
|
1151
|
+
return { event, record, history, changes };
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1154
|
+
const registry = Object.freeze({
|
|
1155
|
+
descriptor,
|
|
1156
|
+
async initialize({
|
|
1157
|
+
operatorIdentities,
|
|
1158
|
+
policyId: policyIdInput,
|
|
1159
|
+
revision: revisionInput,
|
|
1160
|
+
requiredApprovals: requiredApprovalsInput,
|
|
1161
|
+
} = {}) {
|
|
1162
|
+
const policyId = identifier(policyIdInput, "policyId");
|
|
1163
|
+
const revision = Number(revisionInput);
|
|
1164
|
+
const requiredApprovals = Number(requiredApprovalsInput);
|
|
1165
|
+
const operators = normalizeOperators(
|
|
1166
|
+
operatorIdentities,
|
|
1167
|
+
descriptor.tenantId,
|
|
1168
|
+
);
|
|
1169
|
+
if (!Number.isSafeInteger(revision) || revision < 1) {
|
|
1170
|
+
throw new TypeError("operator registry revision is invalid");
|
|
1171
|
+
}
|
|
1172
|
+
if (
|
|
1173
|
+
!Number.isSafeInteger(requiredApprovals) ||
|
|
1174
|
+
requiredApprovals < 1 ||
|
|
1175
|
+
requiredApprovals > operators.length
|
|
1176
|
+
) {
|
|
1177
|
+
throw new TypeError("operator registry approval threshold is invalid");
|
|
1178
|
+
}
|
|
1179
|
+
const policy = policyCore({
|
|
1180
|
+
tenantId: descriptor.tenantId,
|
|
1181
|
+
policyId,
|
|
1182
|
+
revision,
|
|
1183
|
+
requiredApprovals,
|
|
1184
|
+
operators,
|
|
1185
|
+
});
|
|
1186
|
+
const expected = {
|
|
1187
|
+
...policy,
|
|
1188
|
+
operatorCount: operators.length,
|
|
1189
|
+
operators,
|
|
1190
|
+
policyDigest: hash(
|
|
1191
|
+
"chainlesschain.attestor-trust-operations-policy/v1",
|
|
1192
|
+
policy,
|
|
1193
|
+
),
|
|
1194
|
+
};
|
|
1195
|
+
const existing = await load();
|
|
1196
|
+
if (existing) {
|
|
1197
|
+
if (!sameConfiguration(existing.record, expected)) {
|
|
1198
|
+
throw new Error(
|
|
1199
|
+
"operator registry bootstrap differs from its durable state",
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
return snapshot(existing.record, true);
|
|
1203
|
+
}
|
|
1204
|
+
const milliseconds = Number(now());
|
|
1205
|
+
if (!Number.isFinite(milliseconds)) {
|
|
1206
|
+
throw new TypeError("operator registry clock is invalid");
|
|
1207
|
+
}
|
|
1208
|
+
const core = {
|
|
1209
|
+
schema:
|
|
1210
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_RECORD_SCHEMA,
|
|
1211
|
+
tenantId: descriptor.tenantId,
|
|
1212
|
+
policyId,
|
|
1213
|
+
revision,
|
|
1214
|
+
requiredApprovals,
|
|
1215
|
+
operatorCount: operators.length,
|
|
1216
|
+
operators,
|
|
1217
|
+
policyDigest: expected.policyDigest,
|
|
1218
|
+
createdAt: new Date(milliseconds).toISOString(),
|
|
1219
|
+
};
|
|
1220
|
+
const record = freeze({
|
|
1221
|
+
...core,
|
|
1222
|
+
recordDigest:
|
|
1223
|
+
digestGovernedSkillSynthesisAttestorTrustOperatorRegistryRecord(core),
|
|
1224
|
+
});
|
|
1225
|
+
validateGenesisRecord(record, descriptor);
|
|
1226
|
+
const head = verifyLedger();
|
|
1227
|
+
const published = put(ARTIFACT_TYPE, record, {
|
|
1228
|
+
audience: descriptor.audience,
|
|
1229
|
+
purpose: descriptor.purpose,
|
|
1230
|
+
retention: "ledger",
|
|
1231
|
+
});
|
|
1232
|
+
if (
|
|
1233
|
+
!published?.ref ||
|
|
1234
|
+
published.receipt?.persisted !== true ||
|
|
1235
|
+
published.receipt?.readbackVerified !== true ||
|
|
1236
|
+
published.receipt?.integrityVerified !== true ||
|
|
1237
|
+
published.receipt?.retention !== "ledger"
|
|
1238
|
+
) {
|
|
1239
|
+
corrupt("operator registry genesis was not durably read back");
|
|
1240
|
+
}
|
|
1241
|
+
try {
|
|
1242
|
+
const receipt = appendLedger(
|
|
1243
|
+
{
|
|
1244
|
+
artifactTenantId: descriptor.artifactTenantId,
|
|
1245
|
+
correlationId: descriptor.streamId,
|
|
1246
|
+
decision: "committed",
|
|
1247
|
+
eventId:
|
|
1248
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_GENESIS_EVENT,
|
|
1249
|
+
reason: `attestor trust operator registry genesis with ${operators.length} operator(s)`,
|
|
1250
|
+
skillName: null,
|
|
1251
|
+
sourceRefs: [],
|
|
1252
|
+
subjectRef: published.ref,
|
|
1253
|
+
tenantId: descriptor.tenantId,
|
|
1254
|
+
timestamp: record.createdAt,
|
|
1255
|
+
type: GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_GENESIS_EVENT,
|
|
1256
|
+
},
|
|
1257
|
+
{
|
|
1258
|
+
expectedHeadDigest: head.headDigest,
|
|
1259
|
+
expectedSequence: head.sequence,
|
|
1260
|
+
},
|
|
1261
|
+
);
|
|
1262
|
+
if (receipt?.authenticated !== true || receipt.durable !== true) {
|
|
1263
|
+
corrupt("operator registry genesis append was not durable");
|
|
1264
|
+
}
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
const recovered = await load();
|
|
1267
|
+
if (!recovered || canonical(recovered.record) !== canonical(record)) {
|
|
1268
|
+
throw error;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
const stored = await load();
|
|
1272
|
+
if (!stored || canonical(stored.record) !== canonical(record)) {
|
|
1273
|
+
corrupt("operator registry genesis readback differs");
|
|
1274
|
+
}
|
|
1275
|
+
return snapshot(stored.record, false);
|
|
1276
|
+
},
|
|
1277
|
+
async snapshot() {
|
|
1278
|
+
const current = await load();
|
|
1279
|
+
if (!current) throw new Error("operator registry is not initialized");
|
|
1280
|
+
return snapshot(current.record, true);
|
|
1281
|
+
},
|
|
1282
|
+
async prepareChange(input) {
|
|
1283
|
+
const current = await load();
|
|
1284
|
+
if (!current) throw new Error("operator registry is not initialized");
|
|
1285
|
+
const currentTime = Number(now());
|
|
1286
|
+
if (!Number.isFinite(currentTime)) {
|
|
1287
|
+
throw new TypeError("operator registry clock is invalid");
|
|
1288
|
+
}
|
|
1289
|
+
return normalizeChangeInput(
|
|
1290
|
+
input,
|
|
1291
|
+
current.record,
|
|
1292
|
+
current.history,
|
|
1293
|
+
currentTime,
|
|
1294
|
+
);
|
|
1295
|
+
},
|
|
1296
|
+
async executeChange({ request, approvals } = {}) {
|
|
1297
|
+
const loaded = await load();
|
|
1298
|
+
if (!loaded) throw new Error("operator registry is not initialized");
|
|
1299
|
+
const existing = loaded.changes.find(
|
|
1300
|
+
(entry) =>
|
|
1301
|
+
entry.record.request.requestDigest === request?.requestDigest,
|
|
1302
|
+
);
|
|
1303
|
+
if (existing) {
|
|
1304
|
+
const supplied = Array.isArray(approvals)
|
|
1305
|
+
? [...approvals].sort((left, right) =>
|
|
1306
|
+
String(left?.operatorId).localeCompare(String(right?.operatorId)),
|
|
1307
|
+
)
|
|
1308
|
+
: approvals;
|
|
1309
|
+
if (
|
|
1310
|
+
canonical(existing.record.request) !== canonical(request) ||
|
|
1311
|
+
canonical(existing.record.approvals) !== canonical(supplied)
|
|
1312
|
+
) {
|
|
1313
|
+
throw new Error(
|
|
1314
|
+
"operator registry request already has a different durable authorization",
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
return changeResult(existing.record, existing.event, true);
|
|
1318
|
+
}
|
|
1319
|
+
const currentTime = Number(now());
|
|
1320
|
+
if (!Number.isFinite(currentTime)) {
|
|
1321
|
+
throw new TypeError("operator registry clock is invalid");
|
|
1322
|
+
}
|
|
1323
|
+
validateChangeRequest(request, loaded.record, currentTime);
|
|
1324
|
+
dense(approvals, "operator registry approvals");
|
|
1325
|
+
if (approvals.length !== loaded.record.requiredApprovals) {
|
|
1326
|
+
throw new Error(
|
|
1327
|
+
`operator registry change requires exactly ${loaded.record.requiredApprovals} approvals`,
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1330
|
+
const currentOperators = operatorMap(loaded.record);
|
|
1331
|
+
const seenOperators = new Set();
|
|
1332
|
+
const seenKeys = new Set();
|
|
1333
|
+
const accepted = approvals.map((approval) => {
|
|
1334
|
+
const operator = currentOperators.get(approval?.operatorId);
|
|
1335
|
+
if (
|
|
1336
|
+
!operator ||
|
|
1337
|
+
seenOperators.has(operator.operatorId) ||
|
|
1338
|
+
seenKeys.has(operator.keyId)
|
|
1339
|
+
) {
|
|
1340
|
+
throw new Error(
|
|
1341
|
+
"operator registry approvals must be from distinct active operators",
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
normalizeApproval(approval, request, operator, currentTime);
|
|
1345
|
+
seenOperators.add(operator.operatorId);
|
|
1346
|
+
seenKeys.add(operator.keyId);
|
|
1347
|
+
return approval;
|
|
1348
|
+
});
|
|
1349
|
+
accepted.sort((left, right) =>
|
|
1350
|
+
left.operatorId.localeCompare(right.operatorId),
|
|
1351
|
+
);
|
|
1352
|
+
const authorization = buildAuthorization(request, accepted);
|
|
1353
|
+
const operators = applyChange(request, loaded.record, loaded.history);
|
|
1354
|
+
const revision = loaded.record.revision + 1;
|
|
1355
|
+
const policy = policyCore({
|
|
1356
|
+
tenantId: descriptor.tenantId,
|
|
1357
|
+
policyId: loaded.record.policyId,
|
|
1358
|
+
revision,
|
|
1359
|
+
requiredApprovals: loaded.record.requiredApprovals,
|
|
1360
|
+
operators,
|
|
1361
|
+
});
|
|
1362
|
+
const core = {
|
|
1363
|
+
schema:
|
|
1364
|
+
GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_RECORD_SCHEMA,
|
|
1365
|
+
tenantId: descriptor.tenantId,
|
|
1366
|
+
request: structuredClone(request),
|
|
1367
|
+
approvals: structuredClone(accepted),
|
|
1368
|
+
authorization: structuredClone(authorization),
|
|
1369
|
+
priorRecordDigest: loaded.record.recordDigest,
|
|
1370
|
+
policyId: loaded.record.policyId,
|
|
1371
|
+
revision,
|
|
1372
|
+
requiredApprovals: loaded.record.requiredApprovals,
|
|
1373
|
+
operatorCount: operators.length,
|
|
1374
|
+
operators,
|
|
1375
|
+
policyDigest: hash(
|
|
1376
|
+
"chainlesschain.attestor-trust-operations-policy/v1",
|
|
1377
|
+
policy,
|
|
1378
|
+
),
|
|
1379
|
+
effectiveAt: authorization.authorizedAt,
|
|
1380
|
+
};
|
|
1381
|
+
const record = freeze({
|
|
1382
|
+
...core,
|
|
1383
|
+
recordDigest:
|
|
1384
|
+
digestGovernedSkillSynthesisAttestorTrustOperatorRegistryRecord(core),
|
|
1385
|
+
});
|
|
1386
|
+
validateChangeRecord(record, descriptor);
|
|
1387
|
+
const head = verifyLedger();
|
|
1388
|
+
const published = put(ARTIFACT_TYPE, record, {
|
|
1389
|
+
audience: descriptor.audience,
|
|
1390
|
+
purpose: descriptor.purpose,
|
|
1391
|
+
retention: "ledger",
|
|
1392
|
+
});
|
|
1393
|
+
if (
|
|
1394
|
+
!published?.ref ||
|
|
1395
|
+
published.receipt?.persisted !== true ||
|
|
1396
|
+
published.receipt?.readbackVerified !== true ||
|
|
1397
|
+
published.receipt?.integrityVerified !== true ||
|
|
1398
|
+
published.receipt?.retention !== "ledger"
|
|
1399
|
+
) {
|
|
1400
|
+
corrupt("operator registry change was not durably read back");
|
|
1401
|
+
}
|
|
1402
|
+
const eventId = `${GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_EVENT}.${request.requestDigest.slice(7)}`;
|
|
1403
|
+
let appendRecovered = false;
|
|
1404
|
+
try {
|
|
1405
|
+
const receipt = appendLedger(
|
|
1406
|
+
{
|
|
1407
|
+
artifactTenantId: descriptor.artifactTenantId,
|
|
1408
|
+
correlationId: descriptor.streamId,
|
|
1409
|
+
decision: "committed",
|
|
1410
|
+
eventId,
|
|
1411
|
+
reason: `attestor trust operator ${request.operation} authorized by ${accepted.length} operator(s)`,
|
|
1412
|
+
skillName: null,
|
|
1413
|
+
sourceRefs: [loaded.event.subjectRef],
|
|
1414
|
+
subjectRef: published.ref,
|
|
1415
|
+
tenantId: descriptor.tenantId,
|
|
1416
|
+
timestamp: authorization.authorizedAt,
|
|
1417
|
+
type: GOVERNED_SKILL_SYNTHESIS_ATTESTOR_TRUST_OPERATOR_REGISTRY_CHANGE_EVENT,
|
|
1418
|
+
},
|
|
1419
|
+
{
|
|
1420
|
+
expectedHeadDigest: head.headDigest,
|
|
1421
|
+
expectedSequence: head.sequence,
|
|
1422
|
+
},
|
|
1423
|
+
);
|
|
1424
|
+
if (receipt?.authenticated !== true || receipt.durable !== true) {
|
|
1425
|
+
corrupt("operator registry change append was not durable");
|
|
1426
|
+
}
|
|
1427
|
+
} catch (error) {
|
|
1428
|
+
const recovered = await load();
|
|
1429
|
+
const match = recovered?.changes.find(
|
|
1430
|
+
(entry) =>
|
|
1431
|
+
entry.record.request.requestDigest === request.requestDigest,
|
|
1432
|
+
);
|
|
1433
|
+
if (!match || canonical(match.record) !== canonical(record))
|
|
1434
|
+
throw error;
|
|
1435
|
+
appendRecovered = true;
|
|
1436
|
+
}
|
|
1437
|
+
const stored = await load();
|
|
1438
|
+
const committed = stored?.changes.find(
|
|
1439
|
+
(entry) => entry.record.request.requestDigest === request.requestDigest,
|
|
1440
|
+
);
|
|
1441
|
+
if (!committed || canonical(committed.record) !== canonical(record)) {
|
|
1442
|
+
corrupt("operator registry change readback differs");
|
|
1443
|
+
}
|
|
1444
|
+
return changeResult(committed.record, committed.event, appendRecovered);
|
|
1445
|
+
},
|
|
1446
|
+
});
|
|
1447
|
+
REGISTRIES.add(registry);
|
|
1448
|
+
return registry;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
export function isGovernedSkillSynthesisAttestorTrustOperatorRegistry(value) {
|
|
1452
|
+
return REGISTRIES.has(value);
|
|
1453
|
+
}
|