@lostgradient/weft 0.18.0 → 0.19.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.md +3 -1
- package/dist/cli/conformance.js +14 -18
- package/dist/cli/generated/operation-catalog.snapshot.json +258 -16
- package/dist/cli/generated/operation-client.generated.d.ts +30 -6
- package/dist/cli/generated/operation-client.generated.js +4 -0
- package/dist/core/context/child-workflow-pipe.d.ts +1 -1
- package/dist/core/context/child-workflow-pipe.js +1 -1
- package/dist/core/engine/construction.d.ts +2 -0
- package/dist/core/engine/construction.js +3 -0
- package/dist/core/engine/index.js +1 -0
- package/dist/core/engine/listing.d.ts +1 -0
- package/dist/core/engine/listing.js +6 -4
- package/dist/core/weft-error.d.ts +1 -1
- package/dist/core/weft-error.js +2 -1
- package/dist/core/worker-execution-dispatcher.d.ts +8 -0
- package/dist/core/worker-execution-dispatcher.js +4 -0
- package/dist/core/worker-execution-strategy-options.d.ts +16 -0
- package/dist/core/worker-execution-strategy.js +71 -89
- package/dist/core/worker-fault-handling.d.ts +45 -0
- package/dist/core/worker-fault-handling.js +88 -0
- package/dist/core/worker-protocol.d.ts +18 -1
- package/dist/core/worker-protocol.js +1 -1
- package/dist/core/worker-realm-readiness.d.ts +78 -0
- package/dist/core/worker-realm-readiness.js +127 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +28 -1
- package/dist/json-schema.js +1 -1
- package/dist/runtime/portable.d.ts +14 -0
- package/dist/runtime/portable.js +10 -0
- package/dist/server/index.d.ts +71 -8
- package/dist/server/index.js +7 -3
- package/dist/server/operations/get-task-diagnostics.d.ts +43 -18
- package/dist/server/operations/get-task-diagnostics.js +87 -126
- package/dist/server/operations/get-worker-diagnostics.d.ts +177 -0
- package/dist/server/operations/get-worker-diagnostics.js +117 -0
- package/dist/server/operations/list-worker-registration-rejections.d.ts +79 -0
- package/dist/server/operations/list-worker-registration-rejections.js +60 -0
- package/dist/server/operations/list-workers.js +1 -3
- package/dist/server/operations/static-registrations.js +5 -20
- package/dist/server/rest-bindings.js +31 -2
- package/dist/server/runtime/authentication-bridge.js +6 -14
- package/dist/server/runtime/context.d.ts +23 -0
- package/dist/server/runtime/task-dispatch.d.ts +7 -1
- package/dist/server/runtime/task-dispatch.js +126 -55
- package/dist/server/runtime/task-ledger-completion.d.ts +68 -0
- package/dist/server/runtime/task-ledger-completion.js +72 -0
- package/dist/server/runtime/task-ledger-recovery.d.ts +55 -0
- package/dist/server/runtime/task-ledger-recovery.js +73 -0
- package/dist/server/runtime/task-ledger-runtime.d.ts +64 -0
- package/dist/server/runtime/task-ledger-runtime.js +34 -0
- package/dist/server/runtime/task-metrics.d.ts +6 -4
- package/dist/server/runtime/task-polling.d.ts +14 -12
- package/dist/server/runtime/task-polling.js +106 -82
- package/dist/server/runtime/task-reconciliation.d.ts +35 -8
- package/dist/server/runtime/task-reconciliation.js +75 -74
- package/dist/server/runtime/task-result-resolution.d.ts +2 -10
- package/dist/server/runtime/task-result-resolution.js +0 -67
- package/dist/server/runtime/task-result-view.d.ts +82 -0
- package/dist/server/runtime/task-result-view.js +48 -0
- package/dist/server/runtime/websocket-worker-messaging.d.ts +16 -0
- package/dist/server/runtime/websocket-worker-messaging.js +32 -0
- package/dist/server/runtime/websocket-worker-registration.d.ts +41 -0
- package/dist/server/runtime/websocket-worker-registration.js +134 -0
- package/dist/server/runtime/websocket-worker.d.ts +0 -1
- package/dist/server/runtime/websocket-worker.js +64 -116
- package/dist/server/serve-internals.d.ts +9 -6
- package/dist/server/serve-internals.js +14 -40
- package/dist/server/task-ledger-codec.d.ts +40 -0
- package/dist/server/task-ledger-codec.js +195 -0
- package/dist/server/task-ledger-keys.d.ts +13 -0
- package/dist/server/task-ledger-keys.js +5 -0
- package/dist/server/task-ledger-limits.d.ts +34 -0
- package/dist/server/task-ledger-limits.js +5 -0
- package/dist/server/task-ledger-transition-helpers.d.ts +38 -0
- package/dist/server/task-ledger-transition-helpers.js +42 -0
- package/dist/server/task-ledger-transitions-cancellation.d.ts +31 -0
- package/dist/server/task-ledger-transitions-cancellation.js +73 -0
- package/dist/server/task-ledger-transitions.d.ts +129 -0
- package/dist/server/task-ledger-transitions.js +162 -0
- package/dist/server/task-ledger-types.d.ts +220 -0
- package/dist/server/task-ledger-types.js +1 -0
- package/dist/server/task-ledger.d.ts +17 -0
- package/dist/server/task-ledger.js +24 -0
- package/dist/server/task-state.d.ts +25 -152
- package/dist/server/task-state.js +0 -188
- package/dist/server/worker-admission-policy.d.ts +70 -0
- package/dist/server/worker-admission-policy.js +0 -0
- package/dist/storage/compressed-storage.js +1 -1
- package/dist/storage/index.d.ts +0 -2
- package/dist/storage/interface.d.ts +0 -2
- package/dist/storage/interface.js +1 -1
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon.js +1 -1
- package/dist/storage/postgres.js +1 -1
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/testing.js +1 -1
- package/dist/storage/turso.js +1 -1
- package/dist/storage/web-extension.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/web-extension.js +1 -1
- package/dist/worker/index.d.ts +2 -0
- package/dist/worker/index.js +30 -4
- package/dist/worker/manifest/canonical-json.d.ts +20 -0
- package/dist/worker/manifest/canonical-json.js +13 -0
- package/dist/worker/manifest/capabilities.d.ts +24 -0
- package/dist/worker/manifest/capabilities.js +74 -0
- package/dist/worker/manifest/content-digest.d.ts +20 -0
- package/dist/worker/manifest/content-digest.js +13 -0
- package/dist/worker/manifest/declared-shape-digest.d.ts +14 -0
- package/dist/worker/manifest/declared-shape-digest.js +4 -0
- package/dist/worker/manifest/digest.d.ts +82 -0
- package/dist/worker/manifest/digest.js +9 -0
- package/dist/worker/manifest/execution-identity.d.ts +92 -0
- package/dist/worker/manifest/execution-identity.js +36 -0
- package/dist/worker/manifest/failure.d.ts +59 -0
- package/dist/worker/manifest/failure.js +4 -0
- package/dist/worker/manifest/index.d.ts +22 -0
- package/dist/worker/manifest/index.js +23 -0
- package/dist/worker/manifest/internal-realm.d.ts +50 -0
- package/dist/worker/manifest/internal-realm.js +39 -0
- package/dist/worker/manifest/is-record.d.ts +12 -0
- package/dist/worker/manifest/is-record.js +6 -0
- package/dist/worker/manifest/json-scan.d.ts +26 -0
- package/dist/worker/manifest/json-scan.js +88 -0
- package/dist/worker/manifest/limits.d.ts +138 -0
- package/dist/worker/manifest/limits.js +1 -0
- package/dist/worker/manifest/normalize.d.ts +63 -0
- package/dist/worker/manifest/normalize.js +73 -0
- package/dist/worker/manifest/parse-json.d.ts +24 -0
- package/dist/worker/manifest/parse-json.js +15 -0
- package/dist/worker/manifest/parse.d.ts +86 -0
- package/dist/worker/manifest/parse.js +188 -0
- package/dist/worker/manifest/registry-contract-builder.d.ts +120 -0
- package/dist/worker/manifest/registry-contract-builder.js +65 -0
- package/dist/worker/manifest/types.d.ts +263 -0
- package/dist/worker/manifest/types.js +1 -0
- package/dist/worker/manifest/utf8.d.ts +13 -0
- package/dist/worker/manifest/utf8.js +7 -0
- package/dist/worker/options.d.ts +55 -8
- package/dist/worker/options.js +65 -9
- package/dist/worker/protocol-internals.d.ts +1 -2
- package/dist/worker/protocol-internals.js +0 -5
- package/dist/worker/protocol-messages.d.ts +33 -17
- package/dist/worker/protocol-schemas.d.ts +36 -74
- package/dist/worker/protocol-schemas.js +25 -18
- package/dist/worker/protocol-version.d.ts +18 -14
- package/dist/worker/protocol-version.js +1 -1
- package/dist/worker/protocol.js +1 -1
- package/dist/worker/registry/deployment-consistency.d.ts +78 -0
- package/dist/worker/registry/deployment-consistency.js +27 -0
- package/dist/worker/registry/drain.d.ts +14 -0
- package/dist/worker/registry/drain.js +24 -0
- package/dist/worker/registry/rejections.d.ts +31 -0
- package/dist/worker/registry/rejections.js +10 -0
- package/dist/worker/registry/summary.d.ts +0 -3
- package/dist/worker/registry/summary.js +2 -5
- package/dist/worker/registry/types.d.ts +10 -3
- package/dist/worker/registry.d.ts +47 -2
- package/dist/worker/registry.js +45 -32
- package/dist/workers/workflow-worker-entry.d.ts +12 -7
- package/dist/workers/workflow-worker-entry.js +9 -2
- package/package.json +3 -3
- package/dist/server/task-resolved-record.d.ts +0 -12
- package/dist/server/task-resolved-record.js +0 -35
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { WorkerConnectedEvent } from "../../core/events.js";
|
|
2
|
+
import { digestCanonicalWorkerManifest, parseWorkerManifest } from "../../worker/manifest/index.js";
|
|
3
|
+
import {
|
|
4
|
+
REMOTE_WORKER_PROTOCOL_VERSION
|
|
5
|
+
} from "../../worker/protocol.js";
|
|
6
|
+
import { isAuthenticated } from "../principal.js";
|
|
7
|
+
import { rejectRegistration, sendWorkerProtocolMessage } from "./websocket-worker-messaging.js";
|
|
8
|
+
const MAX_WORKER_CONCURRENCY = 1000, DEFAULT_WORKER_CONCURRENCY = 10;
|
|
9
|
+
function rejectAndRecordRegistration(context, ws, code, message, workerId, requestedProtocolVersion, identity) {
|
|
10
|
+
context.registry.recordRejection({
|
|
11
|
+
code,
|
|
12
|
+
...workerId !== void 0 ? { workerId } : {},
|
|
13
|
+
rejectedAt: Date.now(),
|
|
14
|
+
queue: ws.data.queue ?? "default",
|
|
15
|
+
...identity?.deploymentName !== void 0 ? { deploymentName: identity.deploymentName } : {},
|
|
16
|
+
...identity?.buildId !== void 0 ? { buildId: identity.buildId } : {}
|
|
17
|
+
});
|
|
18
|
+
rejectRegistration(ws, code, message, requestedProtocolVersion);
|
|
19
|
+
}
|
|
20
|
+
function principalMayRegisterWorker(principal) {
|
|
21
|
+
if (principal === void 0)
|
|
22
|
+
return !0;
|
|
23
|
+
return isAuthenticated(principal) && principal.hasScope("workers:write");
|
|
24
|
+
}
|
|
25
|
+
function deriveActivitiesFromManifest(manifest) {
|
|
26
|
+
const activities = [];
|
|
27
|
+
for (const [workflowType, workflow] of Object.entries(manifest.workflows))
|
|
28
|
+
for (const activityName of Object.keys(workflow.activities))
|
|
29
|
+
activities.push(`${workflowType}.${activityName}`);
|
|
30
|
+
return activities;
|
|
31
|
+
}
|
|
32
|
+
function buildWorkerRegistrationInfo(message, manifest, acceptedManifestDigest, queue, concurrency) {
|
|
33
|
+
const runtimeVersion = manifest.runtime.version === "" ? void 0 : manifest.runtime.version;
|
|
34
|
+
return {
|
|
35
|
+
id: message.workerId,
|
|
36
|
+
queue,
|
|
37
|
+
activities: deriveActivitiesFromManifest(manifest),
|
|
38
|
+
concurrency,
|
|
39
|
+
deploymentName: manifest.deployment.name,
|
|
40
|
+
buildId: manifest.deployment.buildId,
|
|
41
|
+
...runtimeVersion !== void 0 ? { runtimeVersion } : {},
|
|
42
|
+
manifest,
|
|
43
|
+
acceptedManifestDigest,
|
|
44
|
+
...message.startedAt !== void 0 ? { startedAt: message.startedAt } : {},
|
|
45
|
+
capabilities: manifest.capabilities
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function admitReconnectingSocket(context, ws, message, manifest) {
|
|
49
|
+
const pendingRequeue = context.pendingWorkerRequeues.get(message.workerId), isGracePeriodReconnect = pendingRequeue !== void 0;
|
|
50
|
+
if (isGracePeriodReconnect) {
|
|
51
|
+
clearTimeout(pendingRequeue);
|
|
52
|
+
context.pendingWorkerRequeues.delete(message.workerId);
|
|
53
|
+
}
|
|
54
|
+
const existingSocket = context.workerSockets.get(message.workerId);
|
|
55
|
+
if (!isGracePeriodReconnect && existingSocket !== void 0 && existingSocket !== ws) {
|
|
56
|
+
rejectAndRecordRegistration(context, ws, "invalid_registration", "workerId is already registered to an active connection", message.workerId, message.protocolVersion, { deploymentName: manifest.deployment.name, buildId: manifest.deployment.buildId });
|
|
57
|
+
return !1;
|
|
58
|
+
}
|
|
59
|
+
return !0;
|
|
60
|
+
}
|
|
61
|
+
function evaluateWorkerAdmissionPolicy(policy, request) {
|
|
62
|
+
try {
|
|
63
|
+
return policy(request);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return {
|
|
66
|
+
status: "rejected",
|
|
67
|
+
reason: `workerAdmissionPolicy threw: ${error instanceof Error ? error.message : String(error)}`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function commitWorkerRegistration(context, options, ws, message, manifest, queue, concurrency, acceptedManifestDigest) {
|
|
72
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
73
|
+
return;
|
|
74
|
+
if (!context.registry.checkDeploymentConsistency(manifest.deployment.name, manifest.deployment.buildId, manifest.deployment.artifactDigest).ok) {
|
|
75
|
+
rejectAndRecordRegistration(context, ws, "deployment_conflict", `Deployment "${manifest.deployment.name}" build "${manifest.deployment.buildId}" is already registered with a different artifact digest`, message.workerId, message.protocolVersion, { deploymentName: manifest.deployment.name, buildId: manifest.deployment.buildId });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (!admitReconnectingSocket(context, ws, message, manifest))
|
|
79
|
+
return;
|
|
80
|
+
context.registry.recordDeploymentConsistency(manifest.deployment.name, manifest.deployment.buildId, manifest.deployment.artifactDigest);
|
|
81
|
+
const registrationInfo = buildWorkerRegistrationInfo(message, manifest, acceptedManifestDigest, queue, concurrency);
|
|
82
|
+
ws.data.workerId = message.workerId;
|
|
83
|
+
ws.data.workerRegistered = !0;
|
|
84
|
+
ws.data.workerProtocolVersion = message.protocolVersion;
|
|
85
|
+
context.registry.register(registrationInfo);
|
|
86
|
+
context.workerSockets.set(message.workerId, ws);
|
|
87
|
+
sendWorkerProtocolMessage(ws, {
|
|
88
|
+
type: "registerAck",
|
|
89
|
+
protocolVersion: REMOTE_WORKER_PROTOCOL_VERSION,
|
|
90
|
+
workerId: message.workerId,
|
|
91
|
+
queue,
|
|
92
|
+
concurrency,
|
|
93
|
+
acceptedManifestDigest,
|
|
94
|
+
serverCapabilities: []
|
|
95
|
+
});
|
|
96
|
+
options.engine.dispatchEvent(new WorkerConnectedEvent(message.workerId, queue, registrationInfo.activities, concurrency));
|
|
97
|
+
}
|
|
98
|
+
export async function registerWorker(context, options, ws, message) {
|
|
99
|
+
try {
|
|
100
|
+
await context.taskLedgerRecovery.ready;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
rejectAndRecordRegistration(context, ws, "registration_rejected", `Server task-ledger recovery failed \u2014 cannot admit new workers: ${error instanceof Error ? error.message : String(error)}`, message.workerId, message.protocolVersion);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!principalMayRegisterWorker(ws.data.principal)) {
|
|
106
|
+
rejectAndRecordRegistration(context, ws, "invalid_registration", "Worker registration requires the workers:write scope", message.workerId, message.protocolVersion);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const parsedManifest = parseWorkerManifest(message.manifest);
|
|
110
|
+
if (!parsedManifest.ok) {
|
|
111
|
+
rejectAndRecordRegistration(context, ws, "invalid_registration", parsedManifest.message, message.workerId, message.protocolVersion);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const { manifest, canonicalJson } = parsedManifest;
|
|
115
|
+
if (manifest.protocolVersion !== message.protocolVersion) {
|
|
116
|
+
rejectAndRecordRegistration(context, ws, "invalid_registration", `Manifest declares protocolVersion ${String(manifest.protocolVersion)}, which does not match the register message's protocolVersion ${String(message.protocolVersion)}`, message.workerId, message.protocolVersion, { deploymentName: manifest.deployment.name, buildId: manifest.deployment.buildId });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const queue = ws.data.queue ?? "default";
|
|
120
|
+
if (options.workerAdmissionPolicy !== void 0) {
|
|
121
|
+
const decision = evaluateWorkerAdmissionPolicy(options.workerAdmissionPolicy, {
|
|
122
|
+
principal: ws.data.principal,
|
|
123
|
+
workerId: message.workerId,
|
|
124
|
+
queue,
|
|
125
|
+
manifest
|
|
126
|
+
});
|
|
127
|
+
if (decision.status === "rejected") {
|
|
128
|
+
rejectAndRecordRegistration(context, ws, "registration_rejected", decision.reason, message.workerId, message.protocolVersion, { deploymentName: manifest.deployment.name, buildId: manifest.deployment.buildId });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const rawConcurrency = message.concurrency ?? DEFAULT_WORKER_CONCURRENCY, clampedConcurrency = Math.min(Math.max(1, Math.floor(rawConcurrency)), MAX_WORKER_CONCURRENCY), acceptedManifestDigest = await digestCanonicalWorkerManifest(canonicalJson);
|
|
133
|
+
commitWorkerRegistration(context, options, ws, message, manifest, queue, clampedConcurrency, acceptedManifestDigest);
|
|
134
|
+
}
|
|
@@ -2,6 +2,5 @@ import type { ServerWebSocket } from 'bun';
|
|
|
2
2
|
import type { ServeOptions } from '../index.ts';
|
|
3
3
|
import type { WebSocketData } from '../json-rpc-websocket-runtime.ts';
|
|
4
4
|
import type { ServerContext } from './context.ts';
|
|
5
|
-
export { isInflightRecord } from '../task-state.ts';
|
|
6
5
|
export { withRetry } from './retry.ts';
|
|
7
6
|
export declare function handleWorkerWebSocketMessage(context: ServerContext, options: ServeOptions, ws: ServerWebSocket<WebSocketData>, rawMessage: string | Buffer, cleanupWorkflowIndex: (operationId: string) => void): void;
|
|
@@ -1,104 +1,31 @@
|
|
|
1
|
-
import { decode, encode } from "../../core/codec.js";
|
|
2
|
-
import { WorkerConnectedEvent } from "../../core/events.js";
|
|
3
|
-
import { KEYS } from "../../storage/interface.js";
|
|
4
1
|
import {
|
|
5
2
|
REMOTE_WORKER_PROTOCOL_VERSION,
|
|
6
|
-
REMOTE_WORKER_SUPPORTED_PROTOCOL_VERSIONS,
|
|
7
3
|
parseWorkerToServerMessage
|
|
8
4
|
} from "../../worker/protocol.js";
|
|
9
5
|
import { workerProtocolIncompatibleMessage } from "../../worker/worker-protocol-incompatible-error.js";
|
|
10
|
-
import {
|
|
11
|
-
import { isInflightRecord, readInflightRecord } from "../task-state.js";
|
|
6
|
+
import { renewAttemptLease } from "../task-ledger-transitions.js";
|
|
12
7
|
import { withRetry } from "./retry.js";
|
|
13
|
-
import { recordWorkerCapacitySaturationMetric } from "./task-metrics.js";
|
|
14
8
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from "./task-
|
|
9
|
+
commitTaskLedgerCompletion,
|
|
10
|
+
dispatchTaskDeadLetteredEvent
|
|
11
|
+
} from "./task-ledger-completion.js";
|
|
12
|
+
import { commitTaskLedgerTransition } from "./task-ledger-runtime.js";
|
|
13
|
+
import {
|
|
14
|
+
recordTaskExecutionLatencyMetric,
|
|
15
|
+
recordWorkerCapacitySaturationMetric
|
|
16
|
+
} from "./task-metrics.js";
|
|
17
|
+
import { taskResultPayloadSizeError } from "./task-result-resolution.js";
|
|
18
18
|
import { WORKER_STREAM_RE } from "./websocket-upgrade.js";
|
|
19
|
-
|
|
19
|
+
import {
|
|
20
|
+
rejectProtocolMessage,
|
|
21
|
+
rejectRegistration,
|
|
22
|
+
sendWorkerProtocolMessage
|
|
23
|
+
} from "./websocket-worker-messaging.js";
|
|
24
|
+
import { registerWorker } from "./websocket-worker-registration.js";
|
|
20
25
|
function isWorkerConnection(pathname) {
|
|
21
26
|
return WORKER_STREAM_RE.test(pathname);
|
|
22
27
|
}
|
|
23
|
-
export { isInflightRecord } from "../task-state.js";
|
|
24
28
|
export { withRetry } from "./retry.js";
|
|
25
|
-
function sendWorkerProtocolMessage(ws, message) {
|
|
26
|
-
ws.send(JSON.stringify(message));
|
|
27
|
-
}
|
|
28
|
-
function closeWorkerSocket(ws, code, reason) {
|
|
29
|
-
try {
|
|
30
|
-
ws.unsubscribe(ws.data.pathname);
|
|
31
|
-
} catch {}
|
|
32
|
-
ws.close(code, reason);
|
|
33
|
-
setTimeout(() => {
|
|
34
|
-
try {
|
|
35
|
-
ws.terminate();
|
|
36
|
-
} catch {}
|
|
37
|
-
}, 10);
|
|
38
|
-
}
|
|
39
|
-
function rejectRegistration(ws, code, message, requestedProtocolVersion) {
|
|
40
|
-
sendWorkerProtocolMessage(ws, {
|
|
41
|
-
type: "registerError",
|
|
42
|
-
code,
|
|
43
|
-
message,
|
|
44
|
-
supportedProtocolVersions: REMOTE_WORKER_SUPPORTED_PROTOCOL_VERSIONS,
|
|
45
|
-
...requestedProtocolVersion !== void 0 ? { requestedProtocolVersion } : {}
|
|
46
|
-
});
|
|
47
|
-
closeWorkerSocket(ws, WORKER_REGISTRATION_CLOSE_CODE, code);
|
|
48
|
-
}
|
|
49
|
-
function rejectProtocolMessage(ws, code, message) {
|
|
50
|
-
sendWorkerProtocolMessage(ws, { type: "protocolError", code, message });
|
|
51
|
-
closeWorkerSocket(ws, WORKER_PROTOCOL_CLOSE_CODE, code);
|
|
52
|
-
}
|
|
53
|
-
function principalMayRegisterWorker(principal) {
|
|
54
|
-
if (principal === void 0)
|
|
55
|
-
return !0;
|
|
56
|
-
return isAuthenticated(principal) && principal.hasScope("workers:write");
|
|
57
|
-
}
|
|
58
|
-
function buildWorkerRegistrationInfo(message, queue, concurrency) {
|
|
59
|
-
return {
|
|
60
|
-
id: message.workerId,
|
|
61
|
-
queue,
|
|
62
|
-
activities: [...message.activities],
|
|
63
|
-
concurrency,
|
|
64
|
-
...message.deploymentName !== void 0 ? { deploymentName: message.deploymentName } : {},
|
|
65
|
-
...message.buildId !== void 0 ? { buildId: message.buildId } : {},
|
|
66
|
-
...message.runtimeVersion !== void 0 ? { runtimeVersion: message.runtimeVersion } : {},
|
|
67
|
-
...message.gitSha !== void 0 ? { gitSha: message.gitSha } : {},
|
|
68
|
-
...message.startedAt !== void 0 ? { startedAt: message.startedAt } : {},
|
|
69
|
-
...message.capabilities !== void 0 ? { capabilities: message.capabilities } : {}
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
function registerWorker(context, options, ws, message) {
|
|
73
|
-
if (!principalMayRegisterWorker(ws.data.principal)) {
|
|
74
|
-
rejectRegistration(ws, "invalid_registration", "Worker registration requires the workers:write scope", message.protocolVersion);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const rawConcurrency = message.concurrency ?? DEFAULT_WORKER_CONCURRENCY, clampedConcurrency = Math.min(Math.max(1, Math.floor(rawConcurrency)), MAX_WORKER_CONCURRENCY), queue = ws.data.queue ?? "default", pendingRequeue = context.pendingWorkerRequeues.get(message.workerId), isGracePeriodReconnect = pendingRequeue !== void 0;
|
|
78
|
-
if (isGracePeriodReconnect) {
|
|
79
|
-
clearTimeout(pendingRequeue);
|
|
80
|
-
context.pendingWorkerRequeues.delete(message.workerId);
|
|
81
|
-
}
|
|
82
|
-
const existingSocket = context.workerSockets.get(message.workerId);
|
|
83
|
-
if (!isGracePeriodReconnect && existingSocket !== void 0 && existingSocket !== ws) {
|
|
84
|
-
rejectRegistration(ws, "invalid_registration", "workerId is already registered to an active connection", message.protocolVersion);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
ws.data.workerId = message.workerId;
|
|
88
|
-
ws.data.workerRegistered = !0;
|
|
89
|
-
ws.data.workerProtocolVersion = message.protocolVersion;
|
|
90
|
-
context.registry.register(buildWorkerRegistrationInfo(message, queue, clampedConcurrency));
|
|
91
|
-
context.workerSockets.set(message.workerId, ws);
|
|
92
|
-
sendWorkerProtocolMessage(ws, {
|
|
93
|
-
type: "registerAck",
|
|
94
|
-
protocolVersion: REMOTE_WORKER_PROTOCOL_VERSION,
|
|
95
|
-
workerId: message.workerId,
|
|
96
|
-
queue,
|
|
97
|
-
activities: [...message.activities],
|
|
98
|
-
concurrency: clampedConcurrency
|
|
99
|
-
});
|
|
100
|
-
options.engine.dispatchEvent(new WorkerConnectedEvent(message.workerId, queue, [...message.activities], clampedConcurrency));
|
|
101
|
-
}
|
|
102
29
|
function resolveTaskResultStatus(message) {
|
|
103
30
|
return message.status === "completed" ? "completed" : "failed";
|
|
104
31
|
}
|
|
@@ -121,9 +48,7 @@ function onTaskResultMessage(context, options, ws, message, cleanupWorkflowIndex
|
|
|
121
48
|
return;
|
|
122
49
|
}
|
|
123
50
|
const resolvedStatus = resolveTaskResultStatus(message), payloadError = taskResultPayloadSizeError({
|
|
124
|
-
operationId,
|
|
125
51
|
status: resolvedStatus,
|
|
126
|
-
resolutionReason: resolvedStatus,
|
|
127
52
|
...message.status === "completed" ? { value: message.value } : { error: message.error }
|
|
128
53
|
}, context.payloadSizeMaxBytes);
|
|
129
54
|
context.registry.completeTask(operationId);
|
|
@@ -131,30 +56,40 @@ function onTaskResultMessage(context, options, ws, message, cleanupWorkflowIndex
|
|
|
131
56
|
cleanupWorkflowIndex(operationId);
|
|
132
57
|
recordWorkerCapacitySaturationMetric(context.metricsCollector, context.registry);
|
|
133
58
|
(async () => {
|
|
134
|
-
const inflightRecord = await readInflightRecord(options.engine.storage, operationId);
|
|
135
59
|
if (payloadError !== null) {
|
|
136
60
|
sendWorkerProtocolMessage(ws, {
|
|
137
61
|
type: "protocolError",
|
|
138
62
|
code: "invalid_message",
|
|
139
63
|
message: payloadError.message
|
|
140
64
|
});
|
|
141
|
-
await
|
|
65
|
+
const rejected = await commitTaskLedgerCompletion(options.engine.storage, {
|
|
142
66
|
operationId,
|
|
67
|
+
attemptToken: message.attemptToken,
|
|
143
68
|
status: "failed",
|
|
144
|
-
|
|
145
|
-
inflightRecord,
|
|
146
|
-
error: payloadError.message,
|
|
147
|
-
skipPayloadSizeCheck: !0
|
|
69
|
+
error: payloadError.message
|
|
148
70
|
});
|
|
71
|
+
if (rejected.ok)
|
|
72
|
+
recordTaskExecutionLatencyMetric(context.metricsCollector, { startedAt: rejected.completing.startedAt }, Date.now());
|
|
73
|
+
else {
|
|
74
|
+
console.error(`[weft] Failed to persist oversized task result rejection for task "${operationId}":`, rejected.reason);
|
|
75
|
+
if (rejected.deadLettered !== void 0)
|
|
76
|
+
dispatchTaskDeadLetteredEvent(options, operationId, rejected.deadLettered, workerId);
|
|
77
|
+
}
|
|
149
78
|
return;
|
|
150
79
|
}
|
|
151
|
-
await
|
|
80
|
+
const committed = await commitTaskLedgerCompletion(options.engine.storage, {
|
|
152
81
|
operationId,
|
|
82
|
+
attemptToken: message.attemptToken,
|
|
153
83
|
status: resolvedStatus,
|
|
154
|
-
resolutionReason: resolvedStatus,
|
|
155
|
-
inflightRecord,
|
|
156
84
|
...message.status === "completed" ? { value: message.value } : { error: message.error }
|
|
157
85
|
});
|
|
86
|
+
if (committed.ok)
|
|
87
|
+
recordTaskExecutionLatencyMetric(context.metricsCollector, { startedAt: committed.completing.startedAt }, Date.now());
|
|
88
|
+
else {
|
|
89
|
+
console.error(`[weft] Failed to commit task result for "${operationId}" through the durable ledger:`, committed.reason);
|
|
90
|
+
if (committed.deadLettered !== void 0)
|
|
91
|
+
dispatchTaskDeadLetteredEvent(options, operationId, committed.deadLettered, workerId);
|
|
92
|
+
}
|
|
158
93
|
})().catch((error) => {
|
|
159
94
|
console.error(`[weft] Failed to transition task "${operationId}" to resolved \u2014 inflight record may leak:`, error);
|
|
160
95
|
});
|
|
@@ -169,29 +104,40 @@ function onHeartbeatMessage(context, options, ws, _message) {
|
|
|
169
104
|
if (newDeadline !== void 0) {
|
|
170
105
|
context.deadlineTracker.remove(task.operationId);
|
|
171
106
|
context.deadlineTracker.add({ operationId: task.operationId, deadline: newDeadline });
|
|
172
|
-
const opId = task.operationId, heartbeatWorkerId = ws.data.workerId;
|
|
107
|
+
const opId = task.operationId, heartbeatWorkerId = ws.data.workerId, attemptToken = task.attemptToken;
|
|
173
108
|
withRetry(async () => {
|
|
174
109
|
if (!context.registry.isAssigned(opId))
|
|
175
110
|
return;
|
|
176
111
|
if (!context.registry.getWorkerTasks(heartbeatWorkerId ?? "").find((trackedTask) => trackedTask.operationId === opId))
|
|
177
112
|
return;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
const updated = { ...decoded, deadline: newDeadline, lastHeartbeatAt: Date.now() };
|
|
186
|
-
await options.engine.storage.put(inflightKey, encode(updated));
|
|
187
|
-
}
|
|
113
|
+
await commitTaskLedgerTransition(options.engine.storage, opId, (current, now) => renewAttemptLease(current, {
|
|
114
|
+
attemptToken,
|
|
115
|
+
workerSessionId: heartbeatWorkerId ?? "",
|
|
116
|
+
leaseDurationMilliseconds: task.visibilityTimeout
|
|
117
|
+
}, now), 1);
|
|
188
118
|
}, `extend visibility for task "${opId}"`).catch((error) => {
|
|
189
119
|
console.error(`[weft] Failed to extend visibility for task "${opId}":`, error);
|
|
190
120
|
});
|
|
191
121
|
}
|
|
192
122
|
}
|
|
193
123
|
}
|
|
194
|
-
function
|
|
124
|
+
function extractOptionalWorkerId(parsed) {
|
|
125
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
126
|
+
return;
|
|
127
|
+
const workerId = parsed.workerId;
|
|
128
|
+
return typeof workerId === "string" && workerId.length > 0 ? workerId : void 0;
|
|
129
|
+
}
|
|
130
|
+
function recordAndRejectRegistrationFrame(context, ws, parsed, code, message, requestedProtocolVersion) {
|
|
131
|
+
const rejectedWorkerId = extractOptionalWorkerId(parsed);
|
|
132
|
+
context.registry.recordRejection({
|
|
133
|
+
code,
|
|
134
|
+
...rejectedWorkerId !== void 0 ? { workerId: rejectedWorkerId } : {},
|
|
135
|
+
rejectedAt: Date.now(),
|
|
136
|
+
queue: ws.data.queue ?? "default"
|
|
137
|
+
});
|
|
138
|
+
rejectRegistration(ws, code, message, requestedProtocolVersion);
|
|
139
|
+
}
|
|
140
|
+
function parseAndValidateWorkerFrame(context, ws, rawMessage) {
|
|
195
141
|
const text = typeof rawMessage === "string" ? rawMessage : new TextDecoder().decode(rawMessage);
|
|
196
142
|
let parsed;
|
|
197
143
|
try {
|
|
@@ -202,12 +148,12 @@ function parseAndValidateWorkerFrame(ws, rawMessage) {
|
|
|
202
148
|
}
|
|
203
149
|
const result = parseWorkerToServerMessage(parsed);
|
|
204
150
|
if (!result.ok) {
|
|
205
|
-
if (result.error.code === "invalid_registration" || result.error.code === "unsupported_protocol_version") {
|
|
151
|
+
if (result.error.code === "invalid_registration" || result.error.code === "unsupported_protocol_version" || result.error.code === "deployment_conflict" || result.error.code === "registration_rejected") {
|
|
206
152
|
const message = result.error.code === "unsupported_protocol_version" ? workerProtocolIncompatibleMessage({
|
|
207
153
|
expected: REMOTE_WORKER_PROTOCOL_VERSION,
|
|
208
154
|
received: result.error.requestedProtocolVersion
|
|
209
155
|
}) : result.error.message;
|
|
210
|
-
|
|
156
|
+
recordAndRejectRegistrationFrame(context, ws, parsed, result.error.code, message, result.error.requestedProtocolVersion);
|
|
211
157
|
return { ok: !1 };
|
|
212
158
|
}
|
|
213
159
|
rejectProtocolMessage(ws, result.error.code, result.error.message);
|
|
@@ -218,7 +164,7 @@ function parseAndValidateWorkerFrame(ws, rawMessage) {
|
|
|
218
164
|
export function handleWorkerWebSocketMessage(context, options, ws, rawMessage, cleanupWorkflowIndex) {
|
|
219
165
|
if (!isWorkerConnection(ws.data.pathname))
|
|
220
166
|
return;
|
|
221
|
-
const parsed = parseAndValidateWorkerFrame(ws, rawMessage);
|
|
167
|
+
const parsed = parseAndValidateWorkerFrame(context, ws, rawMessage);
|
|
222
168
|
if (!parsed.ok)
|
|
223
169
|
return;
|
|
224
170
|
const { message } = parsed;
|
|
@@ -228,7 +174,9 @@ export function handleWorkerWebSocketMessage(context, options, ws, rawMessage, c
|
|
|
228
174
|
}
|
|
229
175
|
switch (message.type) {
|
|
230
176
|
case "register": {
|
|
231
|
-
registerWorker(context, options, ws, message)
|
|
177
|
+
registerWorker(context, options, ws, message).catch((error) => {
|
|
178
|
+
console.error(`[weft] Failed to register worker "${message.workerId}":`, error);
|
|
179
|
+
});
|
|
232
180
|
break;
|
|
233
181
|
}
|
|
234
182
|
case "taskResult": {
|
|
@@ -81,6 +81,15 @@ export declare function resolveNetworkConfig(options: ServeOptions): ResolvedNet
|
|
|
81
81
|
/**
|
|
82
82
|
* Builds the initial `ServerContext` from resolved options. All mutable state
|
|
83
83
|
* maps and the metrics collector are allocated here so `serve()` stays linear.
|
|
84
|
+
*
|
|
85
|
+
* Also starts startup task-ledger recovery (WFT-23) against the fully
|
|
86
|
+
* constructed context before returning it. Recovery runs asynchronously —
|
|
87
|
+
* this function itself stays synchronous, same as `serve()` — but its
|
|
88
|
+
* outcome is captured into `context.taskLedgerRecovery.ready` immediately,
|
|
89
|
+
* before any request can reach a gated entry point, rather than in a
|
|
90
|
+
* separate call `serve()` makes later (as the retired `restoreInflightTasks`
|
|
91
|
+
* required): starting it here minimizes the window between context
|
|
92
|
+
* construction and recovery beginning.
|
|
84
93
|
*/
|
|
85
94
|
export declare function buildServerContext(options: ResolvedServeOptions, serverMetricsCollector: MetricsCollector): ServerContext;
|
|
86
95
|
/**
|
|
@@ -113,9 +122,3 @@ export declare function buildBunServeConfig(port: number, hostname: string, deve
|
|
|
113
122
|
* the most-recently-registered item is torn down first.
|
|
114
123
|
*/
|
|
115
124
|
export declare function registerStackDisposers(stack: AsyncDisposableStack, context: ServerContext, options: ServeOptions, broadcastingHandle: EventBroadcastingHandle, onOperationCleanup: (operationId: string) => void): void;
|
|
116
|
-
/**
|
|
117
|
-
* Restores persisted in-flight task records from storage into the in-memory
|
|
118
|
-
* context. Records whose deadline has already elapsed are removed from storage;
|
|
119
|
-
* the engine will retry them on the next dispatch cycle.
|
|
120
|
-
*/
|
|
121
|
-
export declare function restoreInflightTasks(context: ServerContext, options: ServeOptions): void;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { decode } from "../core/codec.js";
|
|
2
1
|
import { getEnginePayloadSizeMaxBytes } from "../core/engine/payload-size-policy.js";
|
|
3
2
|
import { createMcpSessionManager } from "../mcp/session.js";
|
|
4
3
|
import { createMetricsCollectorExporter, MetricsCollector } from "../observability/metrics.js";
|
|
@@ -25,13 +24,13 @@ import {
|
|
|
25
24
|
registerWorkflowEventLifecycle
|
|
26
25
|
} from "./runtime/event-broadcasting.js";
|
|
27
26
|
import { shutdownAllWorkers } from "./runtime/shutdown.js";
|
|
27
|
+
import { runTaskLedgerRecovery } from "./runtime/task-ledger-recovery.js";
|
|
28
28
|
import {
|
|
29
29
|
consumeManualTaskReconciliationForTesting as consumeManual,
|
|
30
30
|
reconcileOrphanedRecords,
|
|
31
31
|
scanExpiredTasks
|
|
32
32
|
} from "./runtime/task-reconciliation.js";
|
|
33
33
|
import { DEFAULT_MAX_STREAM_CONNECTIONS_PER_WORKFLOW } from "./runtime/websocket-stream.js";
|
|
34
|
-
import { isInflightRecord, withRetry } from "./runtime/websocket-worker.js";
|
|
35
34
|
import { TaskQueue } from "./task-queue.js";
|
|
36
35
|
import { createWorkflowEventFeed } from "./workflow-event-feed.js";
|
|
37
36
|
const RECONCILIATION_MULTIPLIER = 12, DEFAULT_WORKER_RECONNECT_GRACE_PERIOD_MS = 2000, MAX_WORKER_RECONNECT_GRACE_PERIOD_MS = 5000;
|
|
@@ -90,7 +89,13 @@ export function resolveNetworkConfig(options) {
|
|
|
90
89
|
}
|
|
91
90
|
export function buildServerContext(options, serverMetricsCollector) {
|
|
92
91
|
const workerRegistry = new WorkerRegistry(options.routingPolicy !== void 0 ? { policy: options.routingPolicy } : void 0), taskQueue = new TaskQueue(options.schedulingPolicy !== void 0 ? { schedulingPolicy: options.schedulingPolicy } : void 0), engine = options.engine, eventFeedBackend = createEngineEventFeedBackend(engine);
|
|
93
|
-
|
|
92
|
+
let resolveTaskLedgerRecovery, rejectTaskLedgerRecovery;
|
|
93
|
+
const taskLedgerRecoveryReady = new Promise((resolve, reject) => {
|
|
94
|
+
resolveTaskLedgerRecovery = resolve;
|
|
95
|
+
rejectTaskLedgerRecovery = reject;
|
|
96
|
+
});
|
|
97
|
+
taskLedgerRecoveryReady.catch(() => {});
|
|
98
|
+
const context = {
|
|
94
99
|
registry: workerRegistry,
|
|
95
100
|
taskQueue,
|
|
96
101
|
workerSockets: new Map,
|
|
@@ -125,8 +130,12 @@ export function buildServerContext(options, serverMetricsCollector) {
|
|
|
125
130
|
pendingWorkerRequeues: new Map,
|
|
126
131
|
scanRunning: !1,
|
|
127
132
|
processingOperations: new Set,
|
|
128
|
-
reconciliationRunning: !1
|
|
133
|
+
reconciliationRunning: !1,
|
|
134
|
+
taskLedgerRecovery: { ready: taskLedgerRecoveryReady },
|
|
135
|
+
stopping: !1
|
|
129
136
|
};
|
|
137
|
+
runTaskLedgerRecovery(context, options).then(resolveTaskLedgerRecovery, rejectTaskLedgerRecovery);
|
|
138
|
+
return context;
|
|
130
139
|
}
|
|
131
140
|
export function buildFetchHandler(serverHolder, context, options) {
|
|
132
141
|
return (request) => {
|
|
@@ -176,6 +185,7 @@ export function registerStackDisposers(stack, context, options, broadcastingHand
|
|
|
176
185
|
reconcileOrphanedRecords(context, options, onOperationCleanup);
|
|
177
186
|
}, reconciliationIntervalMs) : void 0;
|
|
178
187
|
stack.defer(() => {
|
|
188
|
+
context.stopping = !0;
|
|
179
189
|
if (visibilityPollHandle !== void 0)
|
|
180
190
|
clearInterval(visibilityPollHandle);
|
|
181
191
|
if (reconciliationHandle !== void 0)
|
|
@@ -191,39 +201,3 @@ export function registerStackDisposers(stack, context, options, broadcastingHand
|
|
|
191
201
|
context.rateLimiter?.dispose();
|
|
192
202
|
});
|
|
193
203
|
}
|
|
194
|
-
export function restoreInflightTasks(context, options) {
|
|
195
|
-
withRetry(async () => {
|
|
196
|
-
for await (const [key, value] of options.engine.storage.scan("op:inflight:")) {
|
|
197
|
-
const decoded = decode(value);
|
|
198
|
-
if (!isInflightRecord(decoded)) {
|
|
199
|
-
console.error(`[weft] Corrupt inflight record at "${key}" during restore \u2014 skipping`);
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
const record = decoded, now = Date.now();
|
|
203
|
-
if (record.deadline <= now) {
|
|
204
|
-
options.engine.storage.delete(key);
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
const remaining = record.deadline - now;
|
|
208
|
-
context.registry.assignTask(record.workerId, record.operationId, remaining, void 0, record.attemptToken);
|
|
209
|
-
context.deadlineTracker.add({ operationId: record.operationId, deadline: record.deadline });
|
|
210
|
-
const tracked = context.registry.getWorkerTasks(record.workerId).find((t) => t.operationId === record.operationId);
|
|
211
|
-
if (tracked)
|
|
212
|
-
tracked.visibilityTimeout = record.visibilityTimeout;
|
|
213
|
-
rebuildWorkflowIndex(context, record.operationId, record.workflowId);
|
|
214
|
-
}
|
|
215
|
-
}, "restore in-flight tasks from storage").catch((error) => {
|
|
216
|
-
console.error("[weft] Failed to restore in-flight tasks from storage:", error);
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
function rebuildWorkflowIndex(context, operationId, workflowId) {
|
|
220
|
-
if (!workflowId)
|
|
221
|
-
return;
|
|
222
|
-
let opIds = context.workflowOperations.get(workflowId);
|
|
223
|
-
if (!opIds) {
|
|
224
|
-
opIds = new Set;
|
|
225
|
-
context.workflowOperations.set(workflowId, opIds);
|
|
226
|
-
}
|
|
227
|
-
opIds.add(operationId);
|
|
228
|
-
context.operationToWorkflow.set(operationId, workflowId);
|
|
229
|
-
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type guards and encode/decode for the durable remote task ledger (WFT-25).
|
|
3
|
+
*
|
|
4
|
+
* Every stored or wire-derived field is proven from `unknown` — the project
|
|
5
|
+
* brief's Security section requires operation IDs, workflow IDs, names,
|
|
6
|
+
* queues, header counts, header bytes, retry fields, and payload sizes to be
|
|
7
|
+
* bounded before a storage write, and `input`/`headers` are treated as
|
|
8
|
+
* hostile the same way `worker/manifest/parse.ts` treats manifest content.
|
|
9
|
+
*
|
|
10
|
+
* Guards here intentionally check only the fields that are load-bearing for
|
|
11
|
+
* correctness and the fields the brief calls out for bounding — matching the
|
|
12
|
+
* existing `isQueuedRecord`/`isInflightRecord` style in `task-state.ts`
|
|
13
|
+
* rather than re-deriving every field's shape from the type system.
|
|
14
|
+
*
|
|
15
|
+
* @module server/task-ledger-codec
|
|
16
|
+
*/
|
|
17
|
+
import type { RemoteTaskCancelling, RemoteTaskCompleting, RemoteTaskDeadLettered, RemoteTaskLeased, RemoteTaskQueued, RemoteTaskRecord, RemoteTaskTerminal, RemoteTaskTerminalCancelled, RemoteTaskTerminalResolved, RemoteTaskTerminalRetryExhausted } from './task-ledger-types.ts';
|
|
18
|
+
/** Bounded header map: at most {@link MAX_TASK_HEADER_COUNT} entries, each key/value at most {@link MAX_TASK_HEADER_VALUE_BYTES}. */
|
|
19
|
+
export declare function isValidTaskHeaders(value: unknown): value is Readonly<Record<string, string>>;
|
|
20
|
+
export declare function isRemoteTaskQueued(value: unknown): value is RemoteTaskQueued;
|
|
21
|
+
export declare function isRemoteTaskLeased(value: unknown): value is RemoteTaskLeased;
|
|
22
|
+
export declare function isRemoteTaskCompleting(value: unknown): value is RemoteTaskCompleting;
|
|
23
|
+
export declare function isRemoteTaskCancelling(value: unknown): value is RemoteTaskCancelling;
|
|
24
|
+
export declare function isRemoteTaskTerminalResolved(value: unknown): value is RemoteTaskTerminalResolved;
|
|
25
|
+
export declare function isRemoteTaskTerminalCancelled(value: unknown): value is RemoteTaskTerminalCancelled;
|
|
26
|
+
export declare function isRemoteTaskTerminalRetryExhausted(value: unknown): value is RemoteTaskTerminalRetryExhausted;
|
|
27
|
+
export declare function isRemoteTaskTerminal(value: unknown): value is RemoteTaskTerminal;
|
|
28
|
+
export declare function isRemoteTaskDeadLettered(value: unknown): value is RemoteTaskDeadLettered;
|
|
29
|
+
/** Discriminate and validate a decoded value as any {@link RemoteTaskRecord} state. */
|
|
30
|
+
export declare function isRemoteTaskRecord(value: unknown): value is RemoteTaskRecord;
|
|
31
|
+
/** Canonical encoding for a validated {@link RemoteTaskRecord}. */
|
|
32
|
+
export declare function encodeRemoteTaskRecord(record: RemoteTaskRecord): Uint8Array;
|
|
33
|
+
/**
|
|
34
|
+
* Decode and validate a task ledger record. Returns `null` when the bytes are
|
|
35
|
+
* absent or decode to a value that fails bounds validation. This does not
|
|
36
|
+
* catch `decode()` throwing on bytes that are not valid MessagePack; storage
|
|
37
|
+
* bytes are always ones this codebase wrote, so byte-level corruption is
|
|
38
|
+
* treated as a storage integrity failure worth surfacing, not swallowing.
|
|
39
|
+
*/
|
|
40
|
+
export declare function decodeRemoteTaskRecord(bytes: Uint8Array | null): RemoteTaskRecord | null;
|