@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
@@ -10,12 +10,16 @@
10
10
  /**
11
11
  * Current RemoteWorker wire protocol version.
12
12
  *
13
- * **Version 2** (Phase 4 of the workflow-builder refactor) bumps the semantics
14
- * of the `activities: string[]` field in the register message. Each entry is
15
- * now a qualified `${workflowType}.${activityName}` name rather than a bare
16
- * activity name, so the server can dispatch the correct workflow's activity
17
- * implementation when the same activity key is used by multiple workflows. The
18
- * wire shape did not change; only the meaning of the strings.
13
+ * **Version 3** (Canonical Worker Manifest, WFT-27) replaces the register
14
+ * message's parallel top-level identity fields (`activities`, `queue`,
15
+ * `deploymentName`, `buildId`, `runtimeVersion`, `gitSha`, `capabilities`)
16
+ * with a single `manifest: WorkerManifest` field, and the ack echoes
17
+ * `acceptedManifestDigest` plus `serverCapabilities` instead of echoing
18
+ * `activities` back. Both directions of the handshake reshape a strictly
19
+ * larger change than version 2's same-shape/different-semantics bump — so
20
+ * this is a clean break rather than a negotiated one: a v2 peer gets the
21
+ * canonical `WorkerProtocolIncompatibleError`, not a confusing
22
+ * `invalid_registration`.
19
23
  *
20
24
  * @example
21
25
  * ```ts
@@ -24,7 +28,7 @@
24
28
  * const registration = { type: 'register', protocolVersion: REMOTE_WORKER_PROTOCOL_VERSION };
25
29
  * ```
26
30
  */
27
- export declare const REMOTE_WORKER_PROTOCOL_VERSION = 2;
31
+ export declare const REMOTE_WORKER_PROTOCOL_VERSION = 3;
28
32
  /**
29
33
  * Lowest RemoteWorker protocol version accepted by this package.
30
34
  *
@@ -32,10 +36,10 @@ export declare const REMOTE_WORKER_PROTOCOL_VERSION = 2;
32
36
  * ```ts
33
37
  * import { REMOTE_WORKER_MIN_PROTOCOL_VERSION } from '@lostgradient/weft/worker-protocol';
34
38
  *
35
- * const supportsCurrentVersion = REMOTE_WORKER_MIN_PROTOCOL_VERSION === 2;
39
+ * const supportsCurrentVersion = REMOTE_WORKER_MIN_PROTOCOL_VERSION === 3;
36
40
  * ```
37
41
  */
38
- export declare const REMOTE_WORKER_MIN_PROTOCOL_VERSION = 2;
42
+ export declare const REMOTE_WORKER_MIN_PROTOCOL_VERSION = 3;
39
43
  /**
40
44
  * Highest RemoteWorker protocol version accepted by this package.
41
45
  *
@@ -43,10 +47,10 @@ export declare const REMOTE_WORKER_MIN_PROTOCOL_VERSION = 2;
43
47
  * ```ts
44
48
  * import { REMOTE_WORKER_MAX_PROTOCOL_VERSION } from '@lostgradient/weft/worker-protocol';
45
49
  *
46
- * const canUseRequestedVersion = 2 <= REMOTE_WORKER_MAX_PROTOCOL_VERSION;
50
+ * const canUseRequestedVersion = 3 <= REMOTE_WORKER_MAX_PROTOCOL_VERSION;
47
51
  * ```
48
52
  */
49
- export declare const REMOTE_WORKER_MAX_PROTOCOL_VERSION = 2;
53
+ export declare const REMOTE_WORKER_MAX_PROTOCOL_VERSION = 3;
50
54
  /**
51
55
  * Explicit supported RemoteWorker protocol versions.
52
56
  *
@@ -54,10 +58,10 @@ export declare const REMOTE_WORKER_MAX_PROTOCOL_VERSION = 2;
54
58
  * ```ts
55
59
  * import { REMOTE_WORKER_SUPPORTED_PROTOCOL_VERSIONS } from '@lostgradient/weft/worker-protocol';
56
60
  *
57
- * const supported = REMOTE_WORKER_SUPPORTED_PROTOCOL_VERSIONS.includes(2);
61
+ * const supported = REMOTE_WORKER_SUPPORTED_PROTOCOL_VERSIONS.includes(3);
58
62
  * ```
59
63
  */
60
- export declare const REMOTE_WORKER_SUPPORTED_PROTOCOL_VERSIONS: readonly [2];
64
+ export declare const REMOTE_WORKER_SUPPORTED_PROTOCOL_VERSIONS: readonly [3];
61
65
  /**
62
66
  * RemoteWorker protocol version accepted by this package.
63
67
  *
@@ -65,7 +69,7 @@ export declare const REMOTE_WORKER_SUPPORTED_PROTOCOL_VERSIONS: readonly [2];
65
69
  * ```ts
66
70
  * import type { RemoteWorkerProtocolVersion } from '@lostgradient/weft/worker-protocol';
67
71
  *
68
- * const version: RemoteWorkerProtocolVersion = 2;
72
+ * const version: RemoteWorkerProtocolVersion = 3;
69
73
  * ```
70
74
  */
71
75
  export type RemoteWorkerProtocolVersion = (typeof REMOTE_WORKER_SUPPORTED_PROTOCOL_VERSIONS)[number];
@@ -1 +1 @@
1
- export const REMOTE_WORKER_PROTOCOL_VERSION = 2, REMOTE_WORKER_MIN_PROTOCOL_VERSION = 2, REMOTE_WORKER_MAX_PROTOCOL_VERSION = 2, REMOTE_WORKER_SUPPORTED_PROTOCOL_VERSIONS = [REMOTE_WORKER_PROTOCOL_VERSION];
1
+ export const REMOTE_WORKER_PROTOCOL_VERSION = 3, REMOTE_WORKER_MIN_PROTOCOL_VERSION = 3, REMOTE_WORKER_MAX_PROTOCOL_VERSION = 3, REMOTE_WORKER_SUPPORTED_PROTOCOL_VERSIONS = [REMOTE_WORKER_PROTOCOL_VERSION];
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- var c=import.meta.require,l=(z,B,D)=>{if(B!=null){if(typeof B!=="object"&&typeof B!=="function")throw TypeError('Object expected to be assigned to "using" declaration');let X;if(D)X=B[Symbol.asyncDispose];if(X===void 0)X=B[Symbol.dispose];if(typeof X!=="function")throw TypeError("Object not disposable");z.push([D,X,B])}else if(D)z.push([D]);return B},a=(z,B,D)=>{let X=(U)=>B=D?new SuppressedError(U,B,"An error was suppressed during disposal"):(D=!0,U),Z=(U)=>{while(U=z.pop())try{var j=U[1]&&U[1].call(U[2]);if(U[0])return Promise.resolve(j).then(Z,(G)=>(X(G),Z()))}catch(G){X(G)}if(D)throw B};return Z()};function b(z){return z!==null&&typeof z==="object"&&!Array.isArray(z)}function x(z){if(z===null||typeof z==="string"||typeof z==="boolean"||typeof z==="number"&&Number.isFinite(z))return!0;if(Array.isArray(z))return z.every(x);if(!b(z))return!1;return Object.values(z).every(x)}function Y(z){return typeof z==="string"&&z.length>0}function L(z){return Array.isArray(z)&&z.every(Y)}function q(z){if(!b(z))return!1;return Object.values(z).every((B)=>typeof B==="string")}function A(z){if(!b(z))return!1;return Object.values(z).every(x)}function H(z){return typeof z==="number"&&Number.isFinite(z)}function Q(z,B,D){return{ok:!1,error:{code:z,message:B,...D!==void 0?{requestedProtocolVersion:D}:{}}}}function f(z,B,D){let X={};for(let[Z,U,j,G]of D){let J=B[Z];if(J===void 0){if(U)return{ok:!1,error:Q(z,G)};continue}if(!j(J))return{ok:!1,error:Q(z,G)};X[Z]=J}return{ok:!0,values:X}}var $=2,W=2,M=2,I=[2];var w={$ref:"#/$defs/jsonValue"},C={type:"object",additionalProperties:w},g={type:"object",additionalProperties:{type:"string"}},F={const:$},K={register:{type:"object",additionalProperties:!1,required:["type","protocolVersion","workerId","activities"],properties:{type:{const:"register"},protocolVersion:F,workerId:{type:"string",minLength:1},activities:{type:"array",items:{type:"string",minLength:1}},concurrency:{type:"number",minimum:1,maximum:1000},queue:{type:"string",minLength:1},deploymentName:{type:"string",minLength:1},buildId:{type:"string",minLength:1},runtimeVersion:{type:"string",minLength:1},gitSha:{type:"string",minLength:1},startedAt:{type:"number"},capabilities:C}},heartbeat:{type:"object",additionalProperties:!1,required:["type","workerId"],properties:{type:{const:"heartbeat"},workerId:{type:"string",minLength:1}}},taskResult:{oneOf:[{type:"object",additionalProperties:!1,required:["type","operationId","status","value","attemptToken"],properties:{type:{const:"taskResult"},operationId:{type:"string",minLength:1},status:{const:"completed"},value:w,attemptToken:{type:"string",minLength:1}}},{type:"object",additionalProperties:!1,required:["type","operationId","status","error","attemptToken"],properties:{type:{const:"taskResult"},operationId:{type:"string",minLength:1},status:{const:"failed"},error:{type:"string"},attemptToken:{type:"string",minLength:1}}},{type:"object",additionalProperties:!1,required:["type","operationId","status","error","attemptToken"],properties:{type:{const:"taskResult"},operationId:{type:"string",minLength:1},status:{const:"cancelled"},error:{type:"string"},cancelled:{const:!0},attemptToken:{type:"string",minLength:1}}}]},task:{type:"object",additionalProperties:!1,required:["type","operationId","activityName","input","attemptToken"],properties:{type:{const:"task"},operationId:{type:"string",minLength:1},activityName:{type:"string",minLength:1},input:w,attempt:{type:"number",minimum:1},headers:g,workflowExecutionToken:{type:"string",minLength:1},attemptToken:{type:"string",minLength:1}}},cancel:{type:"object",additionalProperties:!1,required:["type","operationId"],properties:{type:{const:"cancel"},operationId:{type:"string",minLength:1}}},shutdown:{type:"object",additionalProperties:!1,required:["type"],properties:{type:{const:"shutdown"}}},registerAck:{type:"object",additionalProperties:!1,required:["type","protocolVersion","workerId","queue","activities","concurrency"],properties:{type:{const:"registerAck"},protocolVersion:F,workerId:{type:"string",minLength:1},queue:{type:"string",minLength:1},activities:{type:"array",items:{type:"string",minLength:1}},concurrency:{type:"number",minimum:1,maximum:1000}}},registerError:{type:"object",additionalProperties:!1,required:["type","code","message","supportedProtocolVersions"],properties:{type:{const:"registerError"},code:{enum:["invalid_registration","unsupported_protocol_version"]},message:{type:"string"},supportedProtocolVersions:{type:"array",items:F},requestedProtocolVersion:{type:"number"}}},protocolError:{type:"object",additionalProperties:!1,required:["type","code","message"],properties:{type:{const:"protocolError"},code:{enum:["invalid_json","invalid_message","unknown_message_type","registration_required"]},message:{type:"string"}}}},P={$schema:"https://json-schema.org/draft/2020-12/schema",$id:`https://weft.dev/schemas/remote-worker-protocol.v${String($)}.json`,title:`Weft RemoteWorker Protocol v${String($)}`,oneOf:Object.keys(K).map((z)=>({$ref:`#/$defs/messages/${z}`})),$defs:{jsonValue:{oneOf:[{type:"null"},{type:"boolean"},{type:"number"},{type:"string"},{type:"array",items:{$ref:"#/$defs/jsonValue"}},{type:"object",additionalProperties:{$ref:"#/$defs/jsonValue"}}]},jsonObject:C,messages:K}};function N(z){let B=z.attemptToken;if(!Y(B))return Q("invalid_message","taskResult.attemptToken must be a non-empty string");return{ok:!0,message:{attemptToken:B}}}function y(z,B){let D=B.value;if(!x(D))return Q("invalid_message","completed taskResult.value must be valid JSON");let X=N(B);if(!X.ok)return X;return{ok:!0,message:{type:"taskResult",operationId:z,status:"completed",value:D,...X.message}}}function T(z,B){let D=B.error;if(typeof D!=="string")return Q("invalid_message","failed taskResult.error must be a string");let X=N(B);if(!X.ok)return X;return{ok:!0,message:{type:"taskResult",operationId:z,status:"failed",error:D,...X.message}}}function _(z,B){let D=B.error;if(typeof D!=="string")return Q("invalid_message","cancelled taskResult.error must be a string");let X=B.cancelled;if(X!==void 0&&X!==!0)return Q("invalid_message","taskResult.cancelled must be true when present");let Z=N(B);if(!Z.ok)return Z;return{ok:!0,message:{type:"taskResult",operationId:z,status:"cancelled",error:D,...X===!0?{cancelled:X}:{},...Z.message}}}var V={completed:y,failed:T,cancelled:_};function k(z){return z==="completed"||z==="failed"||z==="cancelled"}function h(z){let B=z.operationId;if(!Y(B))return Q("invalid_message","taskResult.operationId must be a non-empty string");let D=z.status;if(!k(D))return Q("invalid_message","taskResult.status must be completed, failed, or cancelled");return V[D](B,z)}var S=new Set(["register","heartbeat","taskResult"]),O=new Set(["registerAck","registerError","protocolError","task","cancel","shutdown"]);function E(z){if(z===$)return{ok:!0,message:z};let B=H(z)?z:void 0;return Q("unsupported_protocol_version",`Unsupported RemoteWorker protocol version: ${String(z)}`,B)}var m=[["workerId",!0,Y,"register.workerId must be a non-empty string"],["activities",!0,L,"register.activities must be an array of non-empty strings"],["concurrency",!1,H,"register.concurrency must be a finite number"],["queue",!1,Y,"register.queue must be a non-empty string"],["deploymentName",!1,Y,"register.deploymentName must be a non-empty string when present"],["buildId",!1,Y,"register.buildId must be a non-empty string when present"],["runtimeVersion",!1,Y,"register.runtimeVersion must be a non-empty string when present"],["gitSha",!1,Y,"register.gitSha must be a non-empty string when present"],["startedAt",!1,H,"register.startedAt must be a finite number when present"],["capabilities",!1,A,"register.capabilities must be a JSON object when present"]];function R(z){let B=E(z.protocolVersion);if(!B.ok)return B;let D=f("invalid_registration",z,m);if(!D.ok)return D.error;return{ok:!0,message:{type:"register",protocolVersion:B.message,...D.values}}}function p(z){let B=z.workerId;if(!Y(B))return Q("invalid_message","heartbeat.workerId must be a non-empty string");return{ok:!0,message:{type:"heartbeat",workerId:B}}}var n=[["operationId",!0,Y,"task.operationId must be a non-empty string"],["activityName",!0,Y,"task.activityName must be a non-empty string"],["input",!0,x,"task.input must be valid JSON"],["attempt",!1,H,"task.attempt must be a finite number"],["headers",!1,q,"task.headers must be a string map"],["workflowExecutionToken",!1,Y,"task.workflowExecutionToken must be a non-empty string"],["attemptToken",!0,Y,"task.attemptToken must be a non-empty string"]];function i(z){let B=f("invalid_message",z,n);if(!B.ok)return B.error;return{ok:!0,message:{type:"task",...B.values}}}function v(z){let B=z.operationId;if(!Y(B))return Q("invalid_message","cancel.operationId must be a non-empty string");return{ok:!0,message:{type:"cancel",operationId:B}}}function d(){return{ok:!0,message:{type:"shutdown"}}}function u(z){let B=z.protocolVersion;if(B!==$)return Q("invalid_message",`registerAck.protocolVersion must be ${String($)}`);let{workerId:D,queue:X,activities:Z,concurrency:U}=z;if(!Y(D))return Q("invalid_message","registerAck.workerId must be a non-empty string");if(!Y(X))return Q("invalid_message","registerAck.queue must be a non-empty string");if(!L(Z))return Q("invalid_message","registerAck.activities must be a string array");if(typeof U!=="number"||!Number.isFinite(U))return Q("invalid_message","registerAck.concurrency must be a finite number");return{ok:!0,message:{type:"registerAck",protocolVersion:B,workerId:D,queue:X,activities:Z,concurrency:U}}}function s(z){let{code:B,message:D,supportedProtocolVersions:X,requestedProtocolVersion:Z}=z;if(B!=="invalid_registration"&&B!=="unsupported_protocol_version")return Q("invalid_message","registerError.code is not recognized");if(typeof D!=="string")return Q("invalid_message","registerError.message must be a string");if(!Array.isArray(X)||!X.every((j)=>j===$))return Q("invalid_message","registerError.supportedProtocolVersions is invalid");if(Z!==void 0&&!H(Z))return Q("invalid_message","registerError.requestedProtocolVersion must be a finite number");return{ok:!0,message:{type:"registerError",code:B,message:D,supportedProtocolVersions:X,...Z!==void 0?{requestedProtocolVersion:Z}:{}}}}function t(z){let{code:B,message:D}=z;if(B!=="invalid_json"&&B!=="invalid_message"&&B!=="unknown_message_type"&&B!=="registration_required")return Q("invalid_message","protocolError.code is not recognized");if(typeof D!=="string")return Q("invalid_message","protocolError.message must be a string");return{ok:!0,message:{type:"protocolError",code:B,message:D}}}function $z(z){if(!b(z))return Q("invalid_message","Worker protocol message must be a JSON object");let B=z.type;if(typeof B!=="string")return Q("invalid_message","Worker protocol message.type must be a string");if(!S.has(B))return Q("unknown_message_type",`Unknown worker message type: ${B}`);switch(B){case"register":return R(z);case"heartbeat":return p(z);case"taskResult":return h(z);default:return Q("unknown_message_type",`Unknown worker message type: ${B}`)}}function xz(z){if(!b(z))return Q("invalid_message","Server protocol message must be a JSON object");let B=z.type;if(typeof B!=="string")return Q("invalid_message","Server protocol message.type must be a string");if(!O.has(B))return Q("unknown_message_type",`Unknown server message type: ${B}`);switch(B){case"registerAck":return u(z);case"registerError":return s(z);case"protocolError":return t(z);case"task":return i(z);case"cancel":return v(z);case"shutdown":return d();default:return Q("unknown_message_type",`Unknown server message type: ${B}`)}}export{$z as parseWorkerToServerMessage,xz as parseServerToWorkerMessage,x as isRemoteWorkerJsonValue,I as REMOTE_WORKER_SUPPORTED_PROTOCOL_VERSIONS,$ as REMOTE_WORKER_PROTOCOL_VERSION,P as REMOTE_WORKER_PROTOCOL_JSON_SCHEMA,W as REMOTE_WORKER_MIN_PROTOCOL_VERSION,K as REMOTE_WORKER_MESSAGE_SCHEMAS,M as REMOTE_WORKER_MAX_PROTOCOL_VERSION};
2
+ var t=import.meta.require,l=(z,B,Q)=>{if(B!=null){if(typeof B!=="object"&&typeof B!=="function")throw TypeError('Object expected to be assigned to "using" declaration');let Y;if(Q)Y=B[Symbol.asyncDispose];if(Y===void 0)Y=B[Symbol.dispose];if(typeof Y!=="function")throw TypeError("Object not disposable");z.push([Q,Y,B])}else if(Q)z.push([Q]);return B},a=(z,B,Q)=>{let Y=($)=>B=Q?new SuppressedError($,B,"An error was suppressed during disposal"):(Q=!0,$),Z=($)=>{while($=z.pop())try{var x=$[1]&&$[1].call($[2]);if($[0])return Promise.resolve(x).then(Z,(b)=>(Y(b),Z()))}catch(b){Y(b)}if(Q)throw B};return Z()};function j(z){return z!==null&&typeof z==="object"&&!Array.isArray(z)}function H(z){if(z===null||typeof z==="string"||typeof z==="boolean"||typeof z==="number"&&Number.isFinite(z))return!0;if(Array.isArray(z))return z.every(H);if(!j(z))return!1;return Object.values(z).every(H)}function U(z){return typeof z==="string"&&z.length>0}function K(z){return Array.isArray(z)&&z.every(U)}function N(z){if(!j(z))return!1;return Object.values(z).every((B)=>typeof B==="string")}function G(z){return typeof z==="number"&&Number.isFinite(z)}function X(z,B,Q){return{ok:!1,error:{code:z,message:B,...Q!==void 0?{requestedProtocolVersion:Q}:{}}}}function L(z,B,Q){let Y={};for(let[Z,$,x,b]of Q){let J=B[Z];if(J===void 0){if($)return{ok:!1,error:X(z,b)};continue}if(!x(J))return{ok:!1,error:X(z,b)};Y[Z]=J}return{ok:!0,values:Y}}var D=3,h=3,C=3,W=[3];var F={$ref:"#/$defs/jsonValue"},I={type:"object",additionalProperties:F},M={type:"object",additionalProperties:{type:"string"}},q={const:D},f={register:{type:"object",additionalProperties:!1,required:["type","protocolVersion","workerId","manifest"],properties:{type:{const:"register"},protocolVersion:q,workerId:{type:"string",minLength:1},manifest:{type:"object"},concurrency:{type:"number",minimum:1,maximum:1000},startedAt:{type:"number"}}},heartbeat:{type:"object",additionalProperties:!1,required:["type","workerId"],properties:{type:{const:"heartbeat"},workerId:{type:"string",minLength:1}}},taskResult:{oneOf:[{type:"object",additionalProperties:!1,required:["type","operationId","status","value","attemptToken"],properties:{type:{const:"taskResult"},operationId:{type:"string",minLength:1},status:{const:"completed"},value:F,attemptToken:{type:"string",minLength:1}}},{type:"object",additionalProperties:!1,required:["type","operationId","status","error","attemptToken"],properties:{type:{const:"taskResult"},operationId:{type:"string",minLength:1},status:{const:"failed"},error:{type:"string"},attemptToken:{type:"string",minLength:1}}},{type:"object",additionalProperties:!1,required:["type","operationId","status","error","attemptToken"],properties:{type:{const:"taskResult"},operationId:{type:"string",minLength:1},status:{const:"cancelled"},error:{type:"string"},cancelled:{const:!0},attemptToken:{type:"string",minLength:1}}}]},task:{type:"object",additionalProperties:!1,required:["type","operationId","activityName","input","attemptToken"],properties:{type:{const:"task"},operationId:{type:"string",minLength:1},activityName:{type:"string",minLength:1},input:F,attempt:{type:"number",minimum:1},headers:M,workflowExecutionToken:{type:"string",minLength:1},attemptToken:{type:"string",minLength:1}}},cancel:{type:"object",additionalProperties:!1,required:["type","operationId"],properties:{type:{const:"cancel"},operationId:{type:"string",minLength:1}}},shutdown:{type:"object",additionalProperties:!1,required:["type"],properties:{type:{const:"shutdown"}}},registerAck:{type:"object",additionalProperties:!1,required:["type","protocolVersion","workerId","queue","concurrency","acceptedManifestDigest","serverCapabilities"],properties:{type:{const:"registerAck"},protocolVersion:q,workerId:{type:"string",minLength:1},queue:{type:"string",minLength:1},concurrency:{type:"number",minimum:1,maximum:1000},acceptedManifestDigest:{type:"string",minLength:1},serverCapabilities:{type:"array",items:{type:"string",minLength:1}}}},registerError:{type:"object",additionalProperties:!1,required:["type","code","message","supportedProtocolVersions"],properties:{type:{const:"registerError"},code:{enum:["invalid_registration","unsupported_protocol_version","deployment_conflict","registration_rejected"]},message:{type:"string"},supportedProtocolVersions:{type:"array",items:{type:"number"}},requestedProtocolVersion:{type:"number"}}},protocolError:{type:"object",additionalProperties:!1,required:["type","code","message"],properties:{type:{const:"protocolError"},code:{enum:["invalid_json","invalid_message","unknown_message_type","registration_required"]},message:{type:"string"}}}},P={$schema:"https://json-schema.org/draft/2020-12/schema",$id:`https://weft.dev/schemas/remote-worker-protocol.v${String(D)}.json`,title:`Weft RemoteWorker Protocol v${String(D)}`,oneOf:Object.keys(f).map((z)=>({$ref:`#/$defs/messages/${z}`})),$defs:{jsonValue:{oneOf:[{type:"null"},{type:"boolean"},{type:"number"},{type:"string"},{type:"array",items:{$ref:"#/$defs/jsonValue"}},{type:"object",additionalProperties:{$ref:"#/$defs/jsonValue"}}]},jsonObject:I,messages:f}};function w(z){let B=z.attemptToken;if(!U(B))return X("invalid_message","taskResult.attemptToken must be a non-empty string");return{ok:!0,message:{attemptToken:B}}}function g(z,B){let Q=B.value;if(!H(Q))return X("invalid_message","completed taskResult.value must be valid JSON");let Y=w(B);if(!Y.ok)return Y;return{ok:!0,message:{type:"taskResult",operationId:z,status:"completed",value:Q,...Y.message}}}function y(z,B){let Q=B.error;if(typeof Q!=="string")return X("invalid_message","failed taskResult.error must be a string");let Y=w(B);if(!Y.ok)return Y;return{ok:!0,message:{type:"taskResult",operationId:z,status:"failed",error:Q,...Y.message}}}function T(z,B){let Q=B.error;if(typeof Q!=="string")return X("invalid_message","cancelled taskResult.error must be a string");let Y=B.cancelled;if(Y!==void 0&&Y!==!0)return X("invalid_message","taskResult.cancelled must be true when present");let Z=w(B);if(!Z.ok)return Z;return{ok:!0,message:{type:"taskResult",operationId:z,status:"cancelled",error:Q,...Y===!0?{cancelled:Y}:{},...Z.message}}}var _={completed:g,failed:y,cancelled:T};function V(z){return z==="completed"||z==="failed"||z==="cancelled"}function A(z){let B=z.operationId;if(!U(B))return X("invalid_message","taskResult.operationId must be a non-empty string");let Q=z.status;if(!V(Q))return X("invalid_message","taskResult.status must be completed, failed, or cancelled");return _[Q](B,z)}var k=new Set(["register","heartbeat","taskResult"]),S=new Set(["registerAck","registerError","protocolError","task","cancel","shutdown"]);function m(z){if(z===D)return{ok:!0,message:z};let B=G(z)?z:void 0;return X("unsupported_protocol_version",`Unsupported RemoteWorker protocol version: ${String(z)}`,B)}var O=[["workerId",!0,U,"register.workerId must be a non-empty string"],["manifest",!0,j,"register.manifest must be a JSON object"],["concurrency",!1,G,"register.concurrency must be a finite number"],["startedAt",!1,G,"register.startedAt must be a finite number when present"]];function E(z){let B=m(z.protocolVersion);if(!B.ok)return B;let Q=L("invalid_registration",z,O);if(!Q.ok)return Q.error;return{ok:!0,message:{type:"register",protocolVersion:B.message,...Q.values}}}function R(z){let B=z.workerId;if(!U(B))return X("invalid_message","heartbeat.workerId must be a non-empty string");return{ok:!0,message:{type:"heartbeat",workerId:B}}}var p=[["operationId",!0,U,"task.operationId must be a non-empty string"],["activityName",!0,U,"task.activityName must be a non-empty string"],["input",!0,H,"task.input must be valid JSON"],["attempt",!1,G,"task.attempt must be a finite number"],["headers",!1,N,"task.headers must be a string map"],["workflowExecutionToken",!1,U,"task.workflowExecutionToken must be a non-empty string"],["attemptToken",!0,U,"task.attemptToken must be a non-empty string"]];function n(z){let B=L("invalid_message",z,p);if(!B.ok)return B.error;return{ok:!0,message:{type:"task",...B.values}}}function i(z){let B=z.operationId;if(!U(B))return X("invalid_message","cancel.operationId must be a non-empty string");return{ok:!0,message:{type:"cancel",operationId:B}}}function v(){return{ok:!0,message:{type:"shutdown"}}}function d(z){let B=z.protocolVersion;if(B!==D)return X("invalid_message",`registerAck.protocolVersion must be ${String(D)}`);let{workerId:Q,queue:Y,concurrency:Z,acceptedManifestDigest:$,serverCapabilities:x}=z;if(!U(Q))return X("invalid_message","registerAck.workerId must be a non-empty string");if(!U(Y))return X("invalid_message","registerAck.queue must be a non-empty string");if(typeof Z!=="number"||!Number.isFinite(Z))return X("invalid_message","registerAck.concurrency must be a finite number");if(!U($))return X("invalid_message","registerAck.acceptedManifestDigest must be a non-empty string");if(!K(x))return X("invalid_message","registerAck.serverCapabilities must be an array of non-empty strings");return{ok:!0,message:{type:"registerAck",protocolVersion:B,workerId:Q,queue:Y,concurrency:Z,acceptedManifestDigest:$,serverCapabilities:x}}}function u(z){return z==="invalid_registration"||z==="unsupported_protocol_version"||z==="deployment_conflict"||z==="registration_rejected"}function s(z){let{code:B,message:Q,supportedProtocolVersions:Y,requestedProtocolVersion:Z}=z;if(!u(B))return X("invalid_message","registerError.code is not recognized");if(typeof Q!=="string")return X("invalid_message","registerError.message must be a string");if(!Array.isArray(Y)||!Y.every((x)=>G(x)))return X("invalid_message","registerError.supportedProtocolVersions is invalid");if(Z!==void 0&&!G(Z))return X("invalid_message","registerError.requestedProtocolVersion must be a finite number");return{ok:!0,message:{type:"registerError",code:B,message:Q,supportedProtocolVersions:Y,...Z!==void 0?{requestedProtocolVersion:Z}:{}}}}function c(z){let{code:B,message:Q}=z;if(B!=="invalid_json"&&B!=="invalid_message"&&B!=="unknown_message_type"&&B!=="registration_required")return X("invalid_message","protocolError.code is not recognized");if(typeof Q!=="string")return X("invalid_message","protocolError.message must be a string");return{ok:!0,message:{type:"protocolError",code:B,message:Q}}}function xz(z){if(!j(z))return X("invalid_message","Worker protocol message must be a JSON object");let B=z.type;if(typeof B!=="string")return X("invalid_message","Worker protocol message.type must be a string");if(!k.has(B))return X("unknown_message_type",`Unknown worker message type: ${B}`);switch(B){case"register":return E(z);case"heartbeat":return R(z);case"taskResult":return A(z);default:return X("unknown_message_type",`Unknown worker message type: ${B}`)}}function Dz(z){if(!j(z))return X("invalid_message","Server protocol message must be a JSON object");let B=z.type;if(typeof B!=="string")return X("invalid_message","Server protocol message.type must be a string");if(!S.has(B))return X("unknown_message_type",`Unknown server message type: ${B}`);switch(B){case"registerAck":return d(z);case"registerError":return s(z);case"protocolError":return c(z);case"task":return n(z);case"cancel":return i(z);case"shutdown":return v();default:return X("unknown_message_type",`Unknown server message type: ${B}`)}}export{xz as parseWorkerToServerMessage,Dz as parseServerToWorkerMessage,H as isRemoteWorkerJsonValue,W as REMOTE_WORKER_SUPPORTED_PROTOCOL_VERSIONS,D as REMOTE_WORKER_PROTOCOL_VERSION,P as REMOTE_WORKER_PROTOCOL_JSON_SCHEMA,h as REMOTE_WORKER_MIN_PROTOCOL_VERSION,f as REMOTE_WORKER_MESSAGE_SCHEMAS,C as REMOTE_WORKER_MAX_PROTOCOL_VERSION};
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Process-local enforcement that one `(deploymentName, buildId)` pair never
3
+ * registers two different artifact digests.
4
+ *
5
+ * A build ID is meant to identify one immutable build output. If two workers
6
+ * present the same deployment name and build ID with different artifact
7
+ * digests, at least one of them is lying about what it is running — either a
8
+ * build ID was reused across a redeploy with different bytes, or a worker is
9
+ * misconfigured. Either way, silently accepting both would let routing send
10
+ * work to a worker running code the operator did not intend for that build
11
+ * identity.
12
+ *
13
+ * This is process-local and non-durable: restart-durable enforcement needs
14
+ * revision-aware routing, which is not yet built. The guard also does not
15
+ * evict entries when a worker disconnects — surviving disconnects is the
16
+ * point. A redeploy that reuses a build ID with different bytes must still
17
+ * conflict even if every worker from the first deploy has since dropped off,
18
+ * because the record of what that build ID means must outlive any single
19
+ * connection.
20
+ *
21
+ * @module worker/registry/deployment-consistency
22
+ */
23
+ /** Outcome of {@link DeploymentConsistencyGuard.checkAndRecord}. */
24
+ export type DeploymentConsistencyResult = {
25
+ readonly ok: true;
26
+ } | {
27
+ readonly ok: false;
28
+ readonly existingArtifactDigest: string;
29
+ };
30
+ /**
31
+ * Tracks the artifact digest first seen for each `(deploymentName, buildId)`
32
+ * pair for the lifetime of this process.
33
+ *
34
+ * Keyed by nested maps rather than a joined string: `deploymentName` and
35
+ * `buildId` are validated only as non-empty, byte-bounded strings, with no
36
+ * character-set restriction, so a separator-joined key could collide across
37
+ * two distinct pairs (e.g. `("ab", "c")` and `("a", "bc")` would
38
+ * both join to `"abc"`). Nested maps compare each component
39
+ * independently and cannot collide this way.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * import { DeploymentConsistencyGuard } from '@lostgradient/weft/server';
44
+ *
45
+ * const guard = new DeploymentConsistencyGuard();
46
+ * console.log(guard.checkAndRecord('billing', 'b3', 'sha256:aa').ok); // true
47
+ * console.log(guard.checkAndRecord('billing', 'b3', 'sha256:aa').ok); // true (same digest)
48
+ * console.log(guard.checkAndRecord('billing', 'b3', 'sha256:bb').ok); // false (conflict)
49
+ * ```
50
+ */
51
+ export declare class DeploymentConsistencyGuard {
52
+ #private;
53
+ constructor();
54
+ /**
55
+ * Read-only: does `artifactDigest` conflict with any digest already
56
+ * recorded for `(deploymentName, buildId)`? Never records. Callers with a
57
+ * rejection gate between checking and committing (an admission policy, a
58
+ * hijack guard) must call this before that gate and call {@link record}
59
+ * only once every later gate has also passed, so a worker this function's
60
+ * caller ultimately declines cannot permanently poison a deployment/build
61
+ * slot with an untrusted digest.
62
+ */
63
+ check(deploymentName: string, buildId: string, artifactDigest: string): DeploymentConsistencyResult;
64
+ /**
65
+ * Record `artifactDigest` as the digest of record for `(deploymentName,
66
+ * buildId)` if this is the first sighting; a no-op otherwise. Callers must
67
+ * already have confirmed via {@link check} that this does not conflict.
68
+ */
69
+ record(deploymentName: string, buildId: string, artifactDigest: string): void;
70
+ /**
71
+ * Check whether `artifactDigest` is consistent with any digest already
72
+ * recorded for `(deploymentName, buildId)`, recording it as the digest of
73
+ * record when this is the first sighting. Convenience combining
74
+ * {@link check} and {@link record} for callers with no rejection gate
75
+ * between checking and committing.
76
+ */
77
+ checkAndRecord(deploymentName: string, buildId: string, artifactDigest: string): DeploymentConsistencyResult;
78
+ }
@@ -0,0 +1,27 @@
1
+ export class DeploymentConsistencyGuard {
2
+ #artifactDigestByBuildIdByDeployment;
3
+ constructor() {
4
+ this.#artifactDigestByBuildIdByDeployment = new Map;
5
+ }
6
+ check(deploymentName, buildId, artifactDigest) {
7
+ const existingArtifactDigest = this.#artifactDigestByBuildIdByDeployment.get(deploymentName)?.get(buildId);
8
+ if (existingArtifactDigest === void 0 || existingArtifactDigest === artifactDigest)
9
+ return { ok: !0 };
10
+ return { ok: !1, existingArtifactDigest };
11
+ }
12
+ record(deploymentName, buildId, artifactDigest) {
13
+ let byBuildId = this.#artifactDigestByBuildIdByDeployment.get(deploymentName);
14
+ if (byBuildId === void 0) {
15
+ byBuildId = new Map;
16
+ this.#artifactDigestByBuildIdByDeployment.set(deploymentName, byBuildId);
17
+ }
18
+ if (!byBuildId.has(buildId))
19
+ byBuildId.set(buildId, artifactDigest);
20
+ }
21
+ checkAndRecord(deploymentName, buildId, artifactDigest) {
22
+ const result = this.check(deploymentName, buildId, artifactDigest);
23
+ if (result.ok)
24
+ this.record(deploymentName, buildId, artifactDigest);
25
+ return result;
26
+ }
27
+ }
@@ -0,0 +1,14 @@
1
+ import type { WorkerDrainOptions, WorkerHealth, WorkerInfo } from './types.ts';
2
+ /** A drain marker: when it started and an optional operator-supplied reason. */
3
+ export type DrainRecord = {
4
+ reason?: string;
5
+ startedAt: number;
6
+ };
7
+ export declare function createDrainRecord(options?: WorkerDrainOptions): DrainRecord;
8
+ /**
9
+ * The drain record covering `worker`: its own explicit marker if set,
10
+ * otherwise its deployment's marker (when the worker declares a deployment).
11
+ */
12
+ export declare function drainRecordForWorker(worker: WorkerInfo, deploymentDrainStates: ReadonlyMap<string, DrainRecord>): DrainRecord | undefined;
13
+ export declare function isWorkerDraining(worker: WorkerInfo, deploymentDrainStates: ReadonlyMap<string, DrainRecord>): boolean;
14
+ export declare function workerHealth(worker: WorkerInfo, deploymentDrainStates: ReadonlyMap<string, DrainRecord>): WorkerHealth;
@@ -0,0 +1,24 @@
1
+ export function createDrainRecord(options) {
2
+ return {
3
+ startedAt: options?.updatedAt ?? Date.now(),
4
+ ...options?.reason !== void 0 ? { reason: options.reason } : {}
5
+ };
6
+ }
7
+ export function drainRecordForWorker(worker, deploymentDrainStates) {
8
+ if (worker.drainStartedAt !== void 0)
9
+ return {
10
+ startedAt: worker.drainStartedAt,
11
+ ...worker.drainReason !== void 0 ? { reason: worker.drainReason } : {}
12
+ };
13
+ if (worker.deploymentName === void 0)
14
+ return;
15
+ return deploymentDrainStates.get(worker.deploymentName);
16
+ }
17
+ export function isWorkerDraining(worker, deploymentDrainStates) {
18
+ return drainRecordForWorker(worker, deploymentDrainStates) !== void 0;
19
+ }
20
+ export function workerHealth(worker, deploymentDrainStates) {
21
+ if (!isWorkerDraining(worker, deploymentDrainStates))
22
+ return "active";
23
+ return worker.inFlight > 0 ? "draining" : "drained";
24
+ }
@@ -0,0 +1,31 @@
1
+ import type { RegisterErrorMessage } from '../protocol.ts';
2
+ /** Number of most-recent rejection entries retained before the oldest is evicted. */
3
+ export declare const MAX_REGISTRATION_REJECTION_ENTRIES = 200;
4
+ /**
5
+ * One declined `register` attempt.
6
+ *
7
+ * Deliberately bounded content: the rejection `code` (the same taxonomy
8
+ * `registerError` sends on the wire), the attempted `workerId` when the frame
9
+ * carried one, and whichever deployment/queue identity had already been
10
+ * parsed by the time the gate that rejected it ran. No free-text message and
11
+ * no manifest content — this is an auditable event log, not a diagnostic
12
+ * dump. `workerId` is optional because the earliest rejection gate — a
13
+ * malformed or wire-shape-invalid frame — can fail before any `workerId`
14
+ * is known to be valid.
15
+ */
16
+ export type RegistrationRejectionEntry = Readonly<{
17
+ code: RegisterErrorMessage['code'];
18
+ workerId?: string | undefined;
19
+ rejectedAt: number;
20
+ queue?: string | undefined;
21
+ deploymentName?: string | undefined;
22
+ buildId?: string | undefined;
23
+ }>;
24
+ /**
25
+ * Append `entry` to `log`, evicting the oldest entry once the log exceeds
26
+ * {@link MAX_REGISTRATION_REJECTION_ENTRIES}. Mutates `log` in place and
27
+ * returns it, matching the registry's existing in-place array conventions.
28
+ */
29
+ export declare function appendRegistrationRejection(log: RegistrationRejectionEntry[], entry: RegistrationRejectionEntry): RegistrationRejectionEntry[];
30
+ /** Return the `limit` most recent entries, newest first. */
31
+ export declare function recentRegistrationRejections(log: readonly RegistrationRejectionEntry[], limit: number): RegistrationRejectionEntry[];
@@ -0,0 +1,10 @@
1
+ export const MAX_REGISTRATION_REJECTION_ENTRIES = 200;
2
+ export function appendRegistrationRejection(log, entry) {
3
+ log.push(entry);
4
+ if (log.length > MAX_REGISTRATION_REJECTION_ENTRIES)
5
+ log.splice(0, log.length - MAX_REGISTRATION_REJECTION_ENTRIES);
6
+ return log;
7
+ }
8
+ export function recentRegistrationRejections(log, limit) {
9
+ return log.slice(Math.max(0, log.length - limit)).toReversed();
10
+ }
@@ -19,7 +19,6 @@ export type WorkerSnapshot = {
19
19
  deploymentName?: string | undefined;
20
20
  buildId?: string | undefined;
21
21
  runtimeVersion?: string | undefined;
22
- gitSha?: string | undefined;
23
22
  };
24
23
  /**
25
24
  * Project a single worker snapshot into a {@link WorkerSummary}.
@@ -42,7 +41,6 @@ export type WorkerDeploymentSummary = {
42
41
  deploymentName: string | null;
43
42
  buildId: string | null;
44
43
  runtimeVersion: string | null;
45
- gitSha: string | null;
46
44
  health: WorkerHealth;
47
45
  workers: number;
48
46
  activeWorkers: number;
@@ -55,7 +53,6 @@ type WorkerDeploymentIdentity = {
55
53
  deploymentName?: string | null | undefined;
56
54
  buildId?: string | null | undefined;
57
55
  runtimeVersion?: string | null | undefined;
58
- gitSha?: string | null | undefined;
59
56
  };
60
57
  /** Stable composite key for grouping workers by deployment identity. */
61
58
  export declare function deploymentIdentityKey(identity: WorkerDeploymentIdentity): string;
@@ -15,8 +15,7 @@ export function projectWorkerSummary(snapshot, now) {
15
15
  health: snapshot.health,
16
16
  ...snapshot.deploymentName !== void 0 ? { deploymentName: snapshot.deploymentName } : {},
17
17
  ...snapshot.buildId !== void 0 ? { buildId: snapshot.buildId } : {},
18
- ...snapshot.runtimeVersion !== void 0 ? { runtimeVersion: snapshot.runtimeVersion } : {},
19
- ...snapshot.gitSha !== void 0 ? { gitSha: snapshot.gitSha } : {}
18
+ ...snapshot.runtimeVersion !== void 0 ? { runtimeVersion: snapshot.runtimeVersion } : {}
20
19
  };
21
20
  }
22
21
  export function projectWorkerSummaries(snapshots, now) {
@@ -26,8 +25,7 @@ export function deploymentIdentityKey(identity) {
26
25
  return [
27
26
  identity.deploymentName ?? "",
28
27
  identity.buildId ?? "",
29
- identity.runtimeVersion ?? "",
30
- identity.gitSha ?? ""
28
+ identity.runtimeVersion ?? ""
31
29
  ].join("\x1F");
32
30
  }
33
31
  export function countWorkerHealth(healthValues) {
@@ -55,7 +53,6 @@ export function projectDeploymentSummary(workers, drainActive) {
55
53
  deploymentName: first?.deploymentName ?? null,
56
54
  buildId: first?.buildId ?? null,
57
55
  runtimeVersion: first?.runtimeVersion ?? null,
58
- gitSha: first?.gitSha ?? null,
59
56
  health: deploymentHealth(healthValues, drainActive),
60
57
  workers: workers.length,
61
58
  activeWorkers: healthCounts.active,
@@ -1,3 +1,4 @@
1
+ import type { WorkerManifest } from '../manifest/types.ts';
1
2
  import type { RemoteWorkerCapabilities } from '../protocol.ts';
2
3
  /** Lifecycle health state of a connected worker. */
3
4
  export type WorkerHealth = 'active' | 'draining' | 'drained';
@@ -5,6 +6,11 @@ export type WorkerHealth = 'active' | 'draining' | 'drained';
5
6
  * Full internal state record for a connected worker, held by the registry.
6
7
  * `drainReason` and `drainStartedAt` are present only when a worker-level
7
8
  * drain marker has been set.
9
+ *
10
+ * `manifest` and `acceptedManifestDigest` are internal — not part of
11
+ * {@link WorkerSummary} or any REST/JSON-RPC surface. Exposing bounded
12
+ * worker/deployment detail from the accepted manifest is a diagnostics
13
+ * concern, not a registration concern.
8
14
  */
9
15
  export interface WorkerInfo {
10
16
  id: string;
@@ -19,7 +25,8 @@ export interface WorkerInfo {
19
25
  deploymentName?: string;
20
26
  buildId?: string;
21
27
  runtimeVersion?: string;
22
- gitSha?: string;
28
+ manifest: WorkerManifest;
29
+ acceptedManifestDigest: string;
23
30
  drainReason?: string;
24
31
  drainStartedAt?: number;
25
32
  }
@@ -32,7 +39,8 @@ export type WorkerRegistrationInfo = {
32
39
  deploymentName?: string;
33
40
  buildId?: string;
34
41
  runtimeVersion?: string;
35
- gitSha?: string;
42
+ manifest: WorkerManifest;
43
+ acceptedManifestDigest: string;
36
44
  startedAt?: number;
37
45
  capabilities?: RemoteWorkerCapabilities;
38
46
  };
@@ -60,7 +68,6 @@ export type WorkerSummary = {
60
68
  deploymentName?: string | undefined;
61
69
  buildId?: string | undefined;
62
70
  runtimeVersion?: string | undefined;
63
- gitSha?: string | undefined;
64
71
  };
65
72
  export type WorkerDrainMutationResult = {
66
73
  target: 'worker';
@@ -1,5 +1,8 @@
1
+ import { type DeploymentConsistencyResult } from './registry/deployment-consistency.ts';
2
+ import { type RegistrationRejectionEntry } from './registry/rejections.ts';
1
3
  import { type WorkerDeploymentSummary } from './registry/summary.ts';
2
4
  import type { InFlightTask, RoutingOptions, RoutingPolicy, WorkerDrainMutationResult, WorkerDrainOptions, WorkerInfo, WorkerRegistrationInfo, WorkerRegistryOptions, WorkerSummary } from './registry/types.ts';
5
+ export type { RegistrationRejectionEntry } from './registry/rejections.ts';
3
6
  export type { WorkerDeploymentSummary } from './registry/summary.ts';
4
7
  export type { InFlightTask, RoutingOptions, RoutingPolicy, WorkerDrainMutationResult, WorkerDrainOptions, WorkerHealth, WorkerInfo, WorkerRegistrationInfo, WorkerRegistryOptions, WorkerSummary, } from './registry/types.ts';
5
8
  /**
@@ -12,9 +15,27 @@ export type { InFlightTask, RoutingOptions, RoutingPolicy, WorkerDrainMutationRe
12
15
  *
13
16
  * @example
14
17
  * ```ts
15
- * import { WorkerRegistry } from '@lostgradient/weft';
18
+ * import { WorkerRegistry, type WorkerManifest } from '@lostgradient/weft';
19
+ *
20
+ * const manifest: WorkerManifest = {
21
+ * manifestVersion: 1,
22
+ * protocolVersion: 3,
23
+ * sdkVersion: '0.18.0',
24
+ * runtime: { name: 'bun', version: '1.3.14' },
25
+ * deployment: { name: 'billing', buildId: 'b3', artifactDigest: 'sha256:41d0' },
26
+ * workflows: {},
27
+ * capabilities: {},
28
+ * };
29
+ *
16
30
  * const registry = new WorkerRegistry({ policy: 'least-loaded' });
17
- * registry.register({ id: 'worker-1', queue: 'default', activities: ['sendEmail'], concurrency: 10 });
31
+ * registry.register({
32
+ * id: 'worker-1',
33
+ * queue: 'default',
34
+ * activities: ['sendEmail'],
35
+ * concurrency: 10,
36
+ * manifest,
37
+ * acceptedManifestDigest: 'sha256:deadbeef',
38
+ * });
18
39
  * const best = registry.findWorker('sendEmail', { queue: 'default' });
19
40
  * ```
20
41
  */
@@ -23,6 +44,16 @@ export declare class WorkerRegistry {
23
44
  constructor(options?: WorkerRegistryOptions);
24
45
  /** The routing policy this registry was configured with. */
25
46
  get policy(): RoutingPolicy;
47
+ /**
48
+ * Read-only check of a manifest's `(deploymentName, buildId,
49
+ * artifactDigest)` against digests previously seen for that deployment and
50
+ * build. Callers with a later rejection gate must call
51
+ * {@link recordDeploymentConsistency} only once every such gate has
52
+ * passed, so a declined worker cannot poison a deployment/build slot.
53
+ */
54
+ checkDeploymentConsistency(deploymentName: string, buildId: string, artifactDigest: string): DeploymentConsistencyResult;
55
+ /** Record `artifactDigest` for `(deploymentName, buildId)` once every rejection gate has passed. */
56
+ recordDeploymentConsistency(deploymentName: string, buildId: string, artifactDigest: string): void;
26
57
  /** Register a worker. */
27
58
  register(info: WorkerRegistrationInfo): void;
28
59
  /** Unregister a worker. Purges fair-share counters and in-flight task entries for this worker. */
@@ -46,6 +77,16 @@ export declare class WorkerRegistry {
46
77
  findWorker(activityName: string, options?: RoutingOptions): WorkerInfo | undefined;
47
78
  /** Track a task assignment with a visibility timeout deadline. */
48
79
  assignTask(workerId: string, operationId: string, visibilityTimeout: number, fairShareKey: string | undefined, attemptToken: string): void;
80
+ /**
81
+ * Undo exactly `assignTask`'s effects — capacity increment, fair-share
82
+ * count, and the in-flight entry — when the durable claim that was
83
+ * supposed to follow the reservation never committed. Distinct from
84
+ * {@link completeTask} even though the body is identical: this path means
85
+ * "the reservation was never realized," not "the task finished," and
86
+ * keeping the names apart stops a reader from mistaking an aborted claim
87
+ * for a normal completion at the call site.
88
+ */
89
+ releaseReservation(operationId: string): InFlightTask | undefined;
49
90
  /** Return tasks whose deadline has passed and remove them from tracking. */
50
91
  checkExpiredTasks(now: number): InFlightTask[];
51
92
  /**
@@ -76,6 +117,10 @@ export declare class WorkerRegistry {
76
117
  getWorker(workerId: string): WorkerInfo | undefined;
77
118
  /** Get all registered workers. */
78
119
  getAll(): WorkerInfo[];
120
+ /** Record one declined `register` attempt, evicting the oldest once the bounded log is full. */
121
+ recordRejection(entry: RegistrationRejectionEntry): void;
122
+ /** Return the `limit` most recently recorded rejections, newest first. */
123
+ getRecentRejections(limit: number): RegistrationRejectionEntry[];
79
124
  /** Mark one connected worker as draining so routing excludes it from new tasks. */
80
125
  markWorkerDraining(workerId: string, options?: WorkerDrainOptions): WorkerDrainMutationResult | undefined;
81
126
  /** Clear an explicit worker drain marker. Deployment-level drains can still apply. */