chainlesschain 0.166.17 → 0.166.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -7
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-CVW2n2rh.js → AIOps-CQSBl3hY.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DtuM163O.js → ActionButton-IQt-vRLt.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BXvl_qHu.js → Analytics-DkHC8O7k.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-VDBTFKOp.js → AppLayout-CSti2P2T.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-BNz2xCiG.js → Artifacts-C57hxn3v.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DTu9Hn9P.js → Audit-DByLntcf.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-D2OCPzLe.js → BackgroundAgents-D1Bd2EOa.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-C9M-vG7G.js → Backup-VstHzHng.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BUkCNiUQ.js → BaseInput-CLhdexIf.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-OJ2ajF7l.js → Chat-BefC_JZS.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BdZt76Ab.js → ChatBubbleRenderer-DbXMvgmn.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-C1gXKDsc.js → Checkbox-CEY982vx.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BXbznLg7.js → Codegen-Dcm_DTvJ.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CbCq5N0i.js → Col-C8SSu4KA.js} +1 -1
- package/src/assets/web-panel/assets/{Community-V8Ks6Yun.js → Community-BVV9vwRR.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DkeHQTpQ.js → Compact-B7c5Vo18.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-JeLKwn4Q.js → Compliance-Tq9jSZ5a.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-azfetcBz.js → Cowork-BJUjT09Q.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-Q691i_Gp.js → Cron-CjV9PEaS.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BLjDRbr_.js → Crosschain-Dl7ReL8F.js} +1 -1
- package/src/assets/web-panel/assets/{DID-D8ptXplK.js → DID-CG8PjWmM.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Ctj2VVZL.js → Dashboard-CDBM6wmF.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-D8VgkMJ8.js → Dropdown-8_55bXTD.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CQPe5S6q.js → EmailListRenderer-D3qOU1Cm.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-ySA-7SiN.js → FamilyGuardDashboard-Bv5oWMZF.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-1PMD-fBf.js → Federation-hpZ1xkKd.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-C_jr6U4Q.js → FormItemContext-BN7GT191.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Bu3bKf2B.js → GenericCardRenderer-PCRhfA7M.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Dv4Fa_T5.js → Git-CIlP2vy7.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DOEy1n1k.js → Governance-81lgm7YL.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DkvqzWcJ.js → Inference-Cs24xMOV.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DdUJK949.js → KnowledgeGraph-CrJLVCZo.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CRelcdUZ.js → Logs-DLyO8RKT.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-0AHbbTA4.js → MarkdownRenderer-DBOqijrP.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BuuBKsev.js → Marketplace-Dz7AbQZc.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DlgpEmA1.js → McpTools-BM0aFiWv.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-DTfdqUGy.js → Memory-C9SJzENX.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CXLE5ZFO.js → MobileBridge-BO2HnNRk.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Dz25OB0n.js → MobileProjects-CK_jMz0l.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-kEL0fhgD.js → Mtc-CfhrJsdO.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BE4UvFJU.js → MtcAudit-BE2ELxgI.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-CSApSn8c.js → Multisig-CgR_E3Jd.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-D11FpKWx.js → NLProgramming-Cw9445_K.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CDL-D2CS.js → Notes-DZimkTrb.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C0Q6lO-c.js → NotificationSettings-DxHVh4uT.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Iy-pnX56.js +1 -0
- package/src/assets/web-panel/assets/{Organization-Dc-maVAq.js → Organization-pPXJtWo4.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-B5mUpSvi.js → Overflow-YamdTJwA.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-TjXV53wq.js → P2P-BvqP53x7.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-Blz1Xn2n.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-DKRzQ2O6.js → Permissions-CgFRQqFJ.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Dx2eqft8.js → PersonalDataHub-CFeo3-6O.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-CnYZMB5p.js → Pipeline-BkaRojEK.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BonKpPnp.js → Privacy-BNZEx2Qp.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DWO0Du3J.js → ProjectInit-HUBOgEEK.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C9jqyYRv.js → ProjectSettings-CFMmhROS.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-CYpKwXbz.js → Projects-Dc517BXp.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CW-fkwJq.js → Providers-DTLf8bgj.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Cw0J3Zud.js → QrScannerModal-BBNjPszk.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BH4A4IpR.js → QuickAsk-DCsHvk3G.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CDwMwJus.js → Recommend-Dpr5YSop.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DKIPwzTf.js → RemoteSession-DmKnvqGg.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CklDj7qS.js → Reputation-CSRfuurX.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DJWZKsH0.js → Row-CDj3pQ_w.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BfSLCewo.js → RssFeed-DI0a_kNq.js} +2 -2
- package/src/assets/web-panel/assets/{Search-B0IST--x.js → Search-jAy7B2nU.js} +1 -1
- package/src/assets/web-panel/assets/{Security-h1sYNWtt.js → Security-CkHTqYhc.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Gh0-oxI_.js → Services-CP-0Nzga.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CNDV_jOs.js → Skeleton-BkF5-p3n.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-ByCLTh1n.js → Skills-BDu-utDE.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BfyrJTJz.js → Sla-CQvq_2Qb.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BQzo9kNX.js → SpeechSettings-T7Ap512g.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-B6fS6z_1.js → SyncSettings-Bt-PLvot.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-C-uKCP-l.js → Tasks-yLEcCogg.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-D6oDy7wL.js → Templates-ByN3fmbr.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-D3LT2wNQ.js → Tenant-DBI7hITH.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BteLcv_O.js → Terminal-B77_DlGX.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DHHuxfqH.js → TimelineRenderer-CsLTB_QO.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BRQeykXw.js → Tokens-BV2N6cR8.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-p2XaFWEu.js → Trigger-CjXWGH3p.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BxadBMft.js → Trust-uqXIq3WC.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DRBP7LDk.js → UkeySign-D-6qu4vY.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CZ3NZkh7.js → VideoEditing-CxGmx_2e.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-C3NTBN7G.js → Wallet-DSq7WlAg.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C7LVgMlb.js → WebAuthn-CDBiD57O.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-0_Iveog7.js → WorkflowEditor-Z8f63TSb.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DzzluYo3.js → chat-B-UZFBjy.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DSMYXpaj.js → colors-CFk7K-7Q.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-W76u1VSV.js → compact-item-X0IVMvgi.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-otsESjb5.js → createContext-DWg4j2n6.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-B2IW1J_J.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-3sNLrvug.js → hasIn-CkVCwNNF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BdtmEKat.js → index-5OcZHdzc.js} +1 -1
- package/src/assets/web-panel/assets/{index-DS9WYDMf.js → index-B273IY-E.js} +1 -1
- package/src/assets/web-panel/assets/{index-NaO9AE-F.js → index-B3-B3FWD.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcGJtmHM.js → index-BB4KHHcv.js} +1 -1
- package/src/assets/web-panel/assets/index-BCp2ir-1.js +1 -0
- package/src/assets/web-panel/assets/{index-pVm_iUre.js → index-BG-GaZri.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpcQYzP7.js → index-BPBia4b-.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXLx9HfA.js → index-BipwEbTA.js} +1 -1
- package/src/assets/web-panel/assets/{index-uSCkbpZH.js → index-BoskgPt9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMmdIpLA.js → index-Btqk6zWb.js} +1 -1
- package/src/assets/web-panel/assets/index-Bw4iuzRD.js +1 -0
- package/src/assets/web-panel/assets/{index-DLDh1OW3.js → index-BzXjYUmd.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9X_jqXe.js → index-C1ZAOJRW.js} +1 -1
- package/src/assets/web-panel/assets/{index-vBKPcmAP.js → index-CCLwoATF.js} +1 -1
- package/src/assets/web-panel/assets/{index-vUmYkqmu.js → index-CDfSD07S.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7A5U2IH.js → index-CLlP1E4X.js} +1 -1
- package/src/assets/web-panel/assets/{index-ClOFeZ-t.js → index-CMCEXBRh.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cg86EzNp.js → index-CN8HZlLH.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ddd4mird.js → index-CNKFsBlz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CE7tSow-.js → index-CUBkvFPH.js} +1 -1
- package/src/assets/web-panel/assets/{index-GlIOE4ja.js → index-Ca0mij1n.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdU7IkKX.js → index-CbIAWbma.js} +1 -1
- package/src/assets/web-panel/assets/{index-UUHCwfNq.js → index-CbihMThv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRsD1PT2.js → index-CvOa-IFV.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2aht9-M.js → index-Cvwu_rA_.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJsQio5O.js → index-D-EJNxgR.js} +1 -1
- package/src/assets/web-panel/assets/{index-q7ftLqNt.js → index-D8uEL8Uw.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1I1AYJs.js → index-D92g4l3n.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZKAzEge.js → index-DCqpl_E_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKoFyHYQ.js → index-DLSBks8h.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDlDO2px.js → index-Db_5xhrU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CENWwjef.js → index-DcDggpWR.js} +1 -1
- package/src/assets/web-panel/assets/{index-DH4cl52p.js → index-DeWKPQGs.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTA8Iavv.js → index-DhVHoqGL.js} +3 -3
- package/src/assets/web-panel/assets/{index-DmpaC2yk.js → index-DyWlOuak.js} +1 -1
- package/src/assets/web-panel/assets/{index-BA8mZxd-.js → index-LU54bChK.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtFOt5x1.js → index-OjnAwUJo.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5LOYdYm.js → index-P83LDnCz.js} +1 -1
- package/src/assets/web-panel/assets/{index-MhQQ3h8q.js → index-_-9k3Tna.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DF6llUXV.js → initDefaultProps-CFNgY8lJ.js} +1 -1
- package/src/assets/web-panel/assets/{motion-vtDV8_5K.js → motion-D-5RrDzn.js} +1 -1
- package/src/assets/web-panel/assets/{move-B6c0bdy2.js → move-DN-L0Bd-.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DlLYWuBK.js → mtc-parser-6mgLSeCK.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BH0zOxLN.js → omit-ClWW8eTP.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-D9di8_In.js → pickAttrs-Vystq3R8.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BDSngufM.js → placementArrow-D0H9hK4W.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-C_EawMvP.js → responsiveObserve-B3iNRU-a.js} +1 -1
- package/src/assets/web-panel/assets/{slide-B_4wShxA.js → slide-CiMXe6am.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CbQu6qJ-.js → statusUtils-CGDZJ1uT.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CvXJWYXb.js → styleChecker-D2zA6NkY.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-jjY1XknO.js → useFlexGapSupport-C1sYU6QE.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CMXBdsK_.js → useFs-BzUHSjg2.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-YbAhhPXx.js → usePersonalDataHub-CxB5yZCq.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BFCTKc0H.js → vnode-BfUfMwtc.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CY9aBl5O.js → zoom-CFYmj1TO.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/agent.js +80 -2
- package/src/commands/evolution.js +33 -27
- package/src/commands/serve.js +11 -2
- package/src/data/changelog.json +5 -5
- package/src/gateways/ws/session-protocol.js +1 -0
- package/src/gateways/ws/ws-agent-handler.js +70 -2
- package/src/gateways/ws/ws-server.js +8 -0
- package/src/lib/app-server/cli-agent-kernel-adapter.js +4 -0
- package/src/lib/app-server/graph-runtime.js +115 -8
- package/src/lib/app-server/server.js +21 -2
- package/src/lib/app-server/websocket-transport.js +2 -0
- package/src/lib/evolution/agent-evolution-ingress.js +300 -0
- package/src/lib/evolution/agent-evolution-runtime-composition-brand.js +22 -0
- package/src/lib/evolution/agent-evolution-runtime-composition.js +369 -0
- package/src/lib/evolution/evidence-backed-wiki-maintainer.js +752 -0
- package/src/lib/evolution/evolution-artifact-ports.js +48 -0
- package/src/lib/evolution/evolution-eval-gate.js +11 -4
- package/src/lib/evolution/evolution-evidence-artifact-adapter.js +499 -0
- package/src/lib/evolution/evolution-evidence-projector.js +61 -1
- package/src/lib/evolution/evolution-file-witness.js +601 -0
- package/src/lib/evolution/evolution-ledger-file-backend.js +176 -0
- package/src/lib/evolution/evolution-ledger-ports.js +353 -16
- package/src/lib/evolution/evolution-ledger.js +1355 -54
- package/src/lib/evolution/evolution-run-ledger-adapter.js +409 -0
- package/src/lib/evolution/evolution-run-wiki-maintenance-source.js +300 -0
- package/src/lib/evolution/scheduled-batch-wiki-maintenance-source.js +368 -0
- package/src/lib/evolution/skill-candidate-registry.js +649 -0
- package/src/lib/evolution/skill-promotion-controller.js +109 -38
- package/src/lib/evolution/skill-promotion-review-ledger-adapter.js +451 -0
- package/src/lib/evolution/skill-promotion-review.js +793 -0
- package/src/lib/evolution/skill-registry-transition-capability.js +55 -0
- package/src/lib/evolution/skill-registry-transition-ledger-adapter.js +1280 -0
- package/src/lib/evolution/skill-registry-transition-source.js +372 -0
- package/src/lib/evolution/skill-release-registry.js +2191 -11
- package/src/lib/evolution/skill-target-matrix-eval.js +133 -10
- package/src/lib/evolution/structured-memory-agent-control-plane.js +222 -0
- package/src/lib/evolution/structured-memory-authority-ledger-adapter.js +351 -0
- package/src/lib/evolution/structured-memory-ledger-adapter.js +220 -0
- package/src/lib/evolution/structured-memory-policy-receipt-writer.js +164 -0
- package/src/lib/evolution/structured-memory-post-compact-hook.js +87 -0
- package/src/lib/evolution/structured-memory-promotion-receipt-writer.js +221 -0
- package/src/lib/evolution/structured-memory-semantic-review-pipeline.js +313 -0
- package/src/lib/evolution/wiki-maintainer-ledger-adapter.js +200 -0
- package/src/lib/evolution/wiki-maintenance-trigger-ledger-adapter.js +687 -0
- package/src/lib/evolution-system.js +65 -30
- package/src/lib/mcp-lifecycle-authority.js +21 -2
- package/src/lib/remote-approval-bridge.js +24 -0
- package/src/lib/scheduler-kernel/store.js +62 -14
- package/src/repl/agent-repl.js +45 -0
- package/src/runtime/agent-runtime.js +114 -1
- package/src/runtime/headless-runner.js +19 -0
- package/src/runtime/headless-stream.js +43 -0
- package/src/runtime/runtime-factory.js +19 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-ZRE0OKbj.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-DvV2btIr.js +0 -7
- package/src/assets/web-panel/assets/devWarning-BztaXF84.js +0 -1
- package/src/assets/web-panel/assets/index-4jP_hG_4.js +0 -1
- package/src/assets/web-panel/assets/index-zxwuAXL5.js +0 -1
- package/src/lib/learning/learning-hooks.js +0 -123
|
@@ -13,6 +13,12 @@ import {
|
|
|
13
13
|
verifySkillMutationRequest,
|
|
14
14
|
} from "./skill-mutation-authority.js";
|
|
15
15
|
import { captureSkillEvaluatedPromotionProvider } from "./skill-evaluated-promotion.js";
|
|
16
|
+
import { captureStructuredMemoryPromotionReceiptWriter } from "./structured-memory-promotion-receipt-writer.js";
|
|
17
|
+
import {
|
|
18
|
+
consumeRegistryTransitionCapability,
|
|
19
|
+
issueRegistryTransitionCapability,
|
|
20
|
+
} from "./skill-registry-transition-capability.js";
|
|
21
|
+
import { captureSkillPromotionReviewProvider } from "./skill-promotion-review.js";
|
|
16
22
|
|
|
17
23
|
const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/u;
|
|
18
24
|
const EMPTY_ACTIVE_DOMAIN = "chainlesschain.skill-active/empty/v1\0";
|
|
@@ -23,16 +29,24 @@ export const EMPTY_SKILL_ACTIVE_DIGEST = `sha256:${crypto
|
|
|
23
29
|
.update(EMPTY_ACTIVE_DOMAIN, "utf8")
|
|
24
30
|
.digest("hex")}`;
|
|
25
31
|
|
|
26
|
-
const TRANSITION_CAPABILITIES = new WeakMap();
|
|
27
32
|
const EVALUATED_CONTROL_PLANE_OPTION_KEYS = new Set([
|
|
28
33
|
"candidateRegistry",
|
|
29
34
|
"releaseRegistry",
|
|
30
35
|
"authority",
|
|
31
36
|
"evaluatedPromotionProvider",
|
|
37
|
+
"memoryPromotionReceiptWriter",
|
|
38
|
+
"promotionReviewProvider",
|
|
39
|
+
]);
|
|
40
|
+
const EVALUATED_CONTROL_PLANE_REQUIRED_KEYS = new Set([
|
|
41
|
+
"candidateRegistry",
|
|
42
|
+
"releaseRegistry",
|
|
43
|
+
"authority",
|
|
44
|
+
"evaluatedPromotionProvider",
|
|
32
45
|
]);
|
|
33
46
|
|
|
34
47
|
export const SKILL_EVALUATED_PROMOTION_CONTROL_PLANE_SCHEMA =
|
|
35
48
|
"chainlesschain.skill-evaluated-promotion-control-plane/v1";
|
|
49
|
+
const EVALUATED_PROMOTION_CONTROL_PLANES = new WeakSet();
|
|
36
50
|
|
|
37
51
|
export class SkillPromotionControllerError extends Error {
|
|
38
52
|
constructor(code, message, details = {}) {
|
|
@@ -211,40 +225,7 @@ function assertTransitionSubject(request, expectedDigest) {
|
|
|
211
225
|
}
|
|
212
226
|
}
|
|
213
227
|
|
|
214
|
-
|
|
215
|
-
const capability = Object.freeze(Object.create(null));
|
|
216
|
-
TRANSITION_CAPABILITIES.set(capability, {
|
|
217
|
-
registry,
|
|
218
|
-
payload: deepFreeze(payload),
|
|
219
|
-
status: "issued",
|
|
220
|
-
});
|
|
221
|
-
return capability;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Registry-only broker. It is exported solely to break the module boundary;
|
|
226
|
-
* callers cannot manufacture a WeakMap-backed capability or replay one.
|
|
227
|
-
*/
|
|
228
|
-
export function consumeRegistryTransitionCapability(capability, registry) {
|
|
229
|
-
const state =
|
|
230
|
-
capability && typeof capability === "object"
|
|
231
|
-
? TRANSITION_CAPABILITIES.get(capability)
|
|
232
|
-
: null;
|
|
233
|
-
if (!state || state.registry !== registry) {
|
|
234
|
-
throw failure(
|
|
235
|
-
"SKILL_PROMOTION_TRANSITION_CAPABILITY_INVALID",
|
|
236
|
-
"registry transition capability is forged or bound to another registry",
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
if (state.status !== "issued") {
|
|
240
|
-
throw failure(
|
|
241
|
-
"SKILL_PROMOTION_TRANSITION_CAPABILITY_REPLAYED",
|
|
242
|
-
"registry transition capability has already been consumed",
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
state.status = "consumed";
|
|
246
|
-
return state.payload;
|
|
247
|
-
}
|
|
228
|
+
export { consumeRegistryTransitionCapability };
|
|
248
229
|
|
|
249
230
|
export class SkillPromotionController {
|
|
250
231
|
#tenantId;
|
|
@@ -265,12 +246,21 @@ export class SkillPromotionController {
|
|
|
265
246
|
|
|
266
247
|
#requireEvaluatedPromotion;
|
|
267
248
|
|
|
249
|
+
#memoryPromotionReceiptWriter;
|
|
250
|
+
|
|
251
|
+
#promotionReviewProvider;
|
|
252
|
+
|
|
253
|
+
#requireHumanReview;
|
|
254
|
+
|
|
268
255
|
constructor({
|
|
269
256
|
candidateRegistry,
|
|
270
257
|
releaseRegistry,
|
|
271
258
|
authority,
|
|
272
259
|
evaluatedPromotionProvider = null,
|
|
273
260
|
requireEvaluatedPromotion = false,
|
|
261
|
+
memoryPromotionReceiptWriter = null,
|
|
262
|
+
promotionReviewProvider = null,
|
|
263
|
+
requireHumanReview = false,
|
|
274
264
|
} = {}) {
|
|
275
265
|
if (!candidateRegistry || typeof candidateRegistry.read !== "function") {
|
|
276
266
|
throw failure(
|
|
@@ -320,6 +310,18 @@ export class SkillPromotionController {
|
|
|
320
310
|
"evaluated-only promotion requires a captured evidence provider",
|
|
321
311
|
);
|
|
322
312
|
}
|
|
313
|
+
if (typeof requireHumanReview !== "boolean") {
|
|
314
|
+
throw failure(
|
|
315
|
+
"SKILL_PROMOTION_INVALID",
|
|
316
|
+
"requireHumanReview must be boolean",
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
if (requireHumanReview && promotionReviewProvider === null) {
|
|
320
|
+
throw failure(
|
|
321
|
+
"SKILL_PROMOTION_REVIEW_REQUIRED",
|
|
322
|
+
"human-reviewed promotion requires a captured review provider",
|
|
323
|
+
);
|
|
324
|
+
}
|
|
323
325
|
|
|
324
326
|
this.#tenantId = candidateRegistry.tenantId;
|
|
325
327
|
this.#registryIdentity = releaseRegistry;
|
|
@@ -334,6 +336,17 @@ export class SkillPromotionController {
|
|
|
334
336
|
? null
|
|
335
337
|
: captureSkillEvaluatedPromotionProvider(evaluatedPromotionProvider);
|
|
336
338
|
this.#requireEvaluatedPromotion = requireEvaluatedPromotion;
|
|
339
|
+
this.#memoryPromotionReceiptWriter =
|
|
340
|
+
memoryPromotionReceiptWriter === null
|
|
341
|
+
? null
|
|
342
|
+
: captureStructuredMemoryPromotionReceiptWriter(
|
|
343
|
+
memoryPromotionReceiptWriter,
|
|
344
|
+
);
|
|
345
|
+
this.#promotionReviewProvider =
|
|
346
|
+
promotionReviewProvider === null
|
|
347
|
+
? null
|
|
348
|
+
: captureSkillPromotionReviewProvider(promotionReviewProvider);
|
|
349
|
+
this.#requireHumanReview = requireHumanReview;
|
|
337
350
|
|
|
338
351
|
Object.freeze(candidateRegistry);
|
|
339
352
|
Object.freeze(releaseRegistry);
|
|
@@ -469,7 +482,11 @@ export class SkillPromotionController {
|
|
|
469
482
|
"direct promotion is disabled by the evaluated-only controller policy",
|
|
470
483
|
);
|
|
471
484
|
}
|
|
472
|
-
|
|
485
|
+
const result = await this.#promote(input);
|
|
486
|
+
if (this.#memoryPromotionReceiptWriter === null) return result;
|
|
487
|
+
const memoryAuthorityReceipt =
|
|
488
|
+
await this.#memoryPromotionReceiptWriter.retainPromotion(result);
|
|
489
|
+
return deepFreeze({ ...result, memoryAuthorityReceipt });
|
|
473
490
|
}
|
|
474
491
|
|
|
475
492
|
async promoteEvaluated(input = {}) {
|
|
@@ -494,11 +511,39 @@ export class SkillPromotionController {
|
|
|
494
511
|
state,
|
|
495
512
|
activeContentDigest,
|
|
496
513
|
});
|
|
514
|
+
let reviewBinding = null;
|
|
515
|
+
if (this.#requireHumanReview) {
|
|
516
|
+
const activeRelease =
|
|
517
|
+
state.activeReleaseDigest === null
|
|
518
|
+
? null
|
|
519
|
+
: this.#readRelease(state.activeReleaseDigest);
|
|
520
|
+
reviewBinding = await this.#promotionReviewProvider.verify({
|
|
521
|
+
activeRelease,
|
|
522
|
+
authorization: input.authorization,
|
|
523
|
+
candidate,
|
|
524
|
+
matrixBinding,
|
|
525
|
+
state,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
497
528
|
const result = await this.#promote({
|
|
498
529
|
authorization: input.authorization,
|
|
499
530
|
candidateId: input.candidateId,
|
|
500
531
|
});
|
|
501
|
-
|
|
532
|
+
if (this.#memoryPromotionReceiptWriter === null) {
|
|
533
|
+
return deepFreeze({ ...result, matrixBinding, reviewBinding });
|
|
534
|
+
}
|
|
535
|
+
const memoryAuthorityReceipt =
|
|
536
|
+
await this.#memoryPromotionReceiptWriter.retainPromotion(
|
|
537
|
+
result,
|
|
538
|
+
matrixBinding,
|
|
539
|
+
reviewBinding,
|
|
540
|
+
);
|
|
541
|
+
return deepFreeze({
|
|
542
|
+
...result,
|
|
543
|
+
matrixBinding,
|
|
544
|
+
reviewBinding,
|
|
545
|
+
memoryAuthorityReceipt,
|
|
546
|
+
});
|
|
502
547
|
}
|
|
503
548
|
|
|
504
549
|
async rollback(input = {}) {
|
|
@@ -591,27 +636,53 @@ export function createSkillEvaluatedPromotionControlPlane(options = {}) {
|
|
|
591
636
|
options,
|
|
592
637
|
EVALUATED_CONTROL_PLANE_OPTION_KEYS,
|
|
593
638
|
"evaluated promotion control plane options",
|
|
639
|
+
{ required: EVALUATED_CONTROL_PLANE_REQUIRED_KEYS },
|
|
594
640
|
);
|
|
595
641
|
const provider = captureSkillEvaluatedPromotionProvider(
|
|
596
642
|
options.evaluatedPromotionProvider,
|
|
597
643
|
);
|
|
644
|
+
const reviewProvider = captureSkillPromotionReviewProvider(
|
|
645
|
+
options.promotionReviewProvider,
|
|
646
|
+
);
|
|
598
647
|
const controller = new SkillPromotionController({
|
|
599
648
|
candidateRegistry: options.candidateRegistry,
|
|
600
649
|
releaseRegistry: options.releaseRegistry,
|
|
601
650
|
authority: options.authority,
|
|
602
651
|
evaluatedPromotionProvider: options.evaluatedPromotionProvider,
|
|
652
|
+
memoryPromotionReceiptWriter: options.memoryPromotionReceiptWriter ?? null,
|
|
653
|
+
promotionReviewProvider: options.promotionReviewProvider,
|
|
603
654
|
requireEvaluatedPromotion: true,
|
|
655
|
+
requireHumanReview: true,
|
|
604
656
|
});
|
|
605
|
-
|
|
657
|
+
const controlPlane = {
|
|
606
658
|
schema: SKILL_EVALUATED_PROMOTION_CONTROL_PLANE_SCHEMA,
|
|
607
659
|
tenantId: options.candidateRegistry.tenantId,
|
|
608
660
|
providerAuthorityId: provider.authorityId,
|
|
609
661
|
providerRevision: provider.revision,
|
|
610
662
|
providerHandlerArtifactDigest: provider.handlerArtifactDigest,
|
|
663
|
+
reviewAuthorityId: reviewProvider.authorityId,
|
|
664
|
+
reviewRevision: reviewProvider.revision,
|
|
665
|
+
reviewHandlerArtifactDigest: reviewProvider.handlerArtifactDigest,
|
|
611
666
|
promoteEvaluated: Object.freeze((input) =>
|
|
612
667
|
controller.promoteEvaluated(input),
|
|
613
668
|
),
|
|
614
669
|
rollback: Object.freeze((input) => controller.rollback(input)),
|
|
670
|
+
};
|
|
671
|
+
EVALUATED_PROMOTION_CONTROL_PLANES.add(controlPlane);
|
|
672
|
+
return Object.freeze(controlPlane);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
export function captureSkillEvaluatedPromotionControlPlane(value) {
|
|
676
|
+
if (!value || !EVALUATED_PROMOTION_CONTROL_PLANES.has(value)) {
|
|
677
|
+
throw failure(
|
|
678
|
+
"SKILL_PROMOTION_CONTROL_PLANE_REQUIRED",
|
|
679
|
+
"a branded evaluated promotion control plane is required",
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
return Object.freeze({
|
|
683
|
+
schema: value.schema,
|
|
684
|
+
tenantId: value.tenantId,
|
|
685
|
+
promoteEvaluated: value.promoteEvaluated.bind(value),
|
|
615
686
|
});
|
|
616
687
|
}
|
|
617
688
|
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA,
|
|
3
|
+
isEvolutionLedgerArtifactResolver,
|
|
4
|
+
} from "./evolution-artifact-ports.js";
|
|
5
|
+
import {
|
|
6
|
+
EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA,
|
|
7
|
+
EVOLUTION_LEDGER_DOMAIN_EVENT_SCHEMA,
|
|
8
|
+
} from "./evolution-ledger.js";
|
|
9
|
+
import {
|
|
10
|
+
SKILL_PROMOTION_REVIEW_DECISION_SCHEMA,
|
|
11
|
+
SKILL_PROMOTION_REVIEW_RESOLUTION_SCHEMA,
|
|
12
|
+
captureSkillPromotionReviewPacket,
|
|
13
|
+
verifySkillPromotionReviewDecision,
|
|
14
|
+
verifySkillPromotionReviewPacketArtifact,
|
|
15
|
+
} from "./skill-promotion-review.js";
|
|
16
|
+
|
|
17
|
+
export const SKILL_PROMOTION_REVIEW_REQUEST_EVENT_TYPE =
|
|
18
|
+
"skill.promotion-review.requested";
|
|
19
|
+
export const SKILL_PROMOTION_REVIEW_DECISION_EVENT_TYPE =
|
|
20
|
+
"skill.promotion-review.decided";
|
|
21
|
+
export const SKILL_PROMOTION_REVIEW_LEDGER_CORRUPT_CODE =
|
|
22
|
+
"CC_SKILL_PROMOTION_REVIEW_LEDGER_CORRUPT";
|
|
23
|
+
|
|
24
|
+
const PACKET_TYPE = "skill-promotion-review-packet";
|
|
25
|
+
const DECISION_TYPE = "skill-promotion-review-decision";
|
|
26
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
27
|
+
|
|
28
|
+
function requiredString(value, label) {
|
|
29
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
30
|
+
throw new TypeError(`${label} is required`);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function capture(owner, method, label = method) {
|
|
36
|
+
if (typeof owner?.[method] !== "function") {
|
|
37
|
+
throw new TypeError(`${label} port is required`);
|
|
38
|
+
}
|
|
39
|
+
return (...args) => Reflect.apply(owner[method], owner, args);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function corrupt(message) {
|
|
43
|
+
const error = new Error(message);
|
|
44
|
+
error.code = SKILL_PROMOTION_REVIEW_LEDGER_CORRUPT_CODE;
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function canonical(value) {
|
|
49
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
50
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
51
|
+
return `{${Object.keys(value)
|
|
52
|
+
.sort()
|
|
53
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
|
|
54
|
+
.join(",")}}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeDescriptor(input) {
|
|
58
|
+
if (!Number.isSafeInteger(input?.revision) || input.revision < 1) {
|
|
59
|
+
throw new TypeError("revision must be a positive integer");
|
|
60
|
+
}
|
|
61
|
+
if (!DIGEST.test(input?.handlerArtifactDigest || "")) {
|
|
62
|
+
throw new TypeError("handlerArtifactDigest must be sha256-bound");
|
|
63
|
+
}
|
|
64
|
+
return Object.freeze({
|
|
65
|
+
tenantId: requiredString(input.tenantId, "tenantId"),
|
|
66
|
+
artifactTenantId: requiredString(
|
|
67
|
+
input.artifactTenantId,
|
|
68
|
+
"artifactTenantId",
|
|
69
|
+
),
|
|
70
|
+
streamId: requiredString(input.streamId, "streamId"),
|
|
71
|
+
audience: requiredString(input.audience, "audience"),
|
|
72
|
+
purpose: requiredString(input.purpose, "purpose"),
|
|
73
|
+
authorityId: requiredString(input.authorityId, "authorityId"),
|
|
74
|
+
revision: input.revision,
|
|
75
|
+
handlerArtifactDigest: input.handlerArtifactDigest,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function parseRecord(resolution, descriptor, expectedType) {
|
|
80
|
+
if (
|
|
81
|
+
resolution?.schema !== EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA ||
|
|
82
|
+
resolution.authenticated !== true ||
|
|
83
|
+
resolution.found !== true ||
|
|
84
|
+
!Buffer.isBuffer(resolution.bytes)
|
|
85
|
+
) {
|
|
86
|
+
corrupt("review artifact resolution is unauthenticated or incomplete");
|
|
87
|
+
}
|
|
88
|
+
let record;
|
|
89
|
+
try {
|
|
90
|
+
record = JSON.parse(resolution.bytes.toString("utf8"));
|
|
91
|
+
} catch {
|
|
92
|
+
corrupt("review artifact is not JSON");
|
|
93
|
+
}
|
|
94
|
+
if (
|
|
95
|
+
record?.schema !== EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA ||
|
|
96
|
+
record.tenantId !== descriptor.artifactTenantId ||
|
|
97
|
+
record.audience !== descriptor.audience ||
|
|
98
|
+
record.purpose !== descriptor.purpose ||
|
|
99
|
+
record.retention !== "ledger" ||
|
|
100
|
+
record.type !== expectedType
|
|
101
|
+
) {
|
|
102
|
+
corrupt("review artifact durable binding is invalid");
|
|
103
|
+
}
|
|
104
|
+
return record.value;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export class SkillPromotionReviewLedgerAdapter {
|
|
108
|
+
constructor({
|
|
109
|
+
descriptor: input,
|
|
110
|
+
artifactPorts,
|
|
111
|
+
ledger,
|
|
112
|
+
ledgerArtifactResolver,
|
|
113
|
+
decisionVerifier,
|
|
114
|
+
now = Date.now,
|
|
115
|
+
} = {}) {
|
|
116
|
+
this.descriptor = normalizeDescriptor(input);
|
|
117
|
+
this._put = capture(artifactPorts, "putCanonical");
|
|
118
|
+
this._read = capture(ledger, "read");
|
|
119
|
+
this._verifyLedger = capture(ledger, "verify");
|
|
120
|
+
this._append = capture(ledger, "appendDomainEvent");
|
|
121
|
+
this._verifyDecisionSignature = capture(
|
|
122
|
+
decisionVerifier,
|
|
123
|
+
"verify",
|
|
124
|
+
"decision verifier",
|
|
125
|
+
);
|
|
126
|
+
if (!isEvolutionLedgerArtifactResolver(ledgerArtifactResolver)) {
|
|
127
|
+
throw new TypeError(
|
|
128
|
+
"a branded EvolutionArtifactPorts ledger resolver is required",
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (typeof now !== "function")
|
|
132
|
+
throw new TypeError("now must be a function");
|
|
133
|
+
this._resolveArtifact = ledgerArtifactResolver;
|
|
134
|
+
this._now = now;
|
|
135
|
+
Object.freeze(this);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
_events(type) {
|
|
139
|
+
const events = this._read();
|
|
140
|
+
if (!Array.isArray(events))
|
|
141
|
+
corrupt("EvolutionLedger did not return events");
|
|
142
|
+
return events.filter(
|
|
143
|
+
(event) =>
|
|
144
|
+
event.schema === EVOLUTION_LEDGER_DOMAIN_EVENT_SCHEMA &&
|
|
145
|
+
event.type === type &&
|
|
146
|
+
event.tenantId === this.descriptor.tenantId &&
|
|
147
|
+
event.correlationId === this.descriptor.streamId,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
_resolveEvent(event, expectedType) {
|
|
152
|
+
const identity = this._verifyLedger();
|
|
153
|
+
const resolution = this._resolveArtifact({
|
|
154
|
+
epoch: identity.epoch,
|
|
155
|
+
ledgerId: identity.ledgerId,
|
|
156
|
+
ref: event.subjectRef,
|
|
157
|
+
tenantId: this.descriptor.artifactTenantId,
|
|
158
|
+
});
|
|
159
|
+
if (
|
|
160
|
+
resolution.ref !== event.subjectRef.ref ||
|
|
161
|
+
resolution.digest !== event.subjectRef.digest
|
|
162
|
+
) {
|
|
163
|
+
corrupt("review ledger subject was substituted");
|
|
164
|
+
}
|
|
165
|
+
return parseRecord(resolution, this.descriptor, expectedType);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_packetByDigest(packetDigest) {
|
|
169
|
+
const expectedEventId = `${SKILL_PROMOTION_REVIEW_REQUEST_EVENT_TYPE}.${packetDigest.slice(7)}`;
|
|
170
|
+
const matches = this._events(
|
|
171
|
+
SKILL_PROMOTION_REVIEW_REQUEST_EVENT_TYPE,
|
|
172
|
+
).filter((event) => event.eventId === expectedEventId);
|
|
173
|
+
if (matches.length !== 1) corrupt("review packet is missing or ambiguous");
|
|
174
|
+
const packet = verifySkillPromotionReviewPacketArtifact(
|
|
175
|
+
this._resolveEvent(matches[0], PACKET_TYPE),
|
|
176
|
+
);
|
|
177
|
+
if (packet.packetDigest !== packetDigest)
|
|
178
|
+
corrupt("review packet was substituted");
|
|
179
|
+
return { event: matches[0], packet };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
_decisionEntries() {
|
|
183
|
+
return this._events(SKILL_PROMOTION_REVIEW_DECISION_EVENT_TYPE).map(
|
|
184
|
+
(event) => {
|
|
185
|
+
const decision = this._resolveEvent(event, DECISION_TYPE);
|
|
186
|
+
if (
|
|
187
|
+
decision?.schema !== SKILL_PROMOTION_REVIEW_DECISION_SCHEMA ||
|
|
188
|
+
!DIGEST.test(decision.receiptDigest || "") ||
|
|
189
|
+
event.eventId !==
|
|
190
|
+
`${SKILL_PROMOTION_REVIEW_DECISION_EVENT_TYPE}.${decision.receiptDigest.slice(7)}`
|
|
191
|
+
) {
|
|
192
|
+
corrupt("review decision artifact is invalid");
|
|
193
|
+
}
|
|
194
|
+
const packetEventId = `${SKILL_PROMOTION_REVIEW_REQUEST_EVENT_TYPE}.${decision.packetDigest.slice(7)}`;
|
|
195
|
+
const packetEvents = this._events(
|
|
196
|
+
SKILL_PROMOTION_REVIEW_REQUEST_EVENT_TYPE,
|
|
197
|
+
).filter((candidate) => candidate.eventId === packetEventId);
|
|
198
|
+
if (
|
|
199
|
+
packetEvents.length !== 1 ||
|
|
200
|
+
!Array.isArray(event.sourceRefs) ||
|
|
201
|
+
event.sourceRefs.length !== 1 ||
|
|
202
|
+
canonical(event.sourceRefs[0]) !==
|
|
203
|
+
canonical(packetEvents[0].subjectRef)
|
|
204
|
+
) {
|
|
205
|
+
corrupt("review decision packet lineage is invalid");
|
|
206
|
+
}
|
|
207
|
+
return { event, decision };
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async submitPacket(input) {
|
|
213
|
+
const packet = captureSkillPromotionReviewPacket(input);
|
|
214
|
+
if (packet.tenantId !== this.descriptor.tenantId) {
|
|
215
|
+
throw new TypeError("review packet belongs to another tenant");
|
|
216
|
+
}
|
|
217
|
+
const eventId = `${SKILL_PROMOTION_REVIEW_REQUEST_EVENT_TYPE}.${packet.packetDigest.slice(7)}`;
|
|
218
|
+
const existing = this._events(
|
|
219
|
+
SKILL_PROMOTION_REVIEW_REQUEST_EVENT_TYPE,
|
|
220
|
+
).filter((event) => event.eventId === eventId);
|
|
221
|
+
if (existing.length > 1)
|
|
222
|
+
corrupt("review packet has duplicate ledger events");
|
|
223
|
+
if (existing.length === 1) {
|
|
224
|
+
const recovered = verifySkillPromotionReviewPacketArtifact(
|
|
225
|
+
this._resolveEvent(existing[0], PACKET_TYPE),
|
|
226
|
+
);
|
|
227
|
+
if (canonical(recovered) !== canonical(packet)) {
|
|
228
|
+
corrupt("review packet id resolved substituted content");
|
|
229
|
+
}
|
|
230
|
+
return Object.freeze({
|
|
231
|
+
persisted: true,
|
|
232
|
+
recovered: true,
|
|
233
|
+
packetDigest: packet.packetDigest,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
const head = this._verifyLedger();
|
|
237
|
+
const nowMs = Number(this._now());
|
|
238
|
+
if (!Number.isFinite(nowMs)) throw new TypeError("review clock is invalid");
|
|
239
|
+
const published = this._put(PACKET_TYPE, packet, {
|
|
240
|
+
audience: this.descriptor.audience,
|
|
241
|
+
purpose: this.descriptor.purpose,
|
|
242
|
+
retention: "ledger",
|
|
243
|
+
});
|
|
244
|
+
if (
|
|
245
|
+
published?.receipt?.persisted !== true ||
|
|
246
|
+
published.receipt.readbackVerified !== true ||
|
|
247
|
+
published.receipt.integrityVerified !== true ||
|
|
248
|
+
published.receipt.retention !== "ledger"
|
|
249
|
+
) {
|
|
250
|
+
corrupt("review packet persistence was not durably confirmed");
|
|
251
|
+
}
|
|
252
|
+
const timestamp = new Date(nowMs).toISOString();
|
|
253
|
+
const receipt = this._append(
|
|
254
|
+
{
|
|
255
|
+
artifactTenantId: this.descriptor.artifactTenantId,
|
|
256
|
+
correlationId: this.descriptor.streamId,
|
|
257
|
+
decision: "proposed",
|
|
258
|
+
eventId,
|
|
259
|
+
reason: "skill promotion review requested",
|
|
260
|
+
skillName: packet.skillName,
|
|
261
|
+
sourceRefs: [],
|
|
262
|
+
subjectRef: published.ref,
|
|
263
|
+
tenantId: this.descriptor.tenantId,
|
|
264
|
+
timestamp,
|
|
265
|
+
type: SKILL_PROMOTION_REVIEW_REQUEST_EVENT_TYPE,
|
|
266
|
+
},
|
|
267
|
+
{ expectedHeadDigest: head.headDigest, expectedSequence: head.sequence },
|
|
268
|
+
);
|
|
269
|
+
if (receipt?.authenticated !== true || receipt.durable !== true) {
|
|
270
|
+
corrupt("review packet ledger append was not durable");
|
|
271
|
+
}
|
|
272
|
+
return Object.freeze({
|
|
273
|
+
persisted: true,
|
|
274
|
+
packetDigest: packet.packetDigest,
|
|
275
|
+
ledgerReceiptDigest: receipt.receiptDigest,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async retainDecision(input) {
|
|
280
|
+
if (!DIGEST.test(input?.packetDigest || "")) {
|
|
281
|
+
throw new TypeError("packetDigest is required");
|
|
282
|
+
}
|
|
283
|
+
const { event: packetEvent, packet } = this._packetByDigest(
|
|
284
|
+
input.packetDigest,
|
|
285
|
+
);
|
|
286
|
+
const nowMs = Number(this._now());
|
|
287
|
+
const decision = verifySkillPromotionReviewDecision(
|
|
288
|
+
input.decision,
|
|
289
|
+
packet,
|
|
290
|
+
nowMs,
|
|
291
|
+
);
|
|
292
|
+
if (
|
|
293
|
+
(await this._verifyDecisionSignature({
|
|
294
|
+
decision,
|
|
295
|
+
packet,
|
|
296
|
+
source: "retain",
|
|
297
|
+
})) !== true
|
|
298
|
+
) {
|
|
299
|
+
throw new Error("review decision signature verification failed");
|
|
300
|
+
}
|
|
301
|
+
const decisionsForPacket = this._decisionEntries().filter(
|
|
302
|
+
({ decision: stored }) => stored.packetDigest === packet.packetDigest,
|
|
303
|
+
);
|
|
304
|
+
if (decisionsForPacket.length > 1)
|
|
305
|
+
corrupt("review packet has conflicting decisions");
|
|
306
|
+
if (decisionsForPacket.length === 1) {
|
|
307
|
+
if (canonical(decisionsForPacket[0].decision) !== canonical(decision)) {
|
|
308
|
+
corrupt("review packet already has a different decision");
|
|
309
|
+
}
|
|
310
|
+
return Object.freeze({
|
|
311
|
+
persisted: true,
|
|
312
|
+
recovered: true,
|
|
313
|
+
receiptDigest: decision.receiptDigest,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const head = this._verifyLedger();
|
|
317
|
+
const published = this._put(DECISION_TYPE, decision, {
|
|
318
|
+
audience: this.descriptor.audience,
|
|
319
|
+
purpose: this.descriptor.purpose,
|
|
320
|
+
retention: "ledger",
|
|
321
|
+
});
|
|
322
|
+
if (
|
|
323
|
+
published?.receipt?.persisted !== true ||
|
|
324
|
+
published.receipt.readbackVerified !== true ||
|
|
325
|
+
published.receipt.integrityVerified !== true ||
|
|
326
|
+
published.receipt.retention !== "ledger"
|
|
327
|
+
) {
|
|
328
|
+
corrupt("review decision persistence was not durably confirmed");
|
|
329
|
+
}
|
|
330
|
+
const eventId = `${SKILL_PROMOTION_REVIEW_DECISION_EVENT_TYPE}.${decision.receiptDigest.slice(7)}`;
|
|
331
|
+
const receipt = this._append(
|
|
332
|
+
{
|
|
333
|
+
artifactTenantId: this.descriptor.artifactTenantId,
|
|
334
|
+
correlationId: this.descriptor.streamId,
|
|
335
|
+
decision: decision.decision === "approved" ? "accepted" : "rejected",
|
|
336
|
+
eventId,
|
|
337
|
+
reason: decision.reason,
|
|
338
|
+
skillName: packet.skillName,
|
|
339
|
+
sourceRefs: [packetEvent.subjectRef],
|
|
340
|
+
subjectRef: published.ref,
|
|
341
|
+
tenantId: this.descriptor.tenantId,
|
|
342
|
+
timestamp: decision.decidedAt,
|
|
343
|
+
type: SKILL_PROMOTION_REVIEW_DECISION_EVENT_TYPE,
|
|
344
|
+
},
|
|
345
|
+
{ expectedHeadDigest: head.headDigest, expectedSequence: head.sequence },
|
|
346
|
+
);
|
|
347
|
+
if (receipt?.authenticated !== true || receipt.durable !== true) {
|
|
348
|
+
corrupt("review decision ledger append was not durable");
|
|
349
|
+
}
|
|
350
|
+
return Object.freeze({
|
|
351
|
+
persisted: true,
|
|
352
|
+
receiptDigest: decision.receiptDigest,
|
|
353
|
+
ledgerReceiptDigest: receipt.receiptDigest,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async listReviews() {
|
|
358
|
+
const decisions = this._decisionEntries();
|
|
359
|
+
const output = [];
|
|
360
|
+
for (const event of this._events(
|
|
361
|
+
SKILL_PROMOTION_REVIEW_REQUEST_EVENT_TYPE,
|
|
362
|
+
)) {
|
|
363
|
+
const packet = verifySkillPromotionReviewPacketArtifact(
|
|
364
|
+
this._resolveEvent(event, PACKET_TYPE),
|
|
365
|
+
);
|
|
366
|
+
const matches = decisions.filter(
|
|
367
|
+
({ decision }) => decision.packetDigest === packet.packetDigest,
|
|
368
|
+
);
|
|
369
|
+
if (matches.length > 1)
|
|
370
|
+
corrupt("review packet has conflicting decisions");
|
|
371
|
+
if (matches.length === 1) {
|
|
372
|
+
const decision = verifySkillPromotionReviewDecision(
|
|
373
|
+
matches[0].decision,
|
|
374
|
+
packet,
|
|
375
|
+
Date.parse(matches[0].decision.decidedAt),
|
|
376
|
+
);
|
|
377
|
+
if (
|
|
378
|
+
(await this._verifyDecisionSignature({
|
|
379
|
+
decision,
|
|
380
|
+
packet,
|
|
381
|
+
source: "list",
|
|
382
|
+
ledgerEventDigest: matches[0].event.eventDigest,
|
|
383
|
+
})) !== true
|
|
384
|
+
) {
|
|
385
|
+
throw new Error("review decision signature verification failed");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
output.push(
|
|
389
|
+
Object.freeze({
|
|
390
|
+
packet,
|
|
391
|
+
decision: matches[0]?.decision || null,
|
|
392
|
+
status:
|
|
393
|
+
matches.length === 1 &&
|
|
394
|
+
Date.parse(matches[0].decision.expiresAt) <= Number(this._now())
|
|
395
|
+
? "expired"
|
|
396
|
+
: matches[0]?.decision.decision || "pending",
|
|
397
|
+
}),
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
return Object.freeze(output);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
createDecisionResolver() {
|
|
404
|
+
return Object.freeze({
|
|
405
|
+
resolve: async (request) => {
|
|
406
|
+
if (
|
|
407
|
+
request?.tenantId !== this.descriptor.tenantId ||
|
|
408
|
+
!DIGEST.test(request.receiptDigest || "")
|
|
409
|
+
) {
|
|
410
|
+
corrupt("review decision request is invalid");
|
|
411
|
+
}
|
|
412
|
+
const matches = this._decisionEntries().filter(
|
|
413
|
+
({ decision }) => decision.receiptDigest === request.receiptDigest,
|
|
414
|
+
);
|
|
415
|
+
if (matches.length !== 1)
|
|
416
|
+
corrupt("review decision is missing or ambiguous");
|
|
417
|
+
const { decision, event } = matches[0];
|
|
418
|
+
const { packet } = this._packetByDigest(decision.packetDigest);
|
|
419
|
+
verifySkillPromotionReviewDecision(
|
|
420
|
+
decision,
|
|
421
|
+
packet,
|
|
422
|
+
Number(this._now()),
|
|
423
|
+
);
|
|
424
|
+
if (
|
|
425
|
+
(await this._verifyDecisionSignature({
|
|
426
|
+
decision,
|
|
427
|
+
packet,
|
|
428
|
+
source: "resolve",
|
|
429
|
+
ledgerEventDigest: event.eventDigest,
|
|
430
|
+
})) !== true
|
|
431
|
+
) {
|
|
432
|
+
throw new Error("review decision signature verification failed");
|
|
433
|
+
}
|
|
434
|
+
return Object.freeze({
|
|
435
|
+
schema: SKILL_PROMOTION_REVIEW_RESOLUTION_SCHEMA,
|
|
436
|
+
authorityId: this.descriptor.authorityId,
|
|
437
|
+
handlerArtifactDigest: this.descriptor.handlerArtifactDigest,
|
|
438
|
+
revision: this.descriptor.revision,
|
|
439
|
+
tenantId: this.descriptor.tenantId,
|
|
440
|
+
receiptDigest: decision.receiptDigest,
|
|
441
|
+
decision,
|
|
442
|
+
resolvedAt: new Date(Number(this._now())).toISOString(),
|
|
443
|
+
});
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export function createSkillPromotionReviewLedgerAdapter(options) {
|
|
450
|
+
return new SkillPromotionReviewLedgerAdapter(options);
|
|
451
|
+
}
|