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
|
@@ -4,9 +4,15 @@ import {
|
|
|
4
4
|
compileGraphDefinition,
|
|
5
5
|
executionAttemptId,
|
|
6
6
|
graphDigest,
|
|
7
|
+
validateGraphDefinitionMigrationEvidence,
|
|
7
8
|
writeScopesOverlap,
|
|
8
9
|
} from "./compiler.js";
|
|
9
10
|
import { GraphEventStore } from "./event-store.js";
|
|
11
|
+
import {
|
|
12
|
+
GRAPH_PROJECTION_VERSION,
|
|
13
|
+
assertGraphAuthorityWriter,
|
|
14
|
+
createGraphAuthorityBinding,
|
|
15
|
+
} from "./authority.js";
|
|
10
16
|
|
|
11
17
|
export const GRAPH_RUN_PHASES = Object.freeze(["open", "sealed"]);
|
|
12
18
|
export const GRAPH_RUN_STATUSES = Object.freeze([
|
|
@@ -351,6 +357,7 @@ function stateSnapshot(run) {
|
|
|
351
357
|
version: 1,
|
|
352
358
|
id: run.id,
|
|
353
359
|
definition: run.compiled.definition,
|
|
360
|
+
definitionMigration: clone(run.definitionMigration),
|
|
354
361
|
subgraphDefinitions: run.compiled.subgraphDefinitions,
|
|
355
362
|
phase: run.phase,
|
|
356
363
|
status: run.status,
|
|
@@ -358,6 +365,7 @@ function stateSnapshot(run) {
|
|
|
358
365
|
revisionDigest: run.revisionDigest,
|
|
359
366
|
occurrenceRef: run.occurrenceRef,
|
|
360
367
|
authorityDigest: run.authorityDigest,
|
|
368
|
+
authority: clone(run.authority),
|
|
361
369
|
budget: run.budget,
|
|
362
370
|
budgetUsed: run.budgetUsed,
|
|
363
371
|
budgetReserved: run.budgetReserved,
|
|
@@ -398,12 +406,24 @@ function restoreState(run, snapshot, compiled = null) {
|
|
|
398
406
|
subgraphs: persistedSubgraphRegistry(snapshot.subgraphDefinitions),
|
|
399
407
|
allowUnresolvedSubgraphs: snapshot.subgraphDefinitions == null,
|
|
400
408
|
});
|
|
409
|
+
run.definitionMigration = snapshot.definitionMigration
|
|
410
|
+
? clone(
|
|
411
|
+
validateGraphDefinitionMigrationEvidence(snapshot.definitionMigration),
|
|
412
|
+
)
|
|
413
|
+
: clone(run.compiled.definitionMigration || null);
|
|
401
414
|
run.phase = snapshot.phase;
|
|
402
415
|
run.status = snapshot.status;
|
|
403
416
|
run.graphRevision = snapshot.graphRevision;
|
|
404
417
|
run.revisionDigest = snapshot.revisionDigest;
|
|
405
418
|
run.occurrenceRef = clone(snapshot.occurrenceRef);
|
|
406
419
|
run.authorityDigest = snapshot.authorityDigest;
|
|
420
|
+
if (!snapshot.authority) {
|
|
421
|
+
throw kernelError(
|
|
422
|
+
"CC_GRAPH_AUTHORITY_MIGRATION_REQUIRED",
|
|
423
|
+
"legacy GraphRun snapshot has no writer authority; import it at a verified safe point",
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
run.authority = createGraphAuthorityBinding(snapshot.authority);
|
|
407
427
|
run.budget = clone(snapshot.budget);
|
|
408
428
|
run.budgetUsed = clone(snapshot.budgetUsed);
|
|
409
429
|
run.budgetReserved = clone(snapshot.budgetReserved || emptyBudgetUsage());
|
|
@@ -462,17 +482,49 @@ function attemptProjection(attempt) {
|
|
|
462
482
|
return Object.freeze(clone(attempt));
|
|
463
483
|
}
|
|
464
484
|
|
|
485
|
+
function humanTaskProjection(task) {
|
|
486
|
+
const projected = clone(task);
|
|
487
|
+
if (
|
|
488
|
+
projected.claimExpiresAt == null &&
|
|
489
|
+
Number.isFinite(projected.claimExpiresAtMs)
|
|
490
|
+
) {
|
|
491
|
+
projected.claimExpiresAt = nowIso(() => projected.claimExpiresAtMs);
|
|
492
|
+
}
|
|
493
|
+
delete projected.claimExpiresAtMs;
|
|
494
|
+
delete projected.expiresAtMs;
|
|
495
|
+
return Object.freeze(projected);
|
|
496
|
+
}
|
|
497
|
+
|
|
465
498
|
function runProjection(run) {
|
|
466
499
|
return Object.freeze({
|
|
467
500
|
id: run.id,
|
|
468
501
|
definitionId: run.compiled.definitionId,
|
|
469
502
|
revision: run.compiled.revision,
|
|
470
503
|
revisionDigest: run.revisionDigest,
|
|
504
|
+
definitionMigration: run.definitionMigration
|
|
505
|
+
? Object.freeze({
|
|
506
|
+
schema: run.definitionMigration.schema,
|
|
507
|
+
fromVersion: run.definitionMigration.fromVersion,
|
|
508
|
+
toVersion: run.definitionMigration.toVersion,
|
|
509
|
+
revisionDigest: run.definitionMigration.revisionDigest,
|
|
510
|
+
rollbackDigest: run.definitionMigration.rollbackDigest,
|
|
511
|
+
backupAvailable: true,
|
|
512
|
+
})
|
|
513
|
+
: null,
|
|
471
514
|
occurrenceRef: clone(run.occurrenceRef),
|
|
472
515
|
phase: run.phase,
|
|
473
516
|
status: run.status,
|
|
474
517
|
graphRevision: run.graphRevision,
|
|
475
518
|
authorityDigest: run.authorityDigest,
|
|
519
|
+
originSurface: run.authority.originSurface,
|
|
520
|
+
authoritySource: run.authority.authoritySource,
|
|
521
|
+
authorityMode: run.authority.authorityMode,
|
|
522
|
+
authorityGeneration: run.authority.authorityGeneration,
|
|
523
|
+
writerId: run.authority.writerId,
|
|
524
|
+
writerLeaseId: run.authority.writerLeaseId,
|
|
525
|
+
writerLeaseExpiresAt: run.authority.writerLeaseExpiresAt,
|
|
526
|
+
eventHead: run.authority.eventHead,
|
|
527
|
+
projectionVersion: run.authority.projectionVersion,
|
|
476
528
|
budget: clone(run.budget),
|
|
477
529
|
budgetUsed: clone(run.budgetUsed),
|
|
478
530
|
budgetReserved: clone(run.budgetReserved),
|
|
@@ -527,11 +579,49 @@ function runProjection(run) {
|
|
|
527
579
|
});
|
|
528
580
|
}
|
|
529
581
|
|
|
530
|
-
function buildRun(compiled, options, now) {
|
|
582
|
+
function buildRun(compiled, options, now, writerIdentity, writerLeaseTtlMs) {
|
|
531
583
|
const createdAt = nowIso(now);
|
|
584
|
+
const id = safeIdentifier(options.runId || randomUUID(), "runId");
|
|
585
|
+
const authority = createGraphAuthorityBinding(
|
|
586
|
+
options.authority || {
|
|
587
|
+
logicalRunId: id,
|
|
588
|
+
originSurface: options.originSurface || "cli_team",
|
|
589
|
+
authorityMode:
|
|
590
|
+
writerIdentity.authoritySource === "graph_kernel_shadow"
|
|
591
|
+
? "shadow"
|
|
592
|
+
: "canonical",
|
|
593
|
+
authoritySource: writerIdentity.authoritySource,
|
|
594
|
+
authorityGeneration:
|
|
595
|
+
options.authorityGeneration || writerIdentity.authorityGeneration,
|
|
596
|
+
writerId: writerIdentity.writerId,
|
|
597
|
+
writerLeaseId: writerIdentity.writerLeaseId,
|
|
598
|
+
writerLeaseExpiresAt: new Date(
|
|
599
|
+
now() + Math.max(1, Number(writerLeaseTtlMs)),
|
|
600
|
+
).toISOString(),
|
|
601
|
+
eventHead: null,
|
|
602
|
+
projectionVersion: GRAPH_PROJECTION_VERSION,
|
|
603
|
+
},
|
|
604
|
+
);
|
|
605
|
+
if (authority.logicalRunId !== id) {
|
|
606
|
+
throw kernelError(
|
|
607
|
+
"CC_GRAPH_AUTHORITY_RUN_MISMATCH",
|
|
608
|
+
"Graph authority must be bound to the exact logical run id",
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
if (authority.authorityMode === "legacy") {
|
|
612
|
+
throw kernelError(
|
|
613
|
+
"CC_GRAPH_NON_CANONICAL_WRITER",
|
|
614
|
+
"legacy authority cannot mutate Graph Kernel state",
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
assertGraphAuthorityWriter(authority, writerIdentity, {
|
|
618
|
+
now: now(),
|
|
619
|
+
requireCanonical: false,
|
|
620
|
+
});
|
|
532
621
|
const run = {
|
|
533
|
-
id
|
|
622
|
+
id,
|
|
534
623
|
compiled,
|
|
624
|
+
definitionMigration: clone(compiled.definitionMigration || null),
|
|
535
625
|
phase: "open",
|
|
536
626
|
status: "running",
|
|
537
627
|
graphRevision: compiled.revision,
|
|
@@ -547,6 +637,7 @@ function buildRun(compiled, options, now) {
|
|
|
547
637
|
},
|
|
548
638
|
"cc.graph.authority/v1",
|
|
549
639
|
),
|
|
640
|
+
authority,
|
|
550
641
|
budget: clone(options.budget || compiled.definition.budget || {}),
|
|
551
642
|
budgetUsed: emptyBudgetUsage(),
|
|
552
643
|
budgetReserved: emptyBudgetUsage(),
|
|
@@ -885,6 +976,7 @@ function sweepExpirations(run, now) {
|
|
|
885
976
|
handoff.expiresAtMs <= now
|
|
886
977
|
) {
|
|
887
978
|
handoff.status = "expired";
|
|
979
|
+
handoff.expiredAt = nowIso(() => now);
|
|
888
980
|
handoff.updatedAt = nowIso(() => now);
|
|
889
981
|
changed = true;
|
|
890
982
|
}
|
|
@@ -902,6 +994,7 @@ function sweepExpirations(run, now) {
|
|
|
902
994
|
task.status = "open";
|
|
903
995
|
task.claimActorId = null;
|
|
904
996
|
task.claimLeaseId = null;
|
|
997
|
+
task.claimExpiresAt = null;
|
|
905
998
|
task.claimExpiresAtMs = null;
|
|
906
999
|
task.updatedAt = nowIso(() => now);
|
|
907
1000
|
changed = true;
|
|
@@ -1109,6 +1202,13 @@ function classifyQuiescence(run, now) {
|
|
|
1109
1202
|
) {
|
|
1110
1203
|
return "running";
|
|
1111
1204
|
}
|
|
1205
|
+
if (
|
|
1206
|
+
[...run.humanTasks.values()].some((task) =>
|
|
1207
|
+
["open", "claimed"].includes(task.status),
|
|
1208
|
+
)
|
|
1209
|
+
) {
|
|
1210
|
+
return "waiting_human";
|
|
1211
|
+
}
|
|
1112
1212
|
if (
|
|
1113
1213
|
[...run.attempts.values()].some((attempt) =>
|
|
1114
1214
|
ACTIVE_ATTEMPT.has(attempt.status),
|
|
@@ -1130,13 +1230,6 @@ function classifyQuiescence(run, now) {
|
|
|
1130
1230
|
) {
|
|
1131
1231
|
return "reconciliation_required";
|
|
1132
1232
|
}
|
|
1133
|
-
if (
|
|
1134
|
-
[...run.humanTasks.values()].some((task) =>
|
|
1135
|
-
["open", "claimed"].includes(task.status),
|
|
1136
|
-
)
|
|
1137
|
-
) {
|
|
1138
|
-
return "waiting_human";
|
|
1139
|
-
}
|
|
1140
1233
|
if (
|
|
1141
1234
|
[...run.messages.values()].some((message) =>
|
|
1142
1235
|
["admitted", "delivered", "read"].includes(message.status),
|
|
@@ -1187,6 +1280,11 @@ export class GraphKernel {
|
|
|
1187
1280
|
agingWindowMs = 1000,
|
|
1188
1281
|
maxPendingMessagesPerAgent = 100,
|
|
1189
1282
|
maxLivelockRepeats = 8,
|
|
1283
|
+
writerId = `graph-kernel:${process.pid}:${randomUUID()}`,
|
|
1284
|
+
writerLeaseId = `graph-kernel-lease:${randomUUID()}`,
|
|
1285
|
+
authoritySource = "graph_kernel",
|
|
1286
|
+
authorityGeneration = 1,
|
|
1287
|
+
writerLeaseTtlMs = 24 * 60 * 60 * 1000,
|
|
1190
1288
|
} = {}) {
|
|
1191
1289
|
this.eventStore = eventStore;
|
|
1192
1290
|
this.now = now;
|
|
@@ -1197,6 +1295,13 @@ export class GraphKernel {
|
|
|
1197
1295
|
Number(maxPendingMessagesPerAgent) || 100,
|
|
1198
1296
|
);
|
|
1199
1297
|
this.maxLivelockRepeats = Math.max(2, Number(maxLivelockRepeats) || 8);
|
|
1298
|
+
this.writerIdentity = Object.freeze({
|
|
1299
|
+
authoritySource,
|
|
1300
|
+
authorityGeneration: Math.max(1, Number(authorityGeneration) || 1),
|
|
1301
|
+
writerId: safeIdentifier(writerId, "writerId"),
|
|
1302
|
+
writerLeaseId: safeIdentifier(writerLeaseId, "writerLeaseId"),
|
|
1303
|
+
});
|
|
1304
|
+
this.writerLeaseTtlMs = Math.max(1, Number(writerLeaseTtlMs) || 1);
|
|
1200
1305
|
this.runs = new Map();
|
|
1201
1306
|
this.occurrences = new Map();
|
|
1202
1307
|
}
|
|
@@ -1233,6 +1338,19 @@ export class GraphKernel {
|
|
|
1233
1338
|
`GraphRun is terminal: ${run.status}`,
|
|
1234
1339
|
);
|
|
1235
1340
|
}
|
|
1341
|
+
const expectedHeadHash = run.lastEvent?.hash || run.authority.eventHead;
|
|
1342
|
+
const expectedRevision = run.lastEvent?.seq;
|
|
1343
|
+
if (run.authority.authorityMode === "legacy") {
|
|
1344
|
+
throw kernelError(
|
|
1345
|
+
"CC_GRAPH_NON_CANONICAL_WRITER",
|
|
1346
|
+
"legacy authority cannot mutate Graph Kernel state",
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
assertGraphAuthorityWriter(run.authority, this.writerIdentity, {
|
|
1350
|
+
now: this.now(),
|
|
1351
|
+
expectedEventHead: expectedHeadHash,
|
|
1352
|
+
requireCanonical: false,
|
|
1353
|
+
});
|
|
1236
1354
|
const before = stateSnapshot(run);
|
|
1237
1355
|
try {
|
|
1238
1356
|
const result = mutator();
|
|
@@ -1245,9 +1363,16 @@ export class GraphKernel {
|
|
|
1245
1363
|
{
|
|
1246
1364
|
idempotencyKey: options.idempotencyKey || null,
|
|
1247
1365
|
traceId: options.traceId || null,
|
|
1366
|
+
authority: run.authority,
|
|
1367
|
+
expectedRevision,
|
|
1368
|
+
expectedHeadHash,
|
|
1248
1369
|
},
|
|
1249
1370
|
);
|
|
1250
1371
|
run.lastEvent = event;
|
|
1372
|
+
run.authority = createGraphAuthorityBinding({
|
|
1373
|
+
...run.authority,
|
|
1374
|
+
eventHead: event.hash,
|
|
1375
|
+
});
|
|
1251
1376
|
return result;
|
|
1252
1377
|
} catch (error) {
|
|
1253
1378
|
restoreState(run, before);
|
|
@@ -1271,16 +1396,32 @@ export class GraphKernel {
|
|
|
1271
1396
|
}
|
|
1272
1397
|
return runProjection(existing);
|
|
1273
1398
|
}
|
|
1274
|
-
const run = buildRun(
|
|
1399
|
+
const run = buildRun(
|
|
1400
|
+
compiled,
|
|
1401
|
+
options,
|
|
1402
|
+
this.now,
|
|
1403
|
+
this.writerIdentity,
|
|
1404
|
+
this.writerLeaseTtlMs,
|
|
1405
|
+
);
|
|
1275
1406
|
if (this.runs.has(run.id)) {
|
|
1276
1407
|
throw kernelError(
|
|
1277
1408
|
"CC_GRAPH_RUN_CONFLICT",
|
|
1278
1409
|
`GraphRun id already exists: ${run.id}`,
|
|
1279
1410
|
);
|
|
1280
1411
|
}
|
|
1281
|
-
this.eventStore.start(run.id, {
|
|
1412
|
+
const initialHead = this.eventStore.start(run.id, {
|
|
1282
1413
|
definitionId: compiled.definitionId,
|
|
1283
1414
|
revisionDigest: compiled.revisionDigest,
|
|
1415
|
+
originSurface: run.authority.originSurface,
|
|
1416
|
+
authorityMode: run.authority.authorityMode,
|
|
1417
|
+
authorityGeneration: run.authority.authorityGeneration,
|
|
1418
|
+
writerId: run.authority.writerId,
|
|
1419
|
+
projectionVersion: run.authority.projectionVersion,
|
|
1420
|
+
});
|
|
1421
|
+
run.lastEvent = initialHead;
|
|
1422
|
+
run.authority = createGraphAuthorityBinding({
|
|
1423
|
+
...run.authority,
|
|
1424
|
+
eventHead: initialHead.hash,
|
|
1284
1425
|
});
|
|
1285
1426
|
this.runs.set(run.id, run);
|
|
1286
1427
|
try {
|
|
@@ -1306,7 +1447,7 @@ export class GraphKernel {
|
|
|
1306
1447
|
return runProjection(run);
|
|
1307
1448
|
}
|
|
1308
1449
|
|
|
1309
|
-
recoverRun(runId) {
|
|
1450
|
+
recoverRun(runId, { authority = null } = {}) {
|
|
1310
1451
|
const id = safeIdentifier(runId, "runId");
|
|
1311
1452
|
const events = this.eventStore.read(id);
|
|
1312
1453
|
const latest = [...events]
|
|
@@ -1322,7 +1463,67 @@ export class GraphKernel {
|
|
|
1322
1463
|
const run = { id, lastEvent: latest };
|
|
1323
1464
|
restoreState(run, snapshot);
|
|
1324
1465
|
run.lastEvent = latest;
|
|
1466
|
+
run.authority = createGraphAuthorityBinding({
|
|
1467
|
+
...run.authority,
|
|
1468
|
+
eventHead: latest.hash,
|
|
1469
|
+
});
|
|
1325
1470
|
this.runs.set(id, run);
|
|
1471
|
+
if (authority) {
|
|
1472
|
+
const nextAuthority = createGraphAuthorityBinding({
|
|
1473
|
+
...authority,
|
|
1474
|
+
logicalRunId: id,
|
|
1475
|
+
eventHead: latest.hash,
|
|
1476
|
+
});
|
|
1477
|
+
const previousAuthority = run.authority;
|
|
1478
|
+
if (
|
|
1479
|
+
nextAuthority.authorityGeneration <=
|
|
1480
|
+
previousAuthority.authorityGeneration
|
|
1481
|
+
) {
|
|
1482
|
+
this.runs.delete(id);
|
|
1483
|
+
throw kernelError(
|
|
1484
|
+
"CC_GRAPH_STALE_GENERATION",
|
|
1485
|
+
"recovery authority must use a higher writer generation",
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
assertGraphAuthorityWriter(nextAuthority, this.writerIdentity, {
|
|
1489
|
+
now: this.now(),
|
|
1490
|
+
expectedEventHead: latest.hash,
|
|
1491
|
+
requireCanonical: false,
|
|
1492
|
+
});
|
|
1493
|
+
run.authority = nextAuthority;
|
|
1494
|
+
try {
|
|
1495
|
+
this._transaction(
|
|
1496
|
+
run,
|
|
1497
|
+
"run.authority_transferred",
|
|
1498
|
+
{
|
|
1499
|
+
previousAuthoritySource: previousAuthority.authoritySource,
|
|
1500
|
+
previousAuthorityGeneration: previousAuthority.authorityGeneration,
|
|
1501
|
+
previousWriterId: previousAuthority.writerId,
|
|
1502
|
+
authorityGeneration: nextAuthority.authorityGeneration,
|
|
1503
|
+
writerId: nextAuthority.writerId,
|
|
1504
|
+
},
|
|
1505
|
+
() => {},
|
|
1506
|
+
{
|
|
1507
|
+
allowTerminal: true,
|
|
1508
|
+
idempotencyKey: `authority-transfer:${nextAuthority.authorityGeneration}`,
|
|
1509
|
+
},
|
|
1510
|
+
);
|
|
1511
|
+
} catch (error) {
|
|
1512
|
+
this.runs.delete(id);
|
|
1513
|
+
throw error;
|
|
1514
|
+
}
|
|
1515
|
+
} else {
|
|
1516
|
+
try {
|
|
1517
|
+
assertGraphAuthorityWriter(run.authority, this.writerIdentity, {
|
|
1518
|
+
now: this.now(),
|
|
1519
|
+
expectedEventHead: latest.hash,
|
|
1520
|
+
requireCanonical: false,
|
|
1521
|
+
});
|
|
1522
|
+
} catch (error) {
|
|
1523
|
+
this.runs.delete(id);
|
|
1524
|
+
throw error;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1326
1527
|
if (run.occurrenceRef) {
|
|
1327
1528
|
const key = `${run.occurrenceRef.jobRevision || ""}\0${run.occurrenceRef.occurrenceId || run.occurrenceRef.idempotencyKey || ""}`;
|
|
1328
1529
|
this.occurrences.set(key, id);
|
|
@@ -1367,6 +1568,65 @@ export class GraphKernel {
|
|
|
1367
1568
|
return this.eventStore.read(safeIdentifier(runId, "runId"), options);
|
|
1368
1569
|
}
|
|
1369
1570
|
|
|
1571
|
+
collaborationState(runId) {
|
|
1572
|
+
const run = this._run(runId);
|
|
1573
|
+
return Object.freeze({
|
|
1574
|
+
messages: Object.freeze(
|
|
1575
|
+
[...run.messages.values()]
|
|
1576
|
+
.sort(
|
|
1577
|
+
(left, right) =>
|
|
1578
|
+
left.createdAt.localeCompare(right.createdAt) ||
|
|
1579
|
+
left.id.localeCompare(right.id),
|
|
1580
|
+
)
|
|
1581
|
+
.map((message) => Object.freeze(clone(message))),
|
|
1582
|
+
),
|
|
1583
|
+
messageConsumers: Object.freeze(
|
|
1584
|
+
[...run.messageConsumers.values()]
|
|
1585
|
+
.sort(
|
|
1586
|
+
(left, right) =>
|
|
1587
|
+
left.processedAt.localeCompare(right.processedAt) ||
|
|
1588
|
+
left.messageId.localeCompare(right.messageId),
|
|
1589
|
+
)
|
|
1590
|
+
.map((receipt) => Object.freeze(clone(receipt))),
|
|
1591
|
+
),
|
|
1592
|
+
handoffs: Object.freeze(
|
|
1593
|
+
[...run.handoffs.values()]
|
|
1594
|
+
.sort(
|
|
1595
|
+
(left, right) =>
|
|
1596
|
+
left.createdAt.localeCompare(right.createdAt) ||
|
|
1597
|
+
left.id.localeCompare(right.id),
|
|
1598
|
+
)
|
|
1599
|
+
.map((handoff) => Object.freeze(clone(handoff))),
|
|
1600
|
+
),
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
effectState(runId) {
|
|
1605
|
+
const run = this._run(runId);
|
|
1606
|
+
return Object.freeze(
|
|
1607
|
+
[...run.effects.values()]
|
|
1608
|
+
.sort(
|
|
1609
|
+
(left, right) =>
|
|
1610
|
+
left.createdAt.localeCompare(right.createdAt) ||
|
|
1611
|
+
left.id.localeCompare(right.id),
|
|
1612
|
+
)
|
|
1613
|
+
.map((effect) => Object.freeze(clone(effect))),
|
|
1614
|
+
);
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
humanTasks(runId) {
|
|
1618
|
+
const run = this._run(runId);
|
|
1619
|
+
return Object.freeze(
|
|
1620
|
+
[...run.humanTasks.values()]
|
|
1621
|
+
.sort(
|
|
1622
|
+
(left, right) =>
|
|
1623
|
+
left.createdAt.localeCompare(right.createdAt) ||
|
|
1624
|
+
left.id.localeCompare(right.id),
|
|
1625
|
+
)
|
|
1626
|
+
.map(humanTaskProjection),
|
|
1627
|
+
);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1370
1630
|
registerAgent(runId, { agentId, capacity = 1, resident = true } = {}) {
|
|
1371
1631
|
const run = this._run(runId);
|
|
1372
1632
|
const id = safeIdentifier(agentId, "agentId");
|
|
@@ -1471,6 +1731,7 @@ export class GraphKernel {
|
|
|
1471
1731
|
edges = [],
|
|
1472
1732
|
loops = [],
|
|
1473
1733
|
subgraphCalls = [],
|
|
1734
|
+
metadataPatch = {},
|
|
1474
1735
|
} = {},
|
|
1475
1736
|
) {
|
|
1476
1737
|
const run = this._run(runId);
|
|
@@ -1483,6 +1744,7 @@ export class GraphKernel {
|
|
|
1483
1744
|
edges,
|
|
1484
1745
|
loops,
|
|
1485
1746
|
subgraphCalls,
|
|
1747
|
+
metadataPatch,
|
|
1486
1748
|
};
|
|
1487
1749
|
const requestDigest = graphDigest(request, "cc.graph.append-request/v1");
|
|
1488
1750
|
const replay = run.requestCache.get(key);
|
|
@@ -1538,6 +1800,10 @@ export class GraphKernel {
|
|
|
1538
1800
|
...clone(run.compiled.definition.subgraphCalls),
|
|
1539
1801
|
...clone(subgraphCalls),
|
|
1540
1802
|
],
|
|
1803
|
+
metadata: {
|
|
1804
|
+
...clone(run.compiled.definition.metadata || {}),
|
|
1805
|
+
...clone(metadataPatch || {}),
|
|
1806
|
+
},
|
|
1541
1807
|
};
|
|
1542
1808
|
const compiled = compileGraphDefinition(definition, {
|
|
1543
1809
|
subgraphs: persistedSubgraphRegistry(
|
|
@@ -1942,6 +2208,12 @@ export class GraphKernel {
|
|
|
1942
2208
|
} else {
|
|
1943
2209
|
this.startRun(childCompiled, {
|
|
1944
2210
|
runId: id,
|
|
2211
|
+
originSurface: parent.authority.originSurface,
|
|
2212
|
+
authority: {
|
|
2213
|
+
...parent.authority,
|
|
2214
|
+
logicalRunId: id,
|
|
2215
|
+
eventHead: null,
|
|
2216
|
+
},
|
|
1945
2217
|
parentRunRef: {
|
|
1946
2218
|
runId: parent.id,
|
|
1947
2219
|
nodeId: safeNodeId,
|
|
@@ -2257,6 +2529,8 @@ export class GraphKernel {
|
|
|
2257
2529
|
: null,
|
|
2258
2530
|
leaseId: safeIdentifier(leaseId, "leaseId"),
|
|
2259
2531
|
fence,
|
|
2532
|
+
authorityGeneration: run.authority.authorityGeneration,
|
|
2533
|
+
writerId: run.authority.writerId,
|
|
2260
2534
|
status: "active",
|
|
2261
2535
|
participationStatus: "active",
|
|
2262
2536
|
grant: clone(grant),
|
|
@@ -2266,6 +2540,7 @@ export class GraphKernel {
|
|
|
2266
2540
|
expiresAtMs: now + finitePositive(ttlMs, 60_000),
|
|
2267
2541
|
terminalEvidence: null,
|
|
2268
2542
|
usage: null,
|
|
2543
|
+
error: null,
|
|
2269
2544
|
};
|
|
2270
2545
|
run.attempts.set(id, attempt);
|
|
2271
2546
|
state.attemptIds.push(id);
|
|
@@ -2291,9 +2566,261 @@ export class GraphKernel {
|
|
|
2291
2566
|
});
|
|
2292
2567
|
}
|
|
2293
2568
|
|
|
2569
|
+
/**
|
|
2570
|
+
* Recover a dispatch that was durably assigned by an older writer but never
|
|
2571
|
+
* crossed the effect boundary. Once an effect record exists its outcome can
|
|
2572
|
+
* no longer be inferred from the absence of a terminal attempt, so recovery
|
|
2573
|
+
* must go through reconciliation instead of silently replaying the task.
|
|
2574
|
+
*/
|
|
2575
|
+
reclaimAttempt(
|
|
2576
|
+
runId,
|
|
2577
|
+
attemptId,
|
|
2578
|
+
{ reason = "writer generation takeover" } = {},
|
|
2579
|
+
) {
|
|
2580
|
+
const run = this._run(runId);
|
|
2581
|
+
const id = safeIdentifier(attemptId, "attemptId");
|
|
2582
|
+
return this._transaction(
|
|
2583
|
+
run,
|
|
2584
|
+
"assignment.reclaimed",
|
|
2585
|
+
{ attemptId: id, reason },
|
|
2586
|
+
() => {
|
|
2587
|
+
const attempt = run.attempts.get(id);
|
|
2588
|
+
if (!attempt) {
|
|
2589
|
+
throw kernelError(
|
|
2590
|
+
"CC_GRAPH_ATTEMPT_NOT_FOUND",
|
|
2591
|
+
`assignment attempt not found: ${id}`,
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
if (
|
|
2595
|
+
attempt.status === "expired" &&
|
|
2596
|
+
attempt.participationStatus === "lost"
|
|
2597
|
+
) {
|
|
2598
|
+
return attemptProjection(attempt);
|
|
2599
|
+
}
|
|
2600
|
+
if (!ACTIVE_ATTEMPT.has(attempt.status)) {
|
|
2601
|
+
throw kernelError(
|
|
2602
|
+
"CC_GRAPH_ASSIGNMENT_RECLAIM_TERMINAL",
|
|
2603
|
+
"only an active assignment can be reclaimed",
|
|
2604
|
+
);
|
|
2605
|
+
}
|
|
2606
|
+
if (
|
|
2607
|
+
Number(attempt.authorityGeneration || 0) >=
|
|
2608
|
+
run.authority.authorityGeneration
|
|
2609
|
+
) {
|
|
2610
|
+
throw kernelError(
|
|
2611
|
+
"CC_GRAPH_ASSIGNMENT_RECLAIM_STALE_WRITER",
|
|
2612
|
+
"an assignment can only be reclaimed by a higher authority generation",
|
|
2613
|
+
);
|
|
2614
|
+
}
|
|
2615
|
+
const effects = [...run.effects.values()].filter(
|
|
2616
|
+
(effect) => effect.attemptId === attempt.id,
|
|
2617
|
+
);
|
|
2618
|
+
if (effects.length > 0) {
|
|
2619
|
+
throw kernelError(
|
|
2620
|
+
"CC_GRAPH_ASSIGNMENT_RECLAIM_UNSAFE",
|
|
2621
|
+
"an assignment with an effect record requires audited reconciliation",
|
|
2622
|
+
{ effectIds: effects.map((effect) => effect.id).sort() },
|
|
2623
|
+
);
|
|
2624
|
+
}
|
|
2625
|
+
const state = run.nodeStates.get(attempt.nodeId);
|
|
2626
|
+
if (
|
|
2627
|
+
!state ||
|
|
2628
|
+
state.acceptedAttemptId ||
|
|
2629
|
+
!["running", "pending"].includes(state.status)
|
|
2630
|
+
) {
|
|
2631
|
+
throw kernelError(
|
|
2632
|
+
"CC_GRAPH_ASSIGNMENT_RECLAIM_UNSAFE",
|
|
2633
|
+
"assignment state is not safe to return to the ready frontier",
|
|
2634
|
+
);
|
|
2635
|
+
}
|
|
2636
|
+
const now = this.now();
|
|
2637
|
+
attempt.status = "expired";
|
|
2638
|
+
attempt.participationStatus = "lost";
|
|
2639
|
+
attempt.error = String(reason).slice(0, 4096);
|
|
2640
|
+
attempt.updatedAt = nowIso(() => now);
|
|
2641
|
+
state.status = "pending";
|
|
2642
|
+
state.updatedAt = nowIso(() => now);
|
|
2643
|
+
const agent = run.agents.get(attempt.agentId);
|
|
2644
|
+
if (agent) {
|
|
2645
|
+
agent.status = "idle";
|
|
2646
|
+
agent.updatedAt = nowIso(() => now);
|
|
2647
|
+
}
|
|
2648
|
+
propagate(run, () => now);
|
|
2649
|
+
if (refreshLoopStates(run, () => now)) propagate(run, () => now);
|
|
2650
|
+
run.status = classifyQuiescence(run, now);
|
|
2651
|
+
run.completedAt = TERMINAL_RUN.has(run.status)
|
|
2652
|
+
? nowIso(() => now)
|
|
2653
|
+
: null;
|
|
2654
|
+
return attemptProjection(attempt);
|
|
2655
|
+
},
|
|
2656
|
+
{
|
|
2657
|
+
idempotencyKey: `assignment-reclaim:${id}:${run.authority.authorityGeneration}`,
|
|
2658
|
+
},
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
/**
|
|
2663
|
+
* Rotate a safely recoverable assignment into the current writer generation.
|
|
2664
|
+
* A persisted attempt is never reused after takeover: callers receive a new
|
|
2665
|
+
* lease/fence, while terminal effect receipts retain an explicit custody
|
|
2666
|
+
* chain. An in-flight/unknown effect remains reconciliation-only.
|
|
2667
|
+
*/
|
|
2668
|
+
resumeAttempt(
|
|
2669
|
+
runId,
|
|
2670
|
+
attemptId,
|
|
2671
|
+
{
|
|
2672
|
+
resumedAttemptId = this.createId(),
|
|
2673
|
+
leaseId = this.createId(),
|
|
2674
|
+
ttlMs = 60_000,
|
|
2675
|
+
reason = "writer generation takeover",
|
|
2676
|
+
} = {},
|
|
2677
|
+
) {
|
|
2678
|
+
const run = this._run(runId);
|
|
2679
|
+
const sourceId = safeIdentifier(attemptId, "attemptId");
|
|
2680
|
+
const successorId = safeIdentifier(resumedAttemptId, "attemptId");
|
|
2681
|
+
return this._transaction(
|
|
2682
|
+
run,
|
|
2683
|
+
"assignment.resumed",
|
|
2684
|
+
{ attemptId: sourceId, resumedAttemptId: successorId, reason },
|
|
2685
|
+
() => {
|
|
2686
|
+
const existing = [...run.attempts.values()].find(
|
|
2687
|
+
(candidate) =>
|
|
2688
|
+
candidate.resumedFromAttemptId === sourceId &&
|
|
2689
|
+
candidate.authorityGeneration ===
|
|
2690
|
+
run.authority.authorityGeneration &&
|
|
2691
|
+
candidate.writerId === run.authority.writerId,
|
|
2692
|
+
);
|
|
2693
|
+
if (existing) return attemptProjection(existing);
|
|
2694
|
+
const source = run.attempts.get(sourceId);
|
|
2695
|
+
if (!source) {
|
|
2696
|
+
throw kernelError(
|
|
2697
|
+
"CC_GRAPH_ATTEMPT_NOT_FOUND",
|
|
2698
|
+
`assignment attempt not found: ${sourceId}`,
|
|
2699
|
+
);
|
|
2700
|
+
}
|
|
2701
|
+
const auditedRecovery =
|
|
2702
|
+
source.status === "expired" &&
|
|
2703
|
+
source.participationStatus === "reconciled";
|
|
2704
|
+
if (!ACTIVE_ATTEMPT.has(source.status) && !auditedRecovery) {
|
|
2705
|
+
throw kernelError(
|
|
2706
|
+
"CC_GRAPH_ASSIGNMENT_RESUME_TERMINAL",
|
|
2707
|
+
"only an active or audited-reconciled assignment can be resumed",
|
|
2708
|
+
);
|
|
2709
|
+
}
|
|
2710
|
+
if (
|
|
2711
|
+
Number(source.authorityGeneration || 0) >=
|
|
2712
|
+
run.authority.authorityGeneration
|
|
2713
|
+
) {
|
|
2714
|
+
throw kernelError(
|
|
2715
|
+
"CC_GRAPH_ASSIGNMENT_RESUME_STALE_WRITER",
|
|
2716
|
+
"an assignment can only be resumed by a higher authority generation",
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2719
|
+
const effects = [...run.effects.values()].filter(
|
|
2720
|
+
(effect) => effect.attemptId === source.id,
|
|
2721
|
+
);
|
|
2722
|
+
const unsafe = effects.filter((effect) =>
|
|
2723
|
+
["started", "unknown"].includes(effect.status),
|
|
2724
|
+
);
|
|
2725
|
+
if (unsafe.length > 0) {
|
|
2726
|
+
throw kernelError(
|
|
2727
|
+
"CC_GRAPH_ASSIGNMENT_RESUME_UNSAFE",
|
|
2728
|
+
"an assignment with an unresolved effect requires audited reconciliation",
|
|
2729
|
+
{ effectIds: unsafe.map((effect) => effect.id).sort() },
|
|
2730
|
+
);
|
|
2731
|
+
}
|
|
2732
|
+
const state = run.nodeStates.get(source.nodeId);
|
|
2733
|
+
if (
|
|
2734
|
+
!state ||
|
|
2735
|
+
state.acceptedAttemptId ||
|
|
2736
|
+
!["running", "pending"].includes(state.status)
|
|
2737
|
+
) {
|
|
2738
|
+
throw kernelError(
|
|
2739
|
+
"CC_GRAPH_ASSIGNMENT_RESUME_UNSAFE",
|
|
2740
|
+
"assignment state is not safe to rotate into the new generation",
|
|
2741
|
+
);
|
|
2742
|
+
}
|
|
2743
|
+
if (run.attempts.has(successorId)) {
|
|
2744
|
+
throw kernelError(
|
|
2745
|
+
"CC_GRAPH_ATTEMPT_ID_CONFLICT",
|
|
2746
|
+
`assignment attempt already exists: ${successorId}`,
|
|
2747
|
+
);
|
|
2748
|
+
}
|
|
2749
|
+
const now = this.now();
|
|
2750
|
+
source.status = "expired";
|
|
2751
|
+
source.participationStatus = "superseded";
|
|
2752
|
+
source.error = String(reason).slice(0, 4096);
|
|
2753
|
+
source.updatedAt = nowIso(() => now);
|
|
2754
|
+
const fence = (run.fenceCounters.get(source.nodeId) || 0) + 1;
|
|
2755
|
+
run.fenceCounters.set(source.nodeId, fence);
|
|
2756
|
+
const successor = {
|
|
2757
|
+
...clone(source),
|
|
2758
|
+
id: successorId,
|
|
2759
|
+
attempt:
|
|
2760
|
+
[...run.attempts.values()].filter(
|
|
2761
|
+
(candidate) =>
|
|
2762
|
+
candidate.nodeId === source.nodeId &&
|
|
2763
|
+
JSON.stringify(candidate.iterationPath || []) ===
|
|
2764
|
+
JSON.stringify(source.iterationPath || []),
|
|
2765
|
+
).length + 1,
|
|
2766
|
+
leaseId: safeIdentifier(leaseId, "leaseId"),
|
|
2767
|
+
fence,
|
|
2768
|
+
authorityGeneration: run.authority.authorityGeneration,
|
|
2769
|
+
writerId: run.authority.writerId,
|
|
2770
|
+
status: "active",
|
|
2771
|
+
participationStatus: "active",
|
|
2772
|
+
resumedFromAttemptId: source.id,
|
|
2773
|
+
resumeReason: String(reason).slice(0, 4096),
|
|
2774
|
+
createdAt: nowIso(() => now),
|
|
2775
|
+
updatedAt: nowIso(() => now),
|
|
2776
|
+
expiresAt: nowIso(() => now + finitePositive(ttlMs, 60_000)),
|
|
2777
|
+
expiresAtMs: now + finitePositive(ttlMs, 60_000),
|
|
2778
|
+
terminalEvidence: null,
|
|
2779
|
+
usage: null,
|
|
2780
|
+
error: null,
|
|
2781
|
+
};
|
|
2782
|
+
for (const effect of effects) {
|
|
2783
|
+
effect.custodyHistory ||= [];
|
|
2784
|
+
effect.custodyHistory.push({
|
|
2785
|
+
fromAttemptId: source.id,
|
|
2786
|
+
fromLeaseId: source.leaseId,
|
|
2787
|
+
fromFence: source.fence,
|
|
2788
|
+
toAttemptId: successor.id,
|
|
2789
|
+
toLeaseId: successor.leaseId,
|
|
2790
|
+
toFence: successor.fence,
|
|
2791
|
+
reason: "authority_generation_takeover",
|
|
2792
|
+
transferredAt: nowIso(() => now),
|
|
2793
|
+
});
|
|
2794
|
+
effect.attemptId = successor.id;
|
|
2795
|
+
effect.leaseId = successor.leaseId;
|
|
2796
|
+
effect.fence = successor.fence;
|
|
2797
|
+
effect.updatedAt = nowIso(() => now);
|
|
2798
|
+
}
|
|
2799
|
+
run.attempts.set(successor.id, successor);
|
|
2800
|
+
state.attemptIds.push(successor.id);
|
|
2801
|
+
state.status = "running";
|
|
2802
|
+
state.updatedAt = nowIso(() => now);
|
|
2803
|
+
return attemptProjection(successor);
|
|
2804
|
+
},
|
|
2805
|
+
{
|
|
2806
|
+
idempotencyKey: `assignment-resume:${sourceId}:${run.authority.authorityGeneration}`,
|
|
2807
|
+
},
|
|
2808
|
+
);
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2294
2811
|
_requireAttemptLease(run, attemptId, leaseId, fence, statuses = ["active"]) {
|
|
2295
2812
|
const attempt = run.attempts.get(safeIdentifier(attemptId, "attemptId"));
|
|
2296
2813
|
const now = this.now();
|
|
2814
|
+
if (
|
|
2815
|
+
attempt &&
|
|
2816
|
+
(attempt.authorityGeneration !== run.authority.authorityGeneration ||
|
|
2817
|
+
attempt.writerId !== run.authority.writerId)
|
|
2818
|
+
) {
|
|
2819
|
+
throw kernelError(
|
|
2820
|
+
"CC_GRAPH_STALE_ATTEMPT_AUTHORITY",
|
|
2821
|
+
"attempt belongs to a stale authority generation or writer",
|
|
2822
|
+
);
|
|
2823
|
+
}
|
|
2297
2824
|
if (
|
|
2298
2825
|
!attempt ||
|
|
2299
2826
|
attempt.leaseId !== safeIdentifier(leaseId, "leaseId") ||
|
|
@@ -2323,17 +2850,25 @@ export class GraphKernel {
|
|
|
2323
2850
|
) {
|
|
2324
2851
|
const run = this._run(runId);
|
|
2325
2852
|
const key = String(idempotencyKey || "").trim();
|
|
2326
|
-
const
|
|
2853
|
+
const matching = [...run.effects.values()].filter(
|
|
2327
2854
|
(effect) => effect.idempotencyKey === key,
|
|
2328
2855
|
);
|
|
2329
|
-
if (
|
|
2330
|
-
if (
|
|
2856
|
+
if (matching.length > 0) {
|
|
2857
|
+
if (
|
|
2858
|
+
matching.some((effect) => effect.operationDigest !== operationDigest)
|
|
2859
|
+
) {
|
|
2331
2860
|
throw kernelError(
|
|
2332
2861
|
"CC_GRAPH_EFFECT_IDEMPOTENCY_CONFLICT",
|
|
2333
2862
|
"effect idempotency key was reused for a different operation",
|
|
2334
2863
|
);
|
|
2335
2864
|
}
|
|
2336
|
-
|
|
2865
|
+
// A proved/audited failure means the operation did not commit, so a
|
|
2866
|
+
// later fenced attempt must receive a fresh effect boundary. Started,
|
|
2867
|
+
// unknown, and committed effects continue to deduplicate fail-closed.
|
|
2868
|
+
const authoritative = [...matching]
|
|
2869
|
+
.reverse()
|
|
2870
|
+
.find((effect) => effect.status !== "failed");
|
|
2871
|
+
if (authoritative) return Object.freeze(clone(authoritative));
|
|
2337
2872
|
}
|
|
2338
2873
|
const id = safeIdentifier(effectId, "effectId");
|
|
2339
2874
|
return this._transaction(
|
|
@@ -3003,6 +3538,7 @@ export class GraphKernel {
|
|
|
3003
3538
|
dataPolicy,
|
|
3004
3539
|
causationId = null,
|
|
3005
3540
|
correlationId = null,
|
|
3541
|
+
systemSource = null,
|
|
3006
3542
|
ttlMs = 24 * 60 * 60 * 1000,
|
|
3007
3543
|
} = {},
|
|
3008
3544
|
) {
|
|
@@ -3010,7 +3546,15 @@ export class GraphKernel {
|
|
|
3010
3546
|
const id = safeIdentifier(messageId, "messageId");
|
|
3011
3547
|
const recipient = safeIdentifier(toAgentId, "toAgentId");
|
|
3012
3548
|
const contentDigest = graphDigest(
|
|
3013
|
-
{
|
|
3549
|
+
{
|
|
3550
|
+
recipient,
|
|
3551
|
+
mode,
|
|
3552
|
+
payload,
|
|
3553
|
+
causationId,
|
|
3554
|
+
correlationId,
|
|
3555
|
+
fromAttemptId: fromAttemptId || null,
|
|
3556
|
+
systemSource: systemSource || null,
|
|
3557
|
+
},
|
|
3014
3558
|
"cc.graph.message/v1",
|
|
3015
3559
|
);
|
|
3016
3560
|
const existing = run.messages.get(id);
|
|
@@ -3028,12 +3572,13 @@ export class GraphKernel {
|
|
|
3028
3572
|
"message.admitted",
|
|
3029
3573
|
{ messageId: id, toAgentId: recipient, mode },
|
|
3030
3574
|
() => {
|
|
3031
|
-
const
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3575
|
+
const source =
|
|
3576
|
+
systemSource == null
|
|
3577
|
+
? null
|
|
3578
|
+
: safeIdentifier(systemSource, "systemSource");
|
|
3579
|
+
const attempt = source
|
|
3580
|
+
? null
|
|
3581
|
+
: this._requireAttemptLease(run, fromAttemptId, leaseId, fence);
|
|
3037
3582
|
const policy = validateDataPolicy(dataPolicy);
|
|
3038
3583
|
const sink = `agent:${recipient}`;
|
|
3039
3584
|
if (!sinkAllowed(policy, sink)) {
|
|
@@ -3067,9 +3612,12 @@ export class GraphKernel {
|
|
|
3067
3612
|
const message = {
|
|
3068
3613
|
id,
|
|
3069
3614
|
runId: run.id,
|
|
3070
|
-
fromAttemptId: attempt
|
|
3071
|
-
fromLeaseId: attempt
|
|
3072
|
-
fromFence: attempt
|
|
3615
|
+
fromAttemptId: attempt?.id || null,
|
|
3616
|
+
fromLeaseId: attempt?.leaseId || null,
|
|
3617
|
+
fromFence: attempt?.fence ?? null,
|
|
3618
|
+
systemSource: source,
|
|
3619
|
+
sourceAuthorityGeneration: run.authority.authorityGeneration,
|
|
3620
|
+
sourceWriterId: run.authority.writerId,
|
|
3073
3621
|
toAgentId: recipient,
|
|
3074
3622
|
causationId,
|
|
3075
3623
|
correlationId,
|
|
@@ -3165,6 +3713,16 @@ export class GraphKernel {
|
|
|
3165
3713
|
if (run.messageConsumers.has(dedupKey)) {
|
|
3166
3714
|
return Object.freeze(clone(run.messageConsumers.get(dedupKey)));
|
|
3167
3715
|
}
|
|
3716
|
+
const priorConsumer = [...run.messageConsumers.values()].find(
|
|
3717
|
+
(receipt) => receipt.messageId === id && receipt.agentId === recipient,
|
|
3718
|
+
);
|
|
3719
|
+
if (priorConsumer) {
|
|
3720
|
+
throw kernelError(
|
|
3721
|
+
"CC_GRAPH_MESSAGE_CONSUMER_CONFLICT",
|
|
3722
|
+
"message was already processed by another consumer key",
|
|
3723
|
+
{ consumerKey: priorConsumer.consumerKey },
|
|
3724
|
+
);
|
|
3725
|
+
}
|
|
3168
3726
|
return this._transaction(
|
|
3169
3727
|
run,
|
|
3170
3728
|
"message.processed",
|
|
@@ -3244,13 +3802,33 @@ export class GraphKernel {
|
|
|
3244
3802
|
) {
|
|
3245
3803
|
const run = this._run(runId);
|
|
3246
3804
|
const id = safeIdentifier(handoffId, "handoffId");
|
|
3805
|
+
const requestDigest = graphDigest(
|
|
3806
|
+
{
|
|
3807
|
+
fromAttemptId,
|
|
3808
|
+
leaseId,
|
|
3809
|
+
fence,
|
|
3810
|
+
toAgentId,
|
|
3811
|
+
artifactIds,
|
|
3812
|
+
preconditions,
|
|
3813
|
+
ttlMs,
|
|
3814
|
+
},
|
|
3815
|
+
"cc.graph.handoff-request/v1",
|
|
3816
|
+
);
|
|
3247
3817
|
return this._transaction(
|
|
3248
3818
|
run,
|
|
3249
3819
|
"handoff.offered",
|
|
3250
3820
|
{ handoffId: id, fromAttemptId, toAgentId },
|
|
3251
3821
|
() => {
|
|
3252
3822
|
const existing = run.handoffs.get(id);
|
|
3253
|
-
if (existing)
|
|
3823
|
+
if (existing) {
|
|
3824
|
+
if (existing.requestDigest !== requestDigest) {
|
|
3825
|
+
throw kernelError(
|
|
3826
|
+
"CC_GRAPH_HANDOFF_ID_CONFLICT",
|
|
3827
|
+
"handoffId was reused with different custody content",
|
|
3828
|
+
);
|
|
3829
|
+
}
|
|
3830
|
+
return Object.freeze(clone(existing));
|
|
3831
|
+
}
|
|
3254
3832
|
const attempt = this._requireAttemptLease(
|
|
3255
3833
|
run,
|
|
3256
3834
|
fromAttemptId,
|
|
@@ -3278,6 +3856,7 @@ export class GraphKernel {
|
|
|
3278
3856
|
authorityDigest: run.authorityDigest,
|
|
3279
3857
|
artifactIds: [...artifactIds],
|
|
3280
3858
|
preconditions: clone(preconditions),
|
|
3859
|
+
requestDigest,
|
|
3281
3860
|
status: "offered",
|
|
3282
3861
|
createdAt: nowIso(() => now),
|
|
3283
3862
|
updatedAt: nowIso(() => now),
|
|
@@ -3301,6 +3880,9 @@ export class GraphKernel {
|
|
|
3301
3880
|
{ handoffId: id, agentId: recipient },
|
|
3302
3881
|
() => {
|
|
3303
3882
|
const handoff = run.handoffs.get(id);
|
|
3883
|
+
if (handoff?.status === "accepted" && handoff.toAgentId === recipient) {
|
|
3884
|
+
return Object.freeze(clone(handoff));
|
|
3885
|
+
}
|
|
3304
3886
|
if (
|
|
3305
3887
|
!handoff ||
|
|
3306
3888
|
handoff.status !== "offered" ||
|
|
@@ -3313,7 +3895,8 @@ export class GraphKernel {
|
|
|
3313
3895
|
);
|
|
3314
3896
|
}
|
|
3315
3897
|
handoff.status = "accepted";
|
|
3316
|
-
handoff.
|
|
3898
|
+
handoff.acceptedAt = nowIso(this.now);
|
|
3899
|
+
handoff.updatedAt = handoff.acceptedAt;
|
|
3317
3900
|
return Object.freeze(clone(handoff));
|
|
3318
3901
|
},
|
|
3319
3902
|
);
|
|
@@ -3329,6 +3912,9 @@ export class GraphKernel {
|
|
|
3329
3912
|
{ handoffId: id, reason },
|
|
3330
3913
|
() => {
|
|
3331
3914
|
const handoff = run.handoffs.get(id);
|
|
3915
|
+
if (handoff?.status === "rejected" && handoff.toAgentId === recipient) {
|
|
3916
|
+
return Object.freeze(clone(handoff));
|
|
3917
|
+
}
|
|
3332
3918
|
if (
|
|
3333
3919
|
!handoff ||
|
|
3334
3920
|
handoff.status !== "offered" ||
|
|
@@ -3341,7 +3927,8 @@ export class GraphKernel {
|
|
|
3341
3927
|
}
|
|
3342
3928
|
handoff.status = "rejected";
|
|
3343
3929
|
handoff.reason = String(reason).slice(0, 1024);
|
|
3344
|
-
handoff.
|
|
3930
|
+
handoff.rejectedAt = nowIso(this.now);
|
|
3931
|
+
handoff.updatedAt = handoff.rejectedAt;
|
|
3345
3932
|
return Object.freeze(clone(handoff));
|
|
3346
3933
|
},
|
|
3347
3934
|
);
|
|
@@ -3364,6 +3951,21 @@ export class GraphKernel {
|
|
|
3364
3951
|
{ handoffId: id },
|
|
3365
3952
|
() => {
|
|
3366
3953
|
const handoff = run.handoffs.get(id);
|
|
3954
|
+
if (handoff?.status === "committed") {
|
|
3955
|
+
const assignmentAttempt = run.attempts.get(
|
|
3956
|
+
handoff.committedAttemptId,
|
|
3957
|
+
);
|
|
3958
|
+
if (!assignmentAttempt) {
|
|
3959
|
+
throw kernelError(
|
|
3960
|
+
"CC_GRAPH_HANDOFF_COMMIT_CORRUPT",
|
|
3961
|
+
"committed handoff is missing its assignment attempt",
|
|
3962
|
+
);
|
|
3963
|
+
}
|
|
3964
|
+
return Object.freeze({
|
|
3965
|
+
handoff: Object.freeze(clone(handoff)),
|
|
3966
|
+
assignmentAttempt: attemptProjection(assignmentAttempt),
|
|
3967
|
+
});
|
|
3968
|
+
}
|
|
3367
3969
|
if (!handoff || handoff.status !== "accepted") {
|
|
3368
3970
|
throw kernelError(
|
|
3369
3971
|
"CC_GRAPH_HANDOFF_NOT_COMMITTABLE",
|
|
@@ -3407,8 +4009,12 @@ export class GraphKernel {
|
|
|
3407
4009
|
attempt: attemptNumber,
|
|
3408
4010
|
agentId: handoff.toAgentId,
|
|
3409
4011
|
role: "executor",
|
|
4012
|
+
compensationForNodeId: sender.compensationForNodeId || null,
|
|
4013
|
+
effectIdempotencyKey: sender.effectIdempotencyKey || null,
|
|
3410
4014
|
leaseId: safeIdentifier(leaseId, "leaseId"),
|
|
3411
4015
|
fence,
|
|
4016
|
+
authorityGeneration: run.authority.authorityGeneration,
|
|
4017
|
+
writerId: run.authority.writerId,
|
|
3412
4018
|
status: "active",
|
|
3413
4019
|
participationStatus: "active",
|
|
3414
4020
|
grant: clone(sender.grant),
|
|
@@ -3419,11 +4025,37 @@ export class GraphKernel {
|
|
|
3419
4025
|
terminalEvidence: null,
|
|
3420
4026
|
usage: null,
|
|
3421
4027
|
};
|
|
4028
|
+
for (const effect of run.effects.values()) {
|
|
4029
|
+
if (effect.attemptId !== sender.id) continue;
|
|
4030
|
+
if (effect.status !== "started") {
|
|
4031
|
+
throw kernelError(
|
|
4032
|
+
"CC_GRAPH_HANDOFF_EFFECT_TERMINAL",
|
|
4033
|
+
"custody cannot transfer after the source effect became terminal",
|
|
4034
|
+
{ effectId: effect.id, effectStatus: effect.status },
|
|
4035
|
+
);
|
|
4036
|
+
}
|
|
4037
|
+
effect.custodyHistory ||= [];
|
|
4038
|
+
effect.custodyHistory.push({
|
|
4039
|
+
fromAttemptId: sender.id,
|
|
4040
|
+
fromLeaseId: sender.leaseId,
|
|
4041
|
+
fromFence: sender.fence,
|
|
4042
|
+
toAttemptId: newAttempt.id,
|
|
4043
|
+
toLeaseId: newAttempt.leaseId,
|
|
4044
|
+
toFence: newAttempt.fence,
|
|
4045
|
+
handoffId: handoff.id,
|
|
4046
|
+
transferredAt: nowIso(() => now),
|
|
4047
|
+
});
|
|
4048
|
+
effect.attemptId = newAttempt.id;
|
|
4049
|
+
effect.leaseId = newAttempt.leaseId;
|
|
4050
|
+
effect.fence = newAttempt.fence;
|
|
4051
|
+
effect.updatedAt = nowIso(() => now);
|
|
4052
|
+
}
|
|
3422
4053
|
run.attempts.set(newAttempt.id, newAttempt);
|
|
3423
4054
|
run.nodeStates.get(sender.nodeId).attemptIds.push(newAttempt.id);
|
|
3424
4055
|
handoff.status = "committed";
|
|
3425
4056
|
handoff.committedAttemptId = newAttempt.id;
|
|
3426
|
-
handoff.
|
|
4057
|
+
handoff.committedAt = nowIso(() => now);
|
|
4058
|
+
handoff.updatedAt = handoff.committedAt;
|
|
3427
4059
|
return Object.freeze({
|
|
3428
4060
|
handoff: Object.freeze(clone(handoff)),
|
|
3429
4061
|
assignmentAttempt: attemptProjection(newAttempt),
|
|
@@ -3432,11 +4064,60 @@ export class GraphKernel {
|
|
|
3432
4064
|
);
|
|
3433
4065
|
}
|
|
3434
4066
|
|
|
4067
|
+
expireHandoffForRecovery(
|
|
4068
|
+
runId,
|
|
4069
|
+
handoffId,
|
|
4070
|
+
{ reason = "source writer generation was superseded" } = {},
|
|
4071
|
+
) {
|
|
4072
|
+
const run = this._run(runId);
|
|
4073
|
+
const id = safeIdentifier(handoffId, "handoffId");
|
|
4074
|
+
return this._transaction(
|
|
4075
|
+
run,
|
|
4076
|
+
"handoff.expired",
|
|
4077
|
+
{ handoffId: id, reason },
|
|
4078
|
+
() => {
|
|
4079
|
+
const handoff = run.handoffs.get(id);
|
|
4080
|
+
if (handoff?.status === "expired") {
|
|
4081
|
+
return Object.freeze(clone(handoff));
|
|
4082
|
+
}
|
|
4083
|
+
if (!handoff || !["offered", "accepted"].includes(handoff.status)) {
|
|
4084
|
+
throw kernelError(
|
|
4085
|
+
"CC_GRAPH_HANDOFF_NOT_RECOVERY_EXPIRABLE",
|
|
4086
|
+
"only an open handoff can expire during writer recovery",
|
|
4087
|
+
);
|
|
4088
|
+
}
|
|
4089
|
+
const source = run.attempts.get(handoff.fromAttemptId);
|
|
4090
|
+
if (
|
|
4091
|
+
!source ||
|
|
4092
|
+
Number(source.authorityGeneration || 0) >=
|
|
4093
|
+
run.authority.authorityGeneration
|
|
4094
|
+
) {
|
|
4095
|
+
throw kernelError(
|
|
4096
|
+
"CC_GRAPH_HANDOFF_RECOVERY_AUTHORITY_INVALID",
|
|
4097
|
+
"handoff recovery requires a source from an older authority generation",
|
|
4098
|
+
);
|
|
4099
|
+
}
|
|
4100
|
+
const now = this.now();
|
|
4101
|
+
handoff.status = "expired";
|
|
4102
|
+
handoff.reason = String(reason).slice(0, 1024);
|
|
4103
|
+
handoff.expiredAt = nowIso(() => now);
|
|
4104
|
+
handoff.updatedAt = nowIso(() => now);
|
|
4105
|
+
return Object.freeze(clone(handoff));
|
|
4106
|
+
},
|
|
4107
|
+
{
|
|
4108
|
+
idempotencyKey: `handoff-recovery-expire:${id}:${run.authority.authorityGeneration}`,
|
|
4109
|
+
},
|
|
4110
|
+
);
|
|
4111
|
+
}
|
|
4112
|
+
|
|
3435
4113
|
revokeHandoff(runId, handoffId, fromAttemptId, leaseId, fence) {
|
|
3436
4114
|
const run = this._run(runId);
|
|
3437
4115
|
const id = safeIdentifier(handoffId, "handoffId");
|
|
3438
4116
|
return this._transaction(run, "handoff.revoked", { handoffId: id }, () => {
|
|
3439
4117
|
const handoff = run.handoffs.get(id);
|
|
4118
|
+
if (handoff?.status === "revoked") {
|
|
4119
|
+
return Object.freeze(clone(handoff));
|
|
4120
|
+
}
|
|
3440
4121
|
if (!handoff || !["offered", "accepted"].includes(handoff.status)) {
|
|
3441
4122
|
throw kernelError(
|
|
3442
4123
|
"CC_GRAPH_HANDOFF_NOT_REVOKABLE",
|
|
@@ -3451,7 +4132,8 @@ export class GraphKernel {
|
|
|
3451
4132
|
);
|
|
3452
4133
|
}
|
|
3453
4134
|
handoff.status = "revoked";
|
|
3454
|
-
handoff.
|
|
4135
|
+
handoff.revokedAt = nowIso(this.now);
|
|
4136
|
+
handoff.updatedAt = handoff.revokedAt;
|
|
3455
4137
|
return Object.freeze(clone(handoff));
|
|
3456
4138
|
});
|
|
3457
4139
|
}
|
|
@@ -3509,11 +4191,12 @@ export class GraphKernel {
|
|
|
3509
4191
|
authorityDigest: run.authorityDigest,
|
|
3510
4192
|
status: "open",
|
|
3511
4193
|
nonce: safeIdentifier(nonce, "nonce"),
|
|
3512
|
-
quorum: Math.max(1, Number(quorum) || 1),
|
|
4194
|
+
quorum: Math.min(64, Math.max(1, Math.trunc(Number(quorum)) || 1)),
|
|
3513
4195
|
separationOfDuties: separationOfDuties === true,
|
|
3514
4196
|
decisions: [],
|
|
3515
4197
|
claimActorId: null,
|
|
3516
4198
|
claimLeaseId: null,
|
|
4199
|
+
claimExpiresAt: null,
|
|
3517
4200
|
claimExpiresAtMs: null,
|
|
3518
4201
|
createdAt: nowIso(() => now),
|
|
3519
4202
|
updatedAt: nowIso(() => now),
|
|
@@ -3523,7 +4206,7 @@ export class GraphKernel {
|
|
|
3523
4206
|
};
|
|
3524
4207
|
run.humanTasks.set(id, task);
|
|
3525
4208
|
run.status = "waiting_human";
|
|
3526
|
-
return
|
|
4209
|
+
return humanTaskProjection(task);
|
|
3527
4210
|
},
|
|
3528
4211
|
{ idempotencyKey: `human-task:${id}` },
|
|
3529
4212
|
);
|
|
@@ -3569,8 +4252,9 @@ export class GraphKernel {
|
|
|
3569
4252
|
task.claimActorId = actor;
|
|
3570
4253
|
task.claimLeaseId = safeIdentifier(claimLeaseId, "claimLeaseId");
|
|
3571
4254
|
task.claimExpiresAtMs = now + finitePositive(ttlMs, 300_000);
|
|
4255
|
+
task.claimExpiresAt = nowIso(() => task.claimExpiresAtMs);
|
|
3572
4256
|
task.updatedAt = nowIso(() => now);
|
|
3573
|
-
return
|
|
4257
|
+
return humanTaskProjection(task);
|
|
3574
4258
|
},
|
|
3575
4259
|
);
|
|
3576
4260
|
}
|
|
@@ -3675,11 +4359,15 @@ export class GraphKernel {
|
|
|
3675
4359
|
task.status = "open";
|
|
3676
4360
|
task.claimActorId = null;
|
|
3677
4361
|
task.claimLeaseId = null;
|
|
4362
|
+
task.claimExpiresAt = null;
|
|
3678
4363
|
task.claimExpiresAtMs = null;
|
|
3679
4364
|
}
|
|
3680
4365
|
task.updatedAt = nowIso(() => now);
|
|
3681
4366
|
run.status = classifyQuiescence(run, now);
|
|
3682
|
-
|
|
4367
|
+
if (TERMINAL_RUN.has(run.status)) {
|
|
4368
|
+
run.completedAt = nowIso(() => now);
|
|
4369
|
+
}
|
|
4370
|
+
return humanTaskProjection(task);
|
|
3683
4371
|
},
|
|
3684
4372
|
);
|
|
3685
4373
|
}
|
|
@@ -3699,14 +4387,25 @@ export class GraphKernel {
|
|
|
3699
4387
|
"human task is already terminal",
|
|
3700
4388
|
);
|
|
3701
4389
|
}
|
|
4390
|
+
const now = this.now();
|
|
3702
4391
|
task.status = "cancelled";
|
|
3703
4392
|
task.reason = String(reason).slice(0, 1024);
|
|
3704
|
-
task.updatedAt = nowIso(
|
|
4393
|
+
task.updatedAt = nowIso(() => now);
|
|
4394
|
+
const attempt = run.attempts.get(task.attemptId);
|
|
4395
|
+
if (attempt && attempt.status === "waiting_human") {
|
|
4396
|
+
attempt.status = "cancelled";
|
|
4397
|
+
attempt.participationStatus = "human_cancelled";
|
|
4398
|
+
attempt.updatedAt = nowIso(() => now);
|
|
4399
|
+
}
|
|
3705
4400
|
const state = run.nodeStates.get(task.nodeId);
|
|
3706
4401
|
state.status = "cancelled";
|
|
3707
4402
|
state.blockedRoot = task.id;
|
|
3708
|
-
state.updatedAt = nowIso(
|
|
3709
|
-
|
|
4403
|
+
state.updatedAt = nowIso(() => now);
|
|
4404
|
+
run.status = classifyQuiescence(run, now);
|
|
4405
|
+
if (TERMINAL_RUN.has(run.status)) {
|
|
4406
|
+
run.completedAt = nowIso(() => now);
|
|
4407
|
+
}
|
|
4408
|
+
return humanTaskProjection(task);
|
|
3710
4409
|
},
|
|
3711
4410
|
);
|
|
3712
4411
|
}
|