@lostgradient/weft 0.18.0 → 0.20.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 +1597 -21
- package/dist/cli/generated/operation-client.generated.d.ts +37 -6
- package/dist/cli/generated/operation-client.generated.js +6 -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-detail-schema.d.ts +341 -0
- package/dist/server/operations/get-task-detail-schema.js +118 -0
- package/dist/server/operations/get-task-detail.d.ts +346 -0
- package/dist/server/operations/get-task-detail.js +215 -0
- 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 +8 -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
package/dist/worker/index.js
CHANGED
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
} from "./execute-with-interceptors.js";
|
|
7
7
|
import { HeartbeatManager } from "./heartbeat.js";
|
|
8
8
|
import {
|
|
9
|
-
|
|
9
|
+
assertManifestMatchesWorkflows,
|
|
10
|
+
buildRegisterMessage,
|
|
11
|
+
snapshotWorkflows
|
|
10
12
|
} from "./options.js";
|
|
11
13
|
import {
|
|
12
14
|
parseServerToWorkerMessage
|
|
@@ -25,10 +27,29 @@ function createWorkerWebSocket(url, headers) {
|
|
|
25
27
|
return new WebSocket(url);
|
|
26
28
|
return new WebSocket(url, { headers });
|
|
27
29
|
}
|
|
30
|
+
const WORKER_STREAM_PATH_RE = /^(?:\/api)?\/v1\/tasks\/([^/]+)\/stream$/, QUEUE_NAME_RE = /^[\w-]+$/;
|
|
31
|
+
function assertValidQueueName(queue) {
|
|
32
|
+
if (!QUEUE_NAME_RE.test(queue))
|
|
33
|
+
throw Error(`RemoteWorker queue "${queue}" is not a valid queue name \u2014 the server's worker-stream ` + "route only accepts word characters and hyphens ([\\w-]+).");
|
|
34
|
+
}
|
|
35
|
+
function resolveWorkerConnectUrl(serverUrl, explicitQueue) {
|
|
36
|
+
const parsed = new URL(serverUrl), match = WORKER_STREAM_PATH_RE.exec(parsed.pathname);
|
|
37
|
+
if (match !== null) {
|
|
38
|
+
const embeddedQueue = decodeURIComponent(match[1]);
|
|
39
|
+
assertValidQueueName(embeddedQueue);
|
|
40
|
+
if (explicitQueue !== void 0 && explicitQueue !== embeddedQueue)
|
|
41
|
+
throw Error(`RemoteWorker serverUrl "${serverUrl}" already encodes queue "${embeddedQueue}", which conflicts with the "queue" option "${explicitQueue}". Pass either a bare server origin with the "queue" option, or a complete worker-stream serverUrl with no ` + '"queue" option \u2014 not both.');
|
|
42
|
+
return { url: serverUrl, queue: embeddedQueue };
|
|
43
|
+
}
|
|
44
|
+
const queue = explicitQueue ?? DEFAULT_QUEUE;
|
|
45
|
+
assertValidQueueName(queue);
|
|
46
|
+
return { url: new URL(`/v1/tasks/${encodeURIComponent(queue)}/stream`, serverUrl).toString(), queue };
|
|
47
|
+
}
|
|
28
48
|
|
|
29
49
|
export class RemoteWorker {
|
|
30
50
|
#options;
|
|
31
51
|
#activityTable;
|
|
52
|
+
#connectUrl;
|
|
32
53
|
#ws;
|
|
33
54
|
#inFlight;
|
|
34
55
|
#abortController;
|
|
@@ -42,11 +63,16 @@ export class RemoteWorker {
|
|
|
42
63
|
#workerId;
|
|
43
64
|
constructor(options) {
|
|
44
65
|
this.#activityTable = resolveActivityTable(options);
|
|
66
|
+
if (options.manifest !== void 0)
|
|
67
|
+
assertManifestMatchesWorkflows(options.manifest, options.workflows);
|
|
45
68
|
this.#workerId = options.workerId ?? crypto.randomUUID();
|
|
69
|
+
const resolvedConnection = resolveWorkerConnectUrl(options.serverUrl, options.queue);
|
|
70
|
+
this.#connectUrl = resolvedConnection.url;
|
|
46
71
|
this.#options = {
|
|
47
72
|
...options,
|
|
73
|
+
workflows: snapshotWorkflows(options.workflows),
|
|
48
74
|
concurrency: options.concurrency ?? DEFAULT_CONCURRENCY,
|
|
49
|
-
queue:
|
|
75
|
+
queue: resolvedConnection.queue,
|
|
50
76
|
workerId: this.#workerId
|
|
51
77
|
};
|
|
52
78
|
this.#ws = null;
|
|
@@ -70,11 +96,11 @@ export class RemoteWorker {
|
|
|
70
96
|
return;
|
|
71
97
|
this.#teardownActiveConnection("Superseded by a new connect() call");
|
|
72
98
|
return new Promise((resolve, reject) => {
|
|
73
|
-
const ws = createWorkerWebSocket(this.#
|
|
99
|
+
const ws = createWorkerWebSocket(this.#connectUrl, this.#options.headers);
|
|
74
100
|
this.#ws = ws;
|
|
75
101
|
this.#pendingRegistration = { resolve, reject };
|
|
76
102
|
ws.addEventListener("open", () => {
|
|
77
|
-
this.#sendMessage(buildRegisterMessage(this.#workerId,
|
|
103
|
+
this.#sendMessage(buildRegisterMessage(this.#workerId, this.#options));
|
|
78
104
|
}, { signal: this.#abortController.signal });
|
|
79
105
|
ws.addEventListener("message", (event) => {
|
|
80
106
|
this.#handleMessage(event);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic canonical JSON serialization for arbitrary values.
|
|
3
|
+
*
|
|
4
|
+
* Object keys are sorted at every depth so two values that differ only in
|
|
5
|
+
* key order serialize identically — the property any hash-then-compare
|
|
6
|
+
* scheme (manifest digests, contract hashes) depends on.
|
|
7
|
+
*
|
|
8
|
+
* @module worker/manifest/canonical-json
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Serialize an arbitrary value with object keys sorted at every depth.
|
|
12
|
+
*
|
|
13
|
+
* Accepts `unknown` rather than a `JSONValue` bound because callers such as
|
|
14
|
+
* a JSON-Schema converter's output are structurally JSON-safe by
|
|
15
|
+
* construction (trusted, already-serialized-once data) without necessarily
|
|
16
|
+
* satisfying that type at the TypeScript level. `undefined` values inside
|
|
17
|
+
* objects are dropped, matching `JSON.stringify`'s own behavior, since this
|
|
18
|
+
* function is a drop-in canonical replacement for it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function canonicalJsonStringify(value: unknown): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function sortedEntryKeys(record) {
|
|
2
|
+
return Object.keys(record).toSorted();
|
|
3
|
+
}
|
|
4
|
+
export function canonicalJsonStringify(value) {
|
|
5
|
+
if (value === void 0)
|
|
6
|
+
return "null";
|
|
7
|
+
if (value === null || typeof value !== "object")
|
|
8
|
+
return JSON.stringify(value);
|
|
9
|
+
if (Array.isArray(value))
|
|
10
|
+
return `[${value.map((entry) => canonicalJsonStringify(entry)).join(",")}]`;
|
|
11
|
+
const record = value;
|
|
12
|
+
return `{${sortedEntryKeys(record).filter((key) => record[key] !== void 0).map((key) => `${JSON.stringify(key)}:${canonicalJsonStringify(record[key])}`).join(",")}}`;
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded validation for the open-ended `capabilities` record on a worker
|
|
3
|
+
* manifest.
|
|
4
|
+
*
|
|
5
|
+
* `isJSONValue()` already rejects the values JSON cannot round-trip — cycles,
|
|
6
|
+
* `-0`, exotic prototypes — but it is deliberately unbounded in depth, key
|
|
7
|
+
* count, and string size. A manifest arrives from a worker we do not trust, so
|
|
8
|
+
* the depth and size ceilings have to be imposed here rather than delegated.
|
|
9
|
+
*
|
|
10
|
+
* @module worker/manifest/capabilities
|
|
11
|
+
*/
|
|
12
|
+
import { type JSONValue } from '../../core/json.ts';
|
|
13
|
+
import type { ManifestValidationFailure } from './failure.ts';
|
|
14
|
+
/**
|
|
15
|
+
* Validate the manifest `capabilities` record from untrusted input.
|
|
16
|
+
*
|
|
17
|
+
* Capabilities are descriptive only. Nothing here consults their meaning —
|
|
18
|
+
* a capability never grants authorization or affects routing without an
|
|
19
|
+
* explicit host policy — so this checks shape and size and nothing else.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseManifestCapabilities(value: unknown, path: string): {
|
|
22
|
+
ok: true;
|
|
23
|
+
capabilities: Readonly<Record<string, JSONValue>>;
|
|
24
|
+
} | ManifestValidationFailure;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { isJSONValue } from "../../core/json.js";
|
|
2
|
+
import { manifestFailure } from "./failure.js";
|
|
3
|
+
import { isRecord } from "./is-record.js";
|
|
4
|
+
import {
|
|
5
|
+
MAX_MANIFEST_CAPABILITY_COUNT,
|
|
6
|
+
MAX_MANIFEST_CAPABILITY_DEPTH,
|
|
7
|
+
MAX_MANIFEST_CAPABILITY_STRING_BYTES
|
|
8
|
+
} from "./limits.js";
|
|
9
|
+
import { utf8ByteLength } from "./utf8.js";
|
|
10
|
+
function exceedsCapabilityDepth(value, depth, limit) {
|
|
11
|
+
if (depth > limit)
|
|
12
|
+
return !0;
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return value.some((entry) => exceedsCapabilityDepth(entry, depth + 1, limit));
|
|
15
|
+
if (value !== null && typeof value === "object")
|
|
16
|
+
return Object.values(value).some((entry) => exceedsCapabilityDepth(entry, depth + 1, limit));
|
|
17
|
+
return !1;
|
|
18
|
+
}
|
|
19
|
+
function checkCapabilityValue(value, path) {
|
|
20
|
+
if (typeof value === "string")
|
|
21
|
+
return checkCapabilityString(value, path);
|
|
22
|
+
if (Array.isArray(value))
|
|
23
|
+
return checkCapabilityArray(value, path);
|
|
24
|
+
if (value !== null && typeof value === "object")
|
|
25
|
+
return checkCapabilityObject(value, path);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
function checkCapabilityString(value, path) {
|
|
29
|
+
const bytes = utf8ByteLength(value);
|
|
30
|
+
if (bytes > MAX_MANIFEST_CAPABILITY_STRING_BYTES)
|
|
31
|
+
return manifestFailure("capability_string_too_long", `is ${bytes} bytes, exceeding the maximum capability string size of ${MAX_MANIFEST_CAPABILITY_STRING_BYTES}`, path);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
function checkCapabilityArray(value, path) {
|
|
35
|
+
for (const [index, entry] of value.entries()) {
|
|
36
|
+
const failure = checkCapabilityValue(entry, `${path}[${String(index)}]`);
|
|
37
|
+
if (failure !== void 0)
|
|
38
|
+
return failure;
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
function checkCapabilityObject(record, path) {
|
|
43
|
+
for (const key of Object.keys(record)) {
|
|
44
|
+
const keyPath = `${path}.${key}`, keyFailure = checkCapabilityString(key, keyPath);
|
|
45
|
+
if (keyFailure !== void 0)
|
|
46
|
+
return keyFailure;
|
|
47
|
+
const failure = checkCapabilityValue(record[key], keyPath);
|
|
48
|
+
if (failure !== void 0)
|
|
49
|
+
return failure;
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
export function parseManifestCapabilities(value, path) {
|
|
54
|
+
if (!isRecord(value))
|
|
55
|
+
return manifestFailure("invalid_field", "must be a JSON object", path);
|
|
56
|
+
const record = value, keys = Object.keys(record);
|
|
57
|
+
if (keys.length > MAX_MANIFEST_CAPABILITY_COUNT)
|
|
58
|
+
return manifestFailure("too_many_capabilities", `declares ${keys.length} capabilities, exceeding the maximum of ${MAX_MANIFEST_CAPABILITY_COUNT}`, path);
|
|
59
|
+
const capabilities = Object.create(null);
|
|
60
|
+
for (const key of keys) {
|
|
61
|
+
const entry = record[key], keyPath = `${path}.${key}`, keyFailure = checkCapabilityString(key, keyPath);
|
|
62
|
+
if (keyFailure !== void 0)
|
|
63
|
+
return keyFailure;
|
|
64
|
+
if (exceedsCapabilityDepth(entry, 1, MAX_MANIFEST_CAPABILITY_DEPTH))
|
|
65
|
+
return manifestFailure("capability_too_deep", `exceeds the maximum capability nesting depth of ${MAX_MANIFEST_CAPABILITY_DEPTH}`, keyPath);
|
|
66
|
+
if (!isJSONValue(entry))
|
|
67
|
+
return manifestFailure("invalid_capability_value", "must be a JSON value", keyPath);
|
|
68
|
+
const failure = checkCapabilityValue(entry, keyPath);
|
|
69
|
+
if (failure !== void 0)
|
|
70
|
+
return failure;
|
|
71
|
+
capabilities[key] = entry;
|
|
72
|
+
}
|
|
73
|
+
return { ok: !0, capabilities };
|
|
74
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic SHA-256 content digest, tagged with its algorithm.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the manifest digest so any canonical-JSON content — not
|
|
5
|
+
* only a full {@link WorkerManifest} — can be hashed with the same
|
|
6
|
+
* collision-resistant, algorithm-tagged scheme. `hashString` (FNV-1a) stays
|
|
7
|
+
* reserved for cache-key-quality placeholders; anything a host trusts as a
|
|
8
|
+
* real content identity goes through this function instead.
|
|
9
|
+
*
|
|
10
|
+
* @module worker/manifest/content-digest
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Algorithm tag prefixed to every digest this function produces.
|
|
14
|
+
*
|
|
15
|
+
* Carrying the algorithm in the value means a future change reads as a
|
|
16
|
+
* difference rather than as a silent reinterpretation of the same hex.
|
|
17
|
+
*/
|
|
18
|
+
export declare const CONTENT_DIGEST_ALGORITHM = "sha256";
|
|
19
|
+
/** Digest a UTF-8 string with SHA-256, returning `sha256:<hex>`. */
|
|
20
|
+
export declare function sha256Hex(input: string): Promise<string>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { copyBytesToArrayBuffer } from "../../core/byte-arrays.js";
|
|
2
|
+
import { utf8Encode } from "./utf8.js";
|
|
3
|
+
export const CONTENT_DIGEST_ALGORITHM = "sha256";
|
|
4
|
+
function bytesToHex(bytes) {
|
|
5
|
+
let hex = "";
|
|
6
|
+
for (const byte of bytes)
|
|
7
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
8
|
+
return hex;
|
|
9
|
+
}
|
|
10
|
+
export async function sha256Hex(input) {
|
|
11
|
+
const digest = await crypto.subtle.digest("SHA-256", copyBytesToArrayBuffer(utf8Encode(input)));
|
|
12
|
+
return `${CONTENT_DIGEST_ALGORITHM}:${bytesToHex(new Uint8Array(digest))}`;
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Placeholder content-identity digest shared by every manifest builder that
|
|
3
|
+
* has no real build-tool-supplied digest to report yet.
|
|
4
|
+
*
|
|
5
|
+
* @module worker/manifest/declared-shape-digest
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Tag a derived placeholder value so it is never mistaken for a real,
|
|
9
|
+
* build-tool-supplied content digest. `hashString` is FNV-1a — cache-key
|
|
10
|
+
* quality, not cryptographic — which is exactly why the tag exists: a reader
|
|
11
|
+
* (or a future deterministic builder) can tell a `declared-shape:` value
|
|
12
|
+
* apart from a trusted `sha256:` one at a glance.
|
|
13
|
+
*/
|
|
14
|
+
export declare function declaredShapeDigest(input: string): string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-addressed digest of a worker manifest.
|
|
3
|
+
*
|
|
4
|
+
* The digest is what `(deploymentName, buildId)` consistency, registration
|
|
5
|
+
* acknowledgement, and persisted execution provenance all compare, so a
|
|
6
|
+
* collision would let two different workers pass for one another. That rules
|
|
7
|
+
* out the repository's FNV-1a helpers, which are documented as cache-key
|
|
8
|
+
* quality, and calls for SHA-256 — the same choice, and the same
|
|
9
|
+
* bounded-input-then-hash shape, as the worker replay signature.
|
|
10
|
+
*
|
|
11
|
+
* The digest carries its algorithm as a prefix so a future change is a
|
|
12
|
+
* readable difference rather than a silent reinterpretation of 64 hex
|
|
13
|
+
* characters.
|
|
14
|
+
*
|
|
15
|
+
* @module worker/manifest/digest
|
|
16
|
+
*/
|
|
17
|
+
import type { WorkerManifest } from './types.ts';
|
|
18
|
+
/**
|
|
19
|
+
* Algorithm tag prefixed to every manifest digest this version produces.
|
|
20
|
+
*
|
|
21
|
+
* Carrying the algorithm in the value means a future change reads as a
|
|
22
|
+
* difference rather than as a silent reinterpretation of the same hex.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { WORKER_MANIFEST_DIGEST_ALGORITHM } from '@lostgradient/weft';
|
|
27
|
+
*
|
|
28
|
+
* const digest = 'sha256:41d0e2';
|
|
29
|
+
* console.log(digest.startsWith(`${WORKER_MANIFEST_DIGEST_ALGORITHM}:`)); // true
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare const WORKER_MANIFEST_DIGEST_ALGORITHM = "sha256";
|
|
33
|
+
/**
|
|
34
|
+
* Digest canonical manifest bytes.
|
|
35
|
+
*
|
|
36
|
+
* Separate from {@link computeWorkerManifestDigest} so a caller that already
|
|
37
|
+
* holds the canonical serialization — the manifest parser returns one — does
|
|
38
|
+
* not serialize the manifest twice.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* import { canonicalWorkerManifestJson, digestCanonicalWorkerManifest } from '@lostgradient/weft';
|
|
43
|
+
*
|
|
44
|
+
* const canonical = canonicalWorkerManifestJson({
|
|
45
|
+
* manifestVersion: 1,
|
|
46
|
+
* protocolVersion: 2,
|
|
47
|
+
* sdkVersion: '0.18.0',
|
|
48
|
+
* runtime: { name: 'bun', version: '1.3.14' },
|
|
49
|
+
* deployment: { name: 'billing', buildId: 'b3', artifactDigest: 'sha256:41d0' },
|
|
50
|
+
* workflows: {},
|
|
51
|
+
* capabilities: {},
|
|
52
|
+
* });
|
|
53
|
+
*
|
|
54
|
+
* console.log((await digestCanonicalWorkerManifest(canonical)).startsWith('sha256:'));
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare function digestCanonicalWorkerManifest(canonicalJson: string): Promise<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Compute the content-addressed digest of a worker manifest.
|
|
60
|
+
*
|
|
61
|
+
* Determined entirely by canonical content, so two manifests that differ only
|
|
62
|
+
* in key order digest identically, and any difference the host cares about
|
|
63
|
+
* changes the digest.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* import { computeWorkerManifestDigest, WORKER_MANIFEST_VERSION } from '@lostgradient/weft';
|
|
68
|
+
*
|
|
69
|
+
* const digest = await computeWorkerManifestDigest({
|
|
70
|
+
* manifestVersion: WORKER_MANIFEST_VERSION,
|
|
71
|
+
* protocolVersion: 2,
|
|
72
|
+
* sdkVersion: '0.18.0',
|
|
73
|
+
* runtime: { name: 'bun', version: '1.3.14' },
|
|
74
|
+
* deployment: { name: 'billing', buildId: 'b3', artifactDigest: 'sha256:41d0' },
|
|
75
|
+
* workflows: {},
|
|
76
|
+
* capabilities: {},
|
|
77
|
+
* });
|
|
78
|
+
*
|
|
79
|
+
* console.log(digest.startsWith('sha256:')); // true
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export declare function computeWorkerManifestDigest(manifest: WorkerManifest): Promise<string>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { sha256Hex } from "./content-digest.js";
|
|
2
|
+
import { canonicalWorkerManifestJson } from "./normalize.js";
|
|
3
|
+
export const WORKER_MANIFEST_DIGEST_ALGORITHM = "sha256";
|
|
4
|
+
export async function digestCanonicalWorkerManifest(canonicalJson) {
|
|
5
|
+
return sha256Hex(canonicalJson);
|
|
6
|
+
}
|
|
7
|
+
export async function computeWorkerManifestDigest(manifest) {
|
|
8
|
+
return digestCanonicalWorkerManifest(canonicalWorkerManifestJson(manifest));
|
|
9
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deriving and matching worker execution identity.
|
|
3
|
+
*
|
|
4
|
+
* Routing asks a question — {@link WorkerExecutionRequirement} — and a lease
|
|
5
|
+
* answers it completely — {@link WorkerExecutionIdentity}. Keeping the two
|
|
6
|
+
* types apart is what stops a partially specified routing hint from being
|
|
7
|
+
* mistaken for an observed fact, and building the identity from the accepted
|
|
8
|
+
* manifest rather than from a worker's result is what stops a worker from
|
|
9
|
+
* claiming it ran something it did not.
|
|
10
|
+
*
|
|
11
|
+
* @module worker/manifest/execution-identity
|
|
12
|
+
*/
|
|
13
|
+
import type { WorkerExecutionIdentity, WorkerExecutionRequirement, WorkerManifest } from './types.ts';
|
|
14
|
+
/**
|
|
15
|
+
* Build the complete execution identity for an attempt.
|
|
16
|
+
*
|
|
17
|
+
* Every field comes from the accepted manifest, the accepted manifest digest,
|
|
18
|
+
* and the live session — never from anything the worker reports later.
|
|
19
|
+
* Returns `undefined` when the manifest does not actually advertise the
|
|
20
|
+
* workflow and activity being leased, which is the caller's signal that the
|
|
21
|
+
* worker was never eligible for this task.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { buildWorkerExecutionIdentity, WORKER_MANIFEST_VERSION } from '@lostgradient/weft';
|
|
26
|
+
*
|
|
27
|
+
* const identity = buildWorkerExecutionIdentity({
|
|
28
|
+
* manifest: {
|
|
29
|
+
* manifestVersion: WORKER_MANIFEST_VERSION,
|
|
30
|
+
* protocolVersion: 2,
|
|
31
|
+
* sdkVersion: '0.18.0',
|
|
32
|
+
* runtime: { name: 'bun', version: '1.3.14' },
|
|
33
|
+
* deployment: { name: 'billing', buildId: 'b3', artifactDigest: 'sha256:41d0' },
|
|
34
|
+
* workflows: {
|
|
35
|
+
* checkout: {
|
|
36
|
+
* workflowVersion: '1.0.0',
|
|
37
|
+
* workflowRevision: 'rev-8',
|
|
38
|
+
* contractHash: 'sha256:aa',
|
|
39
|
+
* activities: { charge: { contractHash: 'sha256:bb', implementationRevision: 'r1' } },
|
|
40
|
+
* },
|
|
41
|
+
* },
|
|
42
|
+
* capabilities: {},
|
|
43
|
+
* },
|
|
44
|
+
* manifestDigest: 'sha256:deadbeef',
|
|
45
|
+
* workerId: 'worker-1',
|
|
46
|
+
* workflowType: 'checkout',
|
|
47
|
+
* activityName: 'charge',
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* console.log(identity?.activityContractHash); // 'sha256:bb'
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function buildWorkerExecutionIdentity(source: Readonly<{
|
|
54
|
+
manifest: WorkerManifest;
|
|
55
|
+
manifestDigest: string;
|
|
56
|
+
workerId: string;
|
|
57
|
+
workflowType: string;
|
|
58
|
+
activityName: string;
|
|
59
|
+
}>): WorkerExecutionIdentity | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Test whether an execution identity satisfies a routing requirement.
|
|
62
|
+
*
|
|
63
|
+
* An omitted requirement field means policy may choose any eligible value, so
|
|
64
|
+
* it matches anything. It is deliberately not treated as a demand for an empty
|
|
65
|
+
* string, which is why this cannot be written as a plain field-by-field
|
|
66
|
+
* equality check.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* import { executionIdentitySatisfies, type WorkerExecutionIdentity } from '@lostgradient/weft';
|
|
71
|
+
*
|
|
72
|
+
* const identity: WorkerExecutionIdentity = {
|
|
73
|
+
* workerId: 'worker-1',
|
|
74
|
+
* manifestDigest: 'sha256:deadbeef',
|
|
75
|
+
* protocolVersion: 2,
|
|
76
|
+
* sdkVersion: '0.18.0',
|
|
77
|
+
* runtimeName: 'bun',
|
|
78
|
+
* runtimeVersion: '1.3.14',
|
|
79
|
+
* deploymentName: 'billing',
|
|
80
|
+
* buildId: 'b3',
|
|
81
|
+
* artifactDigest: 'sha256:41d0',
|
|
82
|
+
* workflowType: 'checkout',
|
|
83
|
+
* workflowRevision: 'rev-8',
|
|
84
|
+
* activityName: 'charge',
|
|
85
|
+
* activityContractHash: 'sha256:bb',
|
|
86
|
+
* };
|
|
87
|
+
*
|
|
88
|
+
* console.log(executionIdentitySatisfies({ deploymentName: 'billing' }, identity)); // true
|
|
89
|
+
* console.log(executionIdentitySatisfies({ buildId: 'b4' }, identity)); // false
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export declare function executionIdentitySatisfies(requirement: WorkerExecutionRequirement, identity: WorkerExecutionIdentity): boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function buildWorkerExecutionIdentity(source) {
|
|
2
|
+
const workflow = source.manifest.workflows[source.workflowType];
|
|
3
|
+
if (workflow === void 0)
|
|
4
|
+
return;
|
|
5
|
+
const activity = workflow.activities[source.activityName];
|
|
6
|
+
if (activity === void 0)
|
|
7
|
+
return;
|
|
8
|
+
return {
|
|
9
|
+
workerId: source.workerId,
|
|
10
|
+
manifestDigest: source.manifestDigest,
|
|
11
|
+
protocolVersion: source.manifest.protocolVersion,
|
|
12
|
+
sdkVersion: source.manifest.sdkVersion,
|
|
13
|
+
runtimeName: source.manifest.runtime.name,
|
|
14
|
+
runtimeVersion: source.manifest.runtime.version,
|
|
15
|
+
deploymentName: source.manifest.deployment.name,
|
|
16
|
+
buildId: source.manifest.deployment.buildId,
|
|
17
|
+
artifactDigest: source.manifest.deployment.artifactDigest,
|
|
18
|
+
workflowType: source.workflowType,
|
|
19
|
+
workflowRevision: workflow.workflowRevision,
|
|
20
|
+
activityName: source.activityName,
|
|
21
|
+
activityContractHash: activity.contractHash
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function executionIdentitySatisfies(requirement, identity) {
|
|
25
|
+
return ROUTING_FIELDS.every((field) => {
|
|
26
|
+
const required = requirement[field];
|
|
27
|
+
return required === void 0 || required === identity[field];
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const ROUTING_FIELDS = [
|
|
31
|
+
"deploymentName",
|
|
32
|
+
"buildId",
|
|
33
|
+
"artifactDigest",
|
|
34
|
+
"workflowRevision",
|
|
35
|
+
"activityContractHash"
|
|
36
|
+
];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded failure vocabulary for worker manifest validation.
|
|
3
|
+
*
|
|
4
|
+
* Rejections are returned rather than thrown, matching the RemoteWorker
|
|
5
|
+
* protocol parser: a malformed manifest is an expected wire condition on an
|
|
6
|
+
* untrusted boundary, not an exceptional one.
|
|
7
|
+
*
|
|
8
|
+
* `reason` is a closed union precisely so operators can count manifest
|
|
9
|
+
* rejections by reason without a high-cardinality label. The human-readable
|
|
10
|
+
* `message` and the `path` locating the offending field are for diagnostics
|
|
11
|
+
* and must never be used as metric labels.
|
|
12
|
+
*
|
|
13
|
+
* @module worker/manifest/failure
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Why a worker manifest was rejected.
|
|
17
|
+
*
|
|
18
|
+
* Closed by design — a bounded reason is safe to use as a metric label,
|
|
19
|
+
* unlike the message or path that accompany it.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import type { WorkerManifestRejectionReason } from '@lostgradient/weft';
|
|
24
|
+
*
|
|
25
|
+
* const counts = new Map<WorkerManifestRejectionReason, number>();
|
|
26
|
+
* counts.set('unsupported_manifest_version', 1);
|
|
27
|
+
* console.log(counts.get('unsupported_manifest_version'));
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export type WorkerManifestRejectionReason = 'not_an_object' | 'unsupported_manifest_version' | 'invalid_field' | 'identifier_too_long' | 'too_many_workflows' | 'too_many_activities' | 'too_many_capabilities' | 'capability_too_deep' | 'capability_string_too_long' | 'invalid_capability_value' | 'manifest_too_large' | 'duplicate_key' | 'invalid_json';
|
|
31
|
+
/**
|
|
32
|
+
* A rejected worker manifest, with enough detail to fix the worker and not so
|
|
33
|
+
* much that it leaks manifest content into logs.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { parseWorkerManifest, type ManifestValidationFailure } from '@lostgradient/weft';
|
|
38
|
+
*
|
|
39
|
+
* const result = parseWorkerManifest({ manifestVersion: 99 });
|
|
40
|
+
* if (!result.ok) {
|
|
41
|
+
* const rejection: ManifestValidationFailure = result;
|
|
42
|
+
* console.log(rejection.reason); // 'unsupported_manifest_version'
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export type ManifestValidationFailure = Readonly<{
|
|
47
|
+
ok: false;
|
|
48
|
+
/** Bounded rejection reason, safe as a metric label. */
|
|
49
|
+
reason: WorkerManifestRejectionReason;
|
|
50
|
+
/** Human-readable diagnostic. Never use as a metric label. */
|
|
51
|
+
message: string;
|
|
52
|
+
/** Dotted path to the offending field, when one applies. */
|
|
53
|
+
path?: string;
|
|
54
|
+
}>;
|
|
55
|
+
/**
|
|
56
|
+
* Build a validation failure whose message reads as one sentence about the
|
|
57
|
+
* offending path — `manifest.deployment.buildId must be a non-empty string`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function manifestFailure(reason: WorkerManifestRejectionReason, message: string, path?: string): ManifestValidationFailure;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical worker manifest and execution-identity vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* This is the single definition of how a worker describes itself and how the
|
|
5
|
+
* host records what actually executed an attempt. The durable task ledger,
|
|
6
|
+
* provenance, routing, workflow versioning, and operator diagnostics all
|
|
7
|
+
* consume these types rather than defining their own.
|
|
8
|
+
*
|
|
9
|
+
* @module worker/manifest
|
|
10
|
+
*/
|
|
11
|
+
export { MAX_MANIFEST_ACTIVITY_COUNT, MAX_MANIFEST_CAPABILITY_COUNT, MAX_MANIFEST_CAPABILITY_DEPTH, MAX_MANIFEST_CAPABILITY_STRING_BYTES, MAX_MANIFEST_IDENTIFIER_BYTES, MAX_MANIFEST_WORKFLOW_COUNT, MAX_NORMALIZED_MANIFEST_BYTES, } from './limits.ts';
|
|
12
|
+
export { WORKER_MANIFEST_VERSION } from './types.ts';
|
|
13
|
+
export type { WorkerActivityContract, WorkerDeploymentIdentity, WorkerExecutionIdentity, WorkerExecutionRequirement, WorkerManifest, WorkerRuntimeIdentity, WorkerWorkflowContract, } from './types.ts';
|
|
14
|
+
export { canonicalWorkerManifestJson, normalizeWorkerManifest } from './normalize.ts';
|
|
15
|
+
export { parseWorkerManifest } from './parse.ts';
|
|
16
|
+
export type { WorkerManifestParseResult, WorkerManifestParseSuccess } from './parse.ts';
|
|
17
|
+
export { parseWorkerManifestJson } from './parse-json.ts';
|
|
18
|
+
export type { ManifestValidationFailure, WorkerManifestRejectionReason } from './failure.ts';
|
|
19
|
+
export { WORKER_MANIFEST_DIGEST_ALGORITHM, computeWorkerManifestDigest, digestCanonicalWorkerManifest, } from './digest.ts';
|
|
20
|
+
export { buildWorkerExecutionIdentity, executionIdentitySatisfies } from './execution-identity.ts';
|
|
21
|
+
export { WorkerManifestBuildError, buildWorkerManifestFromRegistry, } from './registry-contract-builder.ts';
|
|
22
|
+
export type { WorkerManifestFromRegistryOptions } from './registry-contract-builder.ts';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export {
|
|
2
|
+
MAX_MANIFEST_ACTIVITY_COUNT,
|
|
3
|
+
MAX_MANIFEST_CAPABILITY_COUNT,
|
|
4
|
+
MAX_MANIFEST_CAPABILITY_DEPTH,
|
|
5
|
+
MAX_MANIFEST_CAPABILITY_STRING_BYTES,
|
|
6
|
+
MAX_MANIFEST_IDENTIFIER_BYTES,
|
|
7
|
+
MAX_MANIFEST_WORKFLOW_COUNT,
|
|
8
|
+
MAX_NORMALIZED_MANIFEST_BYTES
|
|
9
|
+
} from "./limits.js";
|
|
10
|
+
export { WORKER_MANIFEST_VERSION } from "./types.js";
|
|
11
|
+
export { canonicalWorkerManifestJson, normalizeWorkerManifest } from "./normalize.js";
|
|
12
|
+
export { parseWorkerManifest } from "./parse.js";
|
|
13
|
+
export { parseWorkerManifestJson } from "./parse-json.js";
|
|
14
|
+
export {
|
|
15
|
+
WORKER_MANIFEST_DIGEST_ALGORITHM,
|
|
16
|
+
computeWorkerManifestDigest,
|
|
17
|
+
digestCanonicalWorkerManifest
|
|
18
|
+
} from "./digest.js";
|
|
19
|
+
export { buildWorkerExecutionIdentity, executionIdentitySatisfies } from "./execution-identity.js";
|
|
20
|
+
export {
|
|
21
|
+
WorkerManifestBuildError,
|
|
22
|
+
buildWorkerManifestFromRegistry
|
|
23
|
+
} from "./registry-contract-builder.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest builder shared by both sides of the internal Worker realm
|
|
3
|
+
* handshake (WFT-28).
|
|
4
|
+
*
|
|
5
|
+
* Unlike the RemoteWorker SDK, an internal `workflowExecutionMode: 'worker'`
|
|
6
|
+
* realm has no per-workflow activity list or version metadata to report —
|
|
7
|
+
* workflow generators are opaque functions, not a declared activity table —
|
|
8
|
+
* so this builder only asserts the one thing both sides can honestly derive:
|
|
9
|
+
* the identity of a registered workflow *type name*. `workflowVersion` is
|
|
10
|
+
* the uniform {@link DEFAULT_WORKFLOW_VERSION} placeholder rather than each
|
|
11
|
+
* definition's real declared version, so the host and the realm never need
|
|
12
|
+
* to keep a second copy of version metadata in sync (the exact class of
|
|
13
|
+
* drift this handshake exists to catch elsewhere). Real per-workflow
|
|
14
|
+
* contract generation is WFT-29 scope, same as the RemoteWorker SDK's
|
|
15
|
+
* `declared-shape:` placeholders.
|
|
16
|
+
*
|
|
17
|
+
* The host validates that every workflow type it has registered is present
|
|
18
|
+
* in the realm's manifest with a matching contract — a *subset* check, not
|
|
19
|
+
* exact-set equality. A realm bootstrap script legitimately advertises more
|
|
20
|
+
* workflow types than any one host process dispatches (a shared worker pool
|
|
21
|
+
* serving several engines, each using a different slice of it); what must
|
|
22
|
+
* never happen is the host dispatching to a realm that is missing a type it
|
|
23
|
+
* expects. {@link declaredWorkflowContractsMatch} is what the host compares
|
|
24
|
+
* per expected type; {@link buildInternalRealmManifest} is what the realm
|
|
25
|
+
* uses to build its own full advertised manifest.
|
|
26
|
+
*
|
|
27
|
+
* `runtime` is a fixed constant rather than `detectRuntime()`: a browser Web
|
|
28
|
+
* Worker has no `window`/`document` in its own scope, so it detects as
|
|
29
|
+
* `'edge'` while the hosting page detects as `'browser'` — real detection
|
|
30
|
+
* would make otherwise-identical manifests diverge on every browser
|
|
31
|
+
* deployment. Both sides call this same function, so a constant is exactly
|
|
32
|
+
* as comparable and avoids that divergence entirely.
|
|
33
|
+
*
|
|
34
|
+
* @module worker/manifest/internal-realm
|
|
35
|
+
*/
|
|
36
|
+
import { type WorkerManifest, type WorkerWorkflowContract } from './index.ts';
|
|
37
|
+
/** Fixed deployment name for every internal Worker realm manifest. */
|
|
38
|
+
export declare const INTERNAL_WORKER_REALM_DEPLOYMENT_NAME = "internal-worker-realm";
|
|
39
|
+
/** Fixed runtime identity reported by every internal Worker realm manifest. See {@link buildInternalRealmManifest}. */
|
|
40
|
+
export declare const INTERNAL_WORKER_REALM_RUNTIME_NAME = "internal-worker-realm";
|
|
41
|
+
/** Pure function of `workflowType` alone — identical on the host and the realm for the same name. */
|
|
42
|
+
export declare function buildDeclaredWorkflowContract(workflowType: string): WorkerWorkflowContract;
|
|
43
|
+
/** Whether a realm-reported contract for one workflow type matches what the host expects for that same type name. */
|
|
44
|
+
export declare function declaredWorkflowContractsMatch(reported: WorkerWorkflowContract, expected: WorkerWorkflowContract): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Build the manifest an internal Worker realm advertises, or the manifest
|
|
47
|
+
* the engine host expects a realm to advertise — the same function computes
|
|
48
|
+
* both sides so an honest mismatch is the only way the digests can differ.
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildInternalRealmManifest(workflowTypes: readonly string[]): WorkerManifest;
|