@lostgradient/weft 0.17.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.
Files changed (172) hide show
  1. package/README.md +3 -1
  2. package/dist/cli/conformance.js +14 -18
  3. package/dist/cli/generated/operation-catalog.snapshot.json +331 -16
  4. package/dist/cli/generated/operation-client.generated.d.ts +39 -6
  5. package/dist/cli/generated/operation-client.generated.js +6 -0
  6. package/dist/core/context/child-workflow-pipe.d.ts +1 -1
  7. package/dist/core/context/child-workflow-pipe.js +1 -1
  8. package/dist/core/engine/construction.d.ts +2 -0
  9. package/dist/core/engine/construction.js +3 -0
  10. package/dist/core/engine/index.js +1 -0
  11. package/dist/core/engine/listing.d.ts +1 -0
  12. package/dist/core/engine/listing.js +6 -4
  13. package/dist/core/weft-error.d.ts +1 -1
  14. package/dist/core/weft-error.js +2 -1
  15. package/dist/core/worker-execution-dispatcher.d.ts +8 -0
  16. package/dist/core/worker-execution-dispatcher.js +4 -0
  17. package/dist/core/worker-execution-strategy-options.d.ts +16 -0
  18. package/dist/core/worker-execution-strategy.js +71 -89
  19. package/dist/core/worker-fault-handling.d.ts +45 -0
  20. package/dist/core/worker-fault-handling.js +88 -0
  21. package/dist/core/worker-protocol.d.ts +18 -1
  22. package/dist/core/worker-protocol.js +1 -1
  23. package/dist/core/worker-realm-readiness.d.ts +78 -0
  24. package/dist/core/worker-realm-readiness.js +127 -0
  25. package/dist/index.d.ts +3 -1
  26. package/dist/index.js +28 -1
  27. package/dist/json-schema.js +1 -1
  28. package/dist/runtime/portable.d.ts +14 -0
  29. package/dist/runtime/portable.js +10 -0
  30. package/dist/server/authorization-scope.d.ts +32 -2
  31. package/dist/server/index.d.ts +73 -8
  32. package/dist/server/index.js +11 -3
  33. package/dist/server/operations/get-principal.d.ts +75 -0
  34. package/dist/server/operations/get-principal.js +41 -0
  35. package/dist/server/operations/get-task-diagnostics.d.ts +43 -18
  36. package/dist/server/operations/get-task-diagnostics.js +87 -126
  37. package/dist/server/operations/get-worker-diagnostics.d.ts +177 -0
  38. package/dist/server/operations/get-worker-diagnostics.js +117 -0
  39. package/dist/server/operations/list-worker-registration-rejections.d.ts +79 -0
  40. package/dist/server/operations/list-worker-registration-rejections.js +60 -0
  41. package/dist/server/operations/list-workers.js +1 -3
  42. package/dist/server/operations/static-registrations.d.ts +28 -0
  43. package/dist/server/operations/static-registrations.js +248 -0
  44. package/dist/server/rest-bindings.d.ts +5 -0
  45. package/dist/server/rest-bindings.js +34 -313
  46. package/dist/server/runtime/authentication-bridge.js +6 -14
  47. package/dist/server/runtime/context.d.ts +23 -0
  48. package/dist/server/runtime/task-dispatch.d.ts +7 -1
  49. package/dist/server/runtime/task-dispatch.js +126 -55
  50. package/dist/server/runtime/task-ledger-completion.d.ts +68 -0
  51. package/dist/server/runtime/task-ledger-completion.js +72 -0
  52. package/dist/server/runtime/task-ledger-recovery.d.ts +55 -0
  53. package/dist/server/runtime/task-ledger-recovery.js +73 -0
  54. package/dist/server/runtime/task-ledger-runtime.d.ts +64 -0
  55. package/dist/server/runtime/task-ledger-runtime.js +34 -0
  56. package/dist/server/runtime/task-metrics.d.ts +6 -4
  57. package/dist/server/runtime/task-polling.d.ts +14 -12
  58. package/dist/server/runtime/task-polling.js +106 -82
  59. package/dist/server/runtime/task-reconciliation.d.ts +35 -8
  60. package/dist/server/runtime/task-reconciliation.js +75 -74
  61. package/dist/server/runtime/task-result-resolution.d.ts +2 -10
  62. package/dist/server/runtime/task-result-resolution.js +0 -67
  63. package/dist/server/runtime/task-result-view.d.ts +82 -0
  64. package/dist/server/runtime/task-result-view.js +48 -0
  65. package/dist/server/runtime/websocket-worker-messaging.d.ts +16 -0
  66. package/dist/server/runtime/websocket-worker-messaging.js +32 -0
  67. package/dist/server/runtime/websocket-worker-registration.d.ts +41 -0
  68. package/dist/server/runtime/websocket-worker-registration.js +134 -0
  69. package/dist/server/runtime/websocket-worker.d.ts +0 -1
  70. package/dist/server/runtime/websocket-worker.js +64 -116
  71. package/dist/server/serve-internals.d.ts +9 -6
  72. package/dist/server/serve-internals.js +14 -40
  73. package/dist/server/task-ledger-codec.d.ts +40 -0
  74. package/dist/server/task-ledger-codec.js +195 -0
  75. package/dist/server/task-ledger-keys.d.ts +13 -0
  76. package/dist/server/task-ledger-keys.js +5 -0
  77. package/dist/server/task-ledger-limits.d.ts +34 -0
  78. package/dist/server/task-ledger-limits.js +5 -0
  79. package/dist/server/task-ledger-transition-helpers.d.ts +38 -0
  80. package/dist/server/task-ledger-transition-helpers.js +42 -0
  81. package/dist/server/task-ledger-transitions-cancellation.d.ts +31 -0
  82. package/dist/server/task-ledger-transitions-cancellation.js +73 -0
  83. package/dist/server/task-ledger-transitions.d.ts +129 -0
  84. package/dist/server/task-ledger-transitions.js +162 -0
  85. package/dist/server/task-ledger-types.d.ts +220 -0
  86. package/dist/server/task-ledger-types.js +1 -0
  87. package/dist/server/task-ledger.d.ts +17 -0
  88. package/dist/server/task-ledger.js +24 -0
  89. package/dist/server/task-state.d.ts +25 -152
  90. package/dist/server/task-state.js +0 -188
  91. package/dist/server/worker-admission-policy.d.ts +70 -0
  92. package/dist/server/worker-admission-policy.js +0 -0
  93. package/dist/storage/compressed-storage.js +1 -1
  94. package/dist/storage/index.d.ts +0 -2
  95. package/dist/storage/interface.d.ts +0 -2
  96. package/dist/storage/interface.js +1 -1
  97. package/dist/storage/lmdb.js +1 -1
  98. package/dist/storage/memory.js +1 -1
  99. package/dist/storage/neon.js +1 -1
  100. package/dist/storage/postgres.js +1 -1
  101. package/dist/storage/resolve.js +1 -1
  102. package/dist/storage/scoped-storage.js +1 -1
  103. package/dist/storage/testing.js +1 -1
  104. package/dist/storage/turso.js +1 -1
  105. package/dist/storage/web-extension.js +2 -2
  106. package/dist/version.d.ts +1 -1
  107. package/dist/version.js +1 -1
  108. package/dist/web-extension.js +1 -1
  109. package/dist/worker/index.d.ts +2 -0
  110. package/dist/worker/index.js +30 -4
  111. package/dist/worker/manifest/canonical-json.d.ts +20 -0
  112. package/dist/worker/manifest/canonical-json.js +13 -0
  113. package/dist/worker/manifest/capabilities.d.ts +24 -0
  114. package/dist/worker/manifest/capabilities.js +74 -0
  115. package/dist/worker/manifest/content-digest.d.ts +20 -0
  116. package/dist/worker/manifest/content-digest.js +13 -0
  117. package/dist/worker/manifest/declared-shape-digest.d.ts +14 -0
  118. package/dist/worker/manifest/declared-shape-digest.js +4 -0
  119. package/dist/worker/manifest/digest.d.ts +82 -0
  120. package/dist/worker/manifest/digest.js +9 -0
  121. package/dist/worker/manifest/execution-identity.d.ts +92 -0
  122. package/dist/worker/manifest/execution-identity.js +36 -0
  123. package/dist/worker/manifest/failure.d.ts +59 -0
  124. package/dist/worker/manifest/failure.js +4 -0
  125. package/dist/worker/manifest/index.d.ts +22 -0
  126. package/dist/worker/manifest/index.js +23 -0
  127. package/dist/worker/manifest/internal-realm.d.ts +50 -0
  128. package/dist/worker/manifest/internal-realm.js +39 -0
  129. package/dist/worker/manifest/is-record.d.ts +12 -0
  130. package/dist/worker/manifest/is-record.js +6 -0
  131. package/dist/worker/manifest/json-scan.d.ts +26 -0
  132. package/dist/worker/manifest/json-scan.js +88 -0
  133. package/dist/worker/manifest/limits.d.ts +138 -0
  134. package/dist/worker/manifest/limits.js +1 -0
  135. package/dist/worker/manifest/normalize.d.ts +63 -0
  136. package/dist/worker/manifest/normalize.js +73 -0
  137. package/dist/worker/manifest/parse-json.d.ts +24 -0
  138. package/dist/worker/manifest/parse-json.js +15 -0
  139. package/dist/worker/manifest/parse.d.ts +86 -0
  140. package/dist/worker/manifest/parse.js +188 -0
  141. package/dist/worker/manifest/registry-contract-builder.d.ts +120 -0
  142. package/dist/worker/manifest/registry-contract-builder.js +65 -0
  143. package/dist/worker/manifest/types.d.ts +263 -0
  144. package/dist/worker/manifest/types.js +1 -0
  145. package/dist/worker/manifest/utf8.d.ts +13 -0
  146. package/dist/worker/manifest/utf8.js +7 -0
  147. package/dist/worker/options.d.ts +55 -8
  148. package/dist/worker/options.js +65 -9
  149. package/dist/worker/protocol-internals.d.ts +1 -2
  150. package/dist/worker/protocol-internals.js +0 -5
  151. package/dist/worker/protocol-messages.d.ts +33 -17
  152. package/dist/worker/protocol-schemas.d.ts +36 -74
  153. package/dist/worker/protocol-schemas.js +25 -18
  154. package/dist/worker/protocol-version.d.ts +18 -14
  155. package/dist/worker/protocol-version.js +1 -1
  156. package/dist/worker/protocol.js +1 -1
  157. package/dist/worker/registry/deployment-consistency.d.ts +78 -0
  158. package/dist/worker/registry/deployment-consistency.js +27 -0
  159. package/dist/worker/registry/drain.d.ts +14 -0
  160. package/dist/worker/registry/drain.js +24 -0
  161. package/dist/worker/registry/rejections.d.ts +31 -0
  162. package/dist/worker/registry/rejections.js +10 -0
  163. package/dist/worker/registry/summary.d.ts +0 -3
  164. package/dist/worker/registry/summary.js +2 -5
  165. package/dist/worker/registry/types.d.ts +10 -3
  166. package/dist/worker/registry.d.ts +47 -2
  167. package/dist/worker/registry.js +45 -32
  168. package/dist/workers/workflow-worker-entry.d.ts +12 -7
  169. package/dist/workers/workflow-worker-entry.js +9 -2
  170. package/package.json +3 -3
  171. package/dist/server/task-resolved-record.d.ts +0 -12
  172. package/dist/server/task-resolved-record.js +0 -35
@@ -6,7 +6,9 @@ import {
6
6
  } from "./execute-with-interceptors.js";
7
7
  import { HeartbeatManager } from "./heartbeat.js";
8
8
  import {
9
- buildRegisterMessage
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: options.queue ?? DEFAULT_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.#options.serverUrl, this.#options.headers);
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, Object.keys(this.#activityTable), this.#options));
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,4 @@
1
+ import { hashString } from "../../runtime/portable.js";
2
+ export function declaredShapeDigest(input) {
3
+ return `declared-shape:${hashString(input)}`;
4
+ }
@@ -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,4 @@
1
+ export function manifestFailure(reason, message, path) {
2
+ const described = path === void 0 ? message : `${path} ${message}`;
3
+ return path === void 0 ? { ok: !1, reason, message: described } : { ok: !1, reason, message: described, path };
4
+ }
@@ -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;