chainlesschain 0.166.6 → 0.166.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -1
- package/src/assets/web-panel/assets/{AIOps-CVnFjTfG.js → AIOps-BlrmQxa2.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BxkwuuQC.js → ActionButton-zyRyLITg.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-C2670q6z.js → Analytics-DwGz7OGN.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CU_rylW6.js → AppLayout-Dc-nj7Zd.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-DGJmYqnj.js → Artifacts-7Hu3O73p.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BZOQbINJ.js → Audit-Bnoe3-fR.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DghB4QwF.js → BackgroundAgents-BqYFrDKZ.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-l0mwPbta.js → Backup-B3hwW1nz.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Bv3WSZOh.js → BaseInput-XFVxLA4w.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-s88nBdvt.js → Chat-CWRDNw7e.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-Z8CBuB1a.js → ChatBubbleRenderer-Bf30VM8G.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-B5icasEs.js → Checkbox-kFeeBgac.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CsUFqNud.js → Codegen-CCxp8xoI.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DlBtClaW.js → Col-BLKZalle.js} +1 -1
- package/src/assets/web-panel/assets/{Community-xRn1IdLB.js → Community-DD0eWCez.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-naIr8a_o.js → Compact-CHqHWdy0.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Dp9esfFE.js → Compliance-Bw54uPiK.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DQlMYJB-.js → Cowork-DZ13zKNJ.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-Cd0oNLzJ.js → Cron-Dlj5Jevg.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BtDIx3XX.js → Crosschain-Nvz0aR0j.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BOlpzEwg.js → DID-Ct89eUyY.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DV15Axk1.js → Dashboard-BHUH5IAe.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-COXrewN1.js → Dropdown-BhZG-PO7.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-EjIt0Cm7.js → EmailListRenderer-ae4RVwwJ.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DJlhwb-R.js → FamilyGuardDashboard-AOcxLfLr.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BMiFl82k.js → Federation-BK2PJQOF.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BUcDQyxn.js → FormItemContext-C2n3xO2_.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-C25uFxvK.js → GenericCardRenderer-DcyUVFZq.js} +1 -1
- package/src/assets/web-panel/assets/{Git-D20uaEUU.js → Git-tBae9Lt8.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CQxYJjCX.js → Governance-DmNR3AP4.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-m-sczpS5.js → Inference-CoseEigi.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CBsIlU0S.js → KnowledgeGraph-Bdjd1sxr.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-D0-5bqeu.js → Logs-CtlkKIcn.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-BwH414Ms.js → MarkdownRenderer-CZsTBEQE.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-CwDGFu2Q.js → Marketplace-BTYb7nEx.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-7FxJ3omT.js → McpTools-D0iUdW3P.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-DOVyb59U.js → Memory--gwI7w4Y.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DZXl-Hje.js → MobileBridge-JmFBi24T.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DlZ8tRXt.js → MobileProjects-BK01cQRx.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-D7Pjx1s_.js → Mtc-u7avzyoO.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BND7lX8p.js → MtcAudit-gT3z5fo_.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BknS7B-p.js → Multisig-BIqZTz5n.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-7C2-KPHt.js → NLProgramming-DdhVfXYq.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DNWdTndN.js → Notes-CBlyUmY3.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-DCm4KcJZ.js → NotificationSettings-DSyI26N3.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DyVwwjJx.js → OrderTableRenderer-B7IEuNz4.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-Dl2O_qJ9.js → Organization-ozhi4kGS.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BOFCvk9Q.js → Overflow-CBy57RVM.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DgdVowY1.js → P2P-CA-ZRv77.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-DOHc9zm8.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-BHNEUUwa.js → Permissions-DiP632GP.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CaZbrOdz.js → PersonalDataHub-AnU8O-SS.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-DJDnoXmE.js → Pipeline-DDibOUaP.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CBQr2eX8.js → Privacy-D421t36T.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Yghqa06z.js → ProjectInit-DR879ZPJ.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-HBAtJi8U.js → ProjectSettings-Ded8o6aY.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DiCGnMjz.js → Projects-BHPxiucB.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-uJTz77GI.js → Providers-Boivuu-E.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-k3M5ouTp.js → QrScannerModal-xKD_ntKp.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CMji45PH.js → QuickAsk-D3y_A6FV.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DyoZYPls.js → Recommend-BjKM-YC2.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-s1gR38IO.js → RemoteSession-CgqKMXG4.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-D4hIs4vV.js → Reputation-Ci2GjH5p.js} +1 -1
- package/src/assets/web-panel/assets/{Row-mzE-ElTg.js → Row-B0-T2BSh.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-cg7J3hI6.js → RssFeed-Bet-ur-r.js} +3 -3
- package/src/assets/web-panel/assets/{Search-6M0TkGSZ.js → Search-DWtRAS2R.js} +1 -1
- package/src/assets/web-panel/assets/{Security-B1GQ67qh.js → Security-BDfu2Qn6.js} +4 -4
- package/src/assets/web-panel/assets/{Services-D_RwU_5S.js → Services-CvJmvSMM.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-TZjk-dsW.js → Skeleton-CEbrKpdC.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CG6FcaCV.js → Skills-CjVeiETf.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BI190QDv.js → Sla-Dibypxum.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BWmxF2Wu.js → SpeechSettings-XflYXp7a.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CwPiGw6L.js → SyncSettings-ro1Ivx1E.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BIXgCeSk.js → Tasks-CHq1qFbo.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CQFgGCoK.js → Templates-CRnUZxtZ.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DAelEdkL.js → Tenant-jUX72JCA.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BbqWK6qh.js → Terminal-BBhAs7pt.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-cmG3zvY5.js → TimelineRenderer-CD5M3RGp.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-C8444NKU.js → Tokens-sah3v0gu.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-C1Rpnz0I.js → Trigger-BNT3Uz3s.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-u4koPiFD.js → Trust-BytMGPMo.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-oPWKn80Z.js → UkeySign-BkYVMRQs.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-D0CUUY_5.js → VideoEditing-psvb5Q7Y.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-fu9pIAbt.js → Wallet-PFQTW0MH.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Bc5ciuOx.js → WebAuthn-DgO-OwRT.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-BCin4_4N.js → WorkflowEditor-DL2l9VNl.js} +1 -1
- package/src/assets/web-panel/assets/{chat-B2jbM-XN.js → chat-C84akEzN.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CUVoUQ_2.js → colors-DzTWuQKP.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DgOGpkRH.js → compact-item-CR9LJQFv.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DIxU6eKI.js → createContext-CTTov5Kn.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BOhvmlpc.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DoKRKMav.js → hasIn-DALj523o.js} +1 -1
- package/src/assets/web-panel/assets/{index-i5MB455u.js → index-3-XJoYgX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLdpHe6w.js → index-7ux_XohH.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3F_p8NA.js → index-AXnERsyV.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2yo6ast.js → index-B2qpjU-D.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWbL1-N0.js → index-B6QOJbSn.js} +1 -1
- package/src/assets/web-panel/assets/{index-DtyAuGOm.js → index-BDyt57zL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpTFx8Xx.js → index-BHRXKiU1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CF80xxPG.js → index-BKNIzw0U.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRMEO2Tl.js → index-BNaQt49r.js} +1 -1
- package/src/assets/web-panel/assets/{index-ByjrulIZ.js → index-Be6Os5S8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCLeNqIL.js → index-C5eeMl_2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJMHnbmu.js → index-CNmTDXmf.js} +1 -1
- package/src/assets/web-panel/assets/{index-2flsPFIX.js → index-CXTEpfGe.js} +1 -1
- package/src/assets/web-panel/assets/{index-BwYofQJg.js → index-CXaCRXN4.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5ySiGYy.js → index-CZhhFQi9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeWSw-z5.js → index-Cef0n4k_.js} +1 -1
- package/src/assets/web-panel/assets/{index-C846lPGD.js → index-CjkPDSX4.js} +1 -1
- package/src/assets/web-panel/assets/{index-C9TsO9T9.js → index-CmELknPe.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqlwIjJx.js → index-CnxjXIsK.js} +1 -1
- package/src/assets/web-panel/assets/{index-Co4lDEk-.js → index-CqUgXYIQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cj-us-D_.js → index-CunGDV0g.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFUprhNd.js → index-D0MqUnKO.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGTZdZOE.js → index-D9ScRFxz.js} +3 -3
- package/src/assets/web-panel/assets/{index-C88m9lsi.js → index-DBIPnoTj.js} +1 -1
- package/src/assets/web-panel/assets/index-DDyMJexx.js +1 -0
- package/src/assets/web-panel/assets/{index-BXb-Smmm.js → index-DH6OG-ci.js} +1 -1
- package/src/assets/web-panel/assets/{index-eTWPaeBI.js → index-DZER6Lj3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cskz--fd.js → index-D_Bg0LdK.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-ufbAJB.js → index-Dah8M4l2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHugFl3v.js → index-DjE_ew7U.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9mSYovv.js → index-DtW6Zr_i.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ds8r3Fzb.js → index-DxdzSljS.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPXdBnGi.js → index-QW0EXx14.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-f6l4Vm.js → index-W9axN_Ce.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpvyOt5Z.js → index-_8vetPbU.js} +1 -1
- package/src/assets/web-panel/assets/index-btiojFYn.js +1 -0
- package/src/assets/web-panel/assets/{index-eYxUASSG.js → index-eJcFleIV.js} +1 -1
- package/src/assets/web-panel/assets/{index-be8SonCh.js → index-hzzTITR9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtSPhEyC.js → index-s4CFyn-n.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-B4chl7iR.js → initDefaultProps-JuW8mFj7.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DruN9h96.js → motion-Cwb0HBve.js} +1 -1
- package/src/assets/web-panel/assets/{move-Cl0UtCgU.js → move-DHokzMgu.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-FX2ttw70.js → mtc-parser-DX31y6QS.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BDCneY46.js → omit-BCIUYDA6.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DycsEg8b.js → pickAttrs-DMCj4EoZ.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-Bc-dBuMv.js → placementArrow-Dy5K-u1s.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve--Wwaf8lu.js → responsiveObserve-DkGeZu54.js} +1 -1
- package/src/assets/web-panel/assets/{slide-vDmHQqHH.js → slide-Dc5dyJdg.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-9c52yv3G.js → statusUtils-DIUBjOaC.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BT4BiO6_.js → styleChecker-DmyEOxm5.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-C7XxWPcP.js → useFlexGapSupport-FRhakk1e.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BHoMqh4i.js → useFs-clXyV1WQ.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BOLxvsAG.js → usePersonalDataHub-UF6DG41z.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CA7poeeP.js → vnode-CoCt95G7.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B_sAn-co.js → zoom-yVWt-ENm.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/graph.js +215 -5
- package/src/commands/team-distributed.js +342 -2
- package/src/commands/team.js +169 -7
- package/src/data/changelog.json +13 -0
- package/src/generated/cc-agent-protocol.schema.json +6 -1
- package/src/lib/agent-team/task-lease.js +315 -0
- 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 +225 -2
- package/src/lib/app-server/graph-runtime.js +754 -0
- package/src/lib/app-server/rollout-store.js +32 -0
- package/src/lib/app-server/server.js +140 -0
- package/src/lib/autonomous-agent.js +11 -0
- 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 +1 -8
- package/src/lib/graph-kernel/cutover-authority-resolver.js +219 -0
- package/src/lib/graph-kernel/cutover-ledger.js +650 -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/runtime-surface-manifest.js +481 -0
- package/src/lib/graph-kernel/runtime.js +661 -24
- 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/legacy-runtime-authority.js +160 -0
- package/src/lib/orchestrator.js +28 -0
- package/src/lib/scheduler-kernel/graph-authority-adapter.js +512 -0
- package/src/lib/scheduler-kernel/runtime.js +75 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-BiaZfQIM.js +0 -7
- 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
|
@@ -7,6 +7,11 @@ import {
|
|
|
7
7
|
writeScopesOverlap,
|
|
8
8
|
} from "./compiler.js";
|
|
9
9
|
import { GraphEventStore } from "./event-store.js";
|
|
10
|
+
import {
|
|
11
|
+
GRAPH_PROJECTION_VERSION,
|
|
12
|
+
assertGraphAuthorityWriter,
|
|
13
|
+
createGraphAuthorityBinding,
|
|
14
|
+
} from "./authority.js";
|
|
10
15
|
|
|
11
16
|
export const GRAPH_RUN_PHASES = Object.freeze(["open", "sealed"]);
|
|
12
17
|
export const GRAPH_RUN_STATUSES = Object.freeze([
|
|
@@ -358,6 +363,7 @@ function stateSnapshot(run) {
|
|
|
358
363
|
revisionDigest: run.revisionDigest,
|
|
359
364
|
occurrenceRef: run.occurrenceRef,
|
|
360
365
|
authorityDigest: run.authorityDigest,
|
|
366
|
+
authority: clone(run.authority),
|
|
361
367
|
budget: run.budget,
|
|
362
368
|
budgetUsed: run.budgetUsed,
|
|
363
369
|
budgetReserved: run.budgetReserved,
|
|
@@ -404,6 +410,13 @@ function restoreState(run, snapshot, compiled = null) {
|
|
|
404
410
|
run.revisionDigest = snapshot.revisionDigest;
|
|
405
411
|
run.occurrenceRef = clone(snapshot.occurrenceRef);
|
|
406
412
|
run.authorityDigest = snapshot.authorityDigest;
|
|
413
|
+
if (!snapshot.authority) {
|
|
414
|
+
throw kernelError(
|
|
415
|
+
"CC_GRAPH_AUTHORITY_MIGRATION_REQUIRED",
|
|
416
|
+
"legacy GraphRun snapshot has no writer authority; import it at a verified safe point",
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
run.authority = createGraphAuthorityBinding(snapshot.authority);
|
|
407
420
|
run.budget = clone(snapshot.budget);
|
|
408
421
|
run.budgetUsed = clone(snapshot.budgetUsed);
|
|
409
422
|
run.budgetReserved = clone(snapshot.budgetReserved || emptyBudgetUsage());
|
|
@@ -473,6 +486,15 @@ function runProjection(run) {
|
|
|
473
486
|
status: run.status,
|
|
474
487
|
graphRevision: run.graphRevision,
|
|
475
488
|
authorityDigest: run.authorityDigest,
|
|
489
|
+
originSurface: run.authority.originSurface,
|
|
490
|
+
authoritySource: run.authority.authoritySource,
|
|
491
|
+
authorityMode: run.authority.authorityMode,
|
|
492
|
+
authorityGeneration: run.authority.authorityGeneration,
|
|
493
|
+
writerId: run.authority.writerId,
|
|
494
|
+
writerLeaseId: run.authority.writerLeaseId,
|
|
495
|
+
writerLeaseExpiresAt: run.authority.writerLeaseExpiresAt,
|
|
496
|
+
eventHead: run.authority.eventHead,
|
|
497
|
+
projectionVersion: run.authority.projectionVersion,
|
|
476
498
|
budget: clone(run.budget),
|
|
477
499
|
budgetUsed: clone(run.budgetUsed),
|
|
478
500
|
budgetReserved: clone(run.budgetReserved),
|
|
@@ -527,10 +549,47 @@ function runProjection(run) {
|
|
|
527
549
|
});
|
|
528
550
|
}
|
|
529
551
|
|
|
530
|
-
function buildRun(compiled, options, now) {
|
|
552
|
+
function buildRun(compiled, options, now, writerIdentity, writerLeaseTtlMs) {
|
|
531
553
|
const createdAt = nowIso(now);
|
|
554
|
+
const id = safeIdentifier(options.runId || randomUUID(), "runId");
|
|
555
|
+
const authority = createGraphAuthorityBinding(
|
|
556
|
+
options.authority || {
|
|
557
|
+
logicalRunId: id,
|
|
558
|
+
originSurface: options.originSurface || "cli_team",
|
|
559
|
+
authorityMode:
|
|
560
|
+
writerIdentity.authoritySource === "graph_kernel_shadow"
|
|
561
|
+
? "shadow"
|
|
562
|
+
: "canonical",
|
|
563
|
+
authoritySource: writerIdentity.authoritySource,
|
|
564
|
+
authorityGeneration:
|
|
565
|
+
options.authorityGeneration || writerIdentity.authorityGeneration,
|
|
566
|
+
writerId: writerIdentity.writerId,
|
|
567
|
+
writerLeaseId: writerIdentity.writerLeaseId,
|
|
568
|
+
writerLeaseExpiresAt: new Date(
|
|
569
|
+
now() + Math.max(1, Number(writerLeaseTtlMs)),
|
|
570
|
+
).toISOString(),
|
|
571
|
+
eventHead: null,
|
|
572
|
+
projectionVersion: GRAPH_PROJECTION_VERSION,
|
|
573
|
+
},
|
|
574
|
+
);
|
|
575
|
+
if (authority.logicalRunId !== id) {
|
|
576
|
+
throw kernelError(
|
|
577
|
+
"CC_GRAPH_AUTHORITY_RUN_MISMATCH",
|
|
578
|
+
"Graph authority must be bound to the exact logical run id",
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
if (authority.authorityMode === "legacy") {
|
|
582
|
+
throw kernelError(
|
|
583
|
+
"CC_GRAPH_NON_CANONICAL_WRITER",
|
|
584
|
+
"legacy authority cannot mutate Graph Kernel state",
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
assertGraphAuthorityWriter(authority, writerIdentity, {
|
|
588
|
+
now: now(),
|
|
589
|
+
requireCanonical: false,
|
|
590
|
+
});
|
|
532
591
|
const run = {
|
|
533
|
-
id
|
|
592
|
+
id,
|
|
534
593
|
compiled,
|
|
535
594
|
phase: "open",
|
|
536
595
|
status: "running",
|
|
@@ -547,6 +606,7 @@ function buildRun(compiled, options, now) {
|
|
|
547
606
|
},
|
|
548
607
|
"cc.graph.authority/v1",
|
|
549
608
|
),
|
|
609
|
+
authority,
|
|
550
610
|
budget: clone(options.budget || compiled.definition.budget || {}),
|
|
551
611
|
budgetUsed: emptyBudgetUsage(),
|
|
552
612
|
budgetReserved: emptyBudgetUsage(),
|
|
@@ -885,6 +945,7 @@ function sweepExpirations(run, now) {
|
|
|
885
945
|
handoff.expiresAtMs <= now
|
|
886
946
|
) {
|
|
887
947
|
handoff.status = "expired";
|
|
948
|
+
handoff.expiredAt = nowIso(() => now);
|
|
888
949
|
handoff.updatedAt = nowIso(() => now);
|
|
889
950
|
changed = true;
|
|
890
951
|
}
|
|
@@ -1187,6 +1248,11 @@ export class GraphKernel {
|
|
|
1187
1248
|
agingWindowMs = 1000,
|
|
1188
1249
|
maxPendingMessagesPerAgent = 100,
|
|
1189
1250
|
maxLivelockRepeats = 8,
|
|
1251
|
+
writerId = `graph-kernel:${process.pid}:${randomUUID()}`,
|
|
1252
|
+
writerLeaseId = `graph-kernel-lease:${randomUUID()}`,
|
|
1253
|
+
authoritySource = "graph_kernel",
|
|
1254
|
+
authorityGeneration = 1,
|
|
1255
|
+
writerLeaseTtlMs = 24 * 60 * 60 * 1000,
|
|
1190
1256
|
} = {}) {
|
|
1191
1257
|
this.eventStore = eventStore;
|
|
1192
1258
|
this.now = now;
|
|
@@ -1197,6 +1263,13 @@ export class GraphKernel {
|
|
|
1197
1263
|
Number(maxPendingMessagesPerAgent) || 100,
|
|
1198
1264
|
);
|
|
1199
1265
|
this.maxLivelockRepeats = Math.max(2, Number(maxLivelockRepeats) || 8);
|
|
1266
|
+
this.writerIdentity = Object.freeze({
|
|
1267
|
+
authoritySource,
|
|
1268
|
+
authorityGeneration: Math.max(1, Number(authorityGeneration) || 1),
|
|
1269
|
+
writerId: safeIdentifier(writerId, "writerId"),
|
|
1270
|
+
writerLeaseId: safeIdentifier(writerLeaseId, "writerLeaseId"),
|
|
1271
|
+
});
|
|
1272
|
+
this.writerLeaseTtlMs = Math.max(1, Number(writerLeaseTtlMs) || 1);
|
|
1200
1273
|
this.runs = new Map();
|
|
1201
1274
|
this.occurrences = new Map();
|
|
1202
1275
|
}
|
|
@@ -1233,6 +1306,19 @@ export class GraphKernel {
|
|
|
1233
1306
|
`GraphRun is terminal: ${run.status}`,
|
|
1234
1307
|
);
|
|
1235
1308
|
}
|
|
1309
|
+
const expectedHeadHash = run.lastEvent?.hash || run.authority.eventHead;
|
|
1310
|
+
const expectedRevision = run.lastEvent?.seq;
|
|
1311
|
+
if (run.authority.authorityMode === "legacy") {
|
|
1312
|
+
throw kernelError(
|
|
1313
|
+
"CC_GRAPH_NON_CANONICAL_WRITER",
|
|
1314
|
+
"legacy authority cannot mutate Graph Kernel state",
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
assertGraphAuthorityWriter(run.authority, this.writerIdentity, {
|
|
1318
|
+
now: this.now(),
|
|
1319
|
+
expectedEventHead: expectedHeadHash,
|
|
1320
|
+
requireCanonical: false,
|
|
1321
|
+
});
|
|
1236
1322
|
const before = stateSnapshot(run);
|
|
1237
1323
|
try {
|
|
1238
1324
|
const result = mutator();
|
|
@@ -1245,9 +1331,16 @@ export class GraphKernel {
|
|
|
1245
1331
|
{
|
|
1246
1332
|
idempotencyKey: options.idempotencyKey || null,
|
|
1247
1333
|
traceId: options.traceId || null,
|
|
1334
|
+
authority: run.authority,
|
|
1335
|
+
expectedRevision,
|
|
1336
|
+
expectedHeadHash,
|
|
1248
1337
|
},
|
|
1249
1338
|
);
|
|
1250
1339
|
run.lastEvent = event;
|
|
1340
|
+
run.authority = createGraphAuthorityBinding({
|
|
1341
|
+
...run.authority,
|
|
1342
|
+
eventHead: event.hash,
|
|
1343
|
+
});
|
|
1251
1344
|
return result;
|
|
1252
1345
|
} catch (error) {
|
|
1253
1346
|
restoreState(run, before);
|
|
@@ -1271,16 +1364,32 @@ export class GraphKernel {
|
|
|
1271
1364
|
}
|
|
1272
1365
|
return runProjection(existing);
|
|
1273
1366
|
}
|
|
1274
|
-
const run = buildRun(
|
|
1367
|
+
const run = buildRun(
|
|
1368
|
+
compiled,
|
|
1369
|
+
options,
|
|
1370
|
+
this.now,
|
|
1371
|
+
this.writerIdentity,
|
|
1372
|
+
this.writerLeaseTtlMs,
|
|
1373
|
+
);
|
|
1275
1374
|
if (this.runs.has(run.id)) {
|
|
1276
1375
|
throw kernelError(
|
|
1277
1376
|
"CC_GRAPH_RUN_CONFLICT",
|
|
1278
1377
|
`GraphRun id already exists: ${run.id}`,
|
|
1279
1378
|
);
|
|
1280
1379
|
}
|
|
1281
|
-
this.eventStore.start(run.id, {
|
|
1380
|
+
const initialHead = this.eventStore.start(run.id, {
|
|
1282
1381
|
definitionId: compiled.definitionId,
|
|
1283
1382
|
revisionDigest: compiled.revisionDigest,
|
|
1383
|
+
originSurface: run.authority.originSurface,
|
|
1384
|
+
authorityMode: run.authority.authorityMode,
|
|
1385
|
+
authorityGeneration: run.authority.authorityGeneration,
|
|
1386
|
+
writerId: run.authority.writerId,
|
|
1387
|
+
projectionVersion: run.authority.projectionVersion,
|
|
1388
|
+
});
|
|
1389
|
+
run.lastEvent = initialHead;
|
|
1390
|
+
run.authority = createGraphAuthorityBinding({
|
|
1391
|
+
...run.authority,
|
|
1392
|
+
eventHead: initialHead.hash,
|
|
1284
1393
|
});
|
|
1285
1394
|
this.runs.set(run.id, run);
|
|
1286
1395
|
try {
|
|
@@ -1306,7 +1415,7 @@ export class GraphKernel {
|
|
|
1306
1415
|
return runProjection(run);
|
|
1307
1416
|
}
|
|
1308
1417
|
|
|
1309
|
-
recoverRun(runId) {
|
|
1418
|
+
recoverRun(runId, { authority = null } = {}) {
|
|
1310
1419
|
const id = safeIdentifier(runId, "runId");
|
|
1311
1420
|
const events = this.eventStore.read(id);
|
|
1312
1421
|
const latest = [...events]
|
|
@@ -1322,7 +1431,67 @@ export class GraphKernel {
|
|
|
1322
1431
|
const run = { id, lastEvent: latest };
|
|
1323
1432
|
restoreState(run, snapshot);
|
|
1324
1433
|
run.lastEvent = latest;
|
|
1434
|
+
run.authority = createGraphAuthorityBinding({
|
|
1435
|
+
...run.authority,
|
|
1436
|
+
eventHead: latest.hash,
|
|
1437
|
+
});
|
|
1325
1438
|
this.runs.set(id, run);
|
|
1439
|
+
if (authority) {
|
|
1440
|
+
const nextAuthority = createGraphAuthorityBinding({
|
|
1441
|
+
...authority,
|
|
1442
|
+
logicalRunId: id,
|
|
1443
|
+
eventHead: latest.hash,
|
|
1444
|
+
});
|
|
1445
|
+
const previousAuthority = run.authority;
|
|
1446
|
+
if (
|
|
1447
|
+
nextAuthority.authorityGeneration <=
|
|
1448
|
+
previousAuthority.authorityGeneration
|
|
1449
|
+
) {
|
|
1450
|
+
this.runs.delete(id);
|
|
1451
|
+
throw kernelError(
|
|
1452
|
+
"CC_GRAPH_STALE_GENERATION",
|
|
1453
|
+
"recovery authority must use a higher writer generation",
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
assertGraphAuthorityWriter(nextAuthority, this.writerIdentity, {
|
|
1457
|
+
now: this.now(),
|
|
1458
|
+
expectedEventHead: latest.hash,
|
|
1459
|
+
requireCanonical: false,
|
|
1460
|
+
});
|
|
1461
|
+
run.authority = nextAuthority;
|
|
1462
|
+
try {
|
|
1463
|
+
this._transaction(
|
|
1464
|
+
run,
|
|
1465
|
+
"run.authority_transferred",
|
|
1466
|
+
{
|
|
1467
|
+
previousAuthoritySource: previousAuthority.authoritySource,
|
|
1468
|
+
previousAuthorityGeneration: previousAuthority.authorityGeneration,
|
|
1469
|
+
previousWriterId: previousAuthority.writerId,
|
|
1470
|
+
authorityGeneration: nextAuthority.authorityGeneration,
|
|
1471
|
+
writerId: nextAuthority.writerId,
|
|
1472
|
+
},
|
|
1473
|
+
() => {},
|
|
1474
|
+
{
|
|
1475
|
+
allowTerminal: true,
|
|
1476
|
+
idempotencyKey: `authority-transfer:${nextAuthority.authorityGeneration}`,
|
|
1477
|
+
},
|
|
1478
|
+
);
|
|
1479
|
+
} catch (error) {
|
|
1480
|
+
this.runs.delete(id);
|
|
1481
|
+
throw error;
|
|
1482
|
+
}
|
|
1483
|
+
} else {
|
|
1484
|
+
try {
|
|
1485
|
+
assertGraphAuthorityWriter(run.authority, this.writerIdentity, {
|
|
1486
|
+
now: this.now(),
|
|
1487
|
+
expectedEventHead: latest.hash,
|
|
1488
|
+
requireCanonical: false,
|
|
1489
|
+
});
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
this.runs.delete(id);
|
|
1492
|
+
throw error;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1326
1495
|
if (run.occurrenceRef) {
|
|
1327
1496
|
const key = `${run.occurrenceRef.jobRevision || ""}\0${run.occurrenceRef.occurrenceId || run.occurrenceRef.idempotencyKey || ""}`;
|
|
1328
1497
|
this.occurrences.set(key, id);
|
|
@@ -1367,6 +1536,52 @@ export class GraphKernel {
|
|
|
1367
1536
|
return this.eventStore.read(safeIdentifier(runId, "runId"), options);
|
|
1368
1537
|
}
|
|
1369
1538
|
|
|
1539
|
+
collaborationState(runId) {
|
|
1540
|
+
const run = this._run(runId);
|
|
1541
|
+
return Object.freeze({
|
|
1542
|
+
messages: Object.freeze(
|
|
1543
|
+
[...run.messages.values()]
|
|
1544
|
+
.sort(
|
|
1545
|
+
(left, right) =>
|
|
1546
|
+
left.createdAt.localeCompare(right.createdAt) ||
|
|
1547
|
+
left.id.localeCompare(right.id),
|
|
1548
|
+
)
|
|
1549
|
+
.map((message) => Object.freeze(clone(message))),
|
|
1550
|
+
),
|
|
1551
|
+
messageConsumers: Object.freeze(
|
|
1552
|
+
[...run.messageConsumers.values()]
|
|
1553
|
+
.sort(
|
|
1554
|
+
(left, right) =>
|
|
1555
|
+
left.processedAt.localeCompare(right.processedAt) ||
|
|
1556
|
+
left.messageId.localeCompare(right.messageId),
|
|
1557
|
+
)
|
|
1558
|
+
.map((receipt) => Object.freeze(clone(receipt))),
|
|
1559
|
+
),
|
|
1560
|
+
handoffs: Object.freeze(
|
|
1561
|
+
[...run.handoffs.values()]
|
|
1562
|
+
.sort(
|
|
1563
|
+
(left, right) =>
|
|
1564
|
+
left.createdAt.localeCompare(right.createdAt) ||
|
|
1565
|
+
left.id.localeCompare(right.id),
|
|
1566
|
+
)
|
|
1567
|
+
.map((handoff) => Object.freeze(clone(handoff))),
|
|
1568
|
+
),
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
effectState(runId) {
|
|
1573
|
+
const run = this._run(runId);
|
|
1574
|
+
return Object.freeze(
|
|
1575
|
+
[...run.effects.values()]
|
|
1576
|
+
.sort(
|
|
1577
|
+
(left, right) =>
|
|
1578
|
+
left.createdAt.localeCompare(right.createdAt) ||
|
|
1579
|
+
left.id.localeCompare(right.id),
|
|
1580
|
+
)
|
|
1581
|
+
.map((effect) => Object.freeze(clone(effect))),
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1370
1585
|
registerAgent(runId, { agentId, capacity = 1, resident = true } = {}) {
|
|
1371
1586
|
const run = this._run(runId);
|
|
1372
1587
|
const id = safeIdentifier(agentId, "agentId");
|
|
@@ -1471,6 +1686,7 @@ export class GraphKernel {
|
|
|
1471
1686
|
edges = [],
|
|
1472
1687
|
loops = [],
|
|
1473
1688
|
subgraphCalls = [],
|
|
1689
|
+
metadataPatch = {},
|
|
1474
1690
|
} = {},
|
|
1475
1691
|
) {
|
|
1476
1692
|
const run = this._run(runId);
|
|
@@ -1483,6 +1699,7 @@ export class GraphKernel {
|
|
|
1483
1699
|
edges,
|
|
1484
1700
|
loops,
|
|
1485
1701
|
subgraphCalls,
|
|
1702
|
+
metadataPatch,
|
|
1486
1703
|
};
|
|
1487
1704
|
const requestDigest = graphDigest(request, "cc.graph.append-request/v1");
|
|
1488
1705
|
const replay = run.requestCache.get(key);
|
|
@@ -1538,6 +1755,10 @@ export class GraphKernel {
|
|
|
1538
1755
|
...clone(run.compiled.definition.subgraphCalls),
|
|
1539
1756
|
...clone(subgraphCalls),
|
|
1540
1757
|
],
|
|
1758
|
+
metadata: {
|
|
1759
|
+
...clone(run.compiled.definition.metadata || {}),
|
|
1760
|
+
...clone(metadataPatch || {}),
|
|
1761
|
+
},
|
|
1541
1762
|
};
|
|
1542
1763
|
const compiled = compileGraphDefinition(definition, {
|
|
1543
1764
|
subgraphs: persistedSubgraphRegistry(
|
|
@@ -1942,6 +2163,12 @@ export class GraphKernel {
|
|
|
1942
2163
|
} else {
|
|
1943
2164
|
this.startRun(childCompiled, {
|
|
1944
2165
|
runId: id,
|
|
2166
|
+
originSurface: parent.authority.originSurface,
|
|
2167
|
+
authority: {
|
|
2168
|
+
...parent.authority,
|
|
2169
|
+
logicalRunId: id,
|
|
2170
|
+
eventHead: null,
|
|
2171
|
+
},
|
|
1945
2172
|
parentRunRef: {
|
|
1946
2173
|
runId: parent.id,
|
|
1947
2174
|
nodeId: safeNodeId,
|
|
@@ -2257,6 +2484,8 @@ export class GraphKernel {
|
|
|
2257
2484
|
: null,
|
|
2258
2485
|
leaseId: safeIdentifier(leaseId, "leaseId"),
|
|
2259
2486
|
fence,
|
|
2487
|
+
authorityGeneration: run.authority.authorityGeneration,
|
|
2488
|
+
writerId: run.authority.writerId,
|
|
2260
2489
|
status: "active",
|
|
2261
2490
|
participationStatus: "active",
|
|
2262
2491
|
grant: clone(grant),
|
|
@@ -2266,6 +2495,7 @@ export class GraphKernel {
|
|
|
2266
2495
|
expiresAtMs: now + finitePositive(ttlMs, 60_000),
|
|
2267
2496
|
terminalEvidence: null,
|
|
2268
2497
|
usage: null,
|
|
2498
|
+
error: null,
|
|
2269
2499
|
};
|
|
2270
2500
|
run.attempts.set(id, attempt);
|
|
2271
2501
|
state.attemptIds.push(id);
|
|
@@ -2291,9 +2521,261 @@ export class GraphKernel {
|
|
|
2291
2521
|
});
|
|
2292
2522
|
}
|
|
2293
2523
|
|
|
2524
|
+
/**
|
|
2525
|
+
* Recover a dispatch that was durably assigned by an older writer but never
|
|
2526
|
+
* crossed the effect boundary. Once an effect record exists its outcome can
|
|
2527
|
+
* no longer be inferred from the absence of a terminal attempt, so recovery
|
|
2528
|
+
* must go through reconciliation instead of silently replaying the task.
|
|
2529
|
+
*/
|
|
2530
|
+
reclaimAttempt(
|
|
2531
|
+
runId,
|
|
2532
|
+
attemptId,
|
|
2533
|
+
{ reason = "writer generation takeover" } = {},
|
|
2534
|
+
) {
|
|
2535
|
+
const run = this._run(runId);
|
|
2536
|
+
const id = safeIdentifier(attemptId, "attemptId");
|
|
2537
|
+
return this._transaction(
|
|
2538
|
+
run,
|
|
2539
|
+
"assignment.reclaimed",
|
|
2540
|
+
{ attemptId: id, reason },
|
|
2541
|
+
() => {
|
|
2542
|
+
const attempt = run.attempts.get(id);
|
|
2543
|
+
if (!attempt) {
|
|
2544
|
+
throw kernelError(
|
|
2545
|
+
"CC_GRAPH_ATTEMPT_NOT_FOUND",
|
|
2546
|
+
`assignment attempt not found: ${id}`,
|
|
2547
|
+
);
|
|
2548
|
+
}
|
|
2549
|
+
if (
|
|
2550
|
+
attempt.status === "expired" &&
|
|
2551
|
+
attempt.participationStatus === "lost"
|
|
2552
|
+
) {
|
|
2553
|
+
return attemptProjection(attempt);
|
|
2554
|
+
}
|
|
2555
|
+
if (!ACTIVE_ATTEMPT.has(attempt.status)) {
|
|
2556
|
+
throw kernelError(
|
|
2557
|
+
"CC_GRAPH_ASSIGNMENT_RECLAIM_TERMINAL",
|
|
2558
|
+
"only an active assignment can be reclaimed",
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2561
|
+
if (
|
|
2562
|
+
Number(attempt.authorityGeneration || 0) >=
|
|
2563
|
+
run.authority.authorityGeneration
|
|
2564
|
+
) {
|
|
2565
|
+
throw kernelError(
|
|
2566
|
+
"CC_GRAPH_ASSIGNMENT_RECLAIM_STALE_WRITER",
|
|
2567
|
+
"an assignment can only be reclaimed by a higher authority generation",
|
|
2568
|
+
);
|
|
2569
|
+
}
|
|
2570
|
+
const effects = [...run.effects.values()].filter(
|
|
2571
|
+
(effect) => effect.attemptId === attempt.id,
|
|
2572
|
+
);
|
|
2573
|
+
if (effects.length > 0) {
|
|
2574
|
+
throw kernelError(
|
|
2575
|
+
"CC_GRAPH_ASSIGNMENT_RECLAIM_UNSAFE",
|
|
2576
|
+
"an assignment with an effect record requires audited reconciliation",
|
|
2577
|
+
{ effectIds: effects.map((effect) => effect.id).sort() },
|
|
2578
|
+
);
|
|
2579
|
+
}
|
|
2580
|
+
const state = run.nodeStates.get(attempt.nodeId);
|
|
2581
|
+
if (
|
|
2582
|
+
!state ||
|
|
2583
|
+
state.acceptedAttemptId ||
|
|
2584
|
+
!["running", "pending"].includes(state.status)
|
|
2585
|
+
) {
|
|
2586
|
+
throw kernelError(
|
|
2587
|
+
"CC_GRAPH_ASSIGNMENT_RECLAIM_UNSAFE",
|
|
2588
|
+
"assignment state is not safe to return to the ready frontier",
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2591
|
+
const now = this.now();
|
|
2592
|
+
attempt.status = "expired";
|
|
2593
|
+
attempt.participationStatus = "lost";
|
|
2594
|
+
attempt.error = String(reason).slice(0, 4096);
|
|
2595
|
+
attempt.updatedAt = nowIso(() => now);
|
|
2596
|
+
state.status = "pending";
|
|
2597
|
+
state.updatedAt = nowIso(() => now);
|
|
2598
|
+
const agent = run.agents.get(attempt.agentId);
|
|
2599
|
+
if (agent) {
|
|
2600
|
+
agent.status = "idle";
|
|
2601
|
+
agent.updatedAt = nowIso(() => now);
|
|
2602
|
+
}
|
|
2603
|
+
propagate(run, () => now);
|
|
2604
|
+
if (refreshLoopStates(run, () => now)) propagate(run, () => now);
|
|
2605
|
+
run.status = classifyQuiescence(run, now);
|
|
2606
|
+
run.completedAt = TERMINAL_RUN.has(run.status)
|
|
2607
|
+
? nowIso(() => now)
|
|
2608
|
+
: null;
|
|
2609
|
+
return attemptProjection(attempt);
|
|
2610
|
+
},
|
|
2611
|
+
{
|
|
2612
|
+
idempotencyKey: `assignment-reclaim:${id}:${run.authority.authorityGeneration}`,
|
|
2613
|
+
},
|
|
2614
|
+
);
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
/**
|
|
2618
|
+
* Rotate a safely recoverable assignment into the current writer generation.
|
|
2619
|
+
* A persisted attempt is never reused after takeover: callers receive a new
|
|
2620
|
+
* lease/fence, while terminal effect receipts retain an explicit custody
|
|
2621
|
+
* chain. An in-flight/unknown effect remains reconciliation-only.
|
|
2622
|
+
*/
|
|
2623
|
+
resumeAttempt(
|
|
2624
|
+
runId,
|
|
2625
|
+
attemptId,
|
|
2626
|
+
{
|
|
2627
|
+
resumedAttemptId = this.createId(),
|
|
2628
|
+
leaseId = this.createId(),
|
|
2629
|
+
ttlMs = 60_000,
|
|
2630
|
+
reason = "writer generation takeover",
|
|
2631
|
+
} = {},
|
|
2632
|
+
) {
|
|
2633
|
+
const run = this._run(runId);
|
|
2634
|
+
const sourceId = safeIdentifier(attemptId, "attemptId");
|
|
2635
|
+
const successorId = safeIdentifier(resumedAttemptId, "attemptId");
|
|
2636
|
+
return this._transaction(
|
|
2637
|
+
run,
|
|
2638
|
+
"assignment.resumed",
|
|
2639
|
+
{ attemptId: sourceId, resumedAttemptId: successorId, reason },
|
|
2640
|
+
() => {
|
|
2641
|
+
const existing = [...run.attempts.values()].find(
|
|
2642
|
+
(candidate) =>
|
|
2643
|
+
candidate.resumedFromAttemptId === sourceId &&
|
|
2644
|
+
candidate.authorityGeneration ===
|
|
2645
|
+
run.authority.authorityGeneration &&
|
|
2646
|
+
candidate.writerId === run.authority.writerId,
|
|
2647
|
+
);
|
|
2648
|
+
if (existing) return attemptProjection(existing);
|
|
2649
|
+
const source = run.attempts.get(sourceId);
|
|
2650
|
+
if (!source) {
|
|
2651
|
+
throw kernelError(
|
|
2652
|
+
"CC_GRAPH_ATTEMPT_NOT_FOUND",
|
|
2653
|
+
`assignment attempt not found: ${sourceId}`,
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
const auditedRecovery =
|
|
2657
|
+
source.status === "expired" &&
|
|
2658
|
+
source.participationStatus === "reconciled";
|
|
2659
|
+
if (!ACTIVE_ATTEMPT.has(source.status) && !auditedRecovery) {
|
|
2660
|
+
throw kernelError(
|
|
2661
|
+
"CC_GRAPH_ASSIGNMENT_RESUME_TERMINAL",
|
|
2662
|
+
"only an active or audited-reconciled assignment can be resumed",
|
|
2663
|
+
);
|
|
2664
|
+
}
|
|
2665
|
+
if (
|
|
2666
|
+
Number(source.authorityGeneration || 0) >=
|
|
2667
|
+
run.authority.authorityGeneration
|
|
2668
|
+
) {
|
|
2669
|
+
throw kernelError(
|
|
2670
|
+
"CC_GRAPH_ASSIGNMENT_RESUME_STALE_WRITER",
|
|
2671
|
+
"an assignment can only be resumed by a higher authority generation",
|
|
2672
|
+
);
|
|
2673
|
+
}
|
|
2674
|
+
const effects = [...run.effects.values()].filter(
|
|
2675
|
+
(effect) => effect.attemptId === source.id,
|
|
2676
|
+
);
|
|
2677
|
+
const unsafe = effects.filter((effect) =>
|
|
2678
|
+
["started", "unknown"].includes(effect.status),
|
|
2679
|
+
);
|
|
2680
|
+
if (unsafe.length > 0) {
|
|
2681
|
+
throw kernelError(
|
|
2682
|
+
"CC_GRAPH_ASSIGNMENT_RESUME_UNSAFE",
|
|
2683
|
+
"an assignment with an unresolved effect requires audited reconciliation",
|
|
2684
|
+
{ effectIds: unsafe.map((effect) => effect.id).sort() },
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2687
|
+
const state = run.nodeStates.get(source.nodeId);
|
|
2688
|
+
if (
|
|
2689
|
+
!state ||
|
|
2690
|
+
state.acceptedAttemptId ||
|
|
2691
|
+
!["running", "pending"].includes(state.status)
|
|
2692
|
+
) {
|
|
2693
|
+
throw kernelError(
|
|
2694
|
+
"CC_GRAPH_ASSIGNMENT_RESUME_UNSAFE",
|
|
2695
|
+
"assignment state is not safe to rotate into the new generation",
|
|
2696
|
+
);
|
|
2697
|
+
}
|
|
2698
|
+
if (run.attempts.has(successorId)) {
|
|
2699
|
+
throw kernelError(
|
|
2700
|
+
"CC_GRAPH_ATTEMPT_ID_CONFLICT",
|
|
2701
|
+
`assignment attempt already exists: ${successorId}`,
|
|
2702
|
+
);
|
|
2703
|
+
}
|
|
2704
|
+
const now = this.now();
|
|
2705
|
+
source.status = "expired";
|
|
2706
|
+
source.participationStatus = "superseded";
|
|
2707
|
+
source.error = String(reason).slice(0, 4096);
|
|
2708
|
+
source.updatedAt = nowIso(() => now);
|
|
2709
|
+
const fence = (run.fenceCounters.get(source.nodeId) || 0) + 1;
|
|
2710
|
+
run.fenceCounters.set(source.nodeId, fence);
|
|
2711
|
+
const successor = {
|
|
2712
|
+
...clone(source),
|
|
2713
|
+
id: successorId,
|
|
2714
|
+
attempt:
|
|
2715
|
+
[...run.attempts.values()].filter(
|
|
2716
|
+
(candidate) =>
|
|
2717
|
+
candidate.nodeId === source.nodeId &&
|
|
2718
|
+
JSON.stringify(candidate.iterationPath || []) ===
|
|
2719
|
+
JSON.stringify(source.iterationPath || []),
|
|
2720
|
+
).length + 1,
|
|
2721
|
+
leaseId: safeIdentifier(leaseId, "leaseId"),
|
|
2722
|
+
fence,
|
|
2723
|
+
authorityGeneration: run.authority.authorityGeneration,
|
|
2724
|
+
writerId: run.authority.writerId,
|
|
2725
|
+
status: "active",
|
|
2726
|
+
participationStatus: "active",
|
|
2727
|
+
resumedFromAttemptId: source.id,
|
|
2728
|
+
resumeReason: String(reason).slice(0, 4096),
|
|
2729
|
+
createdAt: nowIso(() => now),
|
|
2730
|
+
updatedAt: nowIso(() => now),
|
|
2731
|
+
expiresAt: nowIso(() => now + finitePositive(ttlMs, 60_000)),
|
|
2732
|
+
expiresAtMs: now + finitePositive(ttlMs, 60_000),
|
|
2733
|
+
terminalEvidence: null,
|
|
2734
|
+
usage: null,
|
|
2735
|
+
error: null,
|
|
2736
|
+
};
|
|
2737
|
+
for (const effect of effects) {
|
|
2738
|
+
effect.custodyHistory ||= [];
|
|
2739
|
+
effect.custodyHistory.push({
|
|
2740
|
+
fromAttemptId: source.id,
|
|
2741
|
+
fromLeaseId: source.leaseId,
|
|
2742
|
+
fromFence: source.fence,
|
|
2743
|
+
toAttemptId: successor.id,
|
|
2744
|
+
toLeaseId: successor.leaseId,
|
|
2745
|
+
toFence: successor.fence,
|
|
2746
|
+
reason: "authority_generation_takeover",
|
|
2747
|
+
transferredAt: nowIso(() => now),
|
|
2748
|
+
});
|
|
2749
|
+
effect.attemptId = successor.id;
|
|
2750
|
+
effect.leaseId = successor.leaseId;
|
|
2751
|
+
effect.fence = successor.fence;
|
|
2752
|
+
effect.updatedAt = nowIso(() => now);
|
|
2753
|
+
}
|
|
2754
|
+
run.attempts.set(successor.id, successor);
|
|
2755
|
+
state.attemptIds.push(successor.id);
|
|
2756
|
+
state.status = "running";
|
|
2757
|
+
state.updatedAt = nowIso(() => now);
|
|
2758
|
+
return attemptProjection(successor);
|
|
2759
|
+
},
|
|
2760
|
+
{
|
|
2761
|
+
idempotencyKey: `assignment-resume:${sourceId}:${run.authority.authorityGeneration}`,
|
|
2762
|
+
},
|
|
2763
|
+
);
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2294
2766
|
_requireAttemptLease(run, attemptId, leaseId, fence, statuses = ["active"]) {
|
|
2295
2767
|
const attempt = run.attempts.get(safeIdentifier(attemptId, "attemptId"));
|
|
2296
2768
|
const now = this.now();
|
|
2769
|
+
if (
|
|
2770
|
+
attempt &&
|
|
2771
|
+
(attempt.authorityGeneration !== run.authority.authorityGeneration ||
|
|
2772
|
+
attempt.writerId !== run.authority.writerId)
|
|
2773
|
+
) {
|
|
2774
|
+
throw kernelError(
|
|
2775
|
+
"CC_GRAPH_STALE_ATTEMPT_AUTHORITY",
|
|
2776
|
+
"attempt belongs to a stale authority generation or writer",
|
|
2777
|
+
);
|
|
2778
|
+
}
|
|
2297
2779
|
if (
|
|
2298
2780
|
!attempt ||
|
|
2299
2781
|
attempt.leaseId !== safeIdentifier(leaseId, "leaseId") ||
|
|
@@ -2323,17 +2805,25 @@ export class GraphKernel {
|
|
|
2323
2805
|
) {
|
|
2324
2806
|
const run = this._run(runId);
|
|
2325
2807
|
const key = String(idempotencyKey || "").trim();
|
|
2326
|
-
const
|
|
2808
|
+
const matching = [...run.effects.values()].filter(
|
|
2327
2809
|
(effect) => effect.idempotencyKey === key,
|
|
2328
2810
|
);
|
|
2329
|
-
if (
|
|
2330
|
-
if (
|
|
2811
|
+
if (matching.length > 0) {
|
|
2812
|
+
if (
|
|
2813
|
+
matching.some((effect) => effect.operationDigest !== operationDigest)
|
|
2814
|
+
) {
|
|
2331
2815
|
throw kernelError(
|
|
2332
2816
|
"CC_GRAPH_EFFECT_IDEMPOTENCY_CONFLICT",
|
|
2333
2817
|
"effect idempotency key was reused for a different operation",
|
|
2334
2818
|
);
|
|
2335
2819
|
}
|
|
2336
|
-
|
|
2820
|
+
// A proved/audited failure means the operation did not commit, so a
|
|
2821
|
+
// later fenced attempt must receive a fresh effect boundary. Started,
|
|
2822
|
+
// unknown, and committed effects continue to deduplicate fail-closed.
|
|
2823
|
+
const authoritative = [...matching]
|
|
2824
|
+
.reverse()
|
|
2825
|
+
.find((effect) => effect.status !== "failed");
|
|
2826
|
+
if (authoritative) return Object.freeze(clone(authoritative));
|
|
2337
2827
|
}
|
|
2338
2828
|
const id = safeIdentifier(effectId, "effectId");
|
|
2339
2829
|
return this._transaction(
|
|
@@ -3003,6 +3493,7 @@ export class GraphKernel {
|
|
|
3003
3493
|
dataPolicy,
|
|
3004
3494
|
causationId = null,
|
|
3005
3495
|
correlationId = null,
|
|
3496
|
+
systemSource = null,
|
|
3006
3497
|
ttlMs = 24 * 60 * 60 * 1000,
|
|
3007
3498
|
} = {},
|
|
3008
3499
|
) {
|
|
@@ -3010,7 +3501,15 @@ export class GraphKernel {
|
|
|
3010
3501
|
const id = safeIdentifier(messageId, "messageId");
|
|
3011
3502
|
const recipient = safeIdentifier(toAgentId, "toAgentId");
|
|
3012
3503
|
const contentDigest = graphDigest(
|
|
3013
|
-
{
|
|
3504
|
+
{
|
|
3505
|
+
recipient,
|
|
3506
|
+
mode,
|
|
3507
|
+
payload,
|
|
3508
|
+
causationId,
|
|
3509
|
+
correlationId,
|
|
3510
|
+
fromAttemptId: fromAttemptId || null,
|
|
3511
|
+
systemSource: systemSource || null,
|
|
3512
|
+
},
|
|
3014
3513
|
"cc.graph.message/v1",
|
|
3015
3514
|
);
|
|
3016
3515
|
const existing = run.messages.get(id);
|
|
@@ -3028,12 +3527,13 @@ export class GraphKernel {
|
|
|
3028
3527
|
"message.admitted",
|
|
3029
3528
|
{ messageId: id, toAgentId: recipient, mode },
|
|
3030
3529
|
() => {
|
|
3031
|
-
const
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3530
|
+
const source =
|
|
3531
|
+
systemSource == null
|
|
3532
|
+
? null
|
|
3533
|
+
: safeIdentifier(systemSource, "systemSource");
|
|
3534
|
+
const attempt = source
|
|
3535
|
+
? null
|
|
3536
|
+
: this._requireAttemptLease(run, fromAttemptId, leaseId, fence);
|
|
3037
3537
|
const policy = validateDataPolicy(dataPolicy);
|
|
3038
3538
|
const sink = `agent:${recipient}`;
|
|
3039
3539
|
if (!sinkAllowed(policy, sink)) {
|
|
@@ -3067,9 +3567,12 @@ export class GraphKernel {
|
|
|
3067
3567
|
const message = {
|
|
3068
3568
|
id,
|
|
3069
3569
|
runId: run.id,
|
|
3070
|
-
fromAttemptId: attempt
|
|
3071
|
-
fromLeaseId: attempt
|
|
3072
|
-
fromFence: attempt
|
|
3570
|
+
fromAttemptId: attempt?.id || null,
|
|
3571
|
+
fromLeaseId: attempt?.leaseId || null,
|
|
3572
|
+
fromFence: attempt?.fence ?? null,
|
|
3573
|
+
systemSource: source,
|
|
3574
|
+
sourceAuthorityGeneration: run.authority.authorityGeneration,
|
|
3575
|
+
sourceWriterId: run.authority.writerId,
|
|
3073
3576
|
toAgentId: recipient,
|
|
3074
3577
|
causationId,
|
|
3075
3578
|
correlationId,
|
|
@@ -3165,6 +3668,16 @@ export class GraphKernel {
|
|
|
3165
3668
|
if (run.messageConsumers.has(dedupKey)) {
|
|
3166
3669
|
return Object.freeze(clone(run.messageConsumers.get(dedupKey)));
|
|
3167
3670
|
}
|
|
3671
|
+
const priorConsumer = [...run.messageConsumers.values()].find(
|
|
3672
|
+
(receipt) => receipt.messageId === id && receipt.agentId === recipient,
|
|
3673
|
+
);
|
|
3674
|
+
if (priorConsumer) {
|
|
3675
|
+
throw kernelError(
|
|
3676
|
+
"CC_GRAPH_MESSAGE_CONSUMER_CONFLICT",
|
|
3677
|
+
"message was already processed by another consumer key",
|
|
3678
|
+
{ consumerKey: priorConsumer.consumerKey },
|
|
3679
|
+
);
|
|
3680
|
+
}
|
|
3168
3681
|
return this._transaction(
|
|
3169
3682
|
run,
|
|
3170
3683
|
"message.processed",
|
|
@@ -3244,13 +3757,33 @@ export class GraphKernel {
|
|
|
3244
3757
|
) {
|
|
3245
3758
|
const run = this._run(runId);
|
|
3246
3759
|
const id = safeIdentifier(handoffId, "handoffId");
|
|
3760
|
+
const requestDigest = graphDigest(
|
|
3761
|
+
{
|
|
3762
|
+
fromAttemptId,
|
|
3763
|
+
leaseId,
|
|
3764
|
+
fence,
|
|
3765
|
+
toAgentId,
|
|
3766
|
+
artifactIds,
|
|
3767
|
+
preconditions,
|
|
3768
|
+
ttlMs,
|
|
3769
|
+
},
|
|
3770
|
+
"cc.graph.handoff-request/v1",
|
|
3771
|
+
);
|
|
3247
3772
|
return this._transaction(
|
|
3248
3773
|
run,
|
|
3249
3774
|
"handoff.offered",
|
|
3250
3775
|
{ handoffId: id, fromAttemptId, toAgentId },
|
|
3251
3776
|
() => {
|
|
3252
3777
|
const existing = run.handoffs.get(id);
|
|
3253
|
-
if (existing)
|
|
3778
|
+
if (existing) {
|
|
3779
|
+
if (existing.requestDigest !== requestDigest) {
|
|
3780
|
+
throw kernelError(
|
|
3781
|
+
"CC_GRAPH_HANDOFF_ID_CONFLICT",
|
|
3782
|
+
"handoffId was reused with different custody content",
|
|
3783
|
+
);
|
|
3784
|
+
}
|
|
3785
|
+
return Object.freeze(clone(existing));
|
|
3786
|
+
}
|
|
3254
3787
|
const attempt = this._requireAttemptLease(
|
|
3255
3788
|
run,
|
|
3256
3789
|
fromAttemptId,
|
|
@@ -3278,6 +3811,7 @@ export class GraphKernel {
|
|
|
3278
3811
|
authorityDigest: run.authorityDigest,
|
|
3279
3812
|
artifactIds: [...artifactIds],
|
|
3280
3813
|
preconditions: clone(preconditions),
|
|
3814
|
+
requestDigest,
|
|
3281
3815
|
status: "offered",
|
|
3282
3816
|
createdAt: nowIso(() => now),
|
|
3283
3817
|
updatedAt: nowIso(() => now),
|
|
@@ -3301,6 +3835,9 @@ export class GraphKernel {
|
|
|
3301
3835
|
{ handoffId: id, agentId: recipient },
|
|
3302
3836
|
() => {
|
|
3303
3837
|
const handoff = run.handoffs.get(id);
|
|
3838
|
+
if (handoff?.status === "accepted" && handoff.toAgentId === recipient) {
|
|
3839
|
+
return Object.freeze(clone(handoff));
|
|
3840
|
+
}
|
|
3304
3841
|
if (
|
|
3305
3842
|
!handoff ||
|
|
3306
3843
|
handoff.status !== "offered" ||
|
|
@@ -3313,7 +3850,8 @@ export class GraphKernel {
|
|
|
3313
3850
|
);
|
|
3314
3851
|
}
|
|
3315
3852
|
handoff.status = "accepted";
|
|
3316
|
-
handoff.
|
|
3853
|
+
handoff.acceptedAt = nowIso(this.now);
|
|
3854
|
+
handoff.updatedAt = handoff.acceptedAt;
|
|
3317
3855
|
return Object.freeze(clone(handoff));
|
|
3318
3856
|
},
|
|
3319
3857
|
);
|
|
@@ -3329,6 +3867,9 @@ export class GraphKernel {
|
|
|
3329
3867
|
{ handoffId: id, reason },
|
|
3330
3868
|
() => {
|
|
3331
3869
|
const handoff = run.handoffs.get(id);
|
|
3870
|
+
if (handoff?.status === "rejected" && handoff.toAgentId === recipient) {
|
|
3871
|
+
return Object.freeze(clone(handoff));
|
|
3872
|
+
}
|
|
3332
3873
|
if (
|
|
3333
3874
|
!handoff ||
|
|
3334
3875
|
handoff.status !== "offered" ||
|
|
@@ -3341,7 +3882,8 @@ export class GraphKernel {
|
|
|
3341
3882
|
}
|
|
3342
3883
|
handoff.status = "rejected";
|
|
3343
3884
|
handoff.reason = String(reason).slice(0, 1024);
|
|
3344
|
-
handoff.
|
|
3885
|
+
handoff.rejectedAt = nowIso(this.now);
|
|
3886
|
+
handoff.updatedAt = handoff.rejectedAt;
|
|
3345
3887
|
return Object.freeze(clone(handoff));
|
|
3346
3888
|
},
|
|
3347
3889
|
);
|
|
@@ -3364,6 +3906,21 @@ export class GraphKernel {
|
|
|
3364
3906
|
{ handoffId: id },
|
|
3365
3907
|
() => {
|
|
3366
3908
|
const handoff = run.handoffs.get(id);
|
|
3909
|
+
if (handoff?.status === "committed") {
|
|
3910
|
+
const assignmentAttempt = run.attempts.get(
|
|
3911
|
+
handoff.committedAttemptId,
|
|
3912
|
+
);
|
|
3913
|
+
if (!assignmentAttempt) {
|
|
3914
|
+
throw kernelError(
|
|
3915
|
+
"CC_GRAPH_HANDOFF_COMMIT_CORRUPT",
|
|
3916
|
+
"committed handoff is missing its assignment attempt",
|
|
3917
|
+
);
|
|
3918
|
+
}
|
|
3919
|
+
return Object.freeze({
|
|
3920
|
+
handoff: Object.freeze(clone(handoff)),
|
|
3921
|
+
assignmentAttempt: attemptProjection(assignmentAttempt),
|
|
3922
|
+
});
|
|
3923
|
+
}
|
|
3367
3924
|
if (!handoff || handoff.status !== "accepted") {
|
|
3368
3925
|
throw kernelError(
|
|
3369
3926
|
"CC_GRAPH_HANDOFF_NOT_COMMITTABLE",
|
|
@@ -3407,8 +3964,12 @@ export class GraphKernel {
|
|
|
3407
3964
|
attempt: attemptNumber,
|
|
3408
3965
|
agentId: handoff.toAgentId,
|
|
3409
3966
|
role: "executor",
|
|
3967
|
+
compensationForNodeId: sender.compensationForNodeId || null,
|
|
3968
|
+
effectIdempotencyKey: sender.effectIdempotencyKey || null,
|
|
3410
3969
|
leaseId: safeIdentifier(leaseId, "leaseId"),
|
|
3411
3970
|
fence,
|
|
3971
|
+
authorityGeneration: run.authority.authorityGeneration,
|
|
3972
|
+
writerId: run.authority.writerId,
|
|
3412
3973
|
status: "active",
|
|
3413
3974
|
participationStatus: "active",
|
|
3414
3975
|
grant: clone(sender.grant),
|
|
@@ -3419,11 +3980,37 @@ export class GraphKernel {
|
|
|
3419
3980
|
terminalEvidence: null,
|
|
3420
3981
|
usage: null,
|
|
3421
3982
|
};
|
|
3983
|
+
for (const effect of run.effects.values()) {
|
|
3984
|
+
if (effect.attemptId !== sender.id) continue;
|
|
3985
|
+
if (effect.status !== "started") {
|
|
3986
|
+
throw kernelError(
|
|
3987
|
+
"CC_GRAPH_HANDOFF_EFFECT_TERMINAL",
|
|
3988
|
+
"custody cannot transfer after the source effect became terminal",
|
|
3989
|
+
{ effectId: effect.id, effectStatus: effect.status },
|
|
3990
|
+
);
|
|
3991
|
+
}
|
|
3992
|
+
effect.custodyHistory ||= [];
|
|
3993
|
+
effect.custodyHistory.push({
|
|
3994
|
+
fromAttemptId: sender.id,
|
|
3995
|
+
fromLeaseId: sender.leaseId,
|
|
3996
|
+
fromFence: sender.fence,
|
|
3997
|
+
toAttemptId: newAttempt.id,
|
|
3998
|
+
toLeaseId: newAttempt.leaseId,
|
|
3999
|
+
toFence: newAttempt.fence,
|
|
4000
|
+
handoffId: handoff.id,
|
|
4001
|
+
transferredAt: nowIso(() => now),
|
|
4002
|
+
});
|
|
4003
|
+
effect.attemptId = newAttempt.id;
|
|
4004
|
+
effect.leaseId = newAttempt.leaseId;
|
|
4005
|
+
effect.fence = newAttempt.fence;
|
|
4006
|
+
effect.updatedAt = nowIso(() => now);
|
|
4007
|
+
}
|
|
3422
4008
|
run.attempts.set(newAttempt.id, newAttempt);
|
|
3423
4009
|
run.nodeStates.get(sender.nodeId).attemptIds.push(newAttempt.id);
|
|
3424
4010
|
handoff.status = "committed";
|
|
3425
4011
|
handoff.committedAttemptId = newAttempt.id;
|
|
3426
|
-
handoff.
|
|
4012
|
+
handoff.committedAt = nowIso(() => now);
|
|
4013
|
+
handoff.updatedAt = handoff.committedAt;
|
|
3427
4014
|
return Object.freeze({
|
|
3428
4015
|
handoff: Object.freeze(clone(handoff)),
|
|
3429
4016
|
assignmentAttempt: attemptProjection(newAttempt),
|
|
@@ -3432,11 +4019,60 @@ export class GraphKernel {
|
|
|
3432
4019
|
);
|
|
3433
4020
|
}
|
|
3434
4021
|
|
|
4022
|
+
expireHandoffForRecovery(
|
|
4023
|
+
runId,
|
|
4024
|
+
handoffId,
|
|
4025
|
+
{ reason = "source writer generation was superseded" } = {},
|
|
4026
|
+
) {
|
|
4027
|
+
const run = this._run(runId);
|
|
4028
|
+
const id = safeIdentifier(handoffId, "handoffId");
|
|
4029
|
+
return this._transaction(
|
|
4030
|
+
run,
|
|
4031
|
+
"handoff.expired",
|
|
4032
|
+
{ handoffId: id, reason },
|
|
4033
|
+
() => {
|
|
4034
|
+
const handoff = run.handoffs.get(id);
|
|
4035
|
+
if (handoff?.status === "expired") {
|
|
4036
|
+
return Object.freeze(clone(handoff));
|
|
4037
|
+
}
|
|
4038
|
+
if (!handoff || !["offered", "accepted"].includes(handoff.status)) {
|
|
4039
|
+
throw kernelError(
|
|
4040
|
+
"CC_GRAPH_HANDOFF_NOT_RECOVERY_EXPIRABLE",
|
|
4041
|
+
"only an open handoff can expire during writer recovery",
|
|
4042
|
+
);
|
|
4043
|
+
}
|
|
4044
|
+
const source = run.attempts.get(handoff.fromAttemptId);
|
|
4045
|
+
if (
|
|
4046
|
+
!source ||
|
|
4047
|
+
Number(source.authorityGeneration || 0) >=
|
|
4048
|
+
run.authority.authorityGeneration
|
|
4049
|
+
) {
|
|
4050
|
+
throw kernelError(
|
|
4051
|
+
"CC_GRAPH_HANDOFF_RECOVERY_AUTHORITY_INVALID",
|
|
4052
|
+
"handoff recovery requires a source from an older authority generation",
|
|
4053
|
+
);
|
|
4054
|
+
}
|
|
4055
|
+
const now = this.now();
|
|
4056
|
+
handoff.status = "expired";
|
|
4057
|
+
handoff.reason = String(reason).slice(0, 1024);
|
|
4058
|
+
handoff.expiredAt = nowIso(() => now);
|
|
4059
|
+
handoff.updatedAt = nowIso(() => now);
|
|
4060
|
+
return Object.freeze(clone(handoff));
|
|
4061
|
+
},
|
|
4062
|
+
{
|
|
4063
|
+
idempotencyKey: `handoff-recovery-expire:${id}:${run.authority.authorityGeneration}`,
|
|
4064
|
+
},
|
|
4065
|
+
);
|
|
4066
|
+
}
|
|
4067
|
+
|
|
3435
4068
|
revokeHandoff(runId, handoffId, fromAttemptId, leaseId, fence) {
|
|
3436
4069
|
const run = this._run(runId);
|
|
3437
4070
|
const id = safeIdentifier(handoffId, "handoffId");
|
|
3438
4071
|
return this._transaction(run, "handoff.revoked", { handoffId: id }, () => {
|
|
3439
4072
|
const handoff = run.handoffs.get(id);
|
|
4073
|
+
if (handoff?.status === "revoked") {
|
|
4074
|
+
return Object.freeze(clone(handoff));
|
|
4075
|
+
}
|
|
3440
4076
|
if (!handoff || !["offered", "accepted"].includes(handoff.status)) {
|
|
3441
4077
|
throw kernelError(
|
|
3442
4078
|
"CC_GRAPH_HANDOFF_NOT_REVOKABLE",
|
|
@@ -3451,7 +4087,8 @@ export class GraphKernel {
|
|
|
3451
4087
|
);
|
|
3452
4088
|
}
|
|
3453
4089
|
handoff.status = "revoked";
|
|
3454
|
-
handoff.
|
|
4090
|
+
handoff.revokedAt = nowIso(this.now);
|
|
4091
|
+
handoff.updatedAt = handoff.revokedAt;
|
|
3455
4092
|
return Object.freeze(clone(handoff));
|
|
3456
4093
|
});
|
|
3457
4094
|
}
|