sealbridge 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +499 -31
- package/README.md +529 -110
- package/dist/cli/dist/accept.d.ts +13 -0
- package/dist/cli/dist/accept.js +115 -0
- package/dist/cli/dist/accept.js.map +1 -0
- package/dist/cli/dist/approval-summary.d.ts +1 -1
- package/dist/cli/dist/board.d.ts +10 -9
- package/dist/cli/dist/board.js +62 -31
- package/dist/cli/dist/board.js.map +1 -1
- package/dist/cli/dist/command-identity.d.ts +6 -0
- package/dist/cli/dist/command-identity.js +13 -0
- package/dist/cli/dist/command-identity.js.map +1 -0
- package/dist/cli/dist/config.d.ts +3 -0
- package/dist/cli/dist/config.js +29 -0
- package/dist/cli/dist/config.js.map +1 -0
- package/dist/cli/dist/connections.d.ts +15 -0
- package/dist/cli/dist/connections.js +191 -0
- package/dist/cli/dist/connections.js.map +1 -0
- package/dist/cli/dist/contacts.d.ts +4 -3
- package/dist/cli/dist/contacts.js +39 -28
- package/dist/cli/dist/contacts.js.map +1 -1
- package/dist/cli/dist/doctor.d.ts +9 -0
- package/dist/cli/dist/doctor.js +46 -5
- package/dist/cli/dist/doctor.js.map +1 -1
- package/dist/cli/dist/error-messages.js +24 -0
- package/dist/cli/dist/error-messages.js.map +1 -1
- package/dist/cli/dist/index.js +54 -6
- package/dist/cli/dist/index.js.map +1 -1
- package/dist/cli/dist/invite-output.d.ts +14 -0
- package/dist/cli/dist/invite-output.js +66 -0
- package/dist/cli/dist/invite-output.js.map +1 -0
- package/dist/cli/dist/invite-share.d.ts +6 -3
- package/dist/cli/dist/invite-share.js +40 -15
- package/dist/cli/dist/invite-share.js.map +1 -1
- package/dist/cli/dist/invites-migrate.d.ts +3 -0
- package/dist/cli/dist/invites-migrate.js +33 -0
- package/dist/cli/dist/invites-migrate.js.map +1 -0
- package/dist/cli/dist/mcp.d.ts +7 -5
- package/dist/cli/dist/mcp.js +23 -18
- package/dist/cli/dist/mcp.js.map +1 -1
- package/dist/cli/dist/network.d.ts +11 -1
- package/dist/cli/dist/network.js +150 -87
- package/dist/cli/dist/network.js.map +1 -1
- package/dist/cli/dist/pending.d.ts +8 -0
- package/dist/cli/dist/pending.js +50 -0
- package/dist/cli/dist/pending.js.map +1 -0
- package/dist/cli/dist/public-settings.d.ts +5 -4
- package/dist/cli/dist/public-settings.js +13 -15
- package/dist/cli/dist/public-settings.js.map +1 -1
- package/dist/cli/dist/receipt.d.ts +1 -1
- package/dist/cli/dist/relay-auth.d.ts +3 -0
- package/dist/cli/dist/relay-auth.js +23 -0
- package/dist/cli/dist/relay-auth.js.map +1 -0
- package/dist/cli/dist/requests.d.ts +10 -0
- package/dist/cli/dist/requests.js +91 -0
- package/dist/cli/dist/requests.js.map +1 -0
- package/dist/cli/dist/sdk.d.ts +10 -0
- package/dist/cli/dist/sdk.js +7 -0
- package/dist/cli/dist/sdk.js.map +1 -0
- package/dist/cli/dist/setup-files.d.ts +24 -0
- package/dist/cli/dist/setup-files.js +190 -0
- package/dist/cli/dist/setup-files.js.map +1 -0
- package/dist/cli/dist/setup-hosts.d.ts +29 -0
- package/dist/cli/dist/setup-hosts.js +75 -0
- package/dist/cli/dist/setup-hosts.js.map +1 -0
- package/dist/cli/dist/setup.d.ts +61 -0
- package/dist/cli/dist/setup.js +243 -0
- package/dist/cli/dist/setup.js.map +1 -0
- package/dist/cli/dist/task-input.d.ts +1 -1
- package/dist/cli/dist/wake-hook.d.ts +1 -1
- package/dist/cli/dist/worker-json.d.ts +4 -0
- package/dist/cli/dist/worker-json.js +87 -0
- package/dist/cli/dist/worker-json.js.map +1 -0
- package/dist/examples/two-owner-demo/dist/run-demo.d.ts +13 -13
- package/dist/package.json +1 -1
- package/dist/packages/adapters/dist/codex-config.d.ts +15 -0
- package/dist/packages/adapters/dist/codex-config.js +171 -0
- package/dist/packages/adapters/dist/codex-config.js.map +1 -0
- package/dist/packages/adapters/dist/command.d.ts +7 -3
- package/dist/packages/adapters/dist/command.js +74 -24
- package/dist/packages/adapters/dist/command.js.map +1 -1
- package/dist/packages/adapters/dist/credential-proxy.d.ts +4 -2
- package/dist/packages/adapters/dist/credential-proxy.js +5 -0
- package/dist/packages/adapters/dist/credential-proxy.js.map +1 -1
- package/dist/packages/adapters/dist/index.d.ts +1 -1
- package/dist/packages/adapters/dist/mcp.d.ts +2 -2
- package/dist/packages/adapters/dist/openai.d.ts +2 -2
- package/dist/packages/adapters/dist/sandbox.d.ts +2 -2
- package/dist/packages/adapters/dist/windows-command.d.ts +6 -0
- package/dist/packages/adapters/dist/windows-command.js +37 -0
- package/dist/packages/adapters/dist/windows-command.js.map +1 -0
- package/dist/packages/core/dist/connection.d.ts +58 -0
- package/dist/packages/core/dist/connection.js +132 -0
- package/dist/packages/core/dist/connection.js.map +1 -0
- package/dist/packages/core/dist/delivery-status.d.ts +22 -0
- package/dist/packages/core/dist/delivery-status.js +53 -0
- package/dist/packages/core/dist/delivery-status.js.map +1 -0
- package/dist/packages/core/dist/discovery.d.ts +7 -6
- package/dist/packages/core/dist/discovery.js +19 -20
- package/dist/packages/core/dist/discovery.js.map +1 -1
- package/dist/packages/core/dist/edge.d.ts +1 -0
- package/dist/packages/core/dist/edge.js +1 -0
- package/dist/packages/core/dist/edge.js.map +1 -1
- package/dist/packages/core/dist/index.d.ts +3 -0
- package/dist/packages/core/dist/index.js +3 -0
- package/dist/packages/core/dist/index.js.map +1 -1
- package/dist/packages/core/dist/protocol.d.ts +24 -0
- package/dist/packages/core/dist/protocol.js +54 -0
- package/dist/packages/core/dist/protocol.js.map +1 -0
- package/dist/packages/core/dist/receipts.d.ts +1 -0
- package/dist/packages/core/dist/receipts.js +1 -1
- package/dist/packages/core/dist/receipts.js.map +1 -1
- package/dist/packages/core/dist/schema.d.ts +1 -0
- package/dist/packages/core/dist/schema.js +13 -2
- package/dist/packages/core/dist/schema.js.map +1 -1
- package/dist/packages/ledger/dist/index.d.ts +1 -1
- package/dist/packages/relay/dist/admission.d.ts +14 -0
- package/dist/packages/relay/dist/admission.js +70 -0
- package/dist/packages/relay/dist/admission.js.map +1 -0
- package/dist/packages/relay/dist/fetch.d.ts +8 -2
- package/dist/packages/relay/dist/fetch.js +41 -12
- package/dist/packages/relay/dist/fetch.js.map +1 -1
- package/dist/packages/relay/dist/index.d.ts +23 -12
- package/dist/packages/relay/dist/index.js +130 -78
- package/dist/packages/relay/dist/index.js.map +1 -1
- package/dist/packages/relay/dist/journal.d.ts +1 -1
- package/dist/packages/relay/dist/notifications.d.ts +1 -1
- package/dist/packages/relay/dist/retry.d.ts +23 -0
- package/dist/packages/relay/dist/retry.js +43 -1
- package/dist/packages/relay/dist/retry.js.map +1 -1
- package/dist/packages/relay/dist/ssrf.d.ts +3 -2
- package/dist/packages/relay/dist/ssrf.js.map +1 -1
- package/dist/packages/relay/dist/worker.js +1 -1
- package/dist/packages/relay/dist/worker.js.map +1 -1
- package/dist/packages/runtime/dist/a2a.d.ts +1 -1
- package/dist/packages/runtime/dist/a2a.js +1 -1
- package/dist/packages/runtime/dist/adapter.d.ts +2 -2
- package/dist/packages/runtime/dist/application-config.d.ts +20 -0
- package/dist/packages/runtime/dist/application-config.js +222 -0
- package/dist/packages/runtime/dist/application-config.js.map +1 -0
- package/dist/packages/runtime/dist/approval-providers.d.ts +24 -3
- package/dist/packages/runtime/dist/approval-providers.js +79 -3
- package/dist/packages/runtime/dist/approval-providers.js.map +1 -1
- package/dist/packages/runtime/dist/connection-admission-review.d.ts +39 -0
- package/dist/packages/runtime/dist/connection-admission-review.js +86 -0
- package/dist/packages/runtime/dist/connection-admission-review.js.map +1 -0
- package/dist/packages/runtime/dist/connection-persistence.d.ts +35 -0
- package/dist/packages/runtime/dist/connection-persistence.js +181 -0
- package/dist/packages/runtime/dist/connection-persistence.js.map +1 -0
- package/dist/packages/runtime/dist/connection-records.d.ts +167 -0
- package/dist/packages/runtime/dist/connection-records.js +132 -0
- package/dist/packages/runtime/dist/connection-records.js.map +1 -0
- package/dist/packages/runtime/dist/connection-router.d.ts +45 -0
- package/dist/packages/runtime/dist/connection-router.js +130 -0
- package/dist/packages/runtime/dist/connection-router.js.map +1 -0
- package/dist/packages/runtime/dist/connection-service.d.ts +159 -0
- package/dist/packages/runtime/dist/connection-service.js +239 -0
- package/dist/packages/runtime/dist/connection-service.js.map +1 -0
- package/dist/packages/runtime/dist/connection-transport.d.ts +14 -0
- package/dist/packages/runtime/dist/connection-transport.js +89 -0
- package/dist/packages/runtime/dist/connection-transport.js.map +1 -0
- package/dist/packages/runtime/dist/delivery-records.d.ts +180 -0
- package/dist/packages/runtime/dist/delivery-records.js +205 -0
- package/dist/packages/runtime/dist/delivery-records.js.map +1 -0
- package/dist/packages/runtime/dist/delivery-status-router.d.ts +27 -0
- package/dist/packages/runtime/dist/delivery-status-router.js +150 -0
- package/dist/packages/runtime/dist/delivery-status-router.js.map +1 -0
- package/dist/packages/runtime/dist/delivery-status.d.ts +43 -0
- package/dist/packages/runtime/dist/delivery-status.js +193 -0
- package/dist/packages/runtime/dist/delivery-status.js.map +1 -0
- package/dist/packages/runtime/dist/delivery-store.d.ts +125 -0
- package/dist/packages/runtime/dist/delivery-store.js +1047 -0
- package/dist/packages/runtime/dist/delivery-store.js.map +1 -0
- package/dist/packages/runtime/dist/gateway.d.ts +4 -3
- package/dist/packages/runtime/dist/gateway.js.map +1 -1
- package/dist/packages/runtime/dist/held-request-review.d.ts +49 -0
- package/dist/packages/runtime/dist/held-request-review.js +122 -0
- package/dist/packages/runtime/dist/held-request-review.js.map +1 -0
- package/dist/packages/runtime/dist/held-request-store.d.ts +112 -0
- package/dist/packages/runtime/dist/held-request-store.js +290 -0
- package/dist/packages/runtime/dist/held-request-store.js.map +1 -0
- package/dist/packages/runtime/dist/idempotency-store.d.ts +53 -0
- package/dist/packages/runtime/dist/idempotency-store.js +331 -0
- package/dist/packages/runtime/dist/idempotency-store.js.map +1 -0
- package/dist/packages/runtime/dist/inbox.d.ts +1 -1
- package/dist/packages/runtime/dist/index.d.ts +27 -1
- package/dist/packages/runtime/dist/index.js +26 -0
- package/dist/packages/runtime/dist/index.js.map +1 -1
- package/dist/packages/runtime/dist/invite-store.d.ts +76 -0
- package/dist/packages/runtime/dist/invite-store.js +337 -0
- package/dist/packages/runtime/dist/invite-store.js.map +1 -0
- package/dist/packages/runtime/dist/key-store.d.ts +77 -0
- package/dist/packages/runtime/dist/key-store.js +226 -0
- package/dist/packages/runtime/dist/key-store.js.map +1 -0
- package/dist/packages/runtime/dist/local-identity.d.ts +33 -0
- package/dist/packages/runtime/dist/local-identity.js +278 -0
- package/dist/packages/runtime/dist/local-identity.js.map +1 -0
- package/dist/packages/runtime/dist/mcp-rpc.d.ts +19 -0
- package/dist/packages/runtime/dist/mcp-rpc.js +175 -0
- package/dist/packages/runtime/dist/mcp-rpc.js.map +1 -0
- package/dist/packages/runtime/dist/mcp-server.d.ts +46 -19
- package/dist/packages/runtime/dist/mcp-server.js +427 -184
- package/dist/packages/runtime/dist/mcp-server.js.map +1 -1
- package/dist/packages/runtime/dist/mcp-tool-catalog.d.ts +1 -1
- package/dist/packages/runtime/dist/mcp-tool-catalog.js +172 -13
- package/dist/packages/runtime/dist/mcp-tool-catalog.js.map +1 -1
- package/dist/packages/runtime/dist/mcp.d.ts +2 -2
- package/dist/packages/runtime/dist/message-status.d.ts +32 -0
- package/dist/packages/runtime/dist/message-status.js +29 -0
- package/dist/packages/runtime/dist/message-status.js.map +1 -0
- package/dist/packages/runtime/dist/outbox.d.ts +91 -0
- package/dist/packages/runtime/dist/outbox.js +462 -0
- package/dist/packages/runtime/dist/outbox.js.map +1 -0
- package/dist/packages/runtime/dist/policy.d.ts +1 -1
- package/dist/packages/runtime/dist/protocol-peer-store.d.ts +40 -0
- package/dist/packages/runtime/dist/protocol-peer-store.js +176 -0
- package/dist/packages/runtime/dist/protocol-peer-store.js.map +1 -0
- package/dist/packages/runtime/dist/protocol-release.d.ts +26 -0
- package/dist/packages/runtime/dist/protocol-release.js +76 -0
- package/dist/packages/runtime/dist/protocol-release.js.map +1 -0
- package/dist/packages/runtime/dist/quota.d.ts +1 -1
- package/dist/packages/runtime/dist/relay-selection.d.ts +20 -0
- package/dist/packages/runtime/dist/relay-selection.js +34 -0
- package/dist/packages/runtime/dist/relay-selection.js.map +1 -0
- package/dist/packages/runtime/dist/requester-pending-store.d.ts +59 -0
- package/dist/packages/runtime/dist/requester-pending-store.js +266 -0
- package/dist/packages/runtime/dist/requester-pending-store.js.map +1 -0
- package/dist/packages/runtime/dist/secret-store.d.ts +19 -0
- package/dist/packages/runtime/dist/secret-store.js +51 -0
- package/dist/packages/runtime/dist/secret-store.js.map +1 -0
- package/dist/packages/runtime/dist/session-audit.d.ts +29 -7
- package/dist/packages/runtime/dist/session-audit.js +149 -29
- package/dist/packages/runtime/dist/session-audit.js.map +1 -1
- package/dist/packages/runtime/dist/session.d.ts +101 -9
- package/dist/packages/runtime/dist/session.js +696 -96
- package/dist/packages/runtime/dist/session.js.map +1 -1
- package/dist/packages/runtime/dist/task-journal.d.ts +18 -3
- package/dist/packages/runtime/dist/task-journal.js +271 -18
- package/dist/packages/runtime/dist/task-journal.js.map +1 -1
- package/dist/packages/runtime/dist/verifier.d.ts +3 -2
- package/dist/packages/runtime/dist/verifier.js +1 -0
- package/dist/packages/runtime/dist/verifier.js.map +1 -1
- package/dist/packages/runtime/dist/worker-trust-store.d.ts +111 -0
- package/dist/packages/runtime/dist/worker-trust-store.js +592 -0
- package/dist/packages/runtime/dist/worker-trust-store.js.map +1 -0
- package/dist/packages/sandbox/dist/command-environment.d.ts +2 -0
- package/dist/packages/sandbox/dist/command-environment.js +29 -0
- package/dist/packages/sandbox/dist/command-environment.js.map +1 -0
- package/dist/packages/sandbox/dist/container.d.ts +1 -1
- package/dist/packages/sandbox/dist/index.d.ts +2 -1
- package/dist/packages/sandbox/dist/index.js +1 -0
- package/dist/packages/sandbox/dist/index.js.map +1 -1
- package/dist/spec/README.ja.md +21 -1
- package/dist/spec/README.md +20 -1
- package/dist/spec/agent-card.schema.json +11 -1
- package/dist/spec/delivery-status.schema.json +106 -0
- package/dist/spec/examples/protocol-versions/sample-agent-card.json +35 -0
- package/dist/spec/examples/sample-delivery-status.json +21 -0
- package/dist/spec/examples/sample-task-manifest.0.1.0.json +173 -0
- package/dist/spec/examples/sample-task-manifest.0.2.0.json +173 -0
- package/dist/spec/task-manifest.0.1.0.schema.json +422 -0
- package/dist/spec/task-manifest.0.2.0.schema.json +422 -0
- package/dist/spec/task-manifest.schema.json +9 -418
- package/docs/sdk.ja.md +133 -0
- package/docs/sdk.md +138 -0
- package/package.json +15 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
3
|
-
import { CapabilityGuard } from '
|
|
4
|
-
import type { CapabilityToken, CapabilityContext, ApprovalReceipt } from '
|
|
3
|
+
import { CapabilityGuard } from '../../core/dist/index.js';
|
|
4
|
+
import type { CapabilityToken, CapabilityContext, ApprovalReceipt } from '../../core/dist/index.js';
|
|
5
5
|
import { ApprovalGate } from './policy.js';
|
|
6
6
|
import type { QuotaMeter } from './quota.js';
|
|
7
7
|
export interface ToolBackend {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { SignedDeliveryStatus } from '../../core/dist/index.js';
|
|
2
|
+
import type { DeliveryStore } from './delivery-store.js';
|
|
3
|
+
import type { MessageStatus } from './delivery-records.js';
|
|
4
|
+
export type MessageStatusSelector = {
|
|
5
|
+
messageId: string;
|
|
6
|
+
taskId?: never;
|
|
7
|
+
} | {
|
|
8
|
+
taskId: string;
|
|
9
|
+
messageId?: never;
|
|
10
|
+
};
|
|
11
|
+
export type MessageStatusResult = {
|
|
12
|
+
found: false;
|
|
13
|
+
} | {
|
|
14
|
+
found: true;
|
|
15
|
+
messageId: string;
|
|
16
|
+
taskId: string;
|
|
17
|
+
peerDid: string;
|
|
18
|
+
status: MessageStatus;
|
|
19
|
+
source: 'local' | 'relay' | 'recipient' | 'reply';
|
|
20
|
+
createdAt: number;
|
|
21
|
+
updatedAt: number;
|
|
22
|
+
deliveryDeadline: number;
|
|
23
|
+
retainUntil: number;
|
|
24
|
+
retry: {
|
|
25
|
+
pending: boolean;
|
|
26
|
+
attemptCount: number;
|
|
27
|
+
nextAttemptAt: number | null;
|
|
28
|
+
};
|
|
29
|
+
recipientStatus: SignedDeliveryStatus | null;
|
|
30
|
+
};
|
|
31
|
+
/** Read sender-local facts without starting delivery, changing records or pruning expired bytes. */
|
|
32
|
+
export declare function messageStatus(store: DeliveryStore, selector: MessageStatusSelector): MessageStatusResult;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { deliveryError } from './delivery-records.js';
|
|
2
|
+
/** Read sender-local facts without starting delivery, changing records or pruning expired bytes. */
|
|
3
|
+
export function messageStatus(store, selector) {
|
|
4
|
+
const matches = selector.messageId !== undefined
|
|
5
|
+
? [store.getMessage(selector.messageId)].flatMap(r => r ? [r] : [])
|
|
6
|
+
: store.findTask(selector.taskId);
|
|
7
|
+
if (matches.length > 1)
|
|
8
|
+
throw deliveryError('MESSAGE_STATUS_AMBIGUOUS');
|
|
9
|
+
const message = matches[0];
|
|
10
|
+
if (!message)
|
|
11
|
+
return { found: false };
|
|
12
|
+
const proposal = store.read('outbox', message.messageId);
|
|
13
|
+
const pending = message.status === 'queued' && proposal?.state === 'pending' && store.now() < message.deliveryDeadline;
|
|
14
|
+
// C4 verifies the recipient binding and pins disclosure before publishing each event.
|
|
15
|
+
const recipientStatus = message.protocolVersion === '0.2.0' && message.remoteStatus &&
|
|
16
|
+
store.now() < message.remoteStatus.payload.retainUntil ? message.remoteStatus : null;
|
|
17
|
+
const source = message.status === 'replied' ? 'reply'
|
|
18
|
+
: recipientStatus?.payload.state === message.status ? 'recipient'
|
|
19
|
+
: message.status === 'relayed' ? 'relay' : 'local';
|
|
20
|
+
return {
|
|
21
|
+
found: true, messageId: message.messageId, taskId: message.taskId, peerDid: message.peerOwnerDid,
|
|
22
|
+
status: message.status, source, createdAt: message.createdAt, updatedAt: message.updatedAt,
|
|
23
|
+
deliveryDeadline: message.deliveryDeadline, retainUntil: message.retainUntil,
|
|
24
|
+
// C1 removes completed batch members; absent attempt metadata is reported as zero.
|
|
25
|
+
retry: { pending, attemptCount: proposal?.attemptCount ?? 0, nextAttemptAt: pending ? proposal.nextAttemptAt : null },
|
|
26
|
+
recipientStatus,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=message-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-status.js","sourceRoot":"","sources":["../src/message-status.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAYtD,oGAAoG;AACpG,MAAM,UAAU,aAAa,CAAC,KAAoB,EAAE,QAA+B;IACjF,MAAM,OAAO,GAAG,QAAQ,CAAC,SAAS,KAAK,SAAS;QAC9C,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,aAAa,CAAC,0BAA0B,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,QAAQ,EAAE,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;IACvH,sFAAsF;IACtF,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,KAAK,OAAO,IAAI,OAAO,CAAC,YAAY;QACjF,KAAK,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC;IACvF,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO;QACnD,CAAC,CAAC,eAAe,EAAE,OAAO,CAAC,KAAK,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW;YACjE,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IACrD,OAAO;QACL,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,YAAY;QAChG,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS;QAC1F,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW;QAC5E,mFAAmF;QACnF,KAAK,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,IAAI,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE;QACrH,eAAe;KAChB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { DeliveryStatusRouter } from './delivery-status-router.js';
|
|
2
|
+
import { RecipientDeliveryCoordinator } from './delivery-status.js';
|
|
3
|
+
import type { KeyPair, SignedEnvelope } from '../../core/dist/index.js';
|
|
4
|
+
import type { RelayTransport } from '../../relay/dist/index.js';
|
|
5
|
+
import type { IdentityStoreOptions } from './key-store.js';
|
|
6
|
+
import { DeliveryStore } from './delivery-store.js';
|
|
7
|
+
import type { DeliveryLimits, MessageStatus, OutboxRecordV1 } from './delivery-records.js';
|
|
8
|
+
export interface DeliveryContext {
|
|
9
|
+
messageId: string;
|
|
10
|
+
peerOwnerDid: string;
|
|
11
|
+
relayUrl: string;
|
|
12
|
+
deliveryDeadline: number;
|
|
13
|
+
kind?: OutboxRecordV1['kind'];
|
|
14
|
+
message?: {
|
|
15
|
+
taskId: string;
|
|
16
|
+
peerAgentDid: string;
|
|
17
|
+
protocolVersion: '0.1.0' | '0.2.0';
|
|
18
|
+
operationId?: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export interface GatewayDeliveryOptions {
|
|
22
|
+
identityOptions?: IdentityStoreOptions;
|
|
23
|
+
now?: () => number;
|
|
24
|
+
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
25
|
+
limits?: Partial<DeliveryLimits>;
|
|
26
|
+
resolveTransport: (relayUrl: string, owner: KeyPair) => RelayTransport | Promise<RelayTransport>;
|
|
27
|
+
assertSendAllowed?: (context: OutboxRecordV1) => void | Promise<void>;
|
|
28
|
+
/** Check retained relay bindings before starting a status reader. */
|
|
29
|
+
assertReceiveAllowed?: (relayUrl: string) => void | Promise<void>;
|
|
30
|
+
requestTimeoutMs?: number;
|
|
31
|
+
onError?: (error: Error) => void;
|
|
32
|
+
timers?: Pick<typeof globalThis, 'setTimeout' | 'clearTimeout'>;
|
|
33
|
+
}
|
|
34
|
+
export declare function outboxRetryDelay(attemptCount: number): number;
|
|
35
|
+
/** Durable transport admission, independent of task execution and owner decisions. */
|
|
36
|
+
declare class GatewayDeliveryServiceImplementation {
|
|
37
|
+
readonly owner: KeyPair;
|
|
38
|
+
readonly options: GatewayDeliveryOptions;
|
|
39
|
+
readonly store: DeliveryStore;
|
|
40
|
+
readonly statusRouter: DeliveryStatusRouter;
|
|
41
|
+
recipientStatus: RecipientDeliveryCoordinator;
|
|
42
|
+
private controller;
|
|
43
|
+
private active;
|
|
44
|
+
private timer?;
|
|
45
|
+
private readonly attempts;
|
|
46
|
+
private readonly terminalErrors;
|
|
47
|
+
private readonly maintenance;
|
|
48
|
+
onMaintenance(callback: (signal: AbortSignal) => void | Promise<void>): void;
|
|
49
|
+
private readonly sleep;
|
|
50
|
+
private readonly timers;
|
|
51
|
+
constructor(owner: KeyPair, options: GatewayDeliveryOptions);
|
|
52
|
+
start(): Promise<void>;
|
|
53
|
+
stop(): void;
|
|
54
|
+
private report;
|
|
55
|
+
private schedule;
|
|
56
|
+
prepare(signal?: AbortSignal): Promise<void>;
|
|
57
|
+
private buildBatch;
|
|
58
|
+
private batchOperation;
|
|
59
|
+
private publishBatch;
|
|
60
|
+
private publishBatchLocked;
|
|
61
|
+
private batchWrites;
|
|
62
|
+
/** Publish all immutable members before the batch marker makes any member dispatchable. */
|
|
63
|
+
enqueue(prepared: readonly SignedEnvelope[], context: DeliveryContext): string[];
|
|
64
|
+
/** Independent batches write concurrently without holding the signer lock or blocking relay readers. */
|
|
65
|
+
enqueuePrepared(frames: readonly SignedEnvelope[], context: DeliveryContext, signal?: AbortSignal): Promise<string[]>;
|
|
66
|
+
/** C4 stages status frames and observation references in one prepared transaction. */
|
|
67
|
+
enqueueLocked(frames: readonly SignedEnvelope[], context: DeliveryContext): string[];
|
|
68
|
+
private ordered;
|
|
69
|
+
/** Cleanup can be retried by maintenance after admission has already completed. */
|
|
70
|
+
finishBatch(batchId: string): void;
|
|
71
|
+
drainDue(signal?: AbortSignal): Promise<void>;
|
|
72
|
+
/** One attempt per due control frame; background lifecycle owns subsequent retries. */
|
|
73
|
+
drainStatuses(): Promise<void>;
|
|
74
|
+
sendPrepared(envelopeId: string, signal?: AbortSignal): Promise<void>;
|
|
75
|
+
private attempt;
|
|
76
|
+
private attemptOnce;
|
|
77
|
+
private closeLocked;
|
|
78
|
+
closeMessage(messageId: string, code?: string): void;
|
|
79
|
+
markPeerObserved(messageId: string, observation: {
|
|
80
|
+
status: MessageStatus;
|
|
81
|
+
envelopeId: string;
|
|
82
|
+
replyEnvelopeId?: string;
|
|
83
|
+
}): void;
|
|
84
|
+
}
|
|
85
|
+
/** Structural public interface permits source and built hosts to share a service. */
|
|
86
|
+
export type GatewayDeliveryService = Pick<GatewayDeliveryServiceImplementation, keyof GatewayDeliveryServiceImplementation>;
|
|
87
|
+
export declare const GatewayDeliveryService: {
|
|
88
|
+
new (owner: KeyPair, options: GatewayDeliveryOptions): GatewayDeliveryService;
|
|
89
|
+
readonly prototype: GatewayDeliveryService;
|
|
90
|
+
};
|
|
91
|
+
export {};
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import { DeliveryStatusRouter } from './delivery-status-router.js';
|
|
3
|
+
import { RecipientDeliveryCoordinator } from './delivery-status.js';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { digest } from '../../core/dist/index.js';
|
|
6
|
+
import { isTransientRelayError, relayDeadline, waitForRelayRetry } from '../../relay/dist/index.js';
|
|
7
|
+
import { DeliveryStore } from './delivery-store.js';
|
|
8
|
+
import { deliveryError, DELIVERY_RETENTION_MS, requireDelivery } from './delivery-records.js';
|
|
9
|
+
const delays = [60_000, 300_000, 900_000, 3_600_000];
|
|
10
|
+
export function outboxRetryDelay(attemptCount) { return delays[Math.min(Math.max(attemptCount - 1, 0), 3)]; }
|
|
11
|
+
function errorCode(error) {
|
|
12
|
+
const code = error?.code;
|
|
13
|
+
if (typeof code === 'string' && /^[A-Z][A-Z0-9_]{0,79}$/.test(code))
|
|
14
|
+
return code;
|
|
15
|
+
const message = error instanceof Error ? error.message : '';
|
|
16
|
+
return /^[A-Z][A-Z0-9_]{0,79}$/.test(message) ? message : 'RELAY_UNAVAILABLE';
|
|
17
|
+
}
|
|
18
|
+
/** Durable transport admission, independent of task execution and owner decisions. */
|
|
19
|
+
class GatewayDeliveryServiceImplementation {
|
|
20
|
+
owner;
|
|
21
|
+
options;
|
|
22
|
+
store;
|
|
23
|
+
statusRouter;
|
|
24
|
+
recipientStatus;
|
|
25
|
+
controller = new AbortController();
|
|
26
|
+
active = false;
|
|
27
|
+
timer;
|
|
28
|
+
attempts = new Map();
|
|
29
|
+
terminalErrors = new Map();
|
|
30
|
+
maintenance = new Set();
|
|
31
|
+
onMaintenance(callback) { this.maintenance.add(callback); }
|
|
32
|
+
sleep;
|
|
33
|
+
timers;
|
|
34
|
+
constructor(owner, options) {
|
|
35
|
+
this.owner = owner;
|
|
36
|
+
this.options = options;
|
|
37
|
+
this.store = new DeliveryStore(owner, { ...options.identityOptions, now: options.now, limits: options.limits });
|
|
38
|
+
this.statusRouter = new DeliveryStatusRouter(this);
|
|
39
|
+
this.recipientStatus = new RecipientDeliveryCoordinator(this);
|
|
40
|
+
this.sleep = options.sleep ?? waitForRelayRetry;
|
|
41
|
+
this.timers = options.timers ?? globalThis;
|
|
42
|
+
}
|
|
43
|
+
async start() {
|
|
44
|
+
if (this.active)
|
|
45
|
+
return;
|
|
46
|
+
if (this.controller.signal.aborted)
|
|
47
|
+
this.controller = new AbortController();
|
|
48
|
+
this.active = true;
|
|
49
|
+
if (this.store.ids('observations', e => this.report(e)).length)
|
|
50
|
+
await this.prepare();
|
|
51
|
+
this.recipientStatus.reconcile();
|
|
52
|
+
// Do not create an empty store during lifecycle-only startup.
|
|
53
|
+
if (this.store.ids('observations', e => this.report(e)).length || this.store.ids('batches', e => this.report(e)).length || this.store.ids('receipts', e => this.report(e)).length || this.store.ids('messages', e => this.report(e)).length)
|
|
54
|
+
await this.store.maintainInBackground(error => this.report(error), this.controller.signal);
|
|
55
|
+
for (const callback of this.maintenance) {
|
|
56
|
+
this.controller.signal.throwIfAborted();
|
|
57
|
+
await callback(this.controller.signal);
|
|
58
|
+
}
|
|
59
|
+
await this.drainDue(this.controller.signal);
|
|
60
|
+
await this.statusRouter.start();
|
|
61
|
+
this.schedule();
|
|
62
|
+
}
|
|
63
|
+
stop() {
|
|
64
|
+
this.active = false;
|
|
65
|
+
this.statusRouter.stop();
|
|
66
|
+
this.timers.clearTimeout(this.timer);
|
|
67
|
+
this.controller.abort(deliveryError('OUTBOX_STOPPED'));
|
|
68
|
+
this.terminalErrors.clear();
|
|
69
|
+
}
|
|
70
|
+
report(error) {
|
|
71
|
+
const normalized = deliveryError(errorCode(error));
|
|
72
|
+
if (this.options.onError)
|
|
73
|
+
this.options.onError(normalized);
|
|
74
|
+
else
|
|
75
|
+
console.warn(normalized.message);
|
|
76
|
+
}
|
|
77
|
+
schedule() {
|
|
78
|
+
if (!this.active)
|
|
79
|
+
return;
|
|
80
|
+
this.timer = this.timers.setTimeout(() => {
|
|
81
|
+
void this.prepare().then(() => this.recipientStatus.reconcile()).then(() => this.drainDue(this.controller.signal)).then(async () => {
|
|
82
|
+
void this.statusRouter.refresh().catch(e => this.report(e));
|
|
83
|
+
await this.store.maintainInBackground(error => this.report(error), this.controller.signal);
|
|
84
|
+
for (const id of this.terminalErrors.keys()) {
|
|
85
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
86
|
+
this.controller.signal.throwIfAborted();
|
|
87
|
+
if (!this.store.read('outbox', id))
|
|
88
|
+
this.terminalErrors.delete(id);
|
|
89
|
+
}
|
|
90
|
+
for (const callback of this.maintenance) {
|
|
91
|
+
this.controller.signal.throwIfAborted();
|
|
92
|
+
await callback(this.controller.signal);
|
|
93
|
+
}
|
|
94
|
+
}).catch(e => this.report(e)).finally(() => this.schedule());
|
|
95
|
+
}, 60_000);
|
|
96
|
+
this.timer.unref?.();
|
|
97
|
+
}
|
|
98
|
+
async prepare(signal) {
|
|
99
|
+
await this.store.prepare(AbortSignal.any([this.controller.signal, ...(signal ? [signal] : [])]));
|
|
100
|
+
}
|
|
101
|
+
buildBatch(prepared, context) {
|
|
102
|
+
const frames = structuredClone(prepared), c = structuredClone(context);
|
|
103
|
+
const now = this.store.now(), batchId = randomUUID();
|
|
104
|
+
requireDelivery(frames.length >= 1 && frames.length <= 2 && (c.kind !== 'delivery-status' || frames.length === 1));
|
|
105
|
+
requireDelivery(frames.length !== 2 || frames[1].payload.eventType === 'AUDIT_ENTRY' &&
|
|
106
|
+
frames[1].payload.seq === frames[0].payload.seq + 1 && frames[1].payload.taskId === frames[0].payload.taskId);
|
|
107
|
+
const records = frames.map(e => ({ version: 1, envelopeId: e.payload.envelopeId,
|
|
108
|
+
messageId: c.messageId, batchId, kind: c.kind ?? 'task', localOwnerDid: this.owner.did,
|
|
109
|
+
peerOwnerDid: c.peerOwnerDid, relayUrl: c.relayUrl, envelopeDigest: digest(e), envelope: e,
|
|
110
|
+
createdAt: now, deliveryDeadline: c.deliveryDeadline, state: 'pending', attemptCount: 0, nextAttemptAt: now,
|
|
111
|
+
ordinal: e.payload.seq }));
|
|
112
|
+
const first = frames[0];
|
|
113
|
+
const message = c.message ? { version: 1, messageId: c.messageId,
|
|
114
|
+
localOwnerDid: this.owner.did, peerOwnerDid: c.peerOwnerDid, peerAgentDid: c.message.peerAgentDid,
|
|
115
|
+
taskId: c.message.taskId, relayUrl: c.relayUrl, protocolVersion: c.message.protocolVersion,
|
|
116
|
+
createdAt: now, deliveryDeadline: c.deliveryDeadline, retainUntil: Math.max(now, c.deliveryDeadline) + DELIVERY_RETENTION_MS,
|
|
117
|
+
status: 'queued', updatedAt: now, proposalEnvelopeDigest: digest(first),
|
|
118
|
+
...(c.message.operationId ? { operationId: c.message.operationId } : {}) } : undefined;
|
|
119
|
+
if (message)
|
|
120
|
+
requireDelivery(first.payload.eventType === 'PROPOSE' && first.payload.envelopeId === c.messageId && first.payload.taskId === message.taskId);
|
|
121
|
+
const marker = { version: 1, batchId, messageId: c.messageId, localOwnerDid: this.owner.did,
|
|
122
|
+
envelopeIds: records.map(r => r.envelopeId), createdAt: now, committed: true };
|
|
123
|
+
return { records, message, marker };
|
|
124
|
+
}
|
|
125
|
+
batchOperation(message) {
|
|
126
|
+
if (!message?.operationId)
|
|
127
|
+
return;
|
|
128
|
+
const operation = this.store.read('operations', message.operationId);
|
|
129
|
+
const action = operation?.preparedActions.find(a => a.manifest.taskId === message.taskId);
|
|
130
|
+
requireDelivery(operation && operation.phase !== 'finished' && action &&
|
|
131
|
+
operation.approvals?.some(a => a.childIndex === action.childIndex) &&
|
|
132
|
+
action.peerOwnerDid === message.peerOwnerDid && action.peerAgentDid === message.peerAgentDid &&
|
|
133
|
+
action.relayUrl === message.relayUrl &&
|
|
134
|
+
Date.parse(action.manifest.deadline) === message.deliveryDeadline);
|
|
135
|
+
const prior = JSON.stringify(operation);
|
|
136
|
+
action.messageId = message.messageId;
|
|
137
|
+
operation.messageIds = operation.preparedActions.flatMap(a => a.messageId ? [a.messageId] : []);
|
|
138
|
+
operation.phase = 'sending';
|
|
139
|
+
return { prior, record: operation };
|
|
140
|
+
}
|
|
141
|
+
publishBatch({ records, message, marker }, prepared, operation) {
|
|
142
|
+
const publish = (write) => {
|
|
143
|
+
if (prepared)
|
|
144
|
+
this.store.publishWrite(prepared.find(p => p.kind === write.kind && p.id === write.id));
|
|
145
|
+
else
|
|
146
|
+
this.store.write(write.kind, write.id, write.record);
|
|
147
|
+
};
|
|
148
|
+
const ids = this.store.transaction(() => {
|
|
149
|
+
if (operation && JSON.stringify(this.store.read('operations', operation.record.operationId)) !== operation.prior)
|
|
150
|
+
return;
|
|
151
|
+
return this.publishBatchLocked({ records, message, marker }, publish, operation);
|
|
152
|
+
});
|
|
153
|
+
if (ids)
|
|
154
|
+
void this.statusRouter.refresh().catch(e => this.report(e));
|
|
155
|
+
return ids;
|
|
156
|
+
}
|
|
157
|
+
publishBatchLocked({ records, message, marker }, publish, operation) {
|
|
158
|
+
this.store.assertCapacity(records, message);
|
|
159
|
+
for (const r of records) {
|
|
160
|
+
const prior = this.store.read('outbox', r.envelopeId);
|
|
161
|
+
if (prior)
|
|
162
|
+
throw deliveryError('DELIVERY_RECORD_CONFLICT');
|
|
163
|
+
publish({ kind: 'outbox', id: r.envelopeId, record: r });
|
|
164
|
+
}
|
|
165
|
+
if (message) {
|
|
166
|
+
const prior = this.store.read('messages', message.messageId);
|
|
167
|
+
if (prior)
|
|
168
|
+
throw deliveryError('DELIVERY_RECORD_CONFLICT');
|
|
169
|
+
publish({ kind: 'messages', id: message.messageId, record: message });
|
|
170
|
+
}
|
|
171
|
+
if (operation)
|
|
172
|
+
publish({ kind: 'operations', id: operation.record.operationId, record: operation.record });
|
|
173
|
+
publish({ kind: 'batches', id: marker.batchId, record: marker });
|
|
174
|
+
this.store.committed(marker.batchId);
|
|
175
|
+
return records.map(r => r.envelopeId);
|
|
176
|
+
}
|
|
177
|
+
batchWrites(batch) {
|
|
178
|
+
const writes = batch.records.map(record => ({ kind: 'outbox', id: record.envelopeId, record }));
|
|
179
|
+
if (batch.message)
|
|
180
|
+
writes.push({ kind: 'messages', id: batch.message.messageId, record: batch.message });
|
|
181
|
+
writes.push({ kind: 'batches', id: batch.marker.batchId, record: batch.marker });
|
|
182
|
+
return writes;
|
|
183
|
+
}
|
|
184
|
+
/** Publish all immutable members before the batch marker makes any member dispatchable. */
|
|
185
|
+
enqueue(prepared, context) {
|
|
186
|
+
const batch = this.buildBatch(prepared, context);
|
|
187
|
+
if (!batch.message?.operationId)
|
|
188
|
+
return this.publishBatch(batch);
|
|
189
|
+
for (;;) {
|
|
190
|
+
const operation = this.batchOperation(batch.message);
|
|
191
|
+
const update = this.store.prepareWritesSync([...this.batchWrites(batch),
|
|
192
|
+
{ kind: 'operations', id: operation.record.operationId, record: operation.record }]);
|
|
193
|
+
try {
|
|
194
|
+
// Complete synchronous keyed writes before acquiring the publication lock.
|
|
195
|
+
const ids = this.publishBatch(batch, update, operation);
|
|
196
|
+
if (ids)
|
|
197
|
+
return ids;
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
this.store.discardWrites(update);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/** Independent batches write concurrently without holding the signer lock or blocking relay readers. */
|
|
205
|
+
async enqueuePrepared(frames, context, signal) {
|
|
206
|
+
const batch = this.buildBatch(frames, context);
|
|
207
|
+
const prepared = await this.store.prepareWrites(this.batchWrites(batch));
|
|
208
|
+
try {
|
|
209
|
+
for (;;) {
|
|
210
|
+
const operation = this.batchOperation(batch.message);
|
|
211
|
+
const update = operation ? await this.store.prepareWrites([{ kind: 'operations', id: operation.record.operationId, record: operation.record }]) : [];
|
|
212
|
+
try {
|
|
213
|
+
await this.prepare(signal);
|
|
214
|
+
const ids = this.publishBatch(batch, [...prepared, ...update], operation);
|
|
215
|
+
if (ids)
|
|
216
|
+
return ids;
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
this.store.discardWrites(update);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
this.store.discardWrites(prepared);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/** C4 stages status frames and observation references in one prepared transaction. */
|
|
228
|
+
enqueueLocked(frames, context) {
|
|
229
|
+
return this.publishBatchLocked(this.buildBatch(frames, context), write => this.store.write(write.kind, write.id, write.record));
|
|
230
|
+
}
|
|
231
|
+
async ordered(signal) {
|
|
232
|
+
const records = [];
|
|
233
|
+
for (const id of this.store.ids('batches', e => this.report(e))) {
|
|
234
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
235
|
+
signal.throwIfAborted();
|
|
236
|
+
try {
|
|
237
|
+
records.push(...this.store.committed(id));
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
this.report(error);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return records.sort((a, b) => a.createdAt - b.createdAt || a.ordinal - b.ordinal);
|
|
244
|
+
}
|
|
245
|
+
/** Cleanup can be retried by maintenance after admission has already completed. */
|
|
246
|
+
finishBatch(batchId) {
|
|
247
|
+
try {
|
|
248
|
+
this.store.transaction(() => this.store.finishBatch(batchId));
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
this.report(error);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async drainDue(signal) {
|
|
255
|
+
const combined = AbortSignal.any([this.controller.signal, ...(signal ? [signal] : [])]);
|
|
256
|
+
for (const r of await this.ordered(combined)) {
|
|
257
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
258
|
+
combined.throwIfAborted();
|
|
259
|
+
try {
|
|
260
|
+
await this.attempt(r.envelopeId, combined);
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
this.report(error);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/** One attempt per due control frame; background lifecycle owns subsequent retries. */
|
|
268
|
+
async drainStatuses() {
|
|
269
|
+
try {
|
|
270
|
+
await this.prepare();
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
this.report(error);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
for (const r of this.store.statusOutbox().sort((a, b) => a.createdAt - b.createdAt || a.ordinal - b.ordinal)) {
|
|
277
|
+
if (this.controller.signal.aborted)
|
|
278
|
+
return;
|
|
279
|
+
try {
|
|
280
|
+
await this.attempt(r.envelopeId, this.controller.signal);
|
|
281
|
+
if (!this.controller.signal.aborted)
|
|
282
|
+
this.finishBatch(r.batchId);
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
if (!this.controller.signal.aborted)
|
|
286
|
+
this.report(error);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async sendPrepared(envelopeId, signal) {
|
|
291
|
+
const combined = AbortSignal.any([this.controller.signal, ...(signal ? [signal] : [])]);
|
|
292
|
+
for (;;) {
|
|
293
|
+
combined.throwIfAborted();
|
|
294
|
+
const r = this.store.read('outbox', envelopeId);
|
|
295
|
+
if (!r)
|
|
296
|
+
throw this.terminalErrors.get(envelopeId) ?? deliveryError('OUTBOX_STORE_INVALID');
|
|
297
|
+
const committed = this.store.committed(r.batchId);
|
|
298
|
+
if (!committed.some(member => member.envelopeId === envelopeId))
|
|
299
|
+
throw deliveryError('OUTBOX_STORE_INVALID');
|
|
300
|
+
if (r.state === 'admitted')
|
|
301
|
+
return;
|
|
302
|
+
if (r.state === 'closed')
|
|
303
|
+
throw this.terminalErrors.get(envelopeId) ?? deliveryError(r.lastError ?? 'OUTBOX_DEADLINE_EXPIRED');
|
|
304
|
+
await this.attempt(envelopeId, combined);
|
|
305
|
+
const next = this.store.read('outbox', envelopeId);
|
|
306
|
+
if (next?.state === 'pending') {
|
|
307
|
+
const blockers = this.store.pending(next.messageId).filter(other => other.kind === next.kind && other.ordinal < next.ordinal);
|
|
308
|
+
const time = Math.min(next.deliveryDeadline, Math.max(next.nextAttemptAt, next.lease?.until ?? 0, ...blockers.map(r => Math.max(r.nextAttemptAt, r.lease?.until ?? this.store.now() + 60_000))));
|
|
309
|
+
await this.sleep(Math.max(1, time - this.store.now()), combined);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
attempt(id, signal) {
|
|
314
|
+
const prior = this.attempts.get(id);
|
|
315
|
+
if (prior)
|
|
316
|
+
return prior;
|
|
317
|
+
const run = this.attemptOnce(id, signal).finally(() => this.attempts.delete(id));
|
|
318
|
+
this.attempts.set(id, run);
|
|
319
|
+
return run;
|
|
320
|
+
}
|
|
321
|
+
async attemptOnce(id, signal) {
|
|
322
|
+
await this.prepare(signal);
|
|
323
|
+
const holderId = randomUUID();
|
|
324
|
+
const claimed = this.store.transaction(() => {
|
|
325
|
+
const r = this.store.read('outbox', id);
|
|
326
|
+
if (!r || r.state !== 'pending')
|
|
327
|
+
return;
|
|
328
|
+
const batch = this.store.committed(r.batchId);
|
|
329
|
+
if (!batch.length)
|
|
330
|
+
return;
|
|
331
|
+
const now = this.store.now();
|
|
332
|
+
// Suppression/expiry bookkeeping can replay; every attempt rechecks disclosure and deadline.
|
|
333
|
+
if (r.kind === 'delivery-status' && !this.recipientStatus.dispatchAllowed(r)) {
|
|
334
|
+
this.store.write('outbox', id, { ...r, state: 'closed', lastError: 'DELIVERY_STATUS_NOT_SUPPORTED' });
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (now >= r.deliveryDeadline) {
|
|
338
|
+
if (r.kind === 'delivery-status')
|
|
339
|
+
this.store.write('outbox', id, { ...r, state: 'closed', lastError: 'OUTBOX_DEADLINE_EXPIRED' });
|
|
340
|
+
else
|
|
341
|
+
this.closeLocked(r.messageId, 'OUTBOX_DEADLINE_EXPIRED', 'deadline');
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (r.nextAttemptAt > now || r.lease && r.lease.until > now)
|
|
345
|
+
return;
|
|
346
|
+
if (this.store.pending(r.messageId).some(other => other.kind === r.kind && (other.ordinal < r.ordinal || other.envelopeId !== id && other.lease && other.lease.until > now)))
|
|
347
|
+
return;
|
|
348
|
+
const next = { ...r, lease: { holderId, until: now + 60_000 }, attemptCount: r.attemptCount + 1 };
|
|
349
|
+
// A lost lease or attempt counter only repeats the same immutable envelope.
|
|
350
|
+
this.store.write('outbox', id, next);
|
|
351
|
+
return next;
|
|
352
|
+
});
|
|
353
|
+
if (!claimed)
|
|
354
|
+
return;
|
|
355
|
+
let error, admitted = false, relayDid;
|
|
356
|
+
try {
|
|
357
|
+
const ceiling = Math.min(this.options.requestTimeoutMs ?? 45_000, 45_000, claimed.deliveryDeadline - this.store.now());
|
|
358
|
+
const attemptSignal = AbortSignal.any([signal, AbortSignal.timeout(Math.max(1, ceiling))]);
|
|
359
|
+
await relayDeadline((async () => {
|
|
360
|
+
attemptSignal.throwIfAborted();
|
|
361
|
+
await this.options.assertSendAllowed?.(structuredClone(claimed));
|
|
362
|
+
attemptSignal.throwIfAborted();
|
|
363
|
+
if (claimed.kind === 'delivery-status' && !this.recipientStatus.dispatchAllowed(claimed))
|
|
364
|
+
throw deliveryError('DELIVERY_STATUS_NOT_SUPPORTED');
|
|
365
|
+
const relay = await this.options.resolveTransport(claimed.relayUrl, this.owner);
|
|
366
|
+
attemptSignal.throwIfAborted();
|
|
367
|
+
if (this.store.now() >= claimed.deliveryDeadline)
|
|
368
|
+
throw deliveryError('OUTBOX_DEADLINE_EXPIRED');
|
|
369
|
+
if (claimed.kind === 'delivery-status' && !this.recipientStatus.dispatchAllowed(claimed))
|
|
370
|
+
throw deliveryError('DELIVERY_STATUS_NOT_SUPPORTED');
|
|
371
|
+
await relay.send(structuredClone(claimed.envelope));
|
|
372
|
+
relayDid = relay.getDeliveryStatusPin?.(claimed.envelopeId);
|
|
373
|
+
})(), Math.max(1, ceiling), signal);
|
|
374
|
+
admitted = true;
|
|
375
|
+
}
|
|
376
|
+
catch (e) {
|
|
377
|
+
error = e;
|
|
378
|
+
const value = e;
|
|
379
|
+
admitted = e instanceof Error && e.name === 'RelayDuplicateEnvelopeError' && value.code === 'REPLAYED_ENVELOPE' &&
|
|
380
|
+
value.status === 400 && e.message === 'RELAY_REJECTED: 400';
|
|
381
|
+
}
|
|
382
|
+
// Leave an interrupted attempt's lease for bounded recovery; shutdown does not publish an outcome.
|
|
383
|
+
signal.throwIfAborted();
|
|
384
|
+
await this.prepare(signal);
|
|
385
|
+
this.store.transaction(() => {
|
|
386
|
+
const r = this.store.read('outbox', id);
|
|
387
|
+
if (!r || r.state !== 'pending' || r.lease?.holderId !== holderId)
|
|
388
|
+
return;
|
|
389
|
+
delete r.lease;
|
|
390
|
+
const now = this.store.now();
|
|
391
|
+
if (admitted) {
|
|
392
|
+
r.state = 'admitted';
|
|
393
|
+
r.admittedAt = now;
|
|
394
|
+
delete r.lastError;
|
|
395
|
+
if (relayDid)
|
|
396
|
+
r.relayDid = relayDid;
|
|
397
|
+
const m = r.kind === 'task' && this.store.read('messages', r.messageId);
|
|
398
|
+
if (m && r.envelopeId === m.messageId && m.status === 'queued')
|
|
399
|
+
this.store.write('messages', m.messageId, { ...m, status: 'relayed', updatedAt: now });
|
|
400
|
+
}
|
|
401
|
+
else if (now >= r.deliveryDeadline || !isTransientRelayError(error)) {
|
|
402
|
+
if (error instanceof Error && now < r.deliveryDeadline)
|
|
403
|
+
this.terminalErrors.set(id, error);
|
|
404
|
+
const code = now >= r.deliveryDeadline ? 'OUTBOX_DEADLINE_EXPIRED' : errorCode(error);
|
|
405
|
+
if (r.kind === 'delivery-status')
|
|
406
|
+
this.store.write('outbox', id, { ...r, state: 'closed', lastError: code });
|
|
407
|
+
else
|
|
408
|
+
this.closeLocked(r.messageId, code, now >= r.deliveryDeadline ? 'deadline' : 'local');
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
r.lastError = errorCode(error);
|
|
413
|
+
const detail = error;
|
|
414
|
+
const hint = error instanceof Error && error.name === 'RelayAdmissionError' && detail.status === 429 &&
|
|
415
|
+
detail.code === 'RELAY_RETRY_LATER' && Number.isSafeInteger(detail.retryAfterMs) &&
|
|
416
|
+
detail.retryAfterMs > 0 && detail.retryAfterMs <= 3_600_000 ? detail.retryAfterMs : 0;
|
|
417
|
+
r.nextAttemptAt = Math.min(r.deliveryDeadline, now + Math.max(outboxRetryDelay(r.attemptCount), hint));
|
|
418
|
+
}
|
|
419
|
+
// Admission and retry scheduling may be replayed after interruption; enqueue remains published.
|
|
420
|
+
this.store.write('outbox', id, r);
|
|
421
|
+
});
|
|
422
|
+
if (signal.aborted)
|
|
423
|
+
signal.throwIfAborted();
|
|
424
|
+
}
|
|
425
|
+
closeLocked(messageId, code, closedBy) {
|
|
426
|
+
const now = this.store.now();
|
|
427
|
+
for (const summary of this.store.pending(messageId))
|
|
428
|
+
if (summary.kind === 'task') {
|
|
429
|
+
const r = this.store.read('outbox', summary.envelopeId);
|
|
430
|
+
const next = { ...r, state: 'closed', lastError: code };
|
|
431
|
+
delete next.lease;
|
|
432
|
+
this.store.write('outbox', r.envelopeId, next);
|
|
433
|
+
}
|
|
434
|
+
const m = this.store.read('messages', messageId);
|
|
435
|
+
if (m && ['queued', 'relayed'].includes(m.status))
|
|
436
|
+
this.store.write('messages', messageId, { ...m, status: 'closed', closedBy, lastError: code, updatedAt: now });
|
|
437
|
+
}
|
|
438
|
+
closeMessage(messageId, code = 'CANCELLED') {
|
|
439
|
+
this.store.transaction(() => this.closeLocked(messageId, code, code === 'OUTBOX_DEADLINE_EXPIRED' ? 'deadline' : 'local'));
|
|
440
|
+
}
|
|
441
|
+
markPeerObserved(messageId, observation) {
|
|
442
|
+
const change = () => {
|
|
443
|
+
const m = this.store.read('messages', messageId);
|
|
444
|
+
requireDelivery(observation.envelopeId === messageId);
|
|
445
|
+
const r = this.store.read('outbox', messageId);
|
|
446
|
+
if (r?.state === 'pending') {
|
|
447
|
+
const next = { ...r, state: 'admitted', admittedAt: this.store.now() };
|
|
448
|
+
delete next.lease;
|
|
449
|
+
this.store.write('outbox', r.envelopeId, next);
|
|
450
|
+
}
|
|
451
|
+
if (m && (['queued', 'relayed'].includes(m.status) || observation.status === 'replied'))
|
|
452
|
+
this.store.write('messages', messageId, { ...m, status: observation.status, updatedAt: this.store.now(),
|
|
453
|
+
...(observation.replyEnvelopeId ? { replyEnvelopeId: observation.replyEnvelopeId } : {}) });
|
|
454
|
+
};
|
|
455
|
+
if (observation.replyEnvelopeId)
|
|
456
|
+
this.store.preparedTransaction(change);
|
|
457
|
+
else
|
|
458
|
+
this.store.transaction(change);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
export const GatewayDeliveryService = GatewayDeliveryServiceImplementation;
|
|
462
|
+
//# sourceMappingURL=outbox.js.map
|