@uns-kit/core 3.0.36 → 3.0.37

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 (33) hide show
  1. package/MIGRATIONS.md +55 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/runtime/application-handover-state.d.ts +13 -0
  7. package/dist/runtime/application-handover-state.d.ts.map +1 -0
  8. package/dist/runtime/application-handover-state.js +43 -0
  9. package/dist/runtime/application-handover-state.js.map +1 -0
  10. package/dist/runtime/mqtt-handover-state.d.ts +4 -0
  11. package/dist/runtime/mqtt-handover-state.d.ts.map +1 -1
  12. package/dist/runtime/mqtt-handover-state.js +6 -1
  13. package/dist/runtime/mqtt-handover-state.js.map +1 -1
  14. package/dist/runtime/process-work.d.ts +16 -0
  15. package/dist/runtime/process-work.d.ts.map +1 -0
  16. package/dist/runtime/process-work.js +34 -0
  17. package/dist/runtime/process-work.js.map +1 -0
  18. package/dist/uns/handover-manager.d.ts +5 -1
  19. package/dist/uns/handover-manager.d.ts.map +1 -1
  20. package/dist/uns/handover-manager.js +69 -15
  21. package/dist/uns/handover-manager.js.map +1 -1
  22. package/dist/uns/handover-shutdown.d.ts +2 -0
  23. package/dist/uns/handover-shutdown.d.ts.map +1 -1
  24. package/dist/uns/handover-shutdown.js +1 -1
  25. package/dist/uns/handover-shutdown.js.map +1 -1
  26. package/dist/uns/uns-interfaces.d.ts +5 -0
  27. package/dist/uns/uns-interfaces.d.ts.map +1 -1
  28. package/dist/uns/uns-interfaces.js.map +1 -1
  29. package/dist/uns/uns-proxy-process.d.ts +14 -0
  30. package/dist/uns/uns-proxy-process.d.ts.map +1 -1
  31. package/dist/uns/uns-proxy-process.js +94 -7
  32. package/dist/uns/uns-proxy-process.js.map +1 -1
  33. package/package.json +1 -1
package/MIGRATIONS.md CHANGED
@@ -9,6 +9,61 @@ Agents must inspect the application's existing ownership and shutdown flow
9
9
  before editing it. The examples below describe the intended behavior, not a
10
10
  mechanical search-and-replace operation.
11
11
 
12
+ ## 3.0.37 - Application lifecycle, HTTP ownership and bounded RAM state
13
+
14
+ When crossing `<3.0.37` to `>=3.0.37`, existing non-opt-in consumers retain their
15
+ protocol. The API plugin now closes its HTTP server on shutdown and the cron
16
+ plugin stops owned schedules. Review existing signal handlers so one owner calls
17
+ `drainForMigration()` and reports an unsuccessful drain honestly.
18
+
19
+ For stateful live migration, pass `uns.handoverProtocol` to `UnsProxyProcess`,
20
+ set `deferHandoverUntilReady: true`, install every handler and participant, then
21
+ call `enableMigrationDrain()`. API-only applications call
22
+ `await runtime.prepareHandover(config.uns.instanceMode, config.uns.handover)`
23
+ before that readiness declaration. Use `installSignalHandlers()` only when the
24
+ application has no signal owner already. Declare `unsDatahub.mqttDrain` protocol1
25
+ and `unsDatahub.mqttHandover` protocol2 only after this actual wiring is verified.
26
+
27
+ Use the shared `ProcessWork` to track accepted asynchronous work. Stop timer and
28
+ poller admission in `handoverShutdown.onRelease`; await `accepted.drain()` in
29
+ `handoverShutdown.drain`. `handoverShutdown.onActivate` prepares application
30
+ owners before successor input resumes. Do not discard already admitted input by
31
+ checking `isActive()` in its callback. Do gate new background work. A failed or
32
+ uncertain output stays latched and blocks a successful receipt.
33
+
34
+ Applications with RAM state additionally supply `handoverState: { contract,
35
+ exportState, validateState, importState }`. Use a stable semantic contract, check
36
+ all fields before mutating anything, and include unfinished windows, active
37
+ session IDs, original timestamps, conditions and counters. The shared transport
38
+ bounds plain JSON to 768 KiB, depth32/50,000 nodes within its 1 MiB envelope.
39
+ It refuses oversized state without truncation. Keep credentials and executable
40
+ objects out of snapshots. Matching unchanged MQTT-only consumers retain format2;
41
+ applications opting into new state require matching contracts on both peers.
42
+ Do not bypass a refused comparison with a relabelled contract.
43
+
44
+ Example:
45
+
46
+ ```ts
47
+ const accepted = new ProcessWork();
48
+ const runtime = new UnsProxyProcess(host, {
49
+ processName, handoverProtocol: config.uns.handoverProtocol,
50
+ deferHandoverUntilReady: true,
51
+ handoverShutdown: { onRelease: stopTimers, drain: () => accepted.drain() },
52
+ handoverState: {
53
+ contract: 'accumulator-v1', exportState: () => accumulator,
54
+ validateState: value => accumulatorSchema.parse(value),
55
+ importState: value => { accumulator = accumulatorSchema.parse(value); },
56
+ },
57
+ });
58
+ ```
59
+
60
+ The API plugin keeps passive routes unpublished, rejects passive requests with a
61
+ retryable 503 and drains accepted requests. Cron work is gated by ownership.
62
+ These mechanisms do not move disk queues or eliminate every routing gap. An
63
+ unknown external effect remains uncertain; reconcile the original operation and
64
+ use a reviewed reverse handover for return ownership. Ordinary SDK version
65
+ alignment alone does not enable a live migration.
66
+
12
67
  ## 3.0.36 - Unitless state and passive background publishers
13
68
 
14
69
  When crossing `<3.0.36` to `>=3.0.36`, MQTT handover preserves absent units
package/dist/index.d.ts CHANGED
@@ -17,4 +17,6 @@ export { registerService } from "./tools/datahub/runtime-service-registration.js
17
17
  export type { RegisterServiceOptions, RuntimeServiceDescriptor, RuntimeServiceRegistration, RuntimeServiceRegistrationClient, } from "./tools/datahub/runtime-service-registration.js";
18
18
  export { mqttPublishQueueSchema } from "./uns-mqtt/publish-queue-contract.js";
19
19
  export type { MqttPublishQueueOptions, MqttPublishQueueStatus } from "./uns-mqtt/publish-queue-contract.js";
20
+ export { ProcessWork } from "./runtime/process-work.js";
21
+ export { createApplicationHandoverContract, cloneApplicationHandoverState, type ApplicationHandoverState } from "./runtime/application-handover-state.js";
20
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,4BAA4B,CAAC;AACxE,YAAY,EACV,qBAAqB,EACrB,wBAAwB,EACxB,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,4BAA4B,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,YAAY,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,cAAc,2BAA2B,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AACzF,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC3F,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AACzE,YAAY,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAC9F,OAAO,EACL,SAAS,EACT,eAAe,EACf,WAAW,EACX,WAAW,EACX,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,mCAAmC,CAAC;AAC3C,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,gCAAgC,EAChC,wCAAwC,EACxC,wCAAwC,GACzC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,iDAAiD,CAAC;AAClF,YAAY,EACV,sBAAsB,EACtB,wBAAwB,EACxB,0BAA0B,EAC1B,gCAAgC,GACjC,MAAM,iDAAiD,CAAC;AAEzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,4BAA4B,CAAC;AACxE,YAAY,EACV,qBAAqB,EACrB,wBAAwB,EACxB,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,4BAA4B,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,YAAY,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,cAAc,2BAA2B,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AACzF,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC3F,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AACzE,YAAY,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAC9F,OAAO,EACL,SAAS,EACT,eAAe,EACf,WAAW,EACX,WAAW,EACX,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,mCAAmC,CAAC;AAC3C,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,gCAAgC,EAChC,wCAAwC,EACxC,wCAAwC,GACzC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,iDAAiD,CAAC;AAClF,YAAY,EACV,sBAAsB,EACtB,wBAAwB,EACxB,0BAA0B,EAC1B,gCAAgC,GACjC,MAAM,iDAAiD,CAAC;AAEzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAE5G,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,iCAAiC,EAAE,6BAA6B,EAAE,KAAK,wBAAwB,EAAE,MAAM,yCAAyC,CAAC"}
package/dist/index.js CHANGED
@@ -10,4 +10,6 @@ export { AuthClient, ServiceTokenProvider } from "./tools/auth/index.js";
10
10
  export { UnsClient, LastValueResult, ClientError, RangeResult, BatchRangeTopicResult, BatchRangeResponse, } from "./tools/datahub/datahub-client.js";
11
11
  export { registerService } from "./tools/datahub/runtime-service-registration.js";
12
12
  export { mqttPublishQueueSchema } from "./uns-mqtt/publish-queue-contract.js";
13
+ export { ProcessWork } from "./runtime/process-work.js";
14
+ export { createApplicationHandoverContract, cloneApplicationHandoverState } from "./runtime/application-handover-state.js";
13
15
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAOxE,cAAc,yBAAyB,CAAC;AAExC,cAAc,2BAA2B,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAEzF,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAEzE,OAAO,EACL,SAAS,EACT,eAAe,EACf,WAAW,EACX,WAAW,EACX,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,mCAAmC,CAAC;AAgB3C,OAAO,EAAE,eAAe,EAAE,MAAM,iDAAiD,CAAC;AAQlF,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC","sourcesContent":["export { default as UnsProxyProcess } from \"./uns/uns-proxy-process.js\";\nexport type {\n UnsProxyProcessPlugin,\n UnsProxyProcessPluginApi,\n UnsProxyProcessPluginMethod,\n UnsProxyProcessPluginMethods,\n} from \"./uns/uns-proxy-process.js\";\nexport * from \"./uns/uns-interfaces.js\";\nexport type { HandoverShutdownHooks } from \"./uns/handover-shutdown.js\";\nexport * from \"./uns/service-metadata.js\";\nexport { ConfigFile } from \"./config-file.js\";\nexport { default as logger } from \"./logger.js\";\nexport { getLogger } from \"./logger.js\";\nexport { resolveInfisicalConfig } from \"./uns-config/secret-resolver.js\";\nexport { mqttChannelParameters, resolveMqttChannel } from \"./uns-config/mqtt-channel.js\";\nexport type { MqttChannelConfig, ResolvedMqttChannel } from \"./uns-config/mqtt-channel.js\";\nexport { AuthClient, ServiceTokenProvider } from \"./tools/auth/index.js\";\nexport type { AccessTokenProvider, ServiceTokenProviderOptions } from \"./tools/auth/index.js\";\nexport {\n UnsClient,\n LastValueResult,\n ClientError,\n RangeResult,\n BatchRangeTopicResult,\n BatchRangeResponse,\n} from \"./tools/datahub/datahub-client.js\";\nexport type {\n LastValuePayload,\n CatchAllTimeField,\n CatchAllAggregate,\n RangeQueryOptions,\n RangeColumn,\n RangeStats,\n RangePayload,\n BatchRangeTopicPayload,\n BatchRangeResponsePayload,\n ProviderAssetIdentity,\n AssetIdentityPublicationMetadata,\n AssetProviderIdentityPublicationMetadata,\n AssetIdentityPublicationEvidenceMetadata,\n} from \"./tools/datahub/datahub-client.js\";\nexport { registerService } from \"./tools/datahub/runtime-service-registration.js\";\nexport type {\n RegisterServiceOptions,\n RuntimeServiceDescriptor,\n RuntimeServiceRegistration,\n RuntimeServiceRegistrationClient,\n} from \"./tools/datahub/runtime-service-registration.js\";\n\nexport { mqttPublishQueueSchema } from \"./uns-mqtt/publish-queue-contract.js\";\nexport type { MqttPublishQueueOptions, MqttPublishQueueStatus } from \"./uns-mqtt/publish-queue-contract.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAOxE,cAAc,yBAAyB,CAAC;AAExC,cAAc,2BAA2B,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAEzF,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAEzE,OAAO,EACL,SAAS,EACT,eAAe,EACf,WAAW,EACX,WAAW,EACX,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,mCAAmC,CAAC;AAgB3C,OAAO,EAAE,eAAe,EAAE,MAAM,iDAAiD,CAAC;AAQlF,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAG9E,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,iCAAiC,EAAE,6BAA6B,EAAiC,MAAM,yCAAyC,CAAC","sourcesContent":["export { default as UnsProxyProcess } from \"./uns/uns-proxy-process.js\";\nexport type {\n UnsProxyProcessPlugin,\n UnsProxyProcessPluginApi,\n UnsProxyProcessPluginMethod,\n UnsProxyProcessPluginMethods,\n} from \"./uns/uns-proxy-process.js\";\nexport * from \"./uns/uns-interfaces.js\";\nexport type { HandoverShutdownHooks } from \"./uns/handover-shutdown.js\";\nexport * from \"./uns/service-metadata.js\";\nexport { ConfigFile } from \"./config-file.js\";\nexport { default as logger } from \"./logger.js\";\nexport { getLogger } from \"./logger.js\";\nexport { resolveInfisicalConfig } from \"./uns-config/secret-resolver.js\";\nexport { mqttChannelParameters, resolveMqttChannel } from \"./uns-config/mqtt-channel.js\";\nexport type { MqttChannelConfig, ResolvedMqttChannel } from \"./uns-config/mqtt-channel.js\";\nexport { AuthClient, ServiceTokenProvider } from \"./tools/auth/index.js\";\nexport type { AccessTokenProvider, ServiceTokenProviderOptions } from \"./tools/auth/index.js\";\nexport {\n UnsClient,\n LastValueResult,\n ClientError,\n RangeResult,\n BatchRangeTopicResult,\n BatchRangeResponse,\n} from \"./tools/datahub/datahub-client.js\";\nexport type {\n LastValuePayload,\n CatchAllTimeField,\n CatchAllAggregate,\n RangeQueryOptions,\n RangeColumn,\n RangeStats,\n RangePayload,\n BatchRangeTopicPayload,\n BatchRangeResponsePayload,\n ProviderAssetIdentity,\n AssetIdentityPublicationMetadata,\n AssetProviderIdentityPublicationMetadata,\n AssetIdentityPublicationEvidenceMetadata,\n} from \"./tools/datahub/datahub-client.js\";\nexport { registerService } from \"./tools/datahub/runtime-service-registration.js\";\nexport type {\n RegisterServiceOptions,\n RuntimeServiceDescriptor,\n RuntimeServiceRegistration,\n RuntimeServiceRegistrationClient,\n} from \"./tools/datahub/runtime-service-registration.js\";\n\nexport { mqttPublishQueueSchema } from \"./uns-mqtt/publish-queue-contract.js\";\nexport type { MqttPublishQueueOptions, MqttPublishQueueStatus } from \"./uns-mqtt/publish-queue-contract.js\";\n\nexport { ProcessWork } from \"./runtime/process-work.js\";\nexport { createApplicationHandoverContract, cloneApplicationHandoverState, type ApplicationHandoverState } from \"./runtime/application-handover-state.js\";\n"]}
@@ -0,0 +1,13 @@
1
+ /** Versioned application-owned RAM state; SDK owns bounds, peer binding and transport. */
2
+ export interface ApplicationHandoverState {
3
+ contract: string;
4
+ exportState(): unknown;
5
+ validateState(value: unknown): void;
6
+ importState(value: unknown): void | Promise<void>;
7
+ }
8
+ export declare function validateApplicationHandoverContract(state?: ApplicationHandoverState): void;
9
+ /** Plain bounded JSON only. No files, executable code, credentials or implicit truncation. */
10
+ export declare function cloneApplicationHandoverState(value: unknown): unknown;
11
+ /** Bind application-selected business resources without transmitting their contents. */
12
+ export declare function createApplicationHandoverContract(revision: string, resources: unknown): string;
13
+ //# sourceMappingURL=application-handover-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"application-handover-state.d.ts","sourceRoot":"","sources":["../../src/runtime/application-handover-state.ts"],"names":[],"mappings":"AAEA,0FAA0F;AAC1F,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,IAAI,OAAO,CAAC;IACvB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED,wBAAgB,mCAAmC,CAAC,KAAK,CAAC,EAAE,wBAAwB,GAAG,IAAI,CAM1F;AAED,8FAA8F;AAC9F,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAiBrE;AAED,wFAAwF;AACxF,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,CAM9F"}
@@ -0,0 +1,43 @@
1
+ import { createHash } from 'node:crypto';
2
+ export function validateApplicationHandoverContract(state) {
3
+ if (!state)
4
+ return;
5
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,127}$/.test(state.contract) ||
6
+ [state.exportState, state.validateState, state.importState].some(fn => typeof fn !== "function")) {
7
+ throw new TypeError("Invalid application handover contract");
8
+ }
9
+ }
10
+ /** Plain bounded JSON only. No files, executable code, credentials or implicit truncation. */
11
+ export function cloneApplicationHandoverState(value) {
12
+ let nodes = 0;
13
+ const visit = (item, depth) => {
14
+ if (++nodes > 50000 || depth > 32)
15
+ throw new Error("Application handover state exceeds structural bounds");
16
+ if (item === null || typeof item === "string" || typeof item === "boolean")
17
+ return;
18
+ if (typeof item === "number" && Number.isFinite(item))
19
+ return;
20
+ if (!item || typeof item !== "object" || (!Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype))
21
+ throw new TypeError("Application handover state must be plain JSON");
22
+ for (const [key, child] of Object.entries(item)) {
23
+ if (["__proto__", "constructor", "prototype"].includes(key))
24
+ throw new Error("Unsafe application state key");
25
+ visit(child, depth + 1);
26
+ }
27
+ };
28
+ visit(value, 0);
29
+ const json = JSON.stringify(value);
30
+ if (Buffer.byteLength(json) > 768 * 1024)
31
+ throw new Error("Application handover state exceeds byte bound");
32
+ return JSON.parse(json);
33
+ }
34
+ /** Bind application-selected business resources without transmitting their contents. */
35
+ export function createApplicationHandoverContract(revision, resources) {
36
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,59}$/.test(revision))
37
+ throw Error("Invalid application state revision");
38
+ const normalized = cloneApplicationHandoverState(resources);
39
+ const stable = (value) => Array.isArray(value) ? value.map(stable)
40
+ : value && typeof value === 'object' ? Object.fromEntries(Object.keys(value).sort().map(key => [key, stable(value[key])])) : value;
41
+ return revision + '/' + createHash('sha256').update(JSON.stringify(stable(normalized))).digest('hex');
42
+ }
43
+ //# sourceMappingURL=application-handover-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"application-handover-state.js","sourceRoot":"","sources":["../../src/runtime/application-handover-state.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\n\n/** Versioned application-owned RAM state; SDK owns bounds, peer binding and transport. */\nexport interface ApplicationHandoverState {\n contract: string;\n exportState(): unknown;\n validateState(value: unknown): void;\n importState(value: unknown): void | Promise<void>;\n}\n\nexport function validateApplicationHandoverContract(state?: ApplicationHandoverState): void {\n if (!state) return;\n if (!/^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,127}$/.test(state.contract) ||\n [state.exportState, state.validateState, state.importState].some(fn => typeof fn !== \"function\")) {\n throw new TypeError(\"Invalid application handover contract\");\n }\n}\n\n/** Plain bounded JSON only. No files, executable code, credentials or implicit truncation. */\nexport function cloneApplicationHandoverState(value: unknown): unknown {\n let nodes = 0;\n const visit = (item: unknown, depth: number): void => {\n if (++nodes > 50000 || depth > 32) throw new Error(\"Application handover state exceeds structural bounds\");\n if (item === null || typeof item === \"string\" || typeof item === \"boolean\") return;\n if (typeof item === \"number\" && Number.isFinite(item)) return;\n if (!item || typeof item !== \"object\" || (!Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype))\n throw new TypeError(\"Application handover state must be plain JSON\");\n for (const [key, child] of Object.entries(item)) {\n if ([\"__proto__\", \"constructor\", \"prototype\"].includes(key)) throw new Error(\"Unsafe application state key\");\n visit(child, depth + 1);\n }\n };\n visit(value, 0);\n const json = JSON.stringify(value);\n if (Buffer.byteLength(json) > 768 * 1024) throw new Error(\"Application handover state exceeds byte bound\");\n return JSON.parse(json);\n}\n\n/** Bind application-selected business resources without transmitting their contents. */\nexport function createApplicationHandoverContract(revision: string, resources: unknown): string {\n if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,59}$/.test(revision)) throw Error(\"Invalid application state revision\");\n const normalized = cloneApplicationHandoverState(resources);\n const stable = (value: any): any => Array.isArray(value) ? value.map(stable)\n : value && typeof value === 'object' ? Object.fromEntries(Object.keys(value).sort().map(key => [key, stable(value[key])])) : value;\n return revision + '/' + createHash('sha256').update(JSON.stringify(stable(normalized))).digest('hex');\n}\n"]}
@@ -22,6 +22,10 @@ export declare const mqttProxyHandoverStateSchema: z.ZodObject<{
22
22
  }, z.core.$strict>;
23
23
  export declare const mqttHandoverStateSchema: z.ZodObject<{
24
24
  protocol: z.ZodLiteral<1>;
25
+ application: z.ZodOptional<z.ZodObject<{
26
+ contract: z.ZodString;
27
+ data: z.ZodUnknown;
28
+ }, z.core.$strict>>;
25
29
  proxies: z.ZodArray<z.ZodObject<{
26
30
  instanceName: z.ZodString;
27
31
  compatibility: z.ZodString;
@@ -1 +1 @@
1
- {"version":3,"file":"mqtt-handover-state.d.ts","sourceRoot":"","sources":["../../src/runtime/mqtt-handover-state.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAC5C,eAAO,MAAM,6BAA6B,QAAc,CAAC;AAIzD,eAAO,MAAM,uBAAuB;;;mBAG8E,CAAC;AACnH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;kBASvC,CAAC;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;kBAMlC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAClF,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AACxE,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAIxE;AACD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAExE;AACD,eAAO,MAAM,4BAA4B;;kBAAoD,CAAC;AAC9F,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAS7E"}
1
+ {"version":3,"file":"mqtt-handover-state.d.ts","sourceRoot":"","sources":["../../src/runtime/mqtt-handover-state.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAC5C,eAAO,MAAM,6BAA6B,QAAc,CAAC;AAIzD,eAAO,MAAM,uBAAuB;;;mBAG8E,CAAC;AACnH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;kBASvC,CAAC;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;kBAOlC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAClF,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AACxE,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAMxE;AACD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAExE;AACD,eAAO,MAAM,4BAA4B;;kBAAoD,CAAC;AAC9F,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAS7E"}
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { z } from 'zod';
5
+ import { cloneApplicationHandoverState } from './application-handover-state.js';
5
6
  // Same contract for every SDK MQTT consumer. No application/profile selectors.
6
7
  // Bound into peer compatibility so older required-unit readers reject before freeze.
7
8
  export const MQTT_HANDOVER_STATE_FORMAT = 2;
@@ -26,6 +27,7 @@ export const mqttProxyHandoverStateSchema = z.strictObject({
26
27
  });
27
28
  export const mqttHandoverStateSchema = z.strictObject({
28
29
  protocol: z.literal(1),
30
+ application: z.strictObject({ contract: z.string().min(1).max(128), data: z.unknown() }).optional(),
29
31
  proxies: z.array(mqttProxyHandoverStateSchema).max(64),
30
32
  }).superRefine((state, ctx) => {
31
33
  if (new Set(state.proxies.map(p => p.instanceName)).size !== state.proxies.length)
@@ -35,7 +37,10 @@ export function parseMqttHandoverState(input) {
35
37
  const json = JSON.stringify(input);
36
38
  if (!json || Buffer.byteLength(json) > MQTT_HANDOVER_STATE_MAX_BYTES)
37
39
  throw Error('MQTT handover state exceeds its bound');
38
- return mqttHandoverStateSchema.parse(input);
40
+ const state = mqttHandoverStateSchema.parse(input);
41
+ if (state.application)
42
+ state.application.data = cloneApplicationHandoverState(state.application.data);
43
+ return state;
39
44
  }
40
45
  export function mqttHandoverStateDigest(state) {
41
46
  return createHash('sha256').update(JSON.stringify(parseMqttHandoverState(state))).digest('hex');
@@ -1 +1 @@
1
- {"version":3,"file":"mqtt-handover-state.js","sourceRoot":"","sources":["../../src/runtime/mqtt-handover-state.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { z } from 'zod';\n\n// Same contract for every SDK MQTT consumer. No application/profile selectors.\n// Bound into peer compatibility so older required-unit readers reject before freeze.\nexport const MQTT_HANDOVER_STATE_FORMAT = 2;\nexport const MQTT_HANDOVER_STATE_MAX_BYTES = 1024 * 1024;\nconst text = z.string().min(1).max(65535).refine(v => !v.includes('\\0'));\nconst scalar = z.union([z.number().finite(), z.string().max(65535)]);\nconst value = z.union([scalar, z.array(scalar).max(4096)]);\nexport const mqttHandoverOfferSchema = z.array(z.strictObject({\n instanceName: z.string().min(1).max(256),\n compatibility: z.string().regex(/^[a-f0-9]{64}$/),\n})).max(64).refine(offer => new Set(offer.map(p => p.instanceName)).size === offer.length, 'Duplicate MQTT proxy');\nexport const mqttProxyHandoverStateSchema = z.strictObject({\n instanceName: z.string().min(1).max(256),\n compatibility: z.string().regex(/^[a-f0-9]{64}$/),\n values: z.array(z.strictObject({ topic: text, value, uom: z.string().max(256).optional(), timestamp: z.string().datetime() })).max(10000),\n sequences: z.array(z.tuple([text, z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER)])).max(10000),\n cursor: z.strictObject({ batchSize: z.number().int().min(0).max(100), referenceHash: z.string().regex(/^[a-f0-9]{64}$/) }),\n}).superRefine((state, ctx) => {\n for (const keys of [state.values.map(v => v.topic), state.sequences.map(v => v[0])])\n if (new Set(keys).size !== keys.length) ctx.addIssue({ code: 'custom', message: 'Duplicate MQTT state key' });\n});\nexport const mqttHandoverStateSchema = z.strictObject({\n protocol: z.literal(1),\n proxies: z.array(mqttProxyHandoverStateSchema).max(64),\n}).superRefine((state, ctx) => {\n if (new Set(state.proxies.map(p => p.instanceName)).size !== state.proxies.length)\n ctx.addIssue({ code: 'custom', message: 'Duplicate MQTT proxy' });\n});\nexport type MqttProxyHandoverState = z.infer<typeof mqttProxyHandoverStateSchema>;\nexport type MqttHandoverState = z.infer<typeof mqttHandoverStateSchema>;\nexport function parseMqttHandoverState(input: unknown): MqttHandoverState {\n const json = JSON.stringify(input);\n if (!json || Buffer.byteLength(json) > MQTT_HANDOVER_STATE_MAX_BYTES) throw Error('MQTT handover state exceeds its bound');\n return mqttHandoverStateSchema.parse(input);\n}\nexport function mqttHandoverStateDigest(state: MqttHandoverState): string {\n return createHash('sha256').update(JSON.stringify(parseMqttHandoverState(state))).digest('hex');\n}\nexport const mqttHandoverCapabilitySchema = z.strictObject({ protocolVersion: z.literal(2) });\nexport function readMqttHandoverCapability(packageRoot: string): 2 | undefined {\n const file = path.join(packageRoot, 'package.json');\n let stat: fs.Stats;\n try { stat = fs.lstatSync(file); } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined; throw error; }\n if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1048576) throw Error('Unsafe MQTT handover descriptor');\n const descriptor = JSON.parse(fs.readFileSync(file, 'utf8'))?.unsDatahub?.mqttHandover;\n if (descriptor === undefined) return undefined;\n mqttHandoverCapabilitySchema.parse(descriptor);\n return 2;\n}\n"]}
1
+ {"version":3,"file":"mqtt-handover-state.js","sourceRoot":"","sources":["../../src/runtime/mqtt-handover-state.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { z } from 'zod';\nimport { cloneApplicationHandoverState } from './application-handover-state.js';\n\n// Same contract for every SDK MQTT consumer. No application/profile selectors.\n// Bound into peer compatibility so older required-unit readers reject before freeze.\nexport const MQTT_HANDOVER_STATE_FORMAT = 2;\nexport const MQTT_HANDOVER_STATE_MAX_BYTES = 1024 * 1024;\nconst text = z.string().min(1).max(65535).refine(v => !v.includes('\\0'));\nconst scalar = z.union([z.number().finite(), z.string().max(65535)]);\nconst value = z.union([scalar, z.array(scalar).max(4096)]);\nexport const mqttHandoverOfferSchema = z.array(z.strictObject({\n instanceName: z.string().min(1).max(256),\n compatibility: z.string().regex(/^[a-f0-9]{64}$/),\n})).max(64).refine(offer => new Set(offer.map(p => p.instanceName)).size === offer.length, 'Duplicate MQTT proxy');\nexport const mqttProxyHandoverStateSchema = z.strictObject({\n instanceName: z.string().min(1).max(256),\n compatibility: z.string().regex(/^[a-f0-9]{64}$/),\n values: z.array(z.strictObject({ topic: text, value, uom: z.string().max(256).optional(), timestamp: z.string().datetime() })).max(10000),\n sequences: z.array(z.tuple([text, z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER)])).max(10000),\n cursor: z.strictObject({ batchSize: z.number().int().min(0).max(100), referenceHash: z.string().regex(/^[a-f0-9]{64}$/) }),\n}).superRefine((state, ctx) => {\n for (const keys of [state.values.map(v => v.topic), state.sequences.map(v => v[0])])\n if (new Set(keys).size !== keys.length) ctx.addIssue({ code: 'custom', message: 'Duplicate MQTT state key' });\n});\nexport const mqttHandoverStateSchema = z.strictObject({\n protocol: z.literal(1),\n application: z.strictObject({ contract: z.string().min(1).max(128), data: z.unknown() }).optional(),\n proxies: z.array(mqttProxyHandoverStateSchema).max(64),\n}).superRefine((state, ctx) => {\n if (new Set(state.proxies.map(p => p.instanceName)).size !== state.proxies.length)\n ctx.addIssue({ code: 'custom', message: 'Duplicate MQTT proxy' });\n});\nexport type MqttProxyHandoverState = z.infer<typeof mqttProxyHandoverStateSchema>;\nexport type MqttHandoverState = z.infer<typeof mqttHandoverStateSchema>;\nexport function parseMqttHandoverState(input: unknown): MqttHandoverState {\n const json = JSON.stringify(input);\n if (!json || Buffer.byteLength(json) > MQTT_HANDOVER_STATE_MAX_BYTES) throw Error('MQTT handover state exceeds its bound');\n const state = mqttHandoverStateSchema.parse(input);\n if (state.application) state.application.data = cloneApplicationHandoverState(state.application.data);\n return state;\n}\nexport function mqttHandoverStateDigest(state: MqttHandoverState): string {\n return createHash('sha256').update(JSON.stringify(parseMqttHandoverState(state))).digest('hex');\n}\nexport const mqttHandoverCapabilitySchema = z.strictObject({ protocolVersion: z.literal(2) });\nexport function readMqttHandoverCapability(packageRoot: string): 2 | undefined {\n const file = path.join(packageRoot, 'package.json');\n let stat: fs.Stats;\n try { stat = fs.lstatSync(file); } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined; throw error; }\n if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1048576) throw Error('Unsafe MQTT handover descriptor');\n const descriptor = JSON.parse(fs.readFileSync(file, 'utf8'))?.unsDatahub?.mqttHandover;\n if (descriptor === undefined) return undefined;\n mqttHandoverCapabilitySchema.parse(descriptor);\n return 2;\n}\n"]}
@@ -0,0 +1,16 @@
1
+ /** Accepted application work is separate from transport queues. Errors stay latched. */
2
+ export declare class ProcessWork {
3
+ private readonly active;
4
+ private pending;
5
+ private failed;
6
+ private paused;
7
+ constructor(active?: () => boolean);
8
+ /** Track work already admitted by a transport. Never drop it during release. */
9
+ track<T>(work: Promise<T>): Promise<T>;
10
+ /** Timers/pollers must enter through this ownership gate. */
11
+ run<T>(work: () => Promise<T>): Promise<T | undefined>;
12
+ pause(): void;
13
+ fail(): void;
14
+ drain(): Promise<void>;
15
+ }
16
+ //# sourceMappingURL=process-work.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-work.d.ts","sourceRoot":"","sources":["../../src/runtime/process-work.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,qBAAa,WAAW;IAKV,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJnC,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;gBAEM,MAAM,GAAE,MAAM,OAAoB;IAE/D,gFAAgF;IAChF,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAStC,6DAA6D;IAC7D,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAKtD,KAAK,IAAI,IAAI;IACb,IAAI,IAAI,IAAI;IACN,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}
@@ -0,0 +1,34 @@
1
+ /** Accepted application work is separate from transport queues. Errors stay latched. */
2
+ export class ProcessWork {
3
+ active;
4
+ pending = new Set();
5
+ failed = false;
6
+ paused = false;
7
+ constructor(active = () => true) {
8
+ this.active = active;
9
+ }
10
+ /** Track work already admitted by a transport. Never drop it during release. */
11
+ track(work) {
12
+ this.pending.add(work);
13
+ void work.then(() => this.pending.delete(work), () => {
14
+ this.failed = true;
15
+ this.pending.delete(work);
16
+ });
17
+ return work;
18
+ }
19
+ /** Timers/pollers must enter through this ownership gate. */
20
+ run(work) {
21
+ if (this.paused || !this.active())
22
+ return Promise.resolve(undefined);
23
+ return this.track(Promise.resolve().then(work));
24
+ }
25
+ pause() { this.paused = true; }
26
+ fail() { this.failed = true; }
27
+ async drain() {
28
+ while (this.pending.size)
29
+ await Promise.allSettled([...this.pending]);
30
+ if (this.failed)
31
+ throw new Error("Accepted application work is unconfirmed");
32
+ }
33
+ }
34
+ //# sourceMappingURL=process-work.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-work.js","sourceRoot":"","sources":["../../src/runtime/process-work.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,MAAM,OAAO,WAAW;IAKO;IAJrB,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IACtC,MAAM,GAAG,KAAK,CAAC;IACf,MAAM,GAAG,KAAK,CAAC;IAEvB,YAA6B,SAAwB,GAAG,EAAE,CAAC,IAAI;QAAlC,WAAM,GAAN,MAAM,CAA4B;IAAG,CAAC;IAEnE,gFAAgF;IAChF,KAAK,CAAI,IAAgB;QACvB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvB,KAAK,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE;YACnD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACnB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6DAA6D;IAC7D,GAAG,CAAI,IAAsB;QAC3B,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACrE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,KAAW,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IACrC,IAAI,KAAW,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IACpC,KAAK,CAAC,KAAK;QACT,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI;YAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QACtE,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC/E,CAAC;CACF","sourcesContent":["/** Accepted application work is separate from transport queues. Errors stay latched. */\nexport class ProcessWork {\n private pending = new Set<Promise<unknown>>();\n private failed = false;\n private paused = false;\n\n constructor(private readonly active: () => boolean = () => true) {}\n\n /** Track work already admitted by a transport. Never drop it during release. */\n track<T>(work: Promise<T>): Promise<T> {\n this.pending.add(work);\n void work.then(() => this.pending.delete(work), () => {\n this.failed = true;\n this.pending.delete(work);\n });\n return work;\n }\n\n /** Timers/pollers must enter through this ownership gate. */\n run<T>(work: () => Promise<T>): Promise<T | undefined> {\n if (this.paused || !this.active()) return Promise.resolve(undefined);\n return this.track(Promise.resolve().then(work));\n }\n\n pause(): void { this.paused = true; }\n fail(): void { this.failed = true; }\n async drain(): Promise<void> {\n while (this.pending.size) await Promise.allSettled([...this.pending]);\n if (this.failed) throw new Error(\"Accepted application work is unconfirmed\");\n }\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import { type ApplicationHandoverState } from "../runtime/application-handover-state.js";
1
2
  import { type HandoverShutdownHooks } from "./handover-shutdown.js";
2
3
  import { HandoverManagerEvents } from "../uns-mqtt/mqtt-interfaces.js";
3
4
  import MqttProxy from "../uns-mqtt/mqtt-proxy.js";
@@ -11,6 +12,7 @@ import { UnsEvents } from "./uns-interfaces.js";
11
12
  */
12
13
  export declare class HandoverManager {
13
14
  private readonly statefulHandover;
15
+ private readonly applicationState?;
14
16
  event: HandoverManagerEventEmitter<HandoverManagerEvents>;
15
17
  private processName;
16
18
  private processId;
@@ -36,7 +38,7 @@ export declare class HandoverManager {
36
38
  handoverRequestEnabled: boolean;
37
39
  handoverEnabled: boolean;
38
40
  forceStartEnabled: boolean;
39
- constructor(processName: string, processId: string, mqttProxy: MqttProxy, unsMqttProxies: UnsMqttProxy[], handoverRequestEnabled: boolean, handoverEnabled: boolean, forceStartEnabled: boolean, handoverId?: string, shutdownHooks?: HandoverShutdownHooks, statefulHandover?: boolean);
41
+ constructor(processName: string, processId: string, mqttProxy: MqttProxy, unsMqttProxies: UnsMqttProxy[], handoverRequestEnabled: boolean, handoverEnabled: boolean, forceStartEnabled: boolean, handoverId?: string, shutdownHooks?: HandoverShutdownHooks, statefulHandover?: boolean, applicationState?: ApplicationHandoverState);
40
42
  private getUserProperties;
41
43
  private handoverPayload;
42
44
  private getHandoverId;
@@ -56,6 +58,8 @@ export declare class HandoverManager {
56
58
  private prepareSourceHandover;
57
59
  private sendPreparedState;
58
60
  private acceptPreparedState;
61
+ private activateApplication;
62
+ private deactivateApplication;
59
63
  private finishSourceHandover;
60
64
  private handleHandover;
61
65
  }
@@ -1 +1 @@
1
- {"version":3,"file":"handover-manager.d.ts","sourceRoot":"","sources":["../../src/uns/handover-manager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiD,KAAK,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACnH,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,SAAS,MAAM,2BAA2B,CAAC;AAElD,OAAO,YAAY,MAAM,+BAA+B,CAAC;AACzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAElF,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAQhD;;;;GAIG;AACH,qBAAa,eAAe;IAqCxB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IApC5B,KAAK,EAAE,2BAA2B,CAAC,qBAAqB,CAAC,CAA4D;IAC5H,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,kBAAkB,CAAkB;IAC5C,OAAO,CAAC,kBAAkB,CAAkB;IAC5C,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,iBAAiB,CAA4B;IACrD,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoC;IAClE,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,aAAa,CAAgC;IACrD,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,mBAAmB,CAAqB;IAChD,OAAO,CAAC,iBAAiB,CAA4B;IACrD,OAAO,CAAC,QAAQ,CAA6B;IACtC,sBAAsB,EAAE,OAAO,CAAS;IACxC,eAAe,EAAE,OAAO,CAAQ;IAChC,iBAAiB,EAAE,OAAO,CAAS;gBAGxC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,YAAY,EAAE,EAC9B,sBAAsB,EAAE,OAAO,EAC/B,eAAe,EAAE,OAAO,EACxB,iBAAiB,EAAE,OAAO,EAC1B,UAAU,CAAC,EAAE,MAAM,EACnB,aAAa,CAAC,EAAE,qBAAqB,EACpB,gBAAgB,UAAQ;IAkC3C,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,oBAAoB;IAU5B;;;OAGG;IACU,iBAAiB,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAqFxE;;OAEG;IACH,OAAO,CAAC,UAAU;YAIJ,qBAAqB;YA8ErB,iBAAiB;YAOjB,mBAAmB;YAkBnB,oBAAoB;YA2BpB,cAAc;CAwH7B"}
1
+ {"version":3,"file":"handover-manager.d.ts","sourceRoot":"","sources":["../../src/uns/handover-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AAGxH,OAAO,EAAiD,KAAK,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACnH,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,SAAS,MAAM,2BAA2B,CAAC;AAElD,OAAO,YAAY,MAAM,+BAA+B,CAAC;AACzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAElF,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAQhD;;;;GAIG;AACH,qBAAa,eAAe;IAqCxB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IArC7B,KAAK,EAAE,2BAA2B,CAAC,qBAAqB,CAAC,CAA4D;IAC5H,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,kBAAkB,CAAkB;IAC5C,OAAO,CAAC,kBAAkB,CAAkB;IAC5C,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,iBAAiB,CAA4B;IACrD,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoC;IAClE,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,aAAa,CAAgC;IACrD,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,mBAAmB,CAAqB;IAChD,OAAO,CAAC,iBAAiB,CAA4B;IACrD,OAAO,CAAC,QAAQ,CAA6B;IACtC,sBAAsB,EAAE,OAAO,CAAS;IACxC,eAAe,EAAE,OAAO,CAAQ;IAChC,iBAAiB,EAAE,OAAO,CAAS;gBAGxC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,YAAY,EAAE,EAC9B,sBAAsB,EAAE,OAAO,EAC/B,eAAe,EAAE,OAAO,EACxB,iBAAiB,EAAE,OAAO,EAC1B,UAAU,CAAC,EAAE,MAAM,EACnB,aAAa,CAAC,EAAE,qBAAqB,EACpB,gBAAgB,UAAQ,EACxB,gBAAgB,CAAC,EAAE,wBAAwB;IAkC9D,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,oBAAoB;IAU5B;;;OAGG;IACU,iBAAiB,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAoFxE;;OAEG;IACH,OAAO,CAAC,UAAU;YAIJ,qBAAqB;YAiFrB,iBAAiB;YAOjB,mBAAmB;YA2BnB,mBAAmB;YAQnB,qBAAqB;YAWrB,oBAAoB;YA2BpB,cAAc;CA6H7B"}
@@ -1,3 +1,4 @@
1
+ import { cloneApplicationHandoverState } from "../runtime/application-handover-state.js";
1
2
  import { mqttHandoverOfferSchema, parseMqttHandoverState, mqttHandoverStateDigest } from "../runtime/mqtt-handover-state.js";
2
3
  import logger from "../logger.js";
3
4
  import { runHandoverShutdown, validateHandoverShutdown } from "./handover-shutdown.js";
@@ -17,6 +18,7 @@ function normalizeHandoverId(value) {
17
18
  */
18
19
  export class HandoverManager {
19
20
  statefulHandover;
21
+ applicationState;
20
22
  event = new HandoverManagerEventEmitter();
21
23
  processName;
22
24
  processId;
@@ -42,8 +44,9 @@ export class HandoverManager {
42
44
  handoverRequestEnabled = false;
43
45
  handoverEnabled = true;
44
46
  forceStartEnabled = false;
45
- constructor(processName, processId, mqttProxy, unsMqttProxies, handoverRequestEnabled, handoverEnabled, forceStartEnabled, handoverId, shutdownHooks, statefulHandover = false) {
47
+ constructor(processName, processId, mqttProxy, unsMqttProxies, handoverRequestEnabled, handoverEnabled, forceStartEnabled, handoverId, shutdownHooks, statefulHandover = false, applicationState) {
46
48
  this.statefulHandover = statefulHandover;
49
+ this.applicationState = applicationState;
47
50
  validateHandoverShutdown(shutdownHooks);
48
51
  this.shutdownHooks = shutdownHooks ? { ...shutdownHooks } : undefined;
49
52
  this.processName = processName;
@@ -59,17 +62,23 @@ export class HandoverManager {
59
62
  const version = PACKAGE_INFO.version;
60
63
  this.topicBuilder = new MqttTopicBuilder(`uns-infra/${MqttTopicBuilder.sanitizeTopicPart(packageName)}/${MqttTopicBuilder.sanitizeTopicPart(version)}/${MqttTopicBuilder.sanitizeTopicPart(this.processName)}/`);
61
64
  // Set status as active after a timeout if no other active process are detected.
62
- this.activeTimeout = setTimeout(() => {
65
+ this.activeTimeout = setTimeout(async () => {
63
66
  if (this.statefulHandover && this.handoverId)
64
67
  return; // A reviewed successor never self-promotes.
65
68
  logger.info(`${this.processName} - No active message received within timeout. Assuming no other process is running.`);
69
+ for (const proxy of this.unsMqttProxies)
70
+ proxy.setPublisherActive();
71
+ try {
72
+ await this.activateApplication();
73
+ }
74
+ catch {
75
+ logger.error("Runtime activation failed; successor remains passive");
76
+ return;
77
+ }
66
78
  this.active = true;
67
79
  this.event.emit("handoverManager", { active: this.active });
68
- // Activate all UNS proxy instance publishers and subscribers.
69
- this.unsMqttProxies.forEach((unsProxy) => {
70
- unsProxy.setPublisherActive();
71
- unsProxy.setSubscriberActive();
72
- });
80
+ // Publishers were prepared for application activation; input opens last.
81
+ this.unsMqttProxies.forEach((unsProxy) => unsProxy.setSubscriberActive());
73
82
  }, ACTIVE_TIMEOUT);
74
83
  }
75
84
  getUserProperties(handoverId) {
@@ -185,7 +194,7 @@ export class HandoverManager {
185
194
  setTimeout(async () => {
186
195
  logger.info(`${this.processName} - Requesting handover ${eventHandoverTopic}.`);
187
196
  this.handoverInProgress = true;
188
- await this.mqttProxy.publish(eventHandoverTopic, this.handoverPayload("handover_request", undefined, this.statefulHandover ? { offer: this.unsMqttProxies.map(proxy => proxy.getHandoverCompatibility()) } : {}), {
197
+ await this.mqttProxy.publish(eventHandoverTopic, this.handoverPayload("handover_request", undefined, this.statefulHandover ? { offer: this.unsMqttProxies.map(proxy => proxy.getHandoverCompatibility()), applicationContract: this.applicationState?.contract } : {}), {
189
198
  retain: false,
190
199
  properties: {
191
200
  responseTopic: this.topicBuilder.getHandoverTopic(),
@@ -201,13 +210,13 @@ export class HandoverManager {
201
210
  logger.warn(`${this.processName} - Warning: Source and destination being the same may lead to duplicate messages.`);
202
211
  clearTimeout(this.activeTimeout); // Clear the active timeout if it exists - prevent this process from becoming active after a timeout.
203
212
  this.activeTimeout = undefined;
213
+ for (const proxy of this.unsMqttProxies)
214
+ proxy.setPublisherActive();
215
+ await this.activateApplication();
204
216
  this.active = true;
205
217
  this.event.emit("handoverManager", { active: this.active });
206
218
  // Activate all UNS proxy instance publishers and subscribers.
207
- this.unsMqttProxies.forEach((unsProxy) => {
208
- unsProxy.setPublisherActive();
209
- unsProxy.setSubscriberActive();
210
- });
219
+ this.unsMqttProxies.forEach((unsProxy) => unsProxy.setSubscriberActive());
211
220
  }
212
221
  else {
213
222
  logger.info(`${this.processName} - Waiting for the other process on topic ${event.topic} to become passive.`);
@@ -252,7 +261,11 @@ export class HandoverManager {
252
261
  await this.shutdownHooks?.drain();
253
262
  for (const proxy of this.unsMqttProxies)
254
263
  await proxy.flush();
255
- this.preparedState = parseMqttHandoverState({ protocol: 1, proxies: this.unsMqttProxies.map((proxy, i) => proxy.exportHandoverState({ batchSize: mqttWorkerData[i].batchSize, referenceHash: mqttWorkerData[i].referenceHash })) });
264
+ const application = this.applicationState ? { contract: this.applicationState.contract,
265
+ data: cloneApplicationHandoverState(this.applicationState.exportState()) } : undefined;
266
+ if (application)
267
+ this.applicationState.validateState(application.data);
268
+ this.preparedState = parseMqttHandoverState({ protocol: 1, ...(application ? { application } : {}), proxies: this.unsMqttProxies.map((proxy, i) => proxy.exportHandoverState({ batchSize: mqttWorkerData[i].batchSize, referenceHash: mqttWorkerData[i].referenceHash })) });
256
269
  this.stateDigest = mqttHandoverStateDigest(this.preparedState);
257
270
  await this.sendPreparedState(event, handoverId);
258
271
  let retries = 0;
@@ -308,6 +321,10 @@ export class HandoverManager {
308
321
  const digest = mqttHandoverStateDigest(state);
309
322
  if (digest !== response.stateDigest)
310
323
  throw Error("MQTT handover state digest mismatch");
324
+ if (state.application?.contract !== this.applicationState?.contract)
325
+ throw Error("Application handover contract differs");
326
+ if (state.application)
327
+ this.applicationState.validateState(state.application.data);
311
328
  const byName = new Map(this.unsMqttProxies.map(proxy => [proxy.instanceName, proxy]));
312
329
  if (byName.size !== this.unsMqttProxies.length || state.proxies.length !== byName.size || state.proxies.some(p => !byName.has(p.instanceName)))
313
330
  throw Error("MQTT handover proxy set differs");
@@ -318,14 +335,45 @@ export class HandoverManager {
318
335
  await byName.get(snapshot.instanceName).prepareHandoverResume(snapshot.cursor);
319
336
  for (const snapshot of state.proxies)
320
337
  byName.get(snapshot.instanceName).importHandoverState(snapshot);
338
+ if (state.application)
339
+ await this.applicationState.importState(state.application.data);
321
340
  for (const proxy of this.unsMqttProxies)
322
341
  proxy.setPublisherActive();
323
- for (const proxy of this.unsMqttProxies)
324
- await proxy.activateHandoverResume();
342
+ await this.activateApplication();
343
+ try {
344
+ for (const proxy of this.unsMqttProxies)
345
+ await proxy.activateHandoverResume();
346
+ }
347
+ catch {
348
+ await this.deactivateApplication();
349
+ throw Error("MQTT input activation failed; successor remains passive");
350
+ }
325
351
  this.acceptedStateDigest = digest;
326
352
  this.active = true;
327
353
  this.event.emit("handoverManager", { active: true });
328
354
  }
355
+ async activateApplication() {
356
+ try {
357
+ await this.shutdownHooks?.onActivate?.();
358
+ }
359
+ catch {
360
+ await this.deactivateApplication();
361
+ throw Error("Application activation failed; successor remains passive");
362
+ }
363
+ }
364
+ async deactivateApplication() {
365
+ this.active = false;
366
+ try {
367
+ this.shutdownHooks?.onRelease?.();
368
+ }
369
+ catch { /* Keep admission closed if cleanup itself fails. */ }
370
+ // A failed worker may not acknowledge the stop. Request passive input on
371
+ // every peer, close outputs, and never acknowledge ownership as successful.
372
+ for (const proxy of this.unsMqttProxies) {
373
+ void Promise.resolve().then(() => proxy.setSubscriberPassive(true)).catch(() => { });
374
+ }
375
+ await Promise.allSettled(this.unsMqttProxies.map(proxy => Promise.resolve().then(() => proxy.setPublisherPassive())));
376
+ }
329
377
  async finishSourceHandover() {
330
378
  const result = await runHandoverShutdown(async () => {
331
379
  await this.sourcePreparation;
@@ -376,6 +424,12 @@ export class HandoverManager {
376
424
  return;
377
425
  if (this.statefulHandover) {
378
426
  // Reject incompatible upgrades before freezing the current owner.
427
+ if (response.applicationContract !== this.applicationState?.contract)
428
+ throw Error("Application handover contract differs; source remains active");
429
+ if (this.applicationState) {
430
+ const preliminary = cloneApplicationHandoverState(this.applicationState.exportState());
431
+ this.applicationState.validateState(preliminary);
432
+ }
379
433
  const offer = mqttHandoverOfferSchema.parse(response.offer);
380
434
  const own = this.unsMqttProxies.map(proxy => proxy.getHandoverCompatibility());
381
435
  if (offer.length !== own.length || own.some(p => !offer.some(o => o.instanceName === p.instanceName && o.compatibility === p.compatibility)))