chainlesschain 0.166.6 → 0.166.9
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 +21 -0
- package/package.json +5 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-CVnFjTfG.js → AIOps-DJvpifGx.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BxkwuuQC.js → ActionButton-D2UhuF_A.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-C2670q6z.js → Analytics-C9ho9obu.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CU_rylW6.js → AppLayout-BvRCpFK3.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-DGJmYqnj.js → Artifacts-jxrySvpk.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BZOQbINJ.js → Audit-ovEw0ZYl.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DghB4QwF.js → BackgroundAgents-D7H5z6zS.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-l0mwPbta.js → Backup-BuXob8za.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Bv3WSZOh.js → BaseInput-CtnszuQ2.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-s88nBdvt.js → Chat-B_mYE8fJ.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-Z8CBuB1a.js → ChatBubbleRenderer-DaiA6CsX.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-B5icasEs.js → Checkbox-ierdtcJD.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CsUFqNud.js → Codegen-3VKPJD_B.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DlBtClaW.js → Col-1EdMyf5P.js} +1 -1
- package/src/assets/web-panel/assets/{Community-xRn1IdLB.js → Community-D6H7UA8K.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-naIr8a_o.js → Compact-BBAZvYIm.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Dp9esfFE.js → Compliance-CgsTrAoj.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DQlMYJB-.js → Cowork-tnZgLoTE.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-Cd0oNLzJ.js → Cron-CPpZw2Nh.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BtDIx3XX.js → Crosschain-BymBcp5z.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BOlpzEwg.js → DID-DKPxS_K1.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DV15Axk1.js → Dashboard-DvJhEmH9.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-COXrewN1.js → Dropdown-b8iQyQIZ.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-EjIt0Cm7.js → EmailListRenderer--0-A-zKb.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DJlhwb-R.js → FamilyGuardDashboard-C12iDN06.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BMiFl82k.js → Federation-BRhaE2Z8.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BUcDQyxn.js → FormItemContext-C4dnq7yf.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-C25uFxvK.js → GenericCardRenderer-CGAdbR5n.js} +1 -1
- package/src/assets/web-panel/assets/{Git-D20uaEUU.js → Git-4eA2fYzL.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CQxYJjCX.js → Governance-BJuY9Y-9.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-m-sczpS5.js → Inference-DZ5Dy4up.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CBsIlU0S.js → KnowledgeGraph-BMTjRs4A.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-D0-5bqeu.js → Logs-BKEeQEfJ.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-B05DgASC.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-CwDGFu2Q.js → Marketplace-Dhho4fEg.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-7FxJ3omT.js → McpTools-BW6xxSlU.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-DOVyb59U.js → Memory-mPqlpSkq.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DZXl-Hje.js → MobileBridge-Cb9N_DQI.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DlZ8tRXt.js → MobileProjects-BNs8tah_.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-D7Pjx1s_.js → Mtc-CaxBt8tn.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BND7lX8p.js → MtcAudit-DRPQbp3t.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BknS7B-p.js → Multisig-CRm-d8bK.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-7C2-KPHt.js → NLProgramming-BCCk3LPs.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DNWdTndN.js → Notes-Cv8SN8gN.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-DCm4KcJZ.js → NotificationSettings-CggIvTh5.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DyVwwjJx.js → OrderTableRenderer-BW3_bV12.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-Dl2O_qJ9.js → Organization-BuRE-VCO.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BOFCvk9Q.js → Overflow-DBrm1gju.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DgdVowY1.js → P2P-CWVnDYgS.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-8RKUIpte.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-BHNEUUwa.js → Permissions-tDUAxogW.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CaZbrOdz.js → PersonalDataHub-Dv2-CFis.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-DJDnoXmE.js → Pipeline-DM6kvFni.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CBQr2eX8.js → Privacy-DzK-yL0d.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Yghqa06z.js → ProjectInit-B5UrNpkj.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-HBAtJi8U.js → ProjectSettings-CxJD43oW.js} +2 -2
- package/src/assets/web-panel/assets/Projects-Z2Orm8Ma.js +1 -0
- package/src/assets/web-panel/assets/{Providers-uJTz77GI.js → Providers-CYl4jRYK.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-k3M5ouTp.js → QrScannerModal-DeH2T0fH.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CMji45PH.js → QuickAsk-Bw9k_kTk.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DyoZYPls.js → Recommend-Bl_jGwoi.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-d6u01Kq0.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-D4hIs4vV.js → Reputation-Ds_Dfr-k.js} +1 -1
- package/src/assets/web-panel/assets/{Row-mzE-ElTg.js → Row-BpQUQTNj.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-cg7J3hI6.js → RssFeed-BatpZBGt.js} +3 -3
- package/src/assets/web-panel/assets/{Search-6M0TkGSZ.js → Search-D7hOObzi.js} +1 -1
- package/src/assets/web-panel/assets/{Security-B1GQ67qh.js → Security-dXQ4FKNt.js} +4 -4
- package/src/assets/web-panel/assets/{Services-D_RwU_5S.js → Services-D8wInGPa.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-TZjk-dsW.js → Skeleton-DXQCoYpM.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CG6FcaCV.js → Skills-D3oLRIVn.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BI190QDv.js → Sla-BmP4-LnJ.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BWmxF2Wu.js → SpeechSettings-DLj-xzro.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CwPiGw6L.js → SyncSettings-8KKaaRMr.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BIXgCeSk.js → Tasks-GyK42I7C.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CQFgGCoK.js → Templates-n1y_8UBG.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DAelEdkL.js → Tenant-B39nBph8.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BbqWK6qh.js → Terminal-DCQoOKXe.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-cmG3zvY5.js → TimelineRenderer-BkhbDfXk.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-C8444NKU.js → Tokens-BeHIIU8D.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-C1Rpnz0I.js → Trigger-BHvWXVOb.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-u4koPiFD.js → Trust-y2WaOSto.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-oPWKn80Z.js → UkeySign-DBFeJBzt.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-D0CUUY_5.js → VideoEditing-BEOWitC-.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-fu9pIAbt.js → Wallet-BITedTe6.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Bc5ciuOx.js → WebAuthn-DMwlJVin.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-BCin4_4N.js → WorkflowEditor-CKfGFEu1.js} +1 -1
- package/src/assets/web-panel/assets/{chat-B2jbM-XN.js → chat-DR6gnBGF.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CUVoUQ_2.js → colors-C_Hw5NCn.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DgOGpkRH.js → compact-item-B8IPwsEu.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DIxU6eKI.js → createContext-D-u4JVnJ.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Cq6lSd_q.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DoKRKMav.js → hasIn-CZubdS4D.js} +1 -1
- package/src/assets/web-panel/assets/{index-ByjrulIZ.js → index-11ASqx-A.js} +1 -1
- package/src/assets/web-panel/assets/index-BQ3213fz.js +1 -0
- package/src/assets/web-panel/assets/{index-BwYofQJg.js → index-BXS6wYJZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLdpHe6w.js → index-Bi21Etg4.js} +1 -1
- package/src/assets/web-panel/assets/{index-2flsPFIX.js → index-Bizm87cX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXb-Smmm.js → index-Bv2132PO.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5ySiGYy.js → index-BzoFanEQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWbL1-N0.js → index-C25rOz7O.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cj-us-D_.js → index-C2VmtNaQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-C9TsO9T9.js → index-Cbm8F0M2.js} +1 -1
- package/src/assets/web-panel/assets/{index-eTWPaeBI.js → index-CfniPP5p.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2yo6ast.js → index-Cg2bCeol.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPXdBnGi.js → index-CiXkhOkY.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCLeNqIL.js → index-Cl5RpreD.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtSPhEyC.js → index-CqmLw8cs.js} +1 -1
- package/src/assets/web-panel/assets/{index-eYxUASSG.js → index-Ctje8Vl3.js} +1 -1
- package/src/assets/web-panel/assets/{index-i5MB455u.js → index-D1w96gg9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpvyOt5Z.js → index-D47jt8ET.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeWSw-z5.js → index-DJ6pG79m.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3F_p8NA.js → index-DTosx2Wu.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9mSYovv.js → index-DY6MHo9D.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJMHnbmu.js → index-DcFjRT-K.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-ufbAJB.js → index-DfnzrUxU.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFUprhNd.js → index-DjRrqooR.js} +1 -1
- package/src/assets/web-panel/assets/{index-Co4lDEk-.js → index-DmRcgiYT.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGTZdZOE.js → index-DpjHpRoq.js} +3 -3
- package/src/assets/web-panel/assets/{index-DHugFl3v.js → index-Dt-w3xQ7.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-f6l4Vm.js → index-DuOU5apg.js} +1 -1
- package/src/assets/web-panel/assets/{index-DtyAuGOm.js → index-LvjAKDSl.js} +1 -1
- package/src/assets/web-panel/assets/{index-be8SonCh.js → index-_nL79NT8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRMEO2Tl.js → index-auyNYjLo.js} +1 -1
- package/src/assets/web-panel/assets/{index-C88m9lsi.js → index-cVqbas5C.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqlwIjJx.js → index-ffe7bvqP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpTFx8Xx.js → index-idZKyADQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cskz--fd.js → index-k1Xa5iw1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CF80xxPG.js → index-mYZJxGH6.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ds8r3Fzb.js → index-rYBHSDcJ.js} +1 -1
- package/src/assets/web-panel/assets/index-vJagfET2.js +1 -0
- package/src/assets/web-panel/assets/{index-C846lPGD.js → index-xoNssgz_.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-B4chl7iR.js → initDefaultProps-lcU_dT2r.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DruN9h96.js → motion-Dc7UNXZ1.js} +1 -1
- package/src/assets/web-panel/assets/{move-Cl0UtCgU.js → move-Cll8ZXaL.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-FX2ttw70.js → mtc-parser-CAb_yLIM.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BDCneY46.js → omit-DurYMyTe.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DycsEg8b.js → pickAttrs-BITQyOlw.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-Bc-dBuMv.js → placementArrow-Dg1rH91i.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve--Wwaf8lu.js → responsiveObserve-D_EWIeac.js} +1 -1
- package/src/assets/web-panel/assets/{slide-vDmHQqHH.js → slide-lK4d3zsh.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-9c52yv3G.js → statusUtils-Bflq-Git.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BT4BiO6_.js → styleChecker-Ccp0I-wt.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-C7XxWPcP.js → useFlexGapSupport-CYZbByYA.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BHoMqh4i.js → useFs-B_x0nxFD.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BOLxvsAG.js → usePersonalDataHub-DW55rL85.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CA7poeeP.js → vnode-Ca6MH3W8.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B_sAn-co.js → zoom-NdHelOog.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/graph.js +215 -5
- package/src/commands/orchestrate.js +61 -9
- package/src/commands/team-distributed.js +342 -2
- package/src/commands/team.js +169 -7
- package/src/data/changelog.json +46 -0
- package/src/gateways/ws/message-dispatcher.js +4 -0
- package/src/gateways/ws/session-protocol.js +89 -11
- package/src/gateways/ws/ws-agent-handler.js +65 -9
- package/src/gateways/ws/ws-approval-gate.js +181 -16
- package/src/gateways/ws/ws-server.js +16 -6
- package/src/generated/cc-agent-protocol.schema.json +154 -3
- package/src/lib/agent-team/task-lease.js +487 -3
- package/src/lib/agent-team/team-agent-stream.js +16 -0
- package/src/lib/agent-team/team-distributed-graph-bridge.js +257 -0
- package/src/lib/agent-team/team-distributed-graph-writer.js +523 -0
- package/src/lib/agent-team/team-distributed-queue.js +251 -0
- package/src/lib/agent-team/team-graph-runtime-adapter.js +1918 -0
- package/src/lib/agent-team/team-runner.js +285 -3
- package/src/lib/app-server/graph-runtime.js +1078 -0
- package/src/lib/app-server/rollout-store.js +32 -0
- package/src/lib/app-server/server.js +200 -0
- package/src/lib/approval-grant-ledger.js +69 -0
- package/src/lib/autonomous-agent.js +11 -0
- package/src/lib/codex-app-server-adapter.js +11 -20
- package/src/lib/cowork-graph-authority-adapter.js +455 -0
- package/src/lib/dynamic-workflow-facade.js +83 -16
- package/src/lib/graph-kernel/adapters.js +93 -20
- package/src/lib/graph-kernel/authority.js +324 -0
- package/src/lib/graph-kernel/compiler.js +67 -21
- package/src/lib/graph-kernel/cutover-authority-resolver.js +245 -0
- package/src/lib/graph-kernel/cutover-ledger.js +692 -0
- package/src/lib/graph-kernel/event-store.js +15 -1
- package/src/lib/graph-kernel/graph-runtime-surfaces.json +1427 -0
- package/src/lib/graph-kernel/index.js +4 -0
- package/src/lib/graph-kernel/migration-saga.js +309 -0
- package/src/lib/graph-kernel/retirement-evidence.js +567 -0
- package/src/lib/graph-kernel/runtime-surface-manifest.js +481 -0
- package/src/lib/graph-kernel/runtime.js +737 -38
- package/src/lib/graph-kernel/store-cutover-evidence.js +354 -0
- package/src/lib/graph-kernel/trace-reducer.js +6 -0
- package/src/lib/graph-kernel/trigger-adapter.js +60 -3
- package/src/lib/interaction-adapter.js +61 -9
- package/src/lib/legacy-runtime-authority.js +160 -0
- package/src/lib/orchestrator.js +28 -0
- package/src/lib/policy-decision-event.js +149 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +15 -1
- package/src/lib/record-replay/index.js +1 -0
- package/src/lib/record-replay/playwright-ui-driver.js +327 -0
- package/src/lib/scheduler-kernel/graph-authority-adapter.js +512 -0
- package/src/lib/scheduler-kernel/runtime.js +75 -2
- package/src/lib/webhook-security.js +484 -36
- package/src/runtime/coding-agent-events.cjs +13 -11
- package/src/runtime/headless-runner.js +17 -1
- package/src/runtime/headless-stream.js +66 -3
- package/src/runtime/session-slash-commands.js +57 -6
- package/src/assets/web-panel/assets/MarkdownRenderer-BwH414Ms.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-BiaZfQIM.js +0 -7
- package/src/assets/web-panel/assets/Projects-DiCGnMjz.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-s1gR38IO.js +0 -5
- package/src/assets/web-panel/assets/devWarning-DiSlgCC4.js +0 -1
- package/src/assets/web-panel/assets/index-9WIQEHvv.js +0 -1
- package/src/assets/web-panel/assets/index-B7mDobAl.js +0 -1
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const GRAPH_STORE_CUTOVER_EVIDENCE_SCHEMA =
|
|
4
|
+
"chainlesschain.graph-store-cutover-evidence/v1";
|
|
5
|
+
|
|
6
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
7
|
+
const COMMIT = /^[a-f0-9]{40,64}$/u;
|
|
8
|
+
const STORE = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,159}$/u;
|
|
9
|
+
|
|
10
|
+
function normalizePlatform(value) {
|
|
11
|
+
const platform = String(value || "")
|
|
12
|
+
.trim()
|
|
13
|
+
.toLowerCase();
|
|
14
|
+
if (platform === "win32") return "windows";
|
|
15
|
+
if (platform === "darwin") return "macos";
|
|
16
|
+
return platform;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function evidenceError(code, message, details = {}) {
|
|
20
|
+
const error = new Error(message);
|
|
21
|
+
error.name = "GraphStoreCutoverEvidenceError";
|
|
22
|
+
error.code = code;
|
|
23
|
+
Object.assign(error, details);
|
|
24
|
+
return error;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function stableValue(value) {
|
|
28
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
29
|
+
if (!value || typeof value !== "object") return value;
|
|
30
|
+
return Object.fromEntries(
|
|
31
|
+
Object.keys(value)
|
|
32
|
+
.filter((key) => value[key] !== undefined)
|
|
33
|
+
.sort()
|
|
34
|
+
.map((key) => [key, stableValue(value[key])]),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function graphStoreEvidenceDigest(value) {
|
|
39
|
+
return `sha256:${createHash("sha256")
|
|
40
|
+
.update(JSON.stringify(stableValue(value)), "utf8")
|
|
41
|
+
.digest("hex")}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function assertExactCommitSha(expectedCommit, actualCommit) {
|
|
45
|
+
const actual = String(actualCommit || "")
|
|
46
|
+
.trim()
|
|
47
|
+
.toLowerCase();
|
|
48
|
+
const expected = String(expectedCommit || "")
|
|
49
|
+
.trim()
|
|
50
|
+
.toLowerCase();
|
|
51
|
+
if (!COMMIT.test(actual) || (expected && !COMMIT.test(expected))) {
|
|
52
|
+
throw evidenceError(
|
|
53
|
+
"CC_GRAPH_STORE_EVIDENCE_INVALID",
|
|
54
|
+
"expected and actual commit SHAs must be exact hexadecimal identities",
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (expected && expected !== actual) {
|
|
58
|
+
throw evidenceError(
|
|
59
|
+
"CC_GRAPH_STORE_EVIDENCE_SHA_MISMATCH",
|
|
60
|
+
`expected commit ${expected} does not match checked-out HEAD ${actual}`,
|
|
61
|
+
{ expectedCommit: expected, actualCommit: actual },
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return actual;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function exactDigest(value, field) {
|
|
68
|
+
const output = String(value || "");
|
|
69
|
+
if (!DIGEST.test(output)) {
|
|
70
|
+
throw evidenceError(
|
|
71
|
+
"CC_GRAPH_STORE_EVIDENCE_INVALID",
|
|
72
|
+
`${field} must be a sha256 digest`,
|
|
73
|
+
{ field },
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return output;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalizeStore(value) {
|
|
80
|
+
const surface = String(value?.surface || "").trim();
|
|
81
|
+
const entryId = String(value?.entryId || "").trim();
|
|
82
|
+
const store = String(value?.store || "").trim();
|
|
83
|
+
if (!STORE.test(surface) || !STORE.test(entryId) || !STORE.test(store)) {
|
|
84
|
+
throw evidenceError(
|
|
85
|
+
"CC_GRAPH_STORE_EVIDENCE_INVALID",
|
|
86
|
+
"store evidence has an invalid surface, entry, or store identifier",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
if (value?.recovered !== true || Number(value?.rpoLossCount) !== 0) {
|
|
90
|
+
throw evidenceError(
|
|
91
|
+
"CC_GRAPH_STORE_EVIDENCE_RPO_FAILED",
|
|
92
|
+
`store evidence does not prove RPO=0 recovery: ${store}`,
|
|
93
|
+
{ store },
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return Object.freeze({
|
|
97
|
+
surface,
|
|
98
|
+
entryId,
|
|
99
|
+
store,
|
|
100
|
+
cutpointDigest: exactDigest(value.cutpointDigest, "cutpointDigest"),
|
|
101
|
+
recoveryReceiptDigest: exactDigest(
|
|
102
|
+
value.recoveryReceiptDigest,
|
|
103
|
+
"recoveryReceiptDigest",
|
|
104
|
+
),
|
|
105
|
+
rollbackDrillDigest: exactDigest(
|
|
106
|
+
value.rollbackDrillDigest,
|
|
107
|
+
"rollbackDrillDigest",
|
|
108
|
+
),
|
|
109
|
+
rpoLossCount: 0,
|
|
110
|
+
recovered: true,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function createGraphStoreCutoverEvidence({
|
|
115
|
+
source,
|
|
116
|
+
commitSha,
|
|
117
|
+
platform,
|
|
118
|
+
stores,
|
|
119
|
+
sourceReceipts,
|
|
120
|
+
}) {
|
|
121
|
+
const record = normalizeGraphStoreCutoverEvidence(
|
|
122
|
+
{
|
|
123
|
+
schema: GRAPH_STORE_CUTOVER_EVIDENCE_SCHEMA,
|
|
124
|
+
status: "passed",
|
|
125
|
+
source,
|
|
126
|
+
commitSha,
|
|
127
|
+
platform,
|
|
128
|
+
stores,
|
|
129
|
+
sourceReceipts,
|
|
130
|
+
},
|
|
131
|
+
{ requireEvidenceDigest: false },
|
|
132
|
+
);
|
|
133
|
+
const unsigned = { ...record };
|
|
134
|
+
delete unsigned.evidenceDigest;
|
|
135
|
+
return Object.freeze({
|
|
136
|
+
...unsigned,
|
|
137
|
+
evidenceDigest: graphStoreEvidenceDigest(unsigned),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function normalizeGraphStoreCutoverEvidence(
|
|
142
|
+
value,
|
|
143
|
+
{ requireEvidenceDigest = true } = {},
|
|
144
|
+
) {
|
|
145
|
+
if (
|
|
146
|
+
!value ||
|
|
147
|
+
typeof value !== "object" ||
|
|
148
|
+
Array.isArray(value) ||
|
|
149
|
+
value.schema !== GRAPH_STORE_CUTOVER_EVIDENCE_SCHEMA ||
|
|
150
|
+
value.status !== "passed"
|
|
151
|
+
) {
|
|
152
|
+
throw evidenceError(
|
|
153
|
+
"CC_GRAPH_STORE_EVIDENCE_INVALID",
|
|
154
|
+
"store cutover evidence schema or status is invalid",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
const source = String(value.source || "").trim();
|
|
158
|
+
const commitSha = String(value.commitSha || "")
|
|
159
|
+
.trim()
|
|
160
|
+
.toLowerCase();
|
|
161
|
+
const platform = normalizePlatform(value.platform);
|
|
162
|
+
if (!STORE.test(source) || !COMMIT.test(commitSha) || !platform) {
|
|
163
|
+
throw evidenceError(
|
|
164
|
+
"CC_GRAPH_STORE_EVIDENCE_INVALID",
|
|
165
|
+
"store evidence source, commit SHA, or platform is invalid",
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (!Array.isArray(value.stores) || value.stores.length === 0) {
|
|
169
|
+
throw evidenceError(
|
|
170
|
+
"CC_GRAPH_STORE_EVIDENCE_INVALID",
|
|
171
|
+
"store evidence must contain at least one real store receipt",
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const stores = value.stores
|
|
175
|
+
.map(normalizeStore)
|
|
176
|
+
.sort(
|
|
177
|
+
(left, right) =>
|
|
178
|
+
left.surface.localeCompare(right.surface) ||
|
|
179
|
+
left.entryId.localeCompare(right.entryId) ||
|
|
180
|
+
left.store.localeCompare(right.store),
|
|
181
|
+
);
|
|
182
|
+
if (
|
|
183
|
+
new Set(
|
|
184
|
+
stores.map(
|
|
185
|
+
(entry) => `${entry.surface}\0${entry.entryId}\0${entry.store}`,
|
|
186
|
+
),
|
|
187
|
+
).size !== stores.length
|
|
188
|
+
) {
|
|
189
|
+
throw evidenceError(
|
|
190
|
+
"CC_GRAPH_STORE_EVIDENCE_DUPLICATE",
|
|
191
|
+
"store evidence contains a duplicate store receipt",
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
const sourceReceipts = Object.fromEntries(
|
|
195
|
+
Object.entries(value.sourceReceipts || {})
|
|
196
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
197
|
+
.map(([name, receipt]) => [name, exactDigest(receipt, name)]),
|
|
198
|
+
);
|
|
199
|
+
if (Object.keys(sourceReceipts).length === 0) {
|
|
200
|
+
throw evidenceError(
|
|
201
|
+
"CC_GRAPH_STORE_EVIDENCE_INVALID",
|
|
202
|
+
"store evidence must bind its source journey receipts",
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const normalized = {
|
|
206
|
+
schema: GRAPH_STORE_CUTOVER_EVIDENCE_SCHEMA,
|
|
207
|
+
status: "passed",
|
|
208
|
+
source,
|
|
209
|
+
commitSha,
|
|
210
|
+
platform,
|
|
211
|
+
stores,
|
|
212
|
+
sourceReceipts,
|
|
213
|
+
};
|
|
214
|
+
if (requireEvidenceDigest) {
|
|
215
|
+
const evidenceDigest = exactDigest(value.evidenceDigest, "evidenceDigest");
|
|
216
|
+
if (evidenceDigest !== graphStoreEvidenceDigest(normalized)) {
|
|
217
|
+
throw evidenceError(
|
|
218
|
+
"CC_GRAPH_STORE_EVIDENCE_TAMPERED",
|
|
219
|
+
"store cutover evidence digest does not match its contents",
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
normalized.evidenceDigest = evidenceDigest;
|
|
223
|
+
}
|
|
224
|
+
return Object.freeze(normalized);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function graphStoreCutoverCoverage(manifest, evidenceRecords = []) {
|
|
228
|
+
const records = evidenceRecords.map((record) =>
|
|
229
|
+
normalizeGraphStoreCutoverEvidence(record),
|
|
230
|
+
);
|
|
231
|
+
const commits = new Set(records.map((record) => record.commitSha));
|
|
232
|
+
if (commits.size > 1) {
|
|
233
|
+
throw evidenceError(
|
|
234
|
+
"CC_GRAPH_STORE_EVIDENCE_SHA_MISMATCH",
|
|
235
|
+
"store evidence records must bind one exact commit SHA",
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
const availableStores = new Map();
|
|
239
|
+
for (const record of records) {
|
|
240
|
+
for (const store of record.stores) {
|
|
241
|
+
const key = `${store.surface}\0${store.entryId}\0${store.store}`;
|
|
242
|
+
const receipts = availableStores.get(key) || [];
|
|
243
|
+
receipts.push({
|
|
244
|
+
platform: record.platform,
|
|
245
|
+
evidenceDigest: record.evidenceDigest,
|
|
246
|
+
...store,
|
|
247
|
+
});
|
|
248
|
+
availableStores.set(key, receipts);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const requiredPlatforms = [
|
|
252
|
+
...(manifest.cutoverPolicy?.requiredPlatforms || []),
|
|
253
|
+
]
|
|
254
|
+
.map(normalizePlatform)
|
|
255
|
+
.sort();
|
|
256
|
+
if (requiredPlatforms.length === 0) {
|
|
257
|
+
throw evidenceError(
|
|
258
|
+
"CC_GRAPH_STORE_EVIDENCE_INVALID",
|
|
259
|
+
"runtime manifest must declare required cutover platforms",
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
const entries = manifest.surfaces.flatMap((surface) =>
|
|
263
|
+
surface.entries
|
|
264
|
+
.filter((entry) => entry.cutoverStrategy === "migrate")
|
|
265
|
+
.map((entry) => {
|
|
266
|
+
const stores = [...entry.storeDispositions.migrate].sort();
|
|
267
|
+
const keyFor = (store) =>
|
|
268
|
+
`${surface.originSurface}\0${entry.id}\0${store}`;
|
|
269
|
+
const coveredStores = stores.filter((store) =>
|
|
270
|
+
availableStores.has(keyFor(store)),
|
|
271
|
+
);
|
|
272
|
+
const missingStores = stores.filter(
|
|
273
|
+
(store) => !availableStores.has(keyFor(store)),
|
|
274
|
+
);
|
|
275
|
+
const platformCoverage = stores.map((store) => {
|
|
276
|
+
const coveredPlatforms = [
|
|
277
|
+
...new Set(
|
|
278
|
+
(availableStores.get(keyFor(store)) || []).map(
|
|
279
|
+
(receipt) => receipt.platform,
|
|
280
|
+
),
|
|
281
|
+
),
|
|
282
|
+
].sort();
|
|
283
|
+
const missingPlatforms = requiredPlatforms.filter(
|
|
284
|
+
(platform) => !coveredPlatforms.includes(platform),
|
|
285
|
+
);
|
|
286
|
+
return Object.freeze({
|
|
287
|
+
store,
|
|
288
|
+
coveredPlatforms: Object.freeze(coveredPlatforms),
|
|
289
|
+
missingPlatforms: Object.freeze(missingPlatforms),
|
|
290
|
+
complete: missingPlatforms.length === 0,
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
const incompletePlatformStores = platformCoverage.filter(
|
|
294
|
+
(store) => !store.complete,
|
|
295
|
+
);
|
|
296
|
+
return Object.freeze({
|
|
297
|
+
surface: surface.originSurface,
|
|
298
|
+
entryId: entry.id,
|
|
299
|
+
stores: Object.freeze(stores),
|
|
300
|
+
coveredStores: Object.freeze(coveredStores),
|
|
301
|
+
missingStores: Object.freeze(missingStores),
|
|
302
|
+
platformCoverage: Object.freeze(platformCoverage),
|
|
303
|
+
incompletePlatformStores: Object.freeze(incompletePlatformStores),
|
|
304
|
+
complete:
|
|
305
|
+
missingStores.length === 0 && incompletePlatformStores.length === 0,
|
|
306
|
+
});
|
|
307
|
+
}),
|
|
308
|
+
);
|
|
309
|
+
const storeSlotCount = entries.reduce(
|
|
310
|
+
(count, entry) => count + entry.stores.length,
|
|
311
|
+
0,
|
|
312
|
+
);
|
|
313
|
+
const coveredStoreSlotCount = entries.reduce(
|
|
314
|
+
(count, entry) => count + entry.coveredStores.length,
|
|
315
|
+
0,
|
|
316
|
+
);
|
|
317
|
+
const completeStoreSlotCount = entries.reduce(
|
|
318
|
+
(count, entry) =>
|
|
319
|
+
count + entry.platformCoverage.filter((store) => store.complete).length,
|
|
320
|
+
0,
|
|
321
|
+
);
|
|
322
|
+
const platformStoreSlotCount = storeSlotCount * requiredPlatforms.length;
|
|
323
|
+
const coveredPlatformStoreSlotCount = entries.reduce(
|
|
324
|
+
(count, entry) =>
|
|
325
|
+
count +
|
|
326
|
+
entry.platformCoverage.reduce(
|
|
327
|
+
(storeCount, store) =>
|
|
328
|
+
storeCount +
|
|
329
|
+
store.coveredPlatforms.filter((platform) =>
|
|
330
|
+
requiredPlatforms.includes(platform),
|
|
331
|
+
).length,
|
|
332
|
+
0,
|
|
333
|
+
),
|
|
334
|
+
0,
|
|
335
|
+
);
|
|
336
|
+
return Object.freeze({
|
|
337
|
+
schema: "chainlesschain.graph-store-cutover-coverage/v1",
|
|
338
|
+
commitSha: [...commits][0] || null,
|
|
339
|
+
requiredPlatforms: Object.freeze(requiredPlatforms),
|
|
340
|
+
evidenceRecordCount: records.length,
|
|
341
|
+
migratableEntryCount: entries.length,
|
|
342
|
+
completeEntryCount: entries.filter((entry) => entry.complete).length,
|
|
343
|
+
storeSlotCount,
|
|
344
|
+
coveredStoreSlotCount,
|
|
345
|
+
missingStoreSlotCount: storeSlotCount - coveredStoreSlotCount,
|
|
346
|
+
completeStoreSlotCount,
|
|
347
|
+
incompletePlatformStoreSlotCount: storeSlotCount - completeStoreSlotCount,
|
|
348
|
+
platformStoreSlotCount,
|
|
349
|
+
coveredPlatformStoreSlotCount,
|
|
350
|
+
missingPlatformStoreSlotCount:
|
|
351
|
+
platformStoreSlotCount - coveredPlatformStoreSlotCount,
|
|
352
|
+
entries: Object.freeze(entries),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
@@ -280,6 +280,12 @@ export function reduceGraphTrace(
|
|
|
280
280
|
runId: state.id,
|
|
281
281
|
throughSeq: ordered.at(-1)?.seq || 0,
|
|
282
282
|
eventHeadHash: ordered.at(-1)?.hash || null,
|
|
283
|
+
originSurface: state.authority?.originSurface || null,
|
|
284
|
+
authoritySource: state.authority?.authoritySource || null,
|
|
285
|
+
authorityMode: state.authority?.authorityMode || null,
|
|
286
|
+
authorityGeneration: state.authority?.authorityGeneration || null,
|
|
287
|
+
writerId: state.authority?.writerId || null,
|
|
288
|
+
projectionVersion: state.authority?.projectionVersion || null,
|
|
283
289
|
definitionId: definition.id,
|
|
284
290
|
graphRevision: state.graphRevision,
|
|
285
291
|
revisionDigest: state.revisionDigest,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { MemoryRolloutStore } from "../app-server/rollout-store.js";
|
|
3
|
+
import { createGraphAuthorityBinding } from "./authority.js";
|
|
3
4
|
|
|
4
5
|
const JOURNAL_THREAD_ID = "scheduler-graph-dispatch";
|
|
5
6
|
|
|
@@ -61,6 +62,61 @@ export class SchedulerGraphTriggerAdapter {
|
|
|
61
62
|
this.journal = journal;
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
runtimeClaims() {
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
originSurface: "scheduler",
|
|
68
|
+
surface: "scheduler",
|
|
69
|
+
execution: "real",
|
|
70
|
+
persistence: "durable",
|
|
71
|
+
isolated: true,
|
|
72
|
+
terminalEvidence: true,
|
|
73
|
+
authorityModes: Object.freeze(["canonical"]),
|
|
74
|
+
featureGated: true,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_recover(runId) {
|
|
79
|
+
try {
|
|
80
|
+
return this.kernel.recoverRun(runId);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error?.code !== "CC_GRAPH_STALE_WRITER") throw error;
|
|
83
|
+
}
|
|
84
|
+
const events = this.kernel.eventStore.read(runId);
|
|
85
|
+
const latest = events.at(-1);
|
|
86
|
+
const previous = [...events]
|
|
87
|
+
.reverse()
|
|
88
|
+
.find((event) => event.payload?.state?.authority)?.payload
|
|
89
|
+
.state.authority;
|
|
90
|
+
const writer = this.kernel.writerIdentity;
|
|
91
|
+
if (
|
|
92
|
+
!latest ||
|
|
93
|
+
!previous ||
|
|
94
|
+
Number(writer.authorityGeneration) <= Number(previous.authorityGeneration)
|
|
95
|
+
) {
|
|
96
|
+
const error = new Error(
|
|
97
|
+
"scheduler recovery requires an explicitly higher Graph writer generation",
|
|
98
|
+
);
|
|
99
|
+
error.code = "CC_GRAPH_STALE_GENERATION";
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
return this.kernel.recoverRun(runId, {
|
|
103
|
+
authority: createGraphAuthorityBinding({
|
|
104
|
+
...previous,
|
|
105
|
+
logicalRunId: runId,
|
|
106
|
+
originSurface: "scheduler",
|
|
107
|
+
authorityMode: "canonical",
|
|
108
|
+
authoritySource: "graph_kernel",
|
|
109
|
+
authorityGeneration: writer.authorityGeneration,
|
|
110
|
+
writerId: writer.writerId,
|
|
111
|
+
writerLeaseId: writer.writerLeaseId,
|
|
112
|
+
writerLeaseExpiresAt: new Date(
|
|
113
|
+
this.kernel.now() + this.kernel.writerLeaseTtlMs,
|
|
114
|
+
).toISOString(),
|
|
115
|
+
eventHead: latest.hash,
|
|
116
|
+
}),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
64
120
|
dispatch(occurrence, compiledGraph, options = {}) {
|
|
65
121
|
const key = dispatchKey(occurrence);
|
|
66
122
|
const digest = keyDigest(key);
|
|
@@ -72,7 +128,7 @@ export class SchedulerGraphTriggerAdapter {
|
|
|
72
128
|
try {
|
|
73
129
|
run = this.kernel.getRun(existing.runId);
|
|
74
130
|
} catch {
|
|
75
|
-
run = this.
|
|
131
|
+
run = this._recover(existing.runId);
|
|
76
132
|
}
|
|
77
133
|
return Object.freeze({
|
|
78
134
|
occurrenceStatus: "succeeded",
|
|
@@ -86,7 +142,7 @@ export class SchedulerGraphTriggerAdapter {
|
|
|
86
142
|
.find((entry) => entry.phase === "pending");
|
|
87
143
|
if (pending) {
|
|
88
144
|
try {
|
|
89
|
-
const graphRun = this.
|
|
145
|
+
const graphRun = this._recover(pending.runId);
|
|
90
146
|
if (graphRun.revisionDigest !== compiledGraph.revisionDigest) {
|
|
91
147
|
const error = new Error(
|
|
92
148
|
"pending scheduler dispatch is bound to a different GraphRevision",
|
|
@@ -127,6 +183,7 @@ export class SchedulerGraphTriggerAdapter {
|
|
|
127
183
|
const graphRun = this.kernel.startRun(compiledGraph, {
|
|
128
184
|
...options,
|
|
129
185
|
runId,
|
|
186
|
+
originSurface: "scheduler",
|
|
130
187
|
occurrenceRef: {
|
|
131
188
|
jobRevision: occurrence.jobRevision,
|
|
132
189
|
occurrenceId: occurrence.occurrenceId || occurrence.idempotencyKey,
|
|
@@ -173,7 +230,7 @@ export class SchedulerGraphTriggerAdapter {
|
|
|
173
230
|
try {
|
|
174
231
|
graphRun = this.kernel.getRun(accepted.runId);
|
|
175
232
|
} catch {
|
|
176
|
-
graphRun = this.
|
|
233
|
+
graphRun = this._recover(accepted.runId);
|
|
177
234
|
}
|
|
178
235
|
return Object.freeze({
|
|
179
236
|
occurrenceStatus: "succeeded",
|
|
@@ -205,6 +205,7 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
205
205
|
// null (the default for plain questions) → no binding check, unchanged.
|
|
206
206
|
binding: options.binding || null,
|
|
207
207
|
bindingType: options.bindingType || null,
|
|
208
|
+
requireBinding: options.requireBinding === true,
|
|
208
209
|
});
|
|
209
210
|
|
|
210
211
|
if (approvalRequest) {
|
|
@@ -253,6 +254,7 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
253
254
|
// Approval binding pass-through: a permission-gate producer supplies
|
|
254
255
|
// it; plain questions never carry one (message stays byte-identical).
|
|
255
256
|
...(options.binding ? { binding: options.binding } : {}),
|
|
257
|
+
...(options.requireBinding === true ? { requireBinding: true } : {}),
|
|
256
258
|
},
|
|
257
259
|
);
|
|
258
260
|
}
|
|
@@ -290,6 +292,45 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
290
292
|
return answer === "true" || answer === "yes" || answer === "y";
|
|
291
293
|
}
|
|
292
294
|
|
|
295
|
+
/**
|
|
296
|
+
* Structured approval variant used by Desktop/WebSocket hosts. Unlike the
|
|
297
|
+
* legacy boolean askConfirm path, this preserves the canonical decision
|
|
298
|
+
* lifetime and requires the trusted host to echo the exact request binding.
|
|
299
|
+
*/
|
|
300
|
+
async askApproval(question, options = {}) {
|
|
301
|
+
const {
|
|
302
|
+
binding = null,
|
|
303
|
+
default: dflt = false,
|
|
304
|
+
approval = {},
|
|
305
|
+
...extra
|
|
306
|
+
} = options || {};
|
|
307
|
+
const answer = await this._ask(
|
|
308
|
+
"confirm",
|
|
309
|
+
question,
|
|
310
|
+
{ default: dflt, approval, ...extra },
|
|
311
|
+
binding ? { binding, requireBinding: true } : {},
|
|
312
|
+
);
|
|
313
|
+
const candidate =
|
|
314
|
+
answer?.decision && typeof answer.decision === "object"
|
|
315
|
+
? answer.decision
|
|
316
|
+
: answer;
|
|
317
|
+
if (
|
|
318
|
+
candidate &&
|
|
319
|
+
typeof candidate === "object" &&
|
|
320
|
+
[
|
|
321
|
+
"acceptOnce",
|
|
322
|
+
"acceptForTurn",
|
|
323
|
+
"acceptForSession",
|
|
324
|
+
"decline",
|
|
325
|
+
"cancel",
|
|
326
|
+
].includes(candidate.kind)
|
|
327
|
+
) {
|
|
328
|
+
return candidate;
|
|
329
|
+
}
|
|
330
|
+
if (answer === true) return { kind: "acceptOnce" };
|
|
331
|
+
return { kind: "decline" };
|
|
332
|
+
}
|
|
333
|
+
|
|
293
334
|
/**
|
|
294
335
|
* Agent-core AskUserQuestion transport for Desktop/WebSocket consumers.
|
|
295
336
|
* It uses the same pending map and `session-answer` resolver as MCP
|
|
@@ -369,7 +410,7 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
369
410
|
* Resolves the corresponding pending promise.
|
|
370
411
|
*/
|
|
371
412
|
resolveAnswer(requestId, answer, binding = null) {
|
|
372
|
-
this._resolvePending(requestId, answer, "question", binding);
|
|
413
|
+
return this._resolvePending(requestId, answer, "question", binding);
|
|
373
414
|
}
|
|
374
415
|
|
|
375
416
|
/**
|
|
@@ -441,7 +482,7 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
441
482
|
}
|
|
442
483
|
|
|
443
484
|
resolveHostTool(requestId, payload) {
|
|
444
|
-
this._resolvePending(requestId, payload, "host-tool");
|
|
485
|
+
return this._resolvePending(requestId, payload, "host-tool");
|
|
445
486
|
}
|
|
446
487
|
|
|
447
488
|
rejectAllPending(reason = createAbortError("Interaction interrupted")) {
|
|
@@ -549,7 +590,7 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
549
590
|
) {
|
|
550
591
|
const pending = this._pending.get(requestId);
|
|
551
592
|
if (!pending) {
|
|
552
|
-
return;
|
|
593
|
+
return Object.freeze({ settled: false, reason: "not_pending" });
|
|
553
594
|
}
|
|
554
595
|
// Kind guard: a question-answer must not settle a host-tool request (or
|
|
555
596
|
// vice versa). On a mismatch, IGNORE the message — leave the request pending
|
|
@@ -559,7 +600,7 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
559
600
|
// value). Backward-compatible: callers passing no expectedKind, and
|
|
560
601
|
// pre-kind pending entries, behave exactly as before.
|
|
561
602
|
if (expectedKind && pending.kind && pending.kind !== expectedKind) {
|
|
562
|
-
return;
|
|
603
|
+
return Object.freeze({ settled: false, reason: "kind_mismatch" });
|
|
563
604
|
}
|
|
564
605
|
|
|
565
606
|
// Approval-binding guard (authority §"权限来源"): if this request was raised
|
|
@@ -572,17 +613,18 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
572
613
|
pending.bindingType === "interaction" &&
|
|
573
614
|
!sameInteractionBinding(pending.binding, incomingBinding)
|
|
574
615
|
) {
|
|
575
|
-
return;
|
|
616
|
+
return Object.freeze({ settled: false, reason: "binding_mismatch" });
|
|
576
617
|
}
|
|
577
618
|
|
|
578
619
|
let effective = payload;
|
|
579
620
|
if (
|
|
580
621
|
pending.bindingType !== "interaction" &&
|
|
581
622
|
pending.binding &&
|
|
582
|
-
incomingBinding
|
|
583
|
-
|
|
623
|
+
((pending.requireBinding && !incomingBinding) ||
|
|
624
|
+
(incomingBinding &&
|
|
625
|
+
!verifyApprovalBinding(pending.binding, incomingBinding)))
|
|
584
626
|
) {
|
|
585
|
-
effective = false;
|
|
627
|
+
effective = pending.requireBinding ? { kind: "decline" } : false;
|
|
586
628
|
}
|
|
587
629
|
|
|
588
630
|
this._pending.delete(requestId);
|
|
@@ -594,13 +636,23 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
594
636
|
effective === true ||
|
|
595
637
|
effective === "true" ||
|
|
596
638
|
effective === "yes" ||
|
|
597
|
-
effective === "y"
|
|
639
|
+
effective === "y" ||
|
|
640
|
+
["acceptOnce", "acceptForTurn", "acceptForSession"].includes(
|
|
641
|
+
effective?.kind || effective?.decision?.kind,
|
|
642
|
+
);
|
|
598
643
|
this._notifyPendingApproval("approval.settled", {
|
|
599
644
|
requestId,
|
|
600
645
|
reason: normalizedApproval ? "approved" : "denied",
|
|
601
646
|
});
|
|
602
647
|
}
|
|
603
648
|
pending.resolve(effective);
|
|
649
|
+
return Object.freeze({
|
|
650
|
+
settled: true,
|
|
651
|
+
reason:
|
|
652
|
+
effective !== payload && pending.requireBinding
|
|
653
|
+
? "binding_mismatch_denied"
|
|
654
|
+
: "resolved",
|
|
655
|
+
});
|
|
604
656
|
}
|
|
605
657
|
}
|
|
606
658
|
|