chainlesschain 0.166.2 → 0.166.3
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 +1 -1
- package/src/assets/web-panel/assets/{AIOps-BnZ24WRk.js → AIOps-DZTvbk_H.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-ivXY7MGb.js → ActionButton-IaEVUN3o.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BQS4OD-k.js → Analytics-CCQab0gl.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-wOBuOKTO.js → AppLayout-Bs_0GVpx.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BYuC4nw-.js → Artifacts-Bm83-yJv.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BIeVfBt6.js → Audit-Bah1XDR5.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-hIuyhmJ7.js → BackgroundAgents-BPZA0B5d.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-B6ohdgPQ.js → Backup-BMydWFiM.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BzgYCw6h.js → BaseInput-lhopFSZz.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CwahvZU1.js → Chat-Bbbs5BP1.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-B-1IZ_k8.js → ChatBubbleRenderer-BIAfOKCj.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-26EDP2x0.js → Checkbox-CLEMA9J4.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DYLw1Zvd.js → Codegen-deCSwmnK.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DH_zAvD-.js → Col-CH-n8haq.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DURrTXJu.js → Community-DJuAl6dw.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CQyBqN0p.js → Compact-BMyLBmgs.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-a0DfgKHD.js → Compliance-BK75Ss93.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-C6fY_5Ke.js → Cowork-5XbJwKYq.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DOwmpbTe.js → Cron-Ezgir7IZ.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DEPOIEUs.js → Crosschain-CKuMoYA7.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DaKc-CEV.js → DID-CxS244cH.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-C-0KLXq5.js → Dashboard-wrUKrsJy.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CnsDukzr.js → Dropdown-A6I91Z2J.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Bi_ACD4N.js → EmailListRenderer-BofaiRYY.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BZASpnx3.js → FamilyGuardDashboard-D3eeNA64.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-43oTtzyp.js → Federation-DzK-3AjS.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-oyVadnkZ.js → FormItemContext-Dm6mYA8b.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Cwvfd6TZ.js → GenericCardRenderer-BeWL-Q1b.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Cgi2wvU0.js → Git-CtGCxg9p.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-l8TWDKZ0.js → Governance-DZvFB46G.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Dc8j6TaO.js → Inference-ntqp4cmw.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DgcO-Fwz.js → KnowledgeGraph-CrLYbqLe.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DjiZpiwa.js → Logs-CKA1PRhC.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-DtwoKxDJ.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BeDn1hFc.js → Marketplace-B8aPsXYv.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-PGdVfWK6.js → McpTools-DQOgNdGS.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-e76lY3a1.js → Memory-i9IK9B0M.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DKlQqot_.js → MobileBridge-BynYvpv9.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-C4g6WOEr.js → MobileProjects-DixivGuQ.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-1j3NpRQq.js → Mtc-OWPvWVNr.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-DgwiVd1C.js → MtcAudit-Cqpg5C4A.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-Dzg5lZZY.js → Multisig-CT5kMJIZ.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-D7GYhst-.js → NLProgramming-BXD0Dz0h.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Blcfvaic.js → Notes-T1w5p8N5.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Dc4Y6vfr.js → NotificationSettings-DFCThpKJ.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BJOuM0Kl.js +1 -0
- package/src/assets/web-panel/assets/{Organization-DBXq024L.js → Organization-CSFcw86q.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CFPbdx1Y.js → Overflow-BxVMuLTk.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-w7z61NR5.js → P2P-B8qxY_yX.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-B7eGGZOa.js → PdhVaultBrowser-Bcq27Exy.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-Df5Cl50D.js → Permissions-CGBvJVXP.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-C-We7RF6.js → PersonalDataHub-DM9IEeJ3.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-B6fHPnKy.js → Pipeline-wEskx2-p.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B85BdiIc.js → Privacy-DIA02lLJ.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CcOyazSe.js → ProjectInit-uMXlNLHV.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BE53KfuU.js → ProjectSettings-Sy3NOtt7.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DOvMOoe_.js → Projects-xFjfOkQM.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-5l7sT9lq.js → Providers-CNyOpbg8.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DE37RXy4.js → QrScannerModal-Dk1UVRwI.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BGmGCo7j.js → QuickAsk-B-UqLoLG.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BFdx8Nrm.js → Recommend-CiTBVZvN.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C1w-j_hB.js → RemoteSession-BhABd516.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BOvn-10y.js → Reputation-Dq1xTCcN.js} +1 -1
- package/src/assets/web-panel/assets/{Row-db-a1YO9.js → Row-DbFc4sc7.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DeS43ZGM.js → RssFeed-D7IBkkLm.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BaQYNfeb.js → Search-BVyMlGoa.js} +1 -1
- package/src/assets/web-panel/assets/{Security-yt1ESHDg.js → Security-BzMm9Be_.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BcNqPUsS.js → Services-Dfk0kied.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-X3ceUQWf.js → Skeleton-C8B5mUit.js} +1 -1
- package/src/assets/web-panel/assets/Skills-BNTPoJn0.js +1 -0
- package/src/assets/web-panel/assets/{Sla-C_fdfbuJ.js → Sla-gnFelKfT.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CDqohXXQ.js → SpeechSettings-B12gCQkK.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-B25maYEY.js → SyncSettings-CHWk9cpj.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-B_zs9jkJ.js +1 -0
- package/src/assets/web-panel/assets/{Templates-DUH0iuc4.js → Templates-PjdppQrI.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DrvhHf9T.js → Tenant-CH3aARxE.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-C2lJVYHA.js → Terminal-BqqrYyg3.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-C1Hf_jYn.js → TimelineRenderer-Dx9soq9f.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DniS1NpS.js → Tokens-C66KpDDZ.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-4jrcb8oA.js → Trigger-DHxgGsap.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DiBte55w.js → Trust-3cct4QxN.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CZRtc4VB.js → UkeySign-Cs6UfDEH.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BtYGJDmg.js → VideoEditing-jbu2EAQa.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-D1nrTm7H.js → Wallet-BIykFB9D.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DI91acpx.js → WebAuthn-DFgqKmHG.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-BtQjI50d.js → WorkflowEditor-CNg5bBiH.js} +1 -1
- package/src/assets/web-panel/assets/{chat-I4A-f4FC.js → chat-B-6wzojE.js} +1 -1
- package/src/assets/web-panel/assets/{colors-QZkDkFVt.js → colors-BHAq7y9y.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item--FtCdzpr.js → compact-item-CGJnSTnu.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CaIKIKxP.js → createContext-Cx0ZF512.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CNq_RRw4.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DLYUII6-.js → hasIn-DibIw3z7.js} +1 -1
- package/src/assets/web-panel/assets/{index-OQPIS_am.js → index-0NWSa3Xq.js} +1 -1
- package/src/assets/web-panel/assets/{index-3rVtgqa2.js → index-746-mNhw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxPoPofq.js → index-Aaeca8o4.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4CXFU-w.js → index-B1jAbIxx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLGJmgmx.js → index-B28jdxwR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CL4c5D6G.js → index-B2rOMBeN.js} +1 -1
- package/src/assets/web-panel/assets/{index-vfiDxdN2.js → index-BAoFiV3n.js} +1 -1
- package/src/assets/web-panel/assets/{index-Du2wUavy.js → index-BCtW4F8-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DAuf8r5o.js → index-BEXM3qSV.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGWSmE3G.js → index-BFTGp-aF.js} +1 -1
- package/src/assets/web-panel/assets/{index-eTqL91aV.js → index-BJhS6i02.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQ7OBHka.js → index-BO-XxBD5.js} +1 -1
- package/src/assets/web-panel/assets/{index-hfiZ-i2b.js → index-BR2BiNtx.js} +1 -1
- package/src/assets/web-panel/assets/{index-CBtII-nl.js → index-BRflLAdr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ch0IFpGy.js → index-BTWb3sbj.js} +1 -1
- package/src/assets/web-panel/assets/{index-B65QxJaG.js → index-BZ7GbrgF.js} +1 -1
- package/src/assets/web-panel/assets/index-B_H-RZ9i.js +1 -0
- package/src/assets/web-panel/assets/{index-BebTT7iR.js → index-BiMLsIwd.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ygi2rYL1.js → index-Bmqjip63.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGLHSCHM.js → index-BoySHSgZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJrd_CR3.js → index-Bz3J0eVB.js} +1 -1
- package/src/assets/web-panel/assets/{index-DK2r6Bv7.js → index-C6M2yHVE.js} +1 -1
- package/src/assets/web-panel/assets/{index-yayaayEA.js → index-CH2A_Fdn.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ooo_5jHo.js → index-CUX9mq3M.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2xETCkj.js → index-CY2E4Y99.js} +1 -1
- package/src/assets/web-panel/assets/index-CkH9UQQL.js +1 -0
- package/src/assets/web-panel/assets/{index-CisYjy88.js → index-CoKu7Q9r.js} +1 -1
- package/src/assets/web-panel/assets/{index-DA_TPdzX.js → index-CrjkH_QU.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bs0qPPFb.js → index-Cuity3gy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkJgWW_L.js → index-CwcWpW1q.js} +1 -1
- package/src/assets/web-panel/assets/{index-Deg2S9ZJ.js → index-Cz5ety6j.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXaeZCRP.js → index-DXNcm44v.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVitI3UQ.js → index-DjCJIttz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgMm85BY.js → index-DnsVRqCa.js} +1 -1
- package/src/assets/web-panel/assets/{index-21I79XSf.js → index-SbQacweG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcgtvJbC.js → index-g0V_mLQ9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvdwNnHC.js → index-puIs3mTs.js} +3 -3
- package/src/assets/web-panel/assets/{index-0DYBQ-6e.js → index-sI_1E0co.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3PnIEOH.js → index-woOjtit-.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DDkTvw8k.js → initDefaultProps-Gbl9QOqg.js} +1 -1
- package/src/assets/web-panel/assets/{motion-fkr96tN5.js → motion-BHh6ZzZu.js} +1 -1
- package/src/assets/web-panel/assets/{move-CGBV0SV2.js → move-mX6Rwrd1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DAsCliFt.js → mtc-parser-CuzZa_kI.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BM8PMmKN.js → omit-DjANYkpi.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-C6SdWoTK.js → pickAttrs-C_3v40wY.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BG0EVu-d.js → placementArrow-L2HgRede.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-L10IvfYC.js → responsiveObserve-DIkGMHyT.js} +1 -1
- package/src/assets/web-panel/assets/{slide-TddIkbrn.js → slide-C1hp5tD0.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-FlveF2Yn.js → statusUtils-DH1W2uFA.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BrXRyebg.js → styleChecker-dEaKQcv2.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-B7_oiDXZ.js → useFlexGapSupport-CD7j1zxt.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CQYeRYWb.js → useFs-C8jXMvEM.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DspIkhVi.js → usePersonalDataHub-C4r-8mys.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BCwmwMAW.js → vnode-DDuI6wgY.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B0cEGluV.js → zoom-Dd-DteT6.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/team.js +165 -53
- package/src/data/changelog.json +13 -0
- package/src/lib/agent-team/task-lease.js +718 -6
- package/src/lib/agent-team/team-graph-projection.js +303 -0
- package/src/lib/agent-team/team-message-bridge.js +83 -5
- package/src/lib/agent-team/team-message-tools.js +34 -1
- package/src/lib/agent-team/team-runner.js +1131 -9
- package/src/lib/agent-team/team-session-message-adapter.js +720 -0
- package/src/lib/app-server/cli-agent-kernel-adapter.js +1 -0
- package/src/lib/app-server/protocol.js +43 -0
- package/src/lib/app-server/server.js +8 -11
- package/src/lib/approval-grant-ledger.js +187 -0
- package/src/lib/session-message-fabric.js +265 -3
- package/src/runtime/headless-stream.js +184 -19
- package/src/assets/web-panel/assets/MarkdownRenderer-B5MFCi0N.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-dgM87XIn.js +0 -1
- package/src/assets/web-panel/assets/Skills-DmYeax6C.js +0 -1
- package/src/assets/web-panel/assets/Tasks-3ILwauYw.js +0 -1
- package/src/assets/web-panel/assets/devWarning-D7qVrkrO.js +0 -1
- package/src/assets/web-panel/assets/index-D9Y_ZwZn.js +0 -1
- package/src/assets/web-panel/assets/index-D_cPes4G.js +0 -1
|
@@ -53,6 +53,49 @@ export function validateAppServerMessage(value) {
|
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
const definitionSchemas = new Map();
|
|
57
|
+
|
|
58
|
+
/** Validate one canonical $defs value without maintaining a second hand-written union. */
|
|
59
|
+
export function validateAppServerDefinition(name, value) {
|
|
60
|
+
const definition = APP_SERVER_SCHEMA.$defs?.[name];
|
|
61
|
+
if (!definition) {
|
|
62
|
+
return Object.freeze({
|
|
63
|
+
ok: false,
|
|
64
|
+
errors: Object.freeze([
|
|
65
|
+
Object.freeze({
|
|
66
|
+
path: "#",
|
|
67
|
+
message: `unknown protocol definition ${String(name)}`,
|
|
68
|
+
}),
|
|
69
|
+
]),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
let definitionSchema = definitionSchemas.get(name);
|
|
73
|
+
if (!definitionSchema) {
|
|
74
|
+
definitionSchema = {
|
|
75
|
+
$schema: APP_SERVER_SCHEMA.$schema,
|
|
76
|
+
$ref: `#/$defs/${name}`,
|
|
77
|
+
$defs: APP_SERVER_SCHEMA.$defs,
|
|
78
|
+
};
|
|
79
|
+
definitionSchemas.set(name, definitionSchema);
|
|
80
|
+
}
|
|
81
|
+
const result = validate(value, definitionSchema);
|
|
82
|
+
return Object.freeze({
|
|
83
|
+
ok: result.valid,
|
|
84
|
+
errors: Object.freeze(
|
|
85
|
+
result.errors.map((error) =>
|
|
86
|
+
Object.freeze({
|
|
87
|
+
path: error.instancePath || error.schemaPath || "#",
|
|
88
|
+
message: error.message || error.code || "invalid value",
|
|
89
|
+
}),
|
|
90
|
+
),
|
|
91
|
+
),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function validateApprovalDecision(value) {
|
|
96
|
+
return validateAppServerDefinition("ApprovalDecision", value);
|
|
97
|
+
}
|
|
98
|
+
|
|
56
99
|
export function rpcResult(id, result) {
|
|
57
100
|
return { jsonrpc: "2.0", id, result: result ?? null };
|
|
58
101
|
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
rpcError,
|
|
13
13
|
rpcNotification,
|
|
14
14
|
rpcResult,
|
|
15
|
+
validateApprovalDecision,
|
|
15
16
|
validateAppServerMessage,
|
|
16
17
|
} from "./protocol.js";
|
|
17
18
|
|
|
@@ -746,28 +747,24 @@ export class CcAppServer {
|
|
|
746
747
|
? event.risk
|
|
747
748
|
: "high",
|
|
748
749
|
reason: event.reason || "The Agent Kernel requires approval",
|
|
749
|
-
requestedPermissions:
|
|
750
|
+
requestedPermissions: Array.isArray(event.requested_permissions)
|
|
751
|
+
? event.requested_permissions
|
|
752
|
+
: [],
|
|
750
753
|
};
|
|
751
754
|
}
|
|
752
755
|
|
|
753
756
|
async _requestApproval(turn, event) {
|
|
754
757
|
const request = this._approvalRequest(turn, event);
|
|
755
758
|
const result = await this._requestClient("approval/decide", { request });
|
|
756
|
-
const
|
|
757
|
-
|
|
758
|
-
"acceptOnce",
|
|
759
|
-
"acceptForTurn",
|
|
760
|
-
"acceptForSession",
|
|
761
|
-
"decline",
|
|
762
|
-
"cancel",
|
|
763
|
-
]);
|
|
764
|
-
if (!allowed.has(decision.kind)) {
|
|
759
|
+
const validation = validateApprovalDecision(result);
|
|
760
|
+
if (!validation.ok) {
|
|
765
761
|
throw new JsonRpcError(
|
|
766
762
|
JSON_RPC_ERROR.INVALID_PARAMS,
|
|
767
763
|
"client returned an invalid approval decision",
|
|
764
|
+
{ errors: validation.errors },
|
|
768
765
|
);
|
|
769
766
|
}
|
|
770
|
-
return
|
|
767
|
+
return result;
|
|
771
768
|
}
|
|
772
769
|
|
|
773
770
|
async _completeTurn(turn, status, result) {
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { validateAppServerDefinition } from "./app-server/protocol.js";
|
|
3
|
+
|
|
4
|
+
export const APPROVAL_GRANTS_EVENT = "approval_grants";
|
|
5
|
+
export const APPROVAL_GRANTS_SCHEMA = "chainlesschain.approval-grants/v1";
|
|
6
|
+
|
|
7
|
+
function stableValue(value) {
|
|
8
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
9
|
+
if (!value || typeof value !== "object") return value;
|
|
10
|
+
return Object.fromEntries(
|
|
11
|
+
Object.keys(value)
|
|
12
|
+
.filter((key) => value[key] !== undefined)
|
|
13
|
+
.sort()
|
|
14
|
+
.map((key) => [key, stableValue(value[key])]),
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function stableJson(value) {
|
|
19
|
+
return JSON.stringify(stableValue(value));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function boundedScope(value) {
|
|
23
|
+
const serialized = stableJson(value);
|
|
24
|
+
if (serialized.length <= 1024) return serialized;
|
|
25
|
+
return `sha256:${createHash("sha256").update(serialized).digest("hex")}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function boundedCapability(tool) {
|
|
29
|
+
const candidate = `tool:${String(tool || "unknown")}`;
|
|
30
|
+
if (candidate.length <= 128) return candidate;
|
|
31
|
+
return `tool:sha256:${createHash("sha256").update(candidate).digest("hex")}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function approvalPermissionForContext(ctx = {}, { cwd = null } = {}) {
|
|
35
|
+
const tool = ctx.tool || ctx.toolName || ctx.name || "unknown";
|
|
36
|
+
const args =
|
|
37
|
+
ctx.args ?? (ctx.command != null ? { command: ctx.command } : null);
|
|
38
|
+
const policy = ctx.rule || ctx.riskLevel || ctx.risk || null;
|
|
39
|
+
return Object.freeze({
|
|
40
|
+
capability: boundedCapability(tool),
|
|
41
|
+
scope: boundedScope({ cwd: cwd || ctx.cwd || null, args, policy }),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function permissionKey(permission) {
|
|
46
|
+
return `${permission.capability}\0${permission.scope}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function livePermission(permission, now) {
|
|
50
|
+
const validation = validateAppServerDefinition("PermissionGrant", permission);
|
|
51
|
+
if (!validation.ok) return false;
|
|
52
|
+
return !permission.expiresAt || Date.parse(permission.expiresAt) > now;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function samePermission(left, right) {
|
|
56
|
+
return permissionKey(left) === permissionKey(right);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizePersistedEntry(entry, now) {
|
|
60
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
|
|
61
|
+
if (!livePermission(entry.permission, now)) return null;
|
|
62
|
+
if (
|
|
63
|
+
typeof entry.binding !== "string" ||
|
|
64
|
+
!entry.binding ||
|
|
65
|
+
typeof entry.grantedAt !== "string" ||
|
|
66
|
+
Number.isNaN(Date.parse(entry.grantedAt))
|
|
67
|
+
) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return Object.freeze({
|
|
71
|
+
permission: Object.freeze({ ...entry.permission }),
|
|
72
|
+
binding: entry.binding,
|
|
73
|
+
grantedAt: entry.grantedAt,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class ApprovalGrantLedger {
|
|
78
|
+
constructor({ sessionId, now = () => Date.now() } = {}) {
|
|
79
|
+
if (!sessionId) throw new TypeError("approval grant sessionId is required");
|
|
80
|
+
this.sessionId = String(sessionId);
|
|
81
|
+
this.now = now;
|
|
82
|
+
this.turnId = null;
|
|
83
|
+
this.turnGrants = new Map();
|
|
84
|
+
this.sessionGrants = new Map();
|
|
85
|
+
this.revision = 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
beginTurn(turnId) {
|
|
89
|
+
this.turnId = String(turnId);
|
|
90
|
+
this.turnGrants.clear();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
allows(permission) {
|
|
94
|
+
const now = this.now();
|
|
95
|
+
const key = permissionKey(permission);
|
|
96
|
+
for (const grants of [this.turnGrants, this.sessionGrants]) {
|
|
97
|
+
const entry = grants.get(key);
|
|
98
|
+
if (!entry) continue;
|
|
99
|
+
if (livePermission(entry.permission, now)) return true;
|
|
100
|
+
grants.delete(key);
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
applyDecision(decision, requiredPermission, binding) {
|
|
106
|
+
if (
|
|
107
|
+
decision?.kind !== "acceptForTurn" &&
|
|
108
|
+
decision?.kind !== "acceptForSession"
|
|
109
|
+
) {
|
|
110
|
+
return Object.freeze({
|
|
111
|
+
decision,
|
|
112
|
+
granted: Object.freeze([]),
|
|
113
|
+
persistedScope: false,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const candidates =
|
|
117
|
+
decision.permissions === undefined
|
|
118
|
+
? [requiredPermission]
|
|
119
|
+
: decision.permissions;
|
|
120
|
+
const target =
|
|
121
|
+
decision.kind === "acceptForSession"
|
|
122
|
+
? this.sessionGrants
|
|
123
|
+
: this.turnGrants;
|
|
124
|
+
const granted = candidates
|
|
125
|
+
.filter(
|
|
126
|
+
(permission) =>
|
|
127
|
+
livePermission(permission, this.now()) &&
|
|
128
|
+
samePermission(permission, requiredPermission) &&
|
|
129
|
+
(target.has(permissionKey(permission)) || target.size < 64),
|
|
130
|
+
)
|
|
131
|
+
.slice(0, 1)
|
|
132
|
+
.map((permission) => Object.freeze({ ...permission }));
|
|
133
|
+
const grantedAt = new Date(this.now()).toISOString();
|
|
134
|
+
for (const permission of granted) {
|
|
135
|
+
target.set(
|
|
136
|
+
permissionKey(permission),
|
|
137
|
+
Object.freeze({ permission, binding, grantedAt }),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (granted.length) this.revision += 1;
|
|
141
|
+
return Object.freeze({
|
|
142
|
+
decision: Object.freeze({ ...decision, permissions: granted }),
|
|
143
|
+
granted: Object.freeze(granted),
|
|
144
|
+
persistedScope:
|
|
145
|
+
decision.kind === "acceptForSession" && granted.length > 0,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
toJSON() {
|
|
150
|
+
return {
|
|
151
|
+
schema: APPROVAL_GRANTS_SCHEMA,
|
|
152
|
+
sessionId: this.sessionId,
|
|
153
|
+
revision: this.revision,
|
|
154
|
+
grants: [...this.sessionGrants.values()].map((entry) => ({
|
|
155
|
+
permission: { ...entry.permission },
|
|
156
|
+
binding: entry.binding,
|
|
157
|
+
grantedAt: entry.grantedAt,
|
|
158
|
+
})),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
static fromJSON(value, { sessionId, now = () => Date.now() } = {}) {
|
|
163
|
+
const ledger = new ApprovalGrantLedger({ sessionId, now });
|
|
164
|
+
if (
|
|
165
|
+
!value ||
|
|
166
|
+
typeof value !== "object" ||
|
|
167
|
+
Array.isArray(value) ||
|
|
168
|
+
value.schema !== APPROVAL_GRANTS_SCHEMA ||
|
|
169
|
+
value.sessionId !== ledger.sessionId ||
|
|
170
|
+
!Number.isSafeInteger(value.revision) ||
|
|
171
|
+
value.revision < 0 ||
|
|
172
|
+
!Array.isArray(value.grants) ||
|
|
173
|
+
value.grants.length > 64
|
|
174
|
+
) {
|
|
175
|
+
throw new TypeError("invalid persisted approval grant ledger");
|
|
176
|
+
}
|
|
177
|
+
for (const candidate of value.grants) {
|
|
178
|
+
const entry = normalizePersistedEntry(candidate, now());
|
|
179
|
+
if (!entry) {
|
|
180
|
+
throw new TypeError("invalid persisted approval grant entry");
|
|
181
|
+
}
|
|
182
|
+
ledger.sessionGrants.set(permissionKey(entry.permission), entry);
|
|
183
|
+
}
|
|
184
|
+
ledger.revision = value.revision;
|
|
185
|
+
return ledger;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -38,11 +38,15 @@ export const SESSION_MESSAGE_RECEIPT_STATUSES = Object.freeze([
|
|
|
38
38
|
"full",
|
|
39
39
|
"rate_limited",
|
|
40
40
|
"expired",
|
|
41
|
+
"read",
|
|
42
|
+
"processed",
|
|
43
|
+
"dead_letter",
|
|
41
44
|
]);
|
|
42
45
|
|
|
43
46
|
export const SESSION_MESSAGE_FABRIC_LIMITS = Object.freeze({
|
|
44
47
|
maxMessageBytes: 256 * 1024,
|
|
45
48
|
maxPendingPerRecipient: 100,
|
|
49
|
+
maxPendingBytes: 32 * 1024 * 1024,
|
|
46
50
|
maxReceiptHistory: 5_000,
|
|
47
51
|
maxMessagesPerSenderWindow: 120,
|
|
48
52
|
senderRateWindowMs: 60_000,
|
|
@@ -62,9 +66,21 @@ export const SESSION_MESSAGE_FABRIC_ERROR_CODES = Object.freeze({
|
|
|
62
66
|
});
|
|
63
67
|
|
|
64
68
|
const PENDING_STATUSES = new Set(["held", "delivered"]);
|
|
69
|
+
const MESSAGE_STATUSES = new Set([
|
|
70
|
+
"delivered",
|
|
71
|
+
"held",
|
|
72
|
+
"refused",
|
|
73
|
+
"full",
|
|
74
|
+
"rate_limited",
|
|
75
|
+
"expired",
|
|
76
|
+
]);
|
|
77
|
+
const PROCESSING_STATUSES = new Set(["read", "processed", "dead_letter"]);
|
|
78
|
+
const TERMINAL_PROCESSING_STATUSES = new Set(["processed", "dead_letter"]);
|
|
65
79
|
const RECEIPT_STATUSES = new Set(SESSION_MESSAGE_RECEIPT_STATUSES);
|
|
66
80
|
const POLICIES = new Set(SESSION_MESSAGE_POLICIES);
|
|
67
81
|
const NAME_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/u;
|
|
82
|
+
// Bounded ids explicitly reject ASCII control characters.
|
|
83
|
+
// eslint-disable-next-line no-control-regex
|
|
68
84
|
const ID_PATTERN = /^[^\u0000-\u001f\u007f]{1,256}$/u;
|
|
69
85
|
const MESSAGE_ID_PATTERN = /^[a-zA-Z0-9._:-]{1,128}$/u;
|
|
70
86
|
|
|
@@ -324,6 +340,11 @@ function updateReceipt(state, message, status, reason, now) {
|
|
|
324
340
|
updatedAt: now,
|
|
325
341
|
idleNotifiedAt: null,
|
|
326
342
|
readAt: null,
|
|
343
|
+
processedAt: null,
|
|
344
|
+
deadLetteredAt: null,
|
|
345
|
+
deliveryCount: 0,
|
|
346
|
+
consumerKey: null,
|
|
347
|
+
recipientAttempt: null,
|
|
327
348
|
};
|
|
328
349
|
state.receipts.push(receipt);
|
|
329
350
|
} else {
|
|
@@ -402,6 +423,7 @@ function compactSettledMessages(state) {
|
|
|
402
423
|
state.messages = state.messages.filter((message) => {
|
|
403
424
|
if (!PENDING_STATUSES.has(message.status)) return false;
|
|
404
425
|
if (message.acknowledgedAt == null) return true;
|
|
426
|
+
if (message.retainAfterAck === true) return true;
|
|
405
427
|
if (message.notifyWhenIdle !== true) return false;
|
|
406
428
|
const receipt = receiptFor(
|
|
407
429
|
state,
|
|
@@ -413,6 +435,30 @@ function compactSettledMessages(state) {
|
|
|
413
435
|
return state.messages.length !== before;
|
|
414
436
|
}
|
|
415
437
|
|
|
438
|
+
function trimRetainedSettledMessages(state, limit) {
|
|
439
|
+
const retained = state.messages
|
|
440
|
+
.filter(
|
|
441
|
+
(message) =>
|
|
442
|
+
message.retainAfterAck === true && message.acknowledgedAt != null,
|
|
443
|
+
)
|
|
444
|
+
.sort(
|
|
445
|
+
(left, right) =>
|
|
446
|
+
left.acknowledgedAt - right.acknowledgedAt ||
|
|
447
|
+
left.createdAt - right.createdAt ||
|
|
448
|
+
left.messageId.localeCompare(right.messageId),
|
|
449
|
+
);
|
|
450
|
+
let changed = false;
|
|
451
|
+
while (retained.length > limit) {
|
|
452
|
+
const target = retained.shift();
|
|
453
|
+
const index = state.messages.indexOf(target);
|
|
454
|
+
if (index >= 0) {
|
|
455
|
+
state.messages.splice(index, 1);
|
|
456
|
+
changed = true;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return changed;
|
|
460
|
+
}
|
|
461
|
+
|
|
416
462
|
function applyExpectedMessage(state, channel, message, recipient, now) {
|
|
417
463
|
if (recipient.policy === "refuse") {
|
|
418
464
|
updateReceipt(state, message, "refused", "policy_refuse", now);
|
|
@@ -556,7 +602,7 @@ function validateState(state) {
|
|
|
556
602
|
!MESSAGE_ID_PATTERN.test(message?.messageId || "") ||
|
|
557
603
|
!Number.isSafeInteger(message?.sequence) ||
|
|
558
604
|
message.sequence < 1 ||
|
|
559
|
-
!
|
|
605
|
+
!MESSAGE_STATUSES.has(message?.status) ||
|
|
560
606
|
!channelIds.has(message?.channelId) ||
|
|
561
607
|
typeof message?.payloadDigest !== "string"
|
|
562
608
|
) {
|
|
@@ -572,6 +618,13 @@ function validateState(state) {
|
|
|
572
618
|
) {
|
|
573
619
|
fail("invalid receipt");
|
|
574
620
|
}
|
|
621
|
+
if (
|
|
622
|
+
receipt.deliveryCount != null &&
|
|
623
|
+
(!Number.isSafeInteger(receipt.deliveryCount) ||
|
|
624
|
+
receipt.deliveryCount < 0)
|
|
625
|
+
) {
|
|
626
|
+
fail("invalid receipt delivery count");
|
|
627
|
+
}
|
|
575
628
|
}
|
|
576
629
|
const bucketAuthorities = new Set();
|
|
577
630
|
for (const bucket of state.rateBuckets || []) {
|
|
@@ -606,6 +659,7 @@ export class SessionMessageFabric {
|
|
|
606
659
|
lockTimeoutMs = 10_000,
|
|
607
660
|
maxMessageBytes = SESSION_MESSAGE_FABRIC_LIMITS.maxMessageBytes,
|
|
608
661
|
maxPendingPerRecipient = SESSION_MESSAGE_FABRIC_LIMITS.maxPendingPerRecipient,
|
|
662
|
+
maxPendingBytes = SESSION_MESSAGE_FABRIC_LIMITS.maxPendingBytes,
|
|
609
663
|
maxReceiptHistory = SESSION_MESSAGE_FABRIC_LIMITS.maxReceiptHistory,
|
|
610
664
|
maxMessagesPerSenderWindow = SESSION_MESSAGE_FABRIC_LIMITS.maxMessagesPerSenderWindow,
|
|
611
665
|
senderRateWindowMs = SESSION_MESSAGE_FABRIC_LIMITS.senderRateWindowMs,
|
|
@@ -636,6 +690,10 @@ export class SessionMessageFabric {
|
|
|
636
690
|
maxPendingPerRecipient,
|
|
637
691
|
SESSION_MESSAGE_FABRIC_LIMITS.maxPendingPerRecipient,
|
|
638
692
|
),
|
|
693
|
+
maxPendingBytes: positiveInteger(
|
|
694
|
+
maxPendingBytes,
|
|
695
|
+
SESSION_MESSAGE_FABRIC_LIMITS.maxPendingBytes,
|
|
696
|
+
),
|
|
639
697
|
maxReceiptHistory: positiveInteger(
|
|
640
698
|
maxReceiptHistory,
|
|
641
699
|
SESSION_MESSAGE_FABRIC_LIMITS.maxReceiptHistory,
|
|
@@ -770,6 +828,10 @@ export class SessionMessageFabric {
|
|
|
770
828
|
);
|
|
771
829
|
const outcome = mutator(state, now) || { value: undefined };
|
|
772
830
|
const compacted = compactSettledMessages(state);
|
|
831
|
+
const trimmedSettled = trimRetainedSettledMessages(
|
|
832
|
+
state,
|
|
833
|
+
this._limits.maxReceiptHistory,
|
|
834
|
+
);
|
|
773
835
|
const trimmed = trimReceiptHistory(
|
|
774
836
|
state,
|
|
775
837
|
this._limits.maxReceiptHistory,
|
|
@@ -778,6 +840,7 @@ export class SessionMessageFabric {
|
|
|
778
840
|
swept ||
|
|
779
841
|
prunedRateBuckets ||
|
|
780
842
|
compacted ||
|
|
843
|
+
trimmedSettled ||
|
|
781
844
|
trimmed ||
|
|
782
845
|
outcome.changed === true
|
|
783
846
|
) {
|
|
@@ -985,6 +1048,7 @@ export class SessionMessageFabric {
|
|
|
985
1048
|
sequence = null,
|
|
986
1049
|
ttlMs = this._limits.defaultTtlMs,
|
|
987
1050
|
notifyWhenIdle = false,
|
|
1051
|
+
retainAfterAck = false,
|
|
988
1052
|
} = {}) {
|
|
989
1053
|
const id = messageId(requestedMessageId, this._createId);
|
|
990
1054
|
const cleanSubject = subject == null ? null : String(subject).slice(0, 512);
|
|
@@ -1108,6 +1172,7 @@ export class SessionMessageFabric {
|
|
|
1108
1172
|
status: "held",
|
|
1109
1173
|
reason: "out_of_order",
|
|
1110
1174
|
notifyWhenIdle: notifyWhenIdle === true,
|
|
1175
|
+
retainAfterAck: retainAfterAck === true,
|
|
1111
1176
|
createdAt: now,
|
|
1112
1177
|
updatedAt: now,
|
|
1113
1178
|
deliveredAt: null,
|
|
@@ -1137,10 +1202,17 @@ export class SessionMessageFabric {
|
|
|
1137
1202
|
value: clone(receiptFor(state, sender.authorityId, id)),
|
|
1138
1203
|
};
|
|
1139
1204
|
}
|
|
1205
|
+
const pendingBytes = state.messages
|
|
1206
|
+
.filter(
|
|
1207
|
+
(entry) =>
|
|
1208
|
+
PENDING_STATUSES.has(entry.status) && entry.acknowledgedAt == null,
|
|
1209
|
+
)
|
|
1210
|
+
.reduce((sum, entry) => sum + (entry.messageBytes || 0), 0);
|
|
1140
1211
|
if (
|
|
1141
1212
|
recipient.policy !== "refuse" &&
|
|
1142
|
-
pendingForRecipient(state, recipient).length >=
|
|
1143
|
-
this._limits.maxPendingPerRecipient
|
|
1213
|
+
(pendingForRecipient(state, recipient).length >=
|
|
1214
|
+
this._limits.maxPendingPerRecipient ||
|
|
1215
|
+
pendingBytes + messageBytes > this._limits.maxPendingBytes)
|
|
1144
1216
|
) {
|
|
1145
1217
|
updateReceipt(state, message, "full", "queue_capacity", now);
|
|
1146
1218
|
return {
|
|
@@ -1194,6 +1266,196 @@ export class SessionMessageFabric {
|
|
|
1194
1266
|
});
|
|
1195
1267
|
}
|
|
1196
1268
|
|
|
1269
|
+
/**
|
|
1270
|
+
* Deliver an at-least-once batch without retiring it. This is deliberately
|
|
1271
|
+
* separate from `inbox(..., { acknowledge: true })`, whose historical API
|
|
1272
|
+
* means "read and remove". Processing callers must explicitly settle each
|
|
1273
|
+
* message with `acknowledge()`.
|
|
1274
|
+
*/
|
|
1275
|
+
receive(selector, { limit = 100, markRead = false } = {}) {
|
|
1276
|
+
const boundedLimit = positiveInteger(limit, 100, 100);
|
|
1277
|
+
return this._transaction((state, now) => {
|
|
1278
|
+
const endpoint = requireEndpoint(state, selector);
|
|
1279
|
+
const messages = pendingForRecipient(state, endpoint)
|
|
1280
|
+
.filter((message) => message.status === "delivered")
|
|
1281
|
+
.sort(
|
|
1282
|
+
(left, right) =>
|
|
1283
|
+
(left.senderAuthority === right.senderAuthority
|
|
1284
|
+
? left.sequence - right.sequence
|
|
1285
|
+
: left.createdAt - right.createdAt) ||
|
|
1286
|
+
left.senderAuthority.localeCompare(right.senderAuthority),
|
|
1287
|
+
)
|
|
1288
|
+
.slice(0, boundedLimit);
|
|
1289
|
+
for (const message of messages) {
|
|
1290
|
+
const receipt = receiptFor(
|
|
1291
|
+
state,
|
|
1292
|
+
message.senderAuthority,
|
|
1293
|
+
message.messageId,
|
|
1294
|
+
);
|
|
1295
|
+
if (!receipt) continue;
|
|
1296
|
+
receipt.deliveryCount = (receipt.deliveryCount || 0) + 1;
|
|
1297
|
+
receipt.deliveredAt ||= message.deliveredAt || now;
|
|
1298
|
+
if (markRead) {
|
|
1299
|
+
receipt.status = "read";
|
|
1300
|
+
receipt.readAt ||= now;
|
|
1301
|
+
} else if (!PROCESSING_STATUSES.has(receipt.status)) {
|
|
1302
|
+
receipt.status = "delivered";
|
|
1303
|
+
}
|
|
1304
|
+
receipt.updatedAt = now;
|
|
1305
|
+
}
|
|
1306
|
+
return {
|
|
1307
|
+
changed: messages.length > 0,
|
|
1308
|
+
value: clone(
|
|
1309
|
+
messages.map((message) => ({
|
|
1310
|
+
...message,
|
|
1311
|
+
delivery: receiptFor(
|
|
1312
|
+
state,
|
|
1313
|
+
message.senderAuthority,
|
|
1314
|
+
message.messageId,
|
|
1315
|
+
),
|
|
1316
|
+
})),
|
|
1317
|
+
),
|
|
1318
|
+
};
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/** Persist read/processed/dead-letter evidence for explicit physical ids. */
|
|
1323
|
+
acknowledge(
|
|
1324
|
+
selector,
|
|
1325
|
+
{
|
|
1326
|
+
messageIds = [],
|
|
1327
|
+
consumerKey,
|
|
1328
|
+
status = "processed",
|
|
1329
|
+
reason = null,
|
|
1330
|
+
recipientAttempt = null,
|
|
1331
|
+
} = {},
|
|
1332
|
+
) {
|
|
1333
|
+
if (
|
|
1334
|
+
!Array.isArray(messageIds) ||
|
|
1335
|
+
messageIds.length === 0 ||
|
|
1336
|
+
messageIds.length > 100 ||
|
|
1337
|
+
messageIds.some((id) => !MESSAGE_ID_PATTERN.test(String(id || ""))) ||
|
|
1338
|
+
new Set(messageIds.map(String)).size !== messageIds.length
|
|
1339
|
+
) {
|
|
1340
|
+
throw fabricError(
|
|
1341
|
+
SESSION_MESSAGE_FABRIC_ERROR_CODES.INVALID_ARGUMENT,
|
|
1342
|
+
"messageIds must contain 1-100 unique message identifiers",
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
if (!PROCESSING_STATUSES.has(status)) {
|
|
1346
|
+
throw fabricError(
|
|
1347
|
+
SESSION_MESSAGE_FABRIC_ERROR_CODES.INVALID_ARGUMENT,
|
|
1348
|
+
"processing status must be read, processed or dead_letter",
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
const safeConsumerKey = boundedId(consumerKey, "consumerKey");
|
|
1352
|
+
const safeAttempt =
|
|
1353
|
+
recipientAttempt == null ? null : jsonBody(recipientAttempt).value;
|
|
1354
|
+
const safeReason = String(reason || "poison_message").slice(0, 1024);
|
|
1355
|
+
const ids = messageIds.map(String);
|
|
1356
|
+
return this._transaction((state, now) => {
|
|
1357
|
+
const endpoint = requireEndpoint(state, selector);
|
|
1358
|
+
const plans = ids.map((id) => {
|
|
1359
|
+
const receipt = state.receipts.find(
|
|
1360
|
+
(candidate) =>
|
|
1361
|
+
candidate.messageId === id &&
|
|
1362
|
+
candidate.recipientAuthority === endpoint.authorityId &&
|
|
1363
|
+
candidate.recipientEpoch === endpoint.epoch,
|
|
1364
|
+
);
|
|
1365
|
+
const message = state.messages.find(
|
|
1366
|
+
(candidate) =>
|
|
1367
|
+
candidate.messageId === id &&
|
|
1368
|
+
candidate.recipientAuthority === endpoint.authorityId &&
|
|
1369
|
+
candidate.recipientEpoch === endpoint.epoch,
|
|
1370
|
+
);
|
|
1371
|
+
if (
|
|
1372
|
+
!receipt ||
|
|
1373
|
+
(!message && !TERMINAL_PROCESSING_STATUSES.has(receipt.status))
|
|
1374
|
+
) {
|
|
1375
|
+
throw fabricError(
|
|
1376
|
+
SESSION_MESSAGE_FABRIC_ERROR_CODES.INVALID_ARGUMENT,
|
|
1377
|
+
`message is not addressed to the active recipient: ${id}`,
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
if (TERMINAL_PROCESSING_STATUSES.has(receipt.status)) {
|
|
1381
|
+
if (receipt.consumerKey !== safeConsumerKey) {
|
|
1382
|
+
throw fabricError(
|
|
1383
|
+
SESSION_MESSAGE_FABRIC_ERROR_CODES.MESSAGE_ID_CONFLICT,
|
|
1384
|
+
`message was settled by another consumer: ${id}`,
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
if (receipt.status !== status) {
|
|
1388
|
+
throw fabricError(
|
|
1389
|
+
SESSION_MESSAGE_FABRIC_ERROR_CODES.MESSAGE_ID_CONFLICT,
|
|
1390
|
+
`message already has terminal status ${receipt.status}: ${id}`,
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
return { receipt, message, replay: true };
|
|
1394
|
+
}
|
|
1395
|
+
return { receipt, message, replay: false };
|
|
1396
|
+
});
|
|
1397
|
+
const receipts = [];
|
|
1398
|
+
for (const plan of plans) {
|
|
1399
|
+
const { receipt, message, replay } = plan;
|
|
1400
|
+
if (replay) {
|
|
1401
|
+
receipts.push(clone(receipt));
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
receipt.status = status;
|
|
1405
|
+
receipt.consumerKey = safeConsumerKey;
|
|
1406
|
+
receipt.recipientAttempt = safeAttempt;
|
|
1407
|
+
receipt.updatedAt = now;
|
|
1408
|
+
if (status === "read") receipt.readAt ||= now;
|
|
1409
|
+
if (status === "processed") {
|
|
1410
|
+
receipt.readAt ||= now;
|
|
1411
|
+
receipt.processedAt ||= now;
|
|
1412
|
+
message.acknowledgedAt = now;
|
|
1413
|
+
}
|
|
1414
|
+
if (status === "dead_letter") {
|
|
1415
|
+
receipt.deadLetteredAt ||= now;
|
|
1416
|
+
receipt.reason = safeReason;
|
|
1417
|
+
message.acknowledgedAt = now;
|
|
1418
|
+
}
|
|
1419
|
+
receipts.push(clone(receipt));
|
|
1420
|
+
}
|
|
1421
|
+
return { changed: plans.some((plan) => !plan.replay), value: receipts };
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
/** Bounded retained history for authoritative adapters and audit reducers. */
|
|
1426
|
+
history() {
|
|
1427
|
+
return this._transaction((state) => ({
|
|
1428
|
+
value: clone(
|
|
1429
|
+
state.messages.sort(
|
|
1430
|
+
(left, right) =>
|
|
1431
|
+
left.createdAt - right.createdAt ||
|
|
1432
|
+
left.messageId.localeCompare(right.messageId),
|
|
1433
|
+
),
|
|
1434
|
+
),
|
|
1435
|
+
}));
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
/**
|
|
1439
|
+
* One-revision authority view for host adapters and deterministic reducers.
|
|
1440
|
+
* Unlike a sequence of projection/history/receipt reads, this cannot combine
|
|
1441
|
+
* fields from different concurrent commits.
|
|
1442
|
+
*/
|
|
1443
|
+
auditSnapshot() {
|
|
1444
|
+
return this._transaction((state) => ({
|
|
1445
|
+
value: {
|
|
1446
|
+
schema: SESSION_MESSAGE_FABRIC_SCHEMA,
|
|
1447
|
+
version: state.version,
|
|
1448
|
+
authority: "cli",
|
|
1449
|
+
revision: state.revision,
|
|
1450
|
+
digest: state.digest,
|
|
1451
|
+
limits: clone(this._limits),
|
|
1452
|
+
endpoints: clone(state.endpoints),
|
|
1453
|
+
messages: clone(state.messages),
|
|
1454
|
+
receipts: clone(state.receipts),
|
|
1455
|
+
},
|
|
1456
|
+
}));
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1197
1459
|
receipts(selector) {
|
|
1198
1460
|
return this._transaction((state) => {
|
|
1199
1461
|
const endpoint = requireEndpoint(state, selector);
|