chainlesschain 0.166.18 → 0.166.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-CQSBl3hY.js → AIOps-BjMnsUci.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-IQt-vRLt.js → ActionButton-DdYFP03A.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DkHC8O7k.js → Analytics-Bb8TuuK_.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CSti2P2T.js → AppLayout-Kn3TIOtk.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-C57hxn3v.js → Artifacts-4IxpqI_b.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DByLntcf.js → Audit-BOV2caqU.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-D1Bd2EOa.js → BackgroundAgents-B86k-ay4.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-VstHzHng.js → Backup-C5_ie1e3.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CLhdexIf.js → BaseInput-xpwg6QCW.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BefC_JZS.js → Chat-CVZOcZau.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DbXMvgmn.js → ChatBubbleRenderer-D8fPYhUu.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-CEY982vx.js → Checkbox-MKTq3zdP.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Dcm_DTvJ.js → Codegen-JUk3wzep.js} +1 -1
- package/src/assets/web-panel/assets/{Col-C8SSu4KA.js → Col-JPvTA0kC.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BVV9vwRR.js → Community-Tqgl87NQ.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-B7c5Vo18.js → Compact-BuAFYBmN.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Tq9jSZ5a.js → Compliance-CXS9WIPZ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BJUjT09Q.js → Cowork-Ds2bFHrr.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CjV9PEaS.js → Cron-C_dV_OeW.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-Dl7ReL8F.js → Crosschain-BsO6tIRo.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CG8PjWmM.js → DID-rNOFqBac.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CDBM6wmF.js → Dashboard-C2fcKKVY.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-8_55bXTD.js → Dropdown-Y7Ndr0X_.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D3qOU1Cm.js → EmailListRenderer-DcP_uPmL.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bv5oWMZF.js → FamilyGuardDashboard-Cwn3gDzk.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-hpZ1xkKd.js → Federation-ClQuOsqb.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BN7GT191.js → FormItemContext-DfwtmlcK.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-PCRhfA7M.js → GenericCardRenderer-zNZhtbC5.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CIlP2vy7.js → Git-BZmdW-G5.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-81lgm7YL.js → Governance-CqBk4lEn.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Cs24xMOV.js → Inference-Dbmt3ZXk.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CrJLVCZo.js → KnowledgeGraph-hMTH6qf3.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DLyO8RKT.js → Logs-CkiYgiW2.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DBOqijrP.js → MarkdownRenderer-BL1FElXg.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-Dz7AbQZc.js → Marketplace-CbB6wfin.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BM0aFiWv.js → McpTools-BSwiFtte.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-C9SJzENX.js → Memory-B03PeHrz.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BO2HnNRk.js → MobileBridge-CRGrnd-X.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CK_jMz0l.js → MobileProjects-CE_5v2kO.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CfhrJsdO.js → Mtc-Dp11QVx1.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BE2ELxgI.js → MtcAudit-CYu-3JBu.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-CgR_E3Jd.js → Multisig-CGyJ0Xl3.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Cw9445_K.js → NLProgramming-CoWi_ov1.js} +1 -1
- package/src/assets/web-panel/assets/Notes-CgU9DDo9.js +7 -0
- package/src/assets/web-panel/assets/{NotificationSettings-DxHVh4uT.js → NotificationSettings-DX4I2b4W.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CwHc15Zs.js +1 -0
- package/src/assets/web-panel/assets/{Organization-pPXJtWo4.js → Organization-oopLO_tk.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-YamdTJwA.js → Overflow-BFVvjMyf.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BvqP53x7.js → P2P-CBy5t-Tk.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Blz1Xn2n.js → PdhVaultBrowser--YWJNsu6.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CgFRQqFJ.js → Permissions-C2dZ8scb.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CFeo3-6O.js → PersonalDataHub-Bs3l7Hlx.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-BkaRojEK.js → Pipeline-DEBWbZuD.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BNZEx2Qp.js → Privacy-CjHBb6NU.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-HUBOgEEK.js → ProjectInit-D2WipUz5.js} +2 -2
- package/src/assets/web-panel/assets/ProjectSettings-B6WfnrGB.js +2 -0
- package/src/assets/web-panel/assets/{Projects-Dc517BXp.js → Projects-FF_thRi6.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DTLf8bgj.js → Providers-CCv5M_Dv.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BBNjPszk.js → QrScannerModal-CGthxU54.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DCsHvk3G.js → QuickAsk-CnPmXPFb.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Dpr5YSop.js → Recommend-Df_tGEa9.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DmKnvqGg.js → RemoteSession-Z58N0Loe.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CSRfuurX.js → Reputation-CZTBQ2Vt.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CDj3pQ_w.js → Row-CdK3YpCQ.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DI0a_kNq.js → RssFeed-CFMVT5sb.js} +3 -3
- package/src/assets/web-panel/assets/{Search-jAy7B2nU.js → Search-DrF8RIvD.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CkHTqYhc.js → Security-Ddk2f2DM.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CP-0Nzga.js → Services-CCuK-VOo.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BkF5-p3n.js → Skeleton-DxCpHzeM.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BDu-utDE.js → Skills-t2oTDgcv.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CQvq_2Qb.js → Sla-Df63M9_R.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-T7Ap512g.js → SpeechSettings-l7ox6KuX.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Bt-PLvot.js → SyncSettings-COd_tgPp.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-yLEcCogg.js → Tasks-CzDCcrik.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-ByN3fmbr.js → Templates-D-vfxTGA.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DBI7hITH.js → Tenant-BlpT_Gxc.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B77_DlGX.js → Terminal-D5rq52Hg.js} +2 -2
- package/src/assets/web-panel/assets/TimelineRenderer-DdoMndkK.js +1 -0
- package/src/assets/web-panel/assets/{Tokens-BV2N6cR8.js → Tokens-BhgBsI6g.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CjXWGH3p.js → Trigger-DwwzoikZ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-uqXIq3WC.js → Trust-HkTpzXFb.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-D-6qu4vY.js → UkeySign-D9nk6Ehe.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CxGmx_2e.js → VideoEditing-BKk0rfoy.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DSq7WlAg.js → Wallet-IVWIU_Lq.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CDBiD57O.js → WebAuthn-CFTCpfsG.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-Z8f63TSb.js → WorkflowEditor-Dnf4Obif.js} +1 -1
- package/src/assets/web-panel/assets/{chat-B-UZFBjy.js → chat-D2snERdj.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CFk7K-7Q.js → colors-DKEjK1N1.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-X0IVMvgi.js → compact-item-BEDLwQdt.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DWg4j2n6.js → createContext-DA9iW1a0.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DyhgpXPf.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CkVCwNNF.js → hasIn-5l2xEcY9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BipwEbTA.js → index-5pW4t1iD.js} +1 -1
- package/src/assets/web-panel/assets/{index-5OcZHdzc.js → index-7P1b8HZo.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cvwu_rA_.js → index-B00GeekA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbIAWbma.js → index-B4QYBliR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNKFsBlz.js → index-B7_W_rYd.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoskgPt9.js → index-BO9dQjpt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CvOa-IFV.js → index-BZ5lY2zx.js} +1 -1
- package/src/assets/web-panel/assets/{index-_-9k3Tna.js → index-B_eCoZgW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbihMThv.js → index-BgfImyZI.js} +1 -1
- package/src/assets/web-panel/assets/{index-DLSBks8h.js → index-Bq5P3piS.js} +1 -1
- package/src/assets/web-panel/assets/{index-LU54bChK.js → index-Bqc8tdIF.js} +1 -1
- package/src/assets/web-panel/assets/{index-Btqk6zWb.js → index-C7sKbM5s.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8uEL8Uw.js → index-CGlotREA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLlP1E4X.js → index-CRPrHE7h.js} +1 -1
- package/src/assets/web-panel/assets/{index-DeWKPQGs.js → index-CX1x4AFn.js} +1 -1
- package/src/assets/web-panel/assets/{index-Db_5xhrU.js → index-CbTPvp2_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDfSD07S.js → index-CjWltbi_.js} +1 -1
- package/src/assets/web-panel/assets/index-Ck1jhFE0.js +1 -0
- package/src/assets/web-panel/assets/{index-BPBia4b-.js → index-CkMKuqUs.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1ZAOJRW.js → index-CmruhzFi.js} +1 -1
- package/src/assets/web-panel/assets/{index-P83LDnCz.js → index-Csfb-7hw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DcDggpWR.js → index-CxEtQYdi.js} +1 -1
- package/src/assets/web-panel/assets/{index-OjnAwUJo.js → index-D7z4zB_a.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-EJNxgR.js → index-DLoalqRX.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3-B3FWD.js → index-DU2TXJu-.js} +1 -1
- package/src/assets/web-panel/assets/{index-D92g4l3n.js → index-DXet1xol.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ca0mij1n.js → index-Dbd7eEex.js} +1 -1
- package/src/assets/web-panel/assets/{index-DCqpl_E_.js → index-Dbkghqn_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyWlOuak.js → index-Dg2a9owQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUBkvFPH.js → index-DpqtbXdE.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhVHoqGL.js → index-DqwPFbOb.js} +3 -3
- package/src/assets/web-panel/assets/{index-BG-GaZri.js → index-DvV03Ayk.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMCEXBRh.js → index-NJieh3T1.js} +1 -1
- package/src/assets/web-panel/assets/{index-B273IY-E.js → index-Td8ZwyoC.js} +1 -1
- package/src/assets/web-panel/assets/index-c8jJEHVs.js +1 -0
- package/src/assets/web-panel/assets/{index-BzXjYUmd.js → index-e-e40id6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCLwoATF.js → index-fccZmC_h.js} +1 -1
- package/src/assets/web-panel/assets/{index-CN8HZlLH.js → index-fgYk0QXO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BB4KHHcv.js → index-yYEQXYaU.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CFNgY8lJ.js → initDefaultProps-CPrcB1TC.js} +1 -1
- package/src/assets/web-panel/assets/{motion-D-5RrDzn.js → motion-BQ7mWRh_.js} +1 -1
- package/src/assets/web-panel/assets/{move-DN-L0Bd-.js → move-CzOEq99p.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-6mgLSeCK.js → mtc-parser-BlQI5jrw.js} +1 -1
- package/src/assets/web-panel/assets/{omit-ClWW8eTP.js → omit-DlW4gxmv.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Vystq3R8.js → pickAttrs-BBYms5nP.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-D0H9hK4W.js → placementArrow-az2MPJ_7.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B3iNRU-a.js → responsiveObserve-D1-ibEol.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CiMXe6am.js → slide-DHGqa4GA.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CGDZJ1uT.js → statusUtils-CYGDo6lD.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-D2zA6NkY.js → styleChecker-Cl2EM9K6.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-C1sYU6QE.js → useFlexGapSupport-DdYhzuv1.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BzUHSjg2.js → useFs-DWumPuBW.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CxB5yZCq.js → usePersonalDataHub-DxAO-Aqi.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BfUfMwtc.js → vnode-CplsQf_N.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CFYmj1TO.js → zoom-Cw5HXNSG.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +2 -2
- package/src/commands/agent.js +19 -1
- package/src/commands/evolution-knowledge.js +54 -0
- package/src/commands/evolution-workbench.js +83 -0
- package/src/commands/evolution.js +6 -1
- package/src/commands/serve.js +16 -0
- package/src/commands/skill.js +158 -16
- package/src/data/changelog.json +26 -0
- package/src/gateways/ws/session-protocol.js +2 -0
- package/src/gateways/ws/ws-agent-handler.js +124 -69
- package/src/gateways/ws/ws-server.js +19 -0
- package/src/generated/cc-agent-protocol.schema.json +7 -1
- package/src/harness/jsonl-session-store.js +25 -1
- package/src/index.js +1 -1
- package/src/lib/app-server/cli-agent-kernel-adapter.js +8 -0
- package/src/lib/app-server/graph-runtime.js +40 -1
- package/src/lib/app-server/server.js +181 -3
- package/src/lib/app-server/websocket-transport.js +2 -0
- package/src/lib/evolution/agent-evolution-runtime-composition-brand.js +17 -0
- package/src/lib/evolution/agent-evolution-runtime-composition.js +165 -1
- package/src/lib/evolution/controlled-skill-production-pilot.js +912 -0
- package/src/lib/evolution/evolution-artifact-ports.js +21 -0
- package/src/lib/evolution/evolution-raw-crypto-shred.js +185 -0
- package/src/lib/evolution/evolution-workbench-batch-executor.js +195 -0
- package/src/lib/evolution/evolution-workbench-cli-host.js +169 -0
- package/src/lib/evolution/evolution-workbench-metrics-ledger-adapter.js +648 -0
- package/src/lib/evolution/evolution-workbench-metrics.js +943 -0
- package/src/lib/evolution/evolution-workbench-projection.js +734 -0
- package/src/lib/evolution/evolution-workbench-version-control.js +272 -0
- package/src/lib/evolution/governed-knowledge-conflict-merge.js +352 -0
- package/src/lib/evolution/governed-knowledge-dependency-authority.js +273 -0
- package/src/lib/evolution/governed-knowledge-dependency-ledger-executor.js +646 -0
- package/src/lib/evolution/governed-knowledge-merge-ledger-executor.js +644 -0
- package/src/lib/evolution/governed-knowledge-merge-publisher-authority.js +282 -0
- package/src/lib/evolution/governed-knowledge-node-crypto-authority.js +348 -0
- package/src/lib/evolution/governed-knowledge-rbac-approval-authority.js +621 -0
- package/src/lib/evolution/governed-knowledge-review-host.js +249 -0
- package/src/lib/evolution/governed-knowledge-sync-ledger-adapter.js +643 -0
- package/src/lib/evolution/governed-knowledge-sync-merge-publisher.js +259 -0
- package/src/lib/evolution/governed-knowledge-sync.js +485 -0
- package/src/lib/evolution/governed-knowledge-trust-ledger.js +707 -0
- package/src/lib/evolution/governed-skill-marketplace.js +430 -0
- package/src/lib/evolution/governed-wiki-pruning.js +435 -0
- package/src/lib/evolution/skill-outcome-index-authority.js +268 -0
- package/src/lib/evolution/skill-outcome-source-catalog-authority.js +237 -0
- package/src/lib/public-ide-capability-manifest.js +4 -0
- package/src/lib/skill-outcome-authority.js +43 -0
- package/src/lib/skill-outcome-transcript-authority.js +252 -0
- package/src/lib/skill-retrieval-router-benchmark.js +135 -0
- package/src/lib/skill-retrieval-router.js +235 -0
- package/src/lib/skill-vector-authority.js +325 -0
- package/src/lib/skill-vector-process-authority.js +386 -0
- package/src/repl/agent-repl.js +7 -1
- package/src/runtime/agent-core.js +72 -6
- package/src/runtime/agent-runtime.js +42 -0
- package/src/runtime/headless-runner.js +32 -0
- package/src/runtime/headless-stream.js +36 -1
- package/src/runtime/runtime-factory.js +62 -4
- package/src/assets/web-panel/assets/Notes-DZimkTrb.js +0 -7
- package/src/assets/web-panel/assets/OrderTableRenderer-Iy-pnX56.js +0 -1
- package/src/assets/web-panel/assets/ProjectSettings-CFMmhROS.js +0 -2
- package/src/assets/web-panel/assets/TimelineRenderer-CsLTB_QO.js +0 -1
- package/src/assets/web-panel/assets/devWarning-B2IW1J_J.js +0 -1
- package/src/assets/web-panel/assets/index-BCp2ir-1.js +0 -1
- package/src/assets/web-panel/assets/index-Bw4iuzRD.js +0 -1
|
@@ -0,0 +1,621 @@
|
|
|
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 { getUserPermissions } from "../permission-engine.js";
|
|
11
|
+
|
|
12
|
+
export const GOVERNED_KNOWLEDGE_APPROVAL_RECEIPT_SCHEMA =
|
|
13
|
+
"chainlesschain.governed-knowledge-approval-receipt/v1";
|
|
14
|
+
export const GOVERNED_KNOWLEDGE_AUTHORIZATION_DECISION_SCHEMA =
|
|
15
|
+
"chainlesschain.governed-knowledge-authorization-decision/v1";
|
|
16
|
+
|
|
17
|
+
const RBAC_ADAPTERS = new WeakSet();
|
|
18
|
+
const APPROVAL_ISSUERS = new WeakSet();
|
|
19
|
+
const REVIEWER_REGISTRIES = new WeakSet();
|
|
20
|
+
const AUTHORITIES = new WeakSet();
|
|
21
|
+
const ID = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,255}$/u;
|
|
22
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
23
|
+
const RECEIPT_KEYS = new Set([
|
|
24
|
+
"schema",
|
|
25
|
+
"tenantId",
|
|
26
|
+
"reviewerId",
|
|
27
|
+
"automated",
|
|
28
|
+
"knowledgeId",
|
|
29
|
+
"scope",
|
|
30
|
+
"scopeId",
|
|
31
|
+
"action",
|
|
32
|
+
"contentDigest",
|
|
33
|
+
"approvedAt",
|
|
34
|
+
"expiresAt",
|
|
35
|
+
"receiptDigest",
|
|
36
|
+
"attestation",
|
|
37
|
+
]);
|
|
38
|
+
const ATTESTATION_KEYS = new Set(["algorithm", "keyId", "value"]);
|
|
39
|
+
const MAX_RECEIPT_AGE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
40
|
+
const MAX_FUTURE_SKEW_MS = 5 * 60 * 1000;
|
|
41
|
+
|
|
42
|
+
function exact(value, keys, label) {
|
|
43
|
+
if (
|
|
44
|
+
!value ||
|
|
45
|
+
typeof value !== "object" ||
|
|
46
|
+
Array.isArray(value) ||
|
|
47
|
+
utilTypes.isProxy(value) ||
|
|
48
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(value))
|
|
49
|
+
) {
|
|
50
|
+
throw new TypeError(`${label} must be a plain object`);
|
|
51
|
+
}
|
|
52
|
+
const actual = Reflect.ownKeys(value);
|
|
53
|
+
if (
|
|
54
|
+
actual.length !== keys.size ||
|
|
55
|
+
actual.some((key) => typeof key !== "string" || !keys.has(key))
|
|
56
|
+
) {
|
|
57
|
+
throw new TypeError(`${label} has an invalid shape`);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function identifier(value, label) {
|
|
63
|
+
if (typeof value !== "string" || !ID.test(value)) {
|
|
64
|
+
throw new TypeError(`${label} is invalid`);
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function canonical(value) {
|
|
70
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
71
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
72
|
+
return `{${Object.keys(value)
|
|
73
|
+
.sort()
|
|
74
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
|
|
75
|
+
.join(",")}}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function hash(domain, value) {
|
|
79
|
+
return `sha256:${createHash("sha256")
|
|
80
|
+
.update(domain)
|
|
81
|
+
.update("\0")
|
|
82
|
+
.update(canonical(value))
|
|
83
|
+
.digest("hex")}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function keyObject(value, kind, label) {
|
|
87
|
+
let key;
|
|
88
|
+
try {
|
|
89
|
+
if (
|
|
90
|
+
value &&
|
|
91
|
+
typeof value === "object" &&
|
|
92
|
+
!utilTypes.isProxy(value) &&
|
|
93
|
+
value.type === kind
|
|
94
|
+
) {
|
|
95
|
+
key = value;
|
|
96
|
+
} else {
|
|
97
|
+
key =
|
|
98
|
+
kind === "private" ? createPrivateKey(value) : createPublicKey(value);
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
throw new TypeError(`${label} is not a valid ${kind} key`);
|
|
102
|
+
}
|
|
103
|
+
if (key.asymmetricKeyType !== "ed25519") {
|
|
104
|
+
throw new TypeError(`${label} must be Ed25519`);
|
|
105
|
+
}
|
|
106
|
+
return key;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function publicKeyId(key) {
|
|
110
|
+
const spki = key.export({ format: "der", type: "spki" });
|
|
111
|
+
return `key:ed25519:${createHash("sha256").update(spki).digest("hex")}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function capture(owner, method, label) {
|
|
115
|
+
if (
|
|
116
|
+
!owner ||
|
|
117
|
+
typeof owner !== "object" ||
|
|
118
|
+
utilTypes.isProxy(owner) ||
|
|
119
|
+
typeof owner[method] !== "function"
|
|
120
|
+
) {
|
|
121
|
+
throw new TypeError(`${label}.${method}() is required`);
|
|
122
|
+
}
|
|
123
|
+
return (...args) => Reflect.apply(owner[method], owner, args);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function receiptCore(value) {
|
|
127
|
+
const core = structuredClone(value);
|
|
128
|
+
delete core.receiptDigest;
|
|
129
|
+
delete core.attestation;
|
|
130
|
+
return core;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function digestGovernedKnowledgeApprovalReceipt(value) {
|
|
134
|
+
return hash(GOVERNED_KNOWLEDGE_APPROVAL_RECEIPT_SCHEMA, receiptCore(value));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function governedKnowledgeApprovalSignatureMessage(receiptDigest) {
|
|
138
|
+
if (!DIGEST.test(receiptDigest ?? "")) {
|
|
139
|
+
throw new TypeError("approval receiptDigest is invalid");
|
|
140
|
+
}
|
|
141
|
+
return Buffer.from(
|
|
142
|
+
`chainlesschain.governed-knowledge-approval-signature/v1\0${receiptDigest}`,
|
|
143
|
+
"utf8",
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function receiptCoreFromInput(input, tenantId, reviewerId, now) {
|
|
148
|
+
const approvedAt = Date.parse(input?.approvedAt);
|
|
149
|
+
const expiresAt = Date.parse(input?.expiresAt);
|
|
150
|
+
if (
|
|
151
|
+
!input ||
|
|
152
|
+
typeof input !== "object" ||
|
|
153
|
+
Array.isArray(input) ||
|
|
154
|
+
input.tenantId !== tenantId ||
|
|
155
|
+
input.reviewerId !== reviewerId ||
|
|
156
|
+
input.automated !== false ||
|
|
157
|
+
!identifier(input.knowledgeId, "knowledgeId") ||
|
|
158
|
+
!["team", "org"].includes(input.scope) ||
|
|
159
|
+
!identifier(input.scopeId, "scopeId") ||
|
|
160
|
+
!["upsert", "tombstone", "revoke"].includes(input.action) ||
|
|
161
|
+
!DIGEST.test(input.contentDigest ?? "") ||
|
|
162
|
+
!Number.isFinite(approvedAt) ||
|
|
163
|
+
!Number.isFinite(expiresAt) ||
|
|
164
|
+
approvedAt > now + MAX_FUTURE_SKEW_MS ||
|
|
165
|
+
approvedAt < now - MAX_RECEIPT_AGE_MS ||
|
|
166
|
+
expiresAt <= now ||
|
|
167
|
+
expiresAt > approvedAt + MAX_RECEIPT_AGE_MS
|
|
168
|
+
) {
|
|
169
|
+
throw new Error("governed knowledge approval input is invalid");
|
|
170
|
+
}
|
|
171
|
+
return Object.freeze({
|
|
172
|
+
schema: GOVERNED_KNOWLEDGE_APPROVAL_RECEIPT_SCHEMA,
|
|
173
|
+
tenantId,
|
|
174
|
+
reviewerId,
|
|
175
|
+
automated: false,
|
|
176
|
+
knowledgeId: input.knowledgeId,
|
|
177
|
+
scope: input.scope,
|
|
178
|
+
scopeId: input.scopeId,
|
|
179
|
+
action: input.action,
|
|
180
|
+
contentDigest: input.contentDigest,
|
|
181
|
+
approvedAt: new Date(approvedAt).toISOString(),
|
|
182
|
+
expiresAt: new Date(expiresAt).toISOString(),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function createGovernedKnowledgeEd25519ApprovalIssuer({
|
|
187
|
+
tenantId: tenantIdInput,
|
|
188
|
+
reviewerId: reviewerIdInput,
|
|
189
|
+
privateKey: privateKeyInput,
|
|
190
|
+
now = Date.now,
|
|
191
|
+
} = {}) {
|
|
192
|
+
const tenantId = identifier(tenantIdInput, "tenantId");
|
|
193
|
+
const reviewerId = identifier(reviewerIdInput, "reviewerId");
|
|
194
|
+
if (typeof now !== "function" || utilTypes.isProxy(now)) {
|
|
195
|
+
throw new TypeError("now must be a non-proxy function");
|
|
196
|
+
}
|
|
197
|
+
const privateKey = keyObject(privateKeyInput, "private", "privateKey");
|
|
198
|
+
const keyId = publicKeyId(createPublicKey(privateKey));
|
|
199
|
+
const issuer = Object.freeze({
|
|
200
|
+
tenantId,
|
|
201
|
+
reviewerId,
|
|
202
|
+
keyId,
|
|
203
|
+
issue(input) {
|
|
204
|
+
const currentTime = Number(now());
|
|
205
|
+
if (!Number.isFinite(currentTime))
|
|
206
|
+
throw new TypeError("issuer clock is invalid");
|
|
207
|
+
const core = receiptCoreFromInput(
|
|
208
|
+
input,
|
|
209
|
+
tenantId,
|
|
210
|
+
reviewerId,
|
|
211
|
+
currentTime,
|
|
212
|
+
);
|
|
213
|
+
const receiptDigest = digestGovernedKnowledgeApprovalReceipt(core);
|
|
214
|
+
return Object.freeze({
|
|
215
|
+
...core,
|
|
216
|
+
receiptDigest,
|
|
217
|
+
attestation: Object.freeze({
|
|
218
|
+
algorithm: "Ed25519",
|
|
219
|
+
keyId,
|
|
220
|
+
value: sign(
|
|
221
|
+
null,
|
|
222
|
+
governedKnowledgeApprovalSignatureMessage(receiptDigest),
|
|
223
|
+
privateKey,
|
|
224
|
+
).toString("base64url"),
|
|
225
|
+
}),
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
APPROVAL_ISSUERS.add(issuer);
|
|
230
|
+
return issuer;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function isGovernedKnowledgeApprovalIssuer(value) {
|
|
234
|
+
return APPROVAL_ISSUERS.has(value);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function permissionForGovernedKnowledge({
|
|
238
|
+
scope,
|
|
239
|
+
scopeId,
|
|
240
|
+
operation,
|
|
241
|
+
action,
|
|
242
|
+
} = {}) {
|
|
243
|
+
if (!["project", "team", "org"].includes(scope)) {
|
|
244
|
+
throw new TypeError("knowledge permission scope is invalid");
|
|
245
|
+
}
|
|
246
|
+
identifier(scopeId, "scopeId");
|
|
247
|
+
if (!["publish", "receive"].includes(operation)) {
|
|
248
|
+
throw new TypeError("knowledge permission operation is invalid");
|
|
249
|
+
}
|
|
250
|
+
if (!["upsert", "tombstone", "revoke"].includes(action)) {
|
|
251
|
+
throw new TypeError("knowledge permission action is invalid");
|
|
252
|
+
}
|
|
253
|
+
const encodedScopeId = Buffer.from(scopeId, "utf8").toString("base64url");
|
|
254
|
+
return `knowledge:${scope}:${encodedScopeId}:${operation}:${action}`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function permissionAllowed(effectivePermissions, permission) {
|
|
258
|
+
if (
|
|
259
|
+
effectivePermissions.includes("*") ||
|
|
260
|
+
effectivePermissions.includes(permission)
|
|
261
|
+
) {
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
const segments = permission.split(":");
|
|
265
|
+
for (let length = segments.length - 1; length >= 1; length -= 1) {
|
|
266
|
+
if (
|
|
267
|
+
effectivePermissions.includes(`${segments.slice(0, length).join(":")}:*`)
|
|
268
|
+
) {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function createPermissionEngineGovernedKnowledgeRbac({
|
|
276
|
+
db,
|
|
277
|
+
tenantId: tenantIdInput,
|
|
278
|
+
now = Date.now,
|
|
279
|
+
} = {}) {
|
|
280
|
+
const tenantId = identifier(tenantIdInput, "tenantId");
|
|
281
|
+
if (!db || typeof db !== "object" || utilTypes.isProxy(db)) {
|
|
282
|
+
throw new TypeError("permission database is required");
|
|
283
|
+
}
|
|
284
|
+
if (typeof now !== "function" || utilTypes.isProxy(now)) {
|
|
285
|
+
throw new TypeError("now must be a non-proxy function");
|
|
286
|
+
}
|
|
287
|
+
const adapter = Object.freeze({
|
|
288
|
+
tenantId,
|
|
289
|
+
check({ principalId, permission } = {}) {
|
|
290
|
+
identifier(principalId, "principalId");
|
|
291
|
+
if (typeof permission !== "string" || permission.length > 1024) {
|
|
292
|
+
throw new TypeError("permission is invalid");
|
|
293
|
+
}
|
|
294
|
+
const nowMs = Number(now());
|
|
295
|
+
if (!Number.isFinite(nowMs)) throw new TypeError("RBAC clock is invalid");
|
|
296
|
+
const permissions = getUserPermissions(db, principalId, { nowMs });
|
|
297
|
+
const effectivePermissions = [...permissions.effectivePermissions].sort();
|
|
298
|
+
return Object.freeze({
|
|
299
|
+
authenticated: true,
|
|
300
|
+
tenantId,
|
|
301
|
+
principalId,
|
|
302
|
+
permission,
|
|
303
|
+
allowed: permissionAllowed(effectivePermissions, permission),
|
|
304
|
+
policyDigest: hash("chainlesschain.permission-engine-policy/v1", {
|
|
305
|
+
tenantId,
|
|
306
|
+
principalId,
|
|
307
|
+
permission,
|
|
308
|
+
effectivePermissions,
|
|
309
|
+
}),
|
|
310
|
+
});
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
RBAC_ADAPTERS.add(adapter);
|
|
314
|
+
return adapter;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function normalizeReviewers(reviewers, tenantId) {
|
|
318
|
+
if (
|
|
319
|
+
!Array.isArray(reviewers) ||
|
|
320
|
+
reviewers.length < 1 ||
|
|
321
|
+
reviewers.length > 256
|
|
322
|
+
) {
|
|
323
|
+
throw new TypeError("reviewerIdentities must contain 1..256 reviewers");
|
|
324
|
+
}
|
|
325
|
+
const result = new Map();
|
|
326
|
+
const keyIds = new Set();
|
|
327
|
+
for (const input of reviewers) {
|
|
328
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
329
|
+
throw new TypeError("reviewer identity is invalid");
|
|
330
|
+
}
|
|
331
|
+
if (input.tenantId !== tenantId) {
|
|
332
|
+
throw new Error("reviewer identity crossed its tenant boundary");
|
|
333
|
+
}
|
|
334
|
+
const reviewerId = identifier(input.reviewerId, "reviewerId");
|
|
335
|
+
const publicKey = keyObject(
|
|
336
|
+
input.publicKey,
|
|
337
|
+
"public",
|
|
338
|
+
"reviewer publicKey",
|
|
339
|
+
);
|
|
340
|
+
const keyId = publicKeyId(publicKey);
|
|
341
|
+
if (input.keyId !== undefined && input.keyId !== keyId) {
|
|
342
|
+
throw new Error("reviewer keyId does not bind its Ed25519 public key");
|
|
343
|
+
}
|
|
344
|
+
if (result.has(reviewerId) || keyIds.has(keyId)) {
|
|
345
|
+
throw new Error("reviewer identity or Ed25519 key is duplicated");
|
|
346
|
+
}
|
|
347
|
+
keyIds.add(keyId);
|
|
348
|
+
result.set(reviewerId, { publicKey, keyId });
|
|
349
|
+
}
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function createGovernedKnowledgeReviewerRegistry({
|
|
354
|
+
tenantId: tenantIdInput,
|
|
355
|
+
reviewerIdentities = null,
|
|
356
|
+
resolve = null,
|
|
357
|
+
} = {}) {
|
|
358
|
+
const tenantId = identifier(tenantIdInput, "tenantId");
|
|
359
|
+
let lookup;
|
|
360
|
+
if (reviewerIdentities !== null && resolve !== null) {
|
|
361
|
+
throw new TypeError(
|
|
362
|
+
"reviewer registry accepts identities or resolve, not both",
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
if (reviewerIdentities !== null) {
|
|
366
|
+
const reviewers = normalizeReviewers(reviewerIdentities, tenantId);
|
|
367
|
+
lookup = async ({ reviewerId, keyId }) => {
|
|
368
|
+
const reviewer = reviewers.get(reviewerId);
|
|
369
|
+
return reviewer?.keyId === keyId
|
|
370
|
+
? {
|
|
371
|
+
tenantId,
|
|
372
|
+
reviewerId,
|
|
373
|
+
keyId,
|
|
374
|
+
publicKey: reviewer.publicKey,
|
|
375
|
+
status: "active",
|
|
376
|
+
}
|
|
377
|
+
: null;
|
|
378
|
+
};
|
|
379
|
+
} else if (typeof resolve === "function" && !utilTypes.isProxy(resolve)) {
|
|
380
|
+
lookup = resolve;
|
|
381
|
+
} else {
|
|
382
|
+
throw new TypeError(
|
|
383
|
+
"reviewer registry identities or resolver are required",
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
const registry = Object.freeze({
|
|
387
|
+
tenantId,
|
|
388
|
+
async resolve({ tenantId: requestedTenantId, reviewerId, keyId } = {}) {
|
|
389
|
+
if (
|
|
390
|
+
requestedTenantId !== tenantId ||
|
|
391
|
+
!ID.test(reviewerId ?? "") ||
|
|
392
|
+
!ID.test(keyId ?? "")
|
|
393
|
+
) {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
const resolved = await lookup({ tenantId, reviewerId, keyId });
|
|
397
|
+
if (resolved === null) return null;
|
|
398
|
+
if (
|
|
399
|
+
!resolved ||
|
|
400
|
+
typeof resolved !== "object" ||
|
|
401
|
+
resolved.tenantId !== tenantId ||
|
|
402
|
+
resolved.reviewerId !== reviewerId ||
|
|
403
|
+
resolved.keyId !== keyId ||
|
|
404
|
+
resolved.status !== "active"
|
|
405
|
+
) {
|
|
406
|
+
throw new Error("reviewer registry returned an invalid identity");
|
|
407
|
+
}
|
|
408
|
+
const publicKey = keyObject(
|
|
409
|
+
resolved.publicKey,
|
|
410
|
+
"public",
|
|
411
|
+
"reviewer registry publicKey",
|
|
412
|
+
);
|
|
413
|
+
if (publicKeyId(publicKey) !== keyId) {
|
|
414
|
+
throw new Error("reviewer registry keyId does not bind its public key");
|
|
415
|
+
}
|
|
416
|
+
return Object.freeze({
|
|
417
|
+
tenantId,
|
|
418
|
+
reviewerId,
|
|
419
|
+
keyId,
|
|
420
|
+
publicKey,
|
|
421
|
+
status: "active",
|
|
422
|
+
});
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
REVIEWER_REGISTRIES.add(registry);
|
|
426
|
+
return registry;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export function isGovernedKnowledgeReviewerRegistry(value) {
|
|
430
|
+
return REVIEWER_REGISTRIES.has(value);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function validateApproval(
|
|
434
|
+
receipt,
|
|
435
|
+
knowledge,
|
|
436
|
+
tenantId,
|
|
437
|
+
reviewerRegistry,
|
|
438
|
+
now,
|
|
439
|
+
) {
|
|
440
|
+
exact(receipt, RECEIPT_KEYS, "knowledge approval receipt");
|
|
441
|
+
const attestation = exact(
|
|
442
|
+
receipt.attestation,
|
|
443
|
+
ATTESTATION_KEYS,
|
|
444
|
+
"knowledge approval attestation",
|
|
445
|
+
);
|
|
446
|
+
const reviewer = await reviewerRegistry.resolve({
|
|
447
|
+
tenantId,
|
|
448
|
+
reviewerId: receipt.reviewerId,
|
|
449
|
+
keyId: attestation.keyId,
|
|
450
|
+
});
|
|
451
|
+
const approvedAt = Date.parse(receipt.approvedAt);
|
|
452
|
+
const expiresAt = Date.parse(receipt.expiresAt);
|
|
453
|
+
if (
|
|
454
|
+
receipt.schema !== GOVERNED_KNOWLEDGE_APPROVAL_RECEIPT_SCHEMA ||
|
|
455
|
+
receipt.tenantId !== tenantId ||
|
|
456
|
+
receipt.automated !== false ||
|
|
457
|
+
receipt.knowledgeId !== knowledge.knowledgeId ||
|
|
458
|
+
receipt.scope !== knowledge.scope ||
|
|
459
|
+
receipt.scopeId !== knowledge.scopeId ||
|
|
460
|
+
receipt.action !== knowledge.action ||
|
|
461
|
+
receipt.contentDigest !== knowledge.contentDigest ||
|
|
462
|
+
!reviewer ||
|
|
463
|
+
!Number.isFinite(approvedAt) ||
|
|
464
|
+
!Number.isFinite(expiresAt) ||
|
|
465
|
+
new Date(approvedAt).toISOString() !== receipt.approvedAt ||
|
|
466
|
+
new Date(expiresAt).toISOString() !== receipt.expiresAt ||
|
|
467
|
+
approvedAt > now + MAX_FUTURE_SKEW_MS ||
|
|
468
|
+
approvedAt < now - MAX_RECEIPT_AGE_MS ||
|
|
469
|
+
expiresAt <= now ||
|
|
470
|
+
expiresAt > approvedAt + MAX_RECEIPT_AGE_MS ||
|
|
471
|
+
receipt.receiptDigest !== digestGovernedKnowledgeApprovalReceipt(receipt) ||
|
|
472
|
+
receipt.receiptDigest !== knowledge.approvalReceiptDigest ||
|
|
473
|
+
attestation.algorithm !== "Ed25519" ||
|
|
474
|
+
attestation.keyId !== reviewer.keyId ||
|
|
475
|
+
typeof attestation.value !== "string"
|
|
476
|
+
) {
|
|
477
|
+
throw new Error("knowledge approval receipt is not exactly bound");
|
|
478
|
+
}
|
|
479
|
+
const signature = Buffer.from(attestation.value, "base64url");
|
|
480
|
+
if (
|
|
481
|
+
signature.length !== 64 ||
|
|
482
|
+
signature.toString("base64url") !== attestation.value ||
|
|
483
|
+
!verify(
|
|
484
|
+
null,
|
|
485
|
+
governedKnowledgeApprovalSignatureMessage(receipt.receiptDigest),
|
|
486
|
+
reviewer.publicKey,
|
|
487
|
+
signature,
|
|
488
|
+
)
|
|
489
|
+
) {
|
|
490
|
+
throw new Error("knowledge approval receipt signature is invalid");
|
|
491
|
+
}
|
|
492
|
+
return receipt;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export function createGovernedKnowledgeRbacApprovalAuthority({
|
|
496
|
+
tenantId: tenantIdInput,
|
|
497
|
+
principalId: principalIdInput,
|
|
498
|
+
rbac,
|
|
499
|
+
approvalReader,
|
|
500
|
+
reviewerIdentities = null,
|
|
501
|
+
reviewerRegistry = null,
|
|
502
|
+
now = Date.now,
|
|
503
|
+
} = {}) {
|
|
504
|
+
const tenantId = identifier(tenantIdInput, "tenantId");
|
|
505
|
+
const principalId = identifier(principalIdInput, "principalId");
|
|
506
|
+
if (!RBAC_ADAPTERS.has(rbac) || rbac.tenantId !== tenantId) {
|
|
507
|
+
throw new TypeError(
|
|
508
|
+
"a same-tenant Permission Engine RBAC adapter is required",
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
if (typeof now !== "function" || utilTypes.isProxy(now)) {
|
|
512
|
+
throw new TypeError("now must be a non-proxy function");
|
|
513
|
+
}
|
|
514
|
+
const check = capture(rbac, "check", "rbac");
|
|
515
|
+
const readApproval = capture(approvalReader, "read", "approvalReader");
|
|
516
|
+
if (reviewerIdentities !== null && reviewerRegistry !== null) {
|
|
517
|
+
throw new TypeError(
|
|
518
|
+
"approval authority accepts identities or registry, not both",
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
const registry =
|
|
522
|
+
reviewerRegistry ??
|
|
523
|
+
createGovernedKnowledgeReviewerRegistry({
|
|
524
|
+
tenantId,
|
|
525
|
+
reviewerIdentities,
|
|
526
|
+
});
|
|
527
|
+
if (!REVIEWER_REGISTRIES.has(registry) || registry.tenantId !== tenantId) {
|
|
528
|
+
throw new TypeError("a same-tenant branded reviewer registry is required");
|
|
529
|
+
}
|
|
530
|
+
const authority = Object.freeze({
|
|
531
|
+
tenantId,
|
|
532
|
+
principalId,
|
|
533
|
+
async authorize({ operation, knowledge } = {}) {
|
|
534
|
+
if (
|
|
535
|
+
!knowledge ||
|
|
536
|
+
typeof knowledge !== "object" ||
|
|
537
|
+
Array.isArray(knowledge) ||
|
|
538
|
+
utilTypes.isProxy(knowledge) ||
|
|
539
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(knowledge)) ||
|
|
540
|
+
knowledge.tenantId !== tenantId
|
|
541
|
+
) {
|
|
542
|
+
throw new Error("knowledge authorization crossed its tenant boundary");
|
|
543
|
+
}
|
|
544
|
+
const permission = permissionForGovernedKnowledge({
|
|
545
|
+
scope: knowledge.scope,
|
|
546
|
+
scopeId: knowledge.scopeId,
|
|
547
|
+
operation,
|
|
548
|
+
action: knowledge.action,
|
|
549
|
+
});
|
|
550
|
+
const decision = await check({ principalId, permission });
|
|
551
|
+
if (
|
|
552
|
+
decision?.authenticated !== true ||
|
|
553
|
+
decision.allowed !== true ||
|
|
554
|
+
decision.tenantId !== tenantId ||
|
|
555
|
+
decision.principalId !== principalId ||
|
|
556
|
+
decision.permission !== permission ||
|
|
557
|
+
!DIGEST.test(decision.policyDigest ?? "")
|
|
558
|
+
) {
|
|
559
|
+
throw new Error("Permission Engine denied governed knowledge access");
|
|
560
|
+
}
|
|
561
|
+
let approvalReceiptDigest = null;
|
|
562
|
+
if (["team", "org"].includes(knowledge.scope)) {
|
|
563
|
+
if (!DIGEST.test(knowledge.approvalReceiptDigest ?? "")) {
|
|
564
|
+
throw new Error("team or org knowledge requires human approval");
|
|
565
|
+
}
|
|
566
|
+
const receipt = await readApproval({
|
|
567
|
+
tenantId,
|
|
568
|
+
receiptDigest: knowledge.approvalReceiptDigest,
|
|
569
|
+
});
|
|
570
|
+
const currentTime = Number(now());
|
|
571
|
+
if (!Number.isFinite(currentTime)) {
|
|
572
|
+
throw new TypeError("authorization authority clock is invalid");
|
|
573
|
+
}
|
|
574
|
+
await validateApproval(
|
|
575
|
+
receipt,
|
|
576
|
+
knowledge,
|
|
577
|
+
tenantId,
|
|
578
|
+
registry,
|
|
579
|
+
currentTime,
|
|
580
|
+
);
|
|
581
|
+
approvalReceiptDigest = receipt.receiptDigest;
|
|
582
|
+
}
|
|
583
|
+
const authorizedAtMs = Number(now());
|
|
584
|
+
if (!Number.isFinite(authorizedAtMs)) {
|
|
585
|
+
throw new TypeError("authorization authority clock is invalid");
|
|
586
|
+
}
|
|
587
|
+
const core = {
|
|
588
|
+
schema: GOVERNED_KNOWLEDGE_AUTHORIZATION_DECISION_SCHEMA,
|
|
589
|
+
tenantId,
|
|
590
|
+
principalId,
|
|
591
|
+
operation,
|
|
592
|
+
knowledgeId: knowledge.knowledgeId,
|
|
593
|
+
scope: knowledge.scope,
|
|
594
|
+
scopeId: knowledge.scopeId,
|
|
595
|
+
action: knowledge.action,
|
|
596
|
+
contentDigest: knowledge.contentDigest,
|
|
597
|
+
policyDigest: decision.policyDigest,
|
|
598
|
+
approvalReceiptDigest,
|
|
599
|
+
authorizedAt: new Date(authorizedAtMs).toISOString(),
|
|
600
|
+
};
|
|
601
|
+
return Object.freeze({
|
|
602
|
+
authenticated: true,
|
|
603
|
+
allowed: true,
|
|
604
|
+
tenantId,
|
|
605
|
+
knowledgeId: knowledge.knowledgeId,
|
|
606
|
+
scope: knowledge.scope,
|
|
607
|
+
scopeId: knowledge.scopeId,
|
|
608
|
+
receiptDigest: hash(
|
|
609
|
+
GOVERNED_KNOWLEDGE_AUTHORIZATION_DECISION_SCHEMA,
|
|
610
|
+
core,
|
|
611
|
+
),
|
|
612
|
+
});
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
AUTHORITIES.add(authority);
|
|
616
|
+
return authority;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export function isGovernedKnowledgeRbacApprovalAuthority(value) {
|
|
620
|
+
return AUTHORITIES.has(value);
|
|
621
|
+
}
|