@adonis-agora/durable 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/assets/dashboard.html +116 -3
- package/dist/commands/durable_worker.d.ts +25 -0
- package/dist/commands/durable_worker.d.ts.map +1 -0
- package/dist/commands/durable_worker.js +113 -0
- package/dist/commands/durable_worker.js.map +1 -0
- package/dist/commands/main.d.ts +6 -5
- package/dist/commands/main.d.ts.map +1 -1
- package/dist/commands/main.js +11 -4
- package/dist/commands/main.js.map +1 -1
- package/dist/providers/dashboard_provider.d.ts +37 -0
- package/dist/providers/dashboard_provider.d.ts.map +1 -1
- package/dist/providers/dashboard_provider.js +98 -9
- package/dist/providers/dashboard_provider.js.map +1 -1
- package/dist/providers/durable_provider.d.ts +24 -16
- package/dist/providers/durable_provider.d.ts.map +1 -1
- package/dist/providers/durable_provider.js +195 -20
- package/dist/providers/durable_provider.js.map +1 -1
- package/dist/src/config_types.d.ts +92 -0
- package/dist/src/config_types.d.ts.map +1 -0
- package/dist/src/config_types.js +2 -0
- package/dist/src/config_types.js.map +1 -0
- package/dist/src/dashboard/compat-view.d.ts +13 -0
- package/dist/src/dashboard/compat-view.d.ts.map +1 -0
- package/dist/src/dashboard/compat-view.js +32 -0
- package/dist/src/dashboard/compat-view.js.map +1 -0
- package/dist/src/dashboard/compat.d.ts +76 -0
- package/dist/src/dashboard/compat.d.ts.map +1 -0
- package/dist/src/dashboard/compat.js +130 -0
- package/dist/src/dashboard/compat.js.map +1 -0
- package/dist/src/dashboard/diagnostics-recorder.d.ts +59 -0
- package/dist/src/dashboard/diagnostics-recorder.d.ts.map +1 -0
- package/dist/src/dashboard/diagnostics-recorder.js +75 -0
- package/dist/src/dashboard/diagnostics-recorder.js.map +1 -0
- package/dist/src/dashboard/gateway-adapter.d.ts +37 -0
- package/dist/src/dashboard/gateway-adapter.d.ts.map +1 -0
- package/dist/src/dashboard/gateway-adapter.js +49 -0
- package/dist/src/dashboard/gateway-adapter.js.map +1 -0
- package/dist/src/dashboard/handlers.d.ts +31 -8
- package/dist/src/dashboard/handlers.d.ts.map +1 -1
- package/dist/src/dashboard/handlers.js +2 -1
- package/dist/src/dashboard/handlers.js.map +1 -1
- package/dist/src/dashboard/index.d.ts +10 -3
- package/dist/src/dashboard/index.d.ts.map +1 -1
- package/dist/src/dashboard/index.js +8 -2
- package/dist/src/dashboard/index.js.map +1 -1
- package/dist/src/define_config.d.ts +31 -5
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js +4 -3
- package/dist/src/define_config.js.map +1 -1
- package/dist/src/dispatch-routing.d.ts +105 -0
- package/dist/src/dispatch-routing.d.ts.map +1 -0
- package/dist/src/dispatch-routing.js +114 -0
- package/dist/src/dispatch-routing.js.map +1 -0
- package/dist/src/engine.d.ts +65 -2
- package/dist/src/engine.d.ts.map +1 -1
- package/dist/src/engine.js +131 -3
- package/dist/src/engine.js.map +1 -1
- package/dist/src/handshake/descriptor.d.ts +128 -0
- package/dist/src/handshake/descriptor.d.ts.map +1 -0
- package/dist/src/handshake/descriptor.js +133 -0
- package/dist/src/handshake/descriptor.js.map +1 -0
- package/dist/src/handshake/negotiate.d.ts +83 -0
- package/dist/src/handshake/negotiate.d.ts.map +1 -0
- package/dist/src/handshake/negotiate.js +108 -0
- package/dist/src/handshake/negotiate.js.map +1 -0
- package/dist/src/handshake/routing.d.ts +51 -0
- package/dist/src/handshake/routing.d.ts.map +1 -0
- package/dist/src/handshake/routing.js +57 -0
- package/dist/src/handshake/routing.js.map +1 -0
- package/dist/src/index.d.ts +14 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +12 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/interfaces.d.ts +168 -2
- package/dist/src/interfaces.d.ts.map +1 -1
- package/dist/src/remote-workflow-executor.d.ts +25 -1
- package/dist/src/remote-workflow-executor.d.ts.map +1 -1
- package/dist/src/remote-workflow-executor.js +41 -0
- package/dist/src/remote-workflow-executor.js.map +1 -1
- package/dist/src/role_bindings.d.ts +85 -0
- package/dist/src/role_bindings.d.ts.map +1 -0
- package/dist/src/role_bindings.js +73 -0
- package/dist/src/role_bindings.js.map +1 -0
- package/dist/src/run-gateway/interface.d.ts +89 -0
- package/dist/src/run-gateway/interface.d.ts.map +1 -0
- package/dist/src/run-gateway/interface.js +2 -0
- package/dist/src/run-gateway/interface.js.map +1 -0
- package/dist/src/run-gateway/proxy-run-gateway.d.ts +84 -0
- package/dist/src/run-gateway/proxy-run-gateway.d.ts.map +1 -0
- package/dist/src/run-gateway/proxy-run-gateway.js +142 -0
- package/dist/src/run-gateway/proxy-run-gateway.js.map +1 -0
- package/dist/src/run-gateway/run-request-responder.d.ts +67 -0
- package/dist/src/run-gateway/run-request-responder.d.ts.map +1 -0
- package/dist/src/run-gateway/run-request-responder.js +228 -0
- package/dist/src/run-gateway/run-request-responder.js.map +1 -0
- package/dist/src/run-gateway/store-run-gateway.d.ts +90 -0
- package/dist/src/run-gateway/store-run-gateway.d.ts.map +1 -0
- package/dist/src/run-gateway/store-run-gateway.js +80 -0
- package/dist/src/run-gateway/store-run-gateway.js.map +1 -0
- package/dist/src/run-gateway/tenant-auth.d.ts +19 -0
- package/dist/src/run-gateway/tenant-auth.d.ts.map +1 -0
- package/dist/src/run-gateway/tenant-auth.js +71 -0
- package/dist/src/run-gateway/tenant-auth.js.map +1 -0
- package/dist/src/services/main.d.ts +16 -3
- package/dist/src/services/main.d.ts.map +1 -1
- package/dist/src/services/main.js +23 -4
- package/dist/src/services/main.js.map +1 -1
- package/dist/src/step-name-symbol.d.ts +3 -0
- package/dist/src/step-name-symbol.d.ts.map +1 -1
- package/dist/src/step-name-symbol.js.map +1 -1
- package/dist/src/step-ref.d.ts +4 -0
- package/dist/src/step-ref.d.ts.map +1 -1
- package/dist/src/step-ref.js +2 -0
- package/dist/src/step-ref.js.map +1 -1
- package/dist/src/transports/bullmq/bullmq-transport.d.ts +130 -0
- package/dist/src/transports/bullmq/bullmq-transport.d.ts.map +1 -0
- package/dist/src/transports/bullmq/bullmq-transport.js +562 -0
- package/dist/src/transports/bullmq/bullmq-transport.js.map +1 -0
- package/dist/src/transports/bullmq/deps.d.ts +59 -0
- package/dist/src/transports/bullmq/deps.d.ts.map +1 -0
- package/dist/src/transports/bullmq/deps.js +41 -0
- package/dist/src/transports/bullmq/deps.js.map +1 -0
- package/dist/src/transports/bullmq/index.d.ts +5 -0
- package/dist/src/transports/bullmq/index.d.ts.map +1 -0
- package/dist/src/transports/bullmq/index.js +5 -0
- package/dist/src/transports/bullmq/index.js.map +1 -0
- package/dist/src/transports/bullmq/naming.d.ts +71 -0
- package/dist/src/transports/bullmq/naming.d.ts.map +1 -0
- package/dist/src/transports/bullmq/naming.js +101 -0
- package/dist/src/transports/bullmq/naming.js.map +1 -0
- package/dist/src/transports/bullmq/serialization.d.ts +56 -0
- package/dist/src/transports/bullmq/serialization.d.ts.map +1 -0
- package/dist/src/transports/bullmq/serialization.js +101 -0
- package/dist/src/transports/bullmq/serialization.js.map +1 -0
- package/dist/src/transports/factory.d.ts +31 -0
- package/dist/src/transports/factory.d.ts.map +1 -1
- package/dist/src/transports/factory.js +21 -0
- package/dist/src/transports/factory.js.map +1 -1
- package/dist/src/worker-runtime/index.d.ts +22 -0
- package/dist/src/worker-runtime/index.d.ts.map +1 -0
- package/dist/src/worker-runtime/index.js +27 -0
- package/dist/src/worker-runtime/index.js.map +1 -0
- package/dist/src/worker-runtime/naming.d.ts +24 -0
- package/dist/src/worker-runtime/naming.d.ts.map +1 -0
- package/dist/src/worker-runtime/naming.js +28 -0
- package/dist/src/worker-runtime/naming.js.map +1 -0
- package/dist/src/worker-runtime/registry.d.ts +81 -0
- package/dist/src/worker-runtime/registry.d.ts.map +1 -0
- package/dist/src/worker-runtime/registry.js +44 -0
- package/dist/src/worker-runtime/registry.js.map +1 -0
- package/dist/src/worker-runtime/worker-runtime.d.ts +155 -0
- package/dist/src/worker-runtime/worker-runtime.d.ts.map +1 -0
- package/dist/src/worker-runtime/worker-runtime.js +265 -0
- package/dist/src/worker-runtime/worker-runtime.js.map +1 -0
- package/dist/src/workflow-ctx.d.ts.map +1 -1
- package/dist/src/workflow-ctx.js +3 -0
- package/dist/src/workflow-ctx.js.map +1 -1
- package/dist/src/workflow-turn.d.ts +167 -0
- package/dist/src/workflow-turn.d.ts.map +1 -0
- package/dist/src/workflow-turn.js +372 -0
- package/dist/src/workflow-turn.js.map +1 -0
- package/package.json +7 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker/control-plane handshake descriptor — the single source of truth for routing, compatibility
|
|
3
|
+
* and observability across a store-less durable cluster (design §7.1). Pure data + a deterministic,
|
|
4
|
+
* order-insensitive content hash; NO transport, NO I/O. Every field is part of the cross-language
|
|
5
|
+
* wire contract (the nestjs + python SDKs build the byte-identical shape, design §7.8).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* A worker's (or control-plane's) advertised identity, wire-protocol support, feature capabilities
|
|
9
|
+
* and registered handlers. Published on startup + on change (design §7.2) and consumed by
|
|
10
|
+
* {@link negotiate} (compat) and the capability-aware router (design §7.5). Exactly the §7.1 shape.
|
|
11
|
+
*/
|
|
12
|
+
export interface WorkerDescriptor {
|
|
13
|
+
/** Stable id for this process in the fleet (mirrors aviary's per-instance heartbeat key). */
|
|
14
|
+
instanceId: string;
|
|
15
|
+
/** Execution runtime — a `python` worker and a `node` control-plane interoperate over the wire. */
|
|
16
|
+
runtime: 'node' | 'python';
|
|
17
|
+
/** Which SDK + version produced this descriptor. Observability only; never gates dispatch. */
|
|
18
|
+
sdk: {
|
|
19
|
+
name: string;
|
|
20
|
+
version: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Wire-protocol majors this side speaks. `version` is its preferred/current major; `range` is the
|
|
24
|
+
* inclusive `[min, max]` band it can negotiate down/up to. Overlap of two ranges is what makes two
|
|
25
|
+
* sides compatible (design §7.4); `version` is informational.
|
|
26
|
+
*/
|
|
27
|
+
protocol: {
|
|
28
|
+
version: number;
|
|
29
|
+
range: [number, number];
|
|
30
|
+
};
|
|
31
|
+
/** Named features advertised: `'saga'`, `'signals'`, `'search-attr-v2'`, `'priority'`, … A modern
|
|
32
|
+
* worker MAY advertise `[]` (no named features); an ABSENT field means "legacy v1" — see
|
|
33
|
+
* {@link normalizeDescriptor}. Order-insensitive: treated as a set. */
|
|
34
|
+
capabilities: string[];
|
|
35
|
+
/** Registered workflow handler names → routing targets. Order-insensitive (a set). */
|
|
36
|
+
workflows: string[];
|
|
37
|
+
/** Registered step handler names → routing targets. Order-insensitive (a set). */
|
|
38
|
+
steps: string[];
|
|
39
|
+
/** Optional routing partition (queue/group sharding). */
|
|
40
|
+
partition?: string;
|
|
41
|
+
/** Optional tenant namespace this instance serves. */
|
|
42
|
+
namespace?: string;
|
|
43
|
+
/** Process start time (epoch ms) — a restart changes it, so it participates in the content hash. */
|
|
44
|
+
startedAt: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The current wire-protocol major. The whole protocol as it exists today is defined as **v1**; the
|
|
48
|
+
* handshake exists so a future **v2 breaking change** is detectable rather than silently corrupting
|
|
49
|
+
* (design §7.7).
|
|
50
|
+
*/
|
|
51
|
+
export declare const CURRENT_PROTOCOL_VERSION = 1;
|
|
52
|
+
/**
|
|
53
|
+
* The protocol band assumed for a descriptor that omits `protocol` entirely — an existing aviary
|
|
54
|
+
* worker that predates the handshake. Absence = **legacy v1 baseline, assume compatible** (§7.7).
|
|
55
|
+
*/
|
|
56
|
+
export declare const LEGACY_V1_PROTOCOL: WorkerDescriptor['protocol'];
|
|
57
|
+
/**
|
|
58
|
+
* The feature set a legacy (pre-handshake) worker is assumed to advertise when its `capabilities`
|
|
59
|
+
* field is ABSENT. These are the durable primitives that have existed since v1, so a legacy worker
|
|
60
|
+
* can still be routed work that `requires` them (design §7.5/§7.7).
|
|
61
|
+
*
|
|
62
|
+
* This is the canonical aviary v1 execution surface, byte-for-byte identical across the Adonis,
|
|
63
|
+
* nestjs-durable, and Python SDKs (verified against the aviary codebase). `search-attr-v2` and other
|
|
64
|
+
* modern features are layered ON TOP and are NOT part of the v1 baseline — a legacy worker guarantees
|
|
65
|
+
* only the v1 `search-attributes` surface. Keep this list in lockstep with the other two SDKs.
|
|
66
|
+
*/
|
|
67
|
+
export declare const LEGACY_V1_CAPABILITIES: readonly string[];
|
|
68
|
+
/**
|
|
69
|
+
* A raw, possibly-partial descriptor as it may arrive off the wire from an older SDK: `protocol`
|
|
70
|
+
* and/or `capabilities` may be missing. {@link normalizeDescriptor} fills the legacy-v1 defaults.
|
|
71
|
+
*/
|
|
72
|
+
export type RawWorkerDescriptor = Omit<Partial<WorkerDescriptor>, 'instanceId' | 'runtime'> & {
|
|
73
|
+
instanceId: string;
|
|
74
|
+
runtime: 'node' | 'python';
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* True when a descriptor predates the handshake — it carries no `protocol` field. Such a descriptor
|
|
78
|
+
* is treated as legacy v1 and assumed compatible (design §7.7).
|
|
79
|
+
*/
|
|
80
|
+
export declare function isLegacyDescriptor(raw: RawWorkerDescriptor | WorkerDescriptor): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Fill legacy-v1 defaults so downstream logic always sees a complete {@link WorkerDescriptor}:
|
|
83
|
+
* - absent `protocol` → {@link LEGACY_V1_PROTOCOL} (assume-compatible v1 baseline),
|
|
84
|
+
* - absent `capabilities` → {@link LEGACY_V1_CAPABILITIES} (a modern `[]` is preserved as-is),
|
|
85
|
+
* - absent `workflows`/`steps` → `[]`, absent `sdk` → an `unknown` marker, absent `startedAt` → `0`.
|
|
86
|
+
*
|
|
87
|
+
* The undefined-vs-`[]` distinction is deliberate: an absent field means "legacy, doesn't advertise
|
|
88
|
+
* this axis"; an explicit empty array means "modern SDK that genuinely advertises nothing".
|
|
89
|
+
*/
|
|
90
|
+
export declare function normalizeDescriptor(raw: RawWorkerDescriptor | WorkerDescriptor): WorkerDescriptor;
|
|
91
|
+
/**
|
|
92
|
+
* Compact, liveness lifecycle status carried on the cheap steady-state heartbeat (design §7.2).
|
|
93
|
+
* Distinct from aviary's rich `WorkerStatus` object (owned by the transport layer): this is the
|
|
94
|
+
* lightweight two-tier advertisement, whose job is to carry the {@link descriptorHash} ETag.
|
|
95
|
+
*/
|
|
96
|
+
export type WorkerLifecycle = 'up' | 'draining' | 'quiescing' | 'stopped';
|
|
97
|
+
/**
|
|
98
|
+
* The two-tier heartbeat payload (design §7.2): a cheap `{ ts, status, descriptorHash }` beaten every
|
|
99
|
+
* ~10s. The `descriptorHash` is an **ETag** — the control-plane re-reads the full (expensive)
|
|
100
|
+
* descriptor only when this hash changes, keeping steady-state chatter tiny.
|
|
101
|
+
*/
|
|
102
|
+
export interface HeartbeatStatus {
|
|
103
|
+
/** Beat time, epoch ms. */
|
|
104
|
+
ts: number;
|
|
105
|
+
/** Compact lifecycle status. */
|
|
106
|
+
status: WorkerLifecycle;
|
|
107
|
+
/** ETag over the full descriptor — see {@link descriptorHash}. */
|
|
108
|
+
descriptorHash: string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Stable ETag over a descriptor's content (design §7.2). **Order-insensitive** over the set-valued
|
|
112
|
+
* fields (`capabilities`/`workflows`/`steps`) — the same members in any order yield the same hash —
|
|
113
|
+
* and stable across the object's key insertion order. Any change to a routing/compat-relevant field
|
|
114
|
+
* (including `startedAt` on restart) changes the hash, which is what triggers a full re-read.
|
|
115
|
+
*
|
|
116
|
+
* Accepts a raw/partial descriptor too: it is normalized first, so a legacy descriptor hashes as its
|
|
117
|
+
* v1 baseline.
|
|
118
|
+
*/
|
|
119
|
+
export declare function descriptorHash(descriptor: RawWorkerDescriptor | WorkerDescriptor): string;
|
|
120
|
+
/**
|
|
121
|
+
* Build the compact two-tier heartbeat for a descriptor, stamping the ETag (design §7.2). `ts`
|
|
122
|
+
* defaults to now and `status` to `'up'`.
|
|
123
|
+
*/
|
|
124
|
+
export declare function heartbeatStatus(descriptor: RawWorkerDescriptor | WorkerDescriptor, opts?: {
|
|
125
|
+
ts?: number;
|
|
126
|
+
status?: WorkerLifecycle;
|
|
127
|
+
}): HeartbeatStatus;
|
|
128
|
+
//# sourceMappingURL=descriptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"descriptor.d.ts","sourceRoot":"","sources":["../../../src/handshake/descriptor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6FAA6F;IAC7F,UAAU,EAAE,MAAM,CAAC;IACnB,mGAAmG;IACnG,OAAO,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC3B,8FAA8F;IAC9F,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC;;;;OAIG;IACH,QAAQ,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IACvD;;4EAEwE;IACxE,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,sFAAsF;IACtF,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,kFAAkF;IAClF,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oGAAoG;IACpG,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,gBAAgB,CAAC,UAAU,CAAiC,CAAC;AAE9F;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAYlD,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC,GAAG;IAC5F,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,QAAQ,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,mBAAmB,GAAG,gBAAgB,GAAG,OAAO,CAEvF;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,mBAAmB,GAAG,gBAAgB,GAAG,gBAAgB,CAajG;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,CAAC;AAE1E;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,2BAA2B;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,gCAAgC;IAChC,MAAM,EAAE,eAAe,CAAC;IACxB,kEAAkE;IAClE,cAAc,EAAE,MAAM,CAAC;CACxB;AA0CD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,mBAAmB,GAAG,gBAAgB,GAAG,MAAM,CAGzF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,mBAAmB,GAAG,gBAAgB,EAClD,IAAI,GAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,eAAe,CAAA;CAAO,GACnD,eAAe,CAMjB"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker/control-plane handshake descriptor — the single source of truth for routing, compatibility
|
|
3
|
+
* and observability across a store-less durable cluster (design §7.1). Pure data + a deterministic,
|
|
4
|
+
* order-insensitive content hash; NO transport, NO I/O. Every field is part of the cross-language
|
|
5
|
+
* wire contract (the nestjs + python SDKs build the byte-identical shape, design §7.8).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* The current wire-protocol major. The whole protocol as it exists today is defined as **v1**; the
|
|
9
|
+
* handshake exists so a future **v2 breaking change** is detectable rather than silently corrupting
|
|
10
|
+
* (design §7.7).
|
|
11
|
+
*/
|
|
12
|
+
export const CURRENT_PROTOCOL_VERSION = 1;
|
|
13
|
+
/**
|
|
14
|
+
* The protocol band assumed for a descriptor that omits `protocol` entirely — an existing aviary
|
|
15
|
+
* worker that predates the handshake. Absence = **legacy v1 baseline, assume compatible** (§7.7).
|
|
16
|
+
*/
|
|
17
|
+
export const LEGACY_V1_PROTOCOL = { version: 1, range: [1, 1] };
|
|
18
|
+
/**
|
|
19
|
+
* The feature set a legacy (pre-handshake) worker is assumed to advertise when its `capabilities`
|
|
20
|
+
* field is ABSENT. These are the durable primitives that have existed since v1, so a legacy worker
|
|
21
|
+
* can still be routed work that `requires` them (design §7.5/§7.7).
|
|
22
|
+
*
|
|
23
|
+
* This is the canonical aviary v1 execution surface, byte-for-byte identical across the Adonis,
|
|
24
|
+
* nestjs-durable, and Python SDKs (verified against the aviary codebase). `search-attr-v2` and other
|
|
25
|
+
* modern features are layered ON TOP and are NOT part of the v1 baseline — a legacy worker guarantees
|
|
26
|
+
* only the v1 `search-attributes` surface. Keep this list in lockstep with the other two SDKs.
|
|
27
|
+
*/
|
|
28
|
+
export const LEGACY_V1_CAPABILITIES = Object.freeze([
|
|
29
|
+
'saga',
|
|
30
|
+
'signals',
|
|
31
|
+
'search-attributes',
|
|
32
|
+
'priority',
|
|
33
|
+
'entities',
|
|
34
|
+
'child-workflows',
|
|
35
|
+
'singleton',
|
|
36
|
+
'schedules',
|
|
37
|
+
'continue-as-new',
|
|
38
|
+
'queries',
|
|
39
|
+
'cancellation',
|
|
40
|
+
]);
|
|
41
|
+
/**
|
|
42
|
+
* True when a descriptor predates the handshake — it carries no `protocol` field. Such a descriptor
|
|
43
|
+
* is treated as legacy v1 and assumed compatible (design §7.7).
|
|
44
|
+
*/
|
|
45
|
+
export function isLegacyDescriptor(raw) {
|
|
46
|
+
return raw.protocol === undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Fill legacy-v1 defaults so downstream logic always sees a complete {@link WorkerDescriptor}:
|
|
50
|
+
* - absent `protocol` → {@link LEGACY_V1_PROTOCOL} (assume-compatible v1 baseline),
|
|
51
|
+
* - absent `capabilities` → {@link LEGACY_V1_CAPABILITIES} (a modern `[]` is preserved as-is),
|
|
52
|
+
* - absent `workflows`/`steps` → `[]`, absent `sdk` → an `unknown` marker, absent `startedAt` → `0`.
|
|
53
|
+
*
|
|
54
|
+
* The undefined-vs-`[]` distinction is deliberate: an absent field means "legacy, doesn't advertise
|
|
55
|
+
* this axis"; an explicit empty array means "modern SDK that genuinely advertises nothing".
|
|
56
|
+
*/
|
|
57
|
+
export function normalizeDescriptor(raw) {
|
|
58
|
+
return {
|
|
59
|
+
instanceId: raw.instanceId,
|
|
60
|
+
runtime: raw.runtime,
|
|
61
|
+
sdk: raw.sdk ?? { name: 'unknown', version: '0' },
|
|
62
|
+
protocol: raw.protocol ?? { ...LEGACY_V1_PROTOCOL },
|
|
63
|
+
capabilities: raw.capabilities ?? [...LEGACY_V1_CAPABILITIES],
|
|
64
|
+
workflows: raw.workflows ?? [],
|
|
65
|
+
steps: raw.steps ?? [],
|
|
66
|
+
...(raw.partition !== undefined ? { partition: raw.partition } : {}),
|
|
67
|
+
...(raw.namespace !== undefined ? { namespace: raw.namespace } : {}),
|
|
68
|
+
startedAt: raw.startedAt ?? 0,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Canonical, order-insensitive projection of a descriptor used for hashing. The three set-valued
|
|
72
|
+
* fields are sorted + de-duplicated so member order can never change the hash; scalar fields are
|
|
73
|
+
* taken verbatim; optional fields collapse to `null` so present-with-undefined and absent agree. */
|
|
74
|
+
function canonicalizeForHash(d) {
|
|
75
|
+
const set = (xs) => [...new Set(xs)].sort();
|
|
76
|
+
// Keys are emitted in a FIXED order (this literal's order) so the stringify below is stable
|
|
77
|
+
// regardless of the input object's key insertion order.
|
|
78
|
+
return {
|
|
79
|
+
instanceId: d.instanceId,
|
|
80
|
+
runtime: d.runtime,
|
|
81
|
+
sdk: { name: d.sdk.name, version: d.sdk.version },
|
|
82
|
+
protocol: { version: d.protocol.version, range: [d.protocol.range[0], d.protocol.range[1]] },
|
|
83
|
+
capabilities: set(d.capabilities),
|
|
84
|
+
workflows: set(d.workflows),
|
|
85
|
+
steps: set(d.steps),
|
|
86
|
+
partition: d.partition ?? null,
|
|
87
|
+
namespace: d.namespace ?? null,
|
|
88
|
+
startedAt: d.startedAt,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Deterministic 64-bit FNV-1a hash of a string → 16-char lowercase hex. Chosen over a crypto digest
|
|
93
|
+
* because it is trivially reproducible in every SDK (TS/Python/…) with no dependency, keeping the
|
|
94
|
+
* ETag scheme portable for cross-language conformance (design §7.8).
|
|
95
|
+
*/
|
|
96
|
+
function fnv1a64Hex(input) {
|
|
97
|
+
const OFFSET = 0xcbf29ce484222325n;
|
|
98
|
+
const PRIME = 0x100000001b3n;
|
|
99
|
+
const MASK = 0xffffffffffffffffn;
|
|
100
|
+
let hash = OFFSET;
|
|
101
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
102
|
+
hash ^= BigInt(input.charCodeAt(i) & 0xff);
|
|
103
|
+
// charCodeAt can exceed a byte; fold the high byte in too so non-ASCII still contributes.
|
|
104
|
+
hash ^= BigInt((input.charCodeAt(i) >> 8) & 0xff);
|
|
105
|
+
hash = (hash * PRIME) & MASK;
|
|
106
|
+
}
|
|
107
|
+
return hash.toString(16).padStart(16, '0');
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Stable ETag over a descriptor's content (design §7.2). **Order-insensitive** over the set-valued
|
|
111
|
+
* fields (`capabilities`/`workflows`/`steps`) — the same members in any order yield the same hash —
|
|
112
|
+
* and stable across the object's key insertion order. Any change to a routing/compat-relevant field
|
|
113
|
+
* (including `startedAt` on restart) changes the hash, which is what triggers a full re-read.
|
|
114
|
+
*
|
|
115
|
+
* Accepts a raw/partial descriptor too: it is normalized first, so a legacy descriptor hashes as its
|
|
116
|
+
* v1 baseline.
|
|
117
|
+
*/
|
|
118
|
+
export function descriptorHash(descriptor) {
|
|
119
|
+
const canonical = canonicalizeForHash(normalizeDescriptor(descriptor));
|
|
120
|
+
return fnv1a64Hex(JSON.stringify(canonical));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Build the compact two-tier heartbeat for a descriptor, stamping the ETag (design §7.2). `ts`
|
|
124
|
+
* defaults to now and `status` to `'up'`.
|
|
125
|
+
*/
|
|
126
|
+
export function heartbeatStatus(descriptor, opts = {}) {
|
|
127
|
+
return {
|
|
128
|
+
ts: opts.ts ?? Date.now(),
|
|
129
|
+
status: opts.status ?? 'up',
|
|
130
|
+
descriptorHash: descriptorHash(descriptor),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=descriptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"descriptor.js","sourceRoot":"","sources":["../../../src/handshake/descriptor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bilateral compatibility negotiation between two handshake descriptors (design §7.3/§7.4). Pure
|
|
3
|
+
* logic: given a local and a remote {@link WorkerDescriptor}, compute the negotiated session (highest
|
|
4
|
+
* common protocol major + capability intersection) and classify it into one of three outcomes with a
|
|
5
|
+
* precise, structured reason — never a bare boolean (design §7.6).
|
|
6
|
+
*/
|
|
7
|
+
import { type RawWorkerDescriptor, type WorkerDescriptor } from './descriptor.js';
|
|
8
|
+
/**
|
|
9
|
+
* The three compatibility outcomes (design §7.4):
|
|
10
|
+
* - `compatible` — protocol ranges intersect + full capability parity → dispatch freely.
|
|
11
|
+
* - `degraded` — ranges intersect but a capability is missing on one side → dispatch, but route
|
|
12
|
+
* capability-requiring work only to capable workers (soft warning).
|
|
13
|
+
* - `incompatible` — no protocol-range overlap → do NOT dispatch; red flag with the exact reason.
|
|
14
|
+
*/
|
|
15
|
+
export type NegotiationOutcome = 'compatible' | 'degraded' | 'incompatible';
|
|
16
|
+
/** Structured failure/warning payload — carries the precise delta so a diagnostics event can render
|
|
17
|
+
* it on the timeline + dashboard health panel (design §7.6). Never a bare boolean. */
|
|
18
|
+
export interface NegotiationReason {
|
|
19
|
+
/** Stable machine code for alerting/telescope. */
|
|
20
|
+
code: 'protocol.incompatible' | 'capability.unavailable';
|
|
21
|
+
/** Human-readable one-liner (dashboard/telescope copy). */
|
|
22
|
+
message: string;
|
|
23
|
+
/** The exact delta: which protocol ranges failed to overlap, or which capabilities are missing. */
|
|
24
|
+
detail: {
|
|
25
|
+
localRange?: [number, number];
|
|
26
|
+
remoteRange?: [number, number];
|
|
27
|
+
/** Capabilities the local side advertises that the remote lacks. */
|
|
28
|
+
missingOnRemote?: string[];
|
|
29
|
+
/** Capabilities the remote side advertises that the local lacks. */
|
|
30
|
+
missingOnLocal?: string[];
|
|
31
|
+
/** Explicitly-required capabilities not satisfied by the negotiated session. */
|
|
32
|
+
missingRequired?: string[];
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** The negotiated session + full delta between two sides. Carries both normalized descriptors so a
|
|
36
|
+
* downstream diagnostics event can attach them wholesale (design §7.6). */
|
|
37
|
+
export interface NegotiationResult {
|
|
38
|
+
outcome: NegotiationOutcome;
|
|
39
|
+
/** Highest common protocol major, or `null` when ranges do not overlap (incompatible). */
|
|
40
|
+
negotiatedProtocol: number | null;
|
|
41
|
+
protocol: {
|
|
42
|
+
localRange: [number, number];
|
|
43
|
+
remoteRange: [number, number];
|
|
44
|
+
};
|
|
45
|
+
capabilities: {
|
|
46
|
+
/** Intersection — the capabilities the negotiated session can rely on everywhere. */
|
|
47
|
+
shared: string[];
|
|
48
|
+
/** Local advertises, remote lacks. */
|
|
49
|
+
missingOnRemote: string[];
|
|
50
|
+
/** Remote advertises, local lacks. */
|
|
51
|
+
missingOnLocal: string[];
|
|
52
|
+
};
|
|
53
|
+
/** Present for `degraded` + `incompatible`; absent for `compatible`. */
|
|
54
|
+
reason?: NegotiationReason;
|
|
55
|
+
local: WorkerDescriptor;
|
|
56
|
+
remote: WorkerDescriptor;
|
|
57
|
+
}
|
|
58
|
+
export interface NegotiateOptions {
|
|
59
|
+
/**
|
|
60
|
+
* Capabilities the local side needs the negotiated session to provide. A required capability the
|
|
61
|
+
* remote lacks degrades the session (dispatch generally, but capability-requiring runs park via
|
|
62
|
+
* the router, design §7.5) — it never, on its own, makes the pair *incompatible*: only a protocol
|
|
63
|
+
* range gap does that (design §7.4).
|
|
64
|
+
*/
|
|
65
|
+
required?: string[];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Negotiate compatibility between `local` and `remote` (design §7.3/§7.4). Accepts raw/partial
|
|
69
|
+
* descriptors (a missing `protocol`/`capabilities` is normalized to the legacy-v1 baseline, so a
|
|
70
|
+
* legacy peer negotiates as **assume-compatible**, design §7.7).
|
|
71
|
+
*
|
|
72
|
+
* Outcome logic:
|
|
73
|
+
* 1. no protocol-range overlap → `incompatible` (`protocol.incompatible`);
|
|
74
|
+
* 2. else if a `required` capability is missing on the remote → `degraded` (`capability.unavailable`);
|
|
75
|
+
* 3. else if the capability sets differ at all (either direction) → `degraded`;
|
|
76
|
+
* 4. else full parity → `compatible`.
|
|
77
|
+
*
|
|
78
|
+
* **Bilateral-symmetric** on outcome for the parity path: `negotiate(a, b).outcome ===
|
|
79
|
+
* negotiate(b, a).outcome` (the `missingOnLocal`/`missingOnRemote` delta swaps). `required` is the
|
|
80
|
+
* caller's own need and intentionally asymmetric.
|
|
81
|
+
*/
|
|
82
|
+
export declare function negotiate(localRaw: RawWorkerDescriptor | WorkerDescriptor, remoteRaw: RawWorkerDescriptor | WorkerDescriptor, opts?: NegotiateOptions): NegotiationResult;
|
|
83
|
+
//# sourceMappingURL=negotiate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"negotiate.d.ts","sourceRoot":"","sources":["../../../src/handshake/negotiate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EAEtB,MAAM,iBAAiB,CAAC;AAEzB;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG,UAAU,GAAG,cAAc,CAAC;AAE5E;uFACuF;AACvF,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,IAAI,EAAE,uBAAuB,GAAG,wBAAwB,CAAC;IACzD,2DAA2D;IAC3D,OAAO,EAAE,MAAM,CAAC;IAChB,mGAAmG;IACnG,MAAM,EAAE;QACN,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC9B,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,oEAAoE;QACpE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;QAC3B,oEAAoE;QACpE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAC1B,gFAAgF;QAChF,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;KAC5B,CAAC;CACH;AAED;4EAC4E;AAC5E,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,0FAA0F;IAC1F,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,EAAE;QAAE,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IAC1E,YAAY,EAAE;QACZ,qFAAqF;QACrF,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,sCAAsC;QACtC,eAAe,EAAE,MAAM,EAAE,CAAC;QAC1B,sCAAsC;QACtC,cAAc,EAAE,MAAM,EAAE,CAAC;KAC1B,CAAC;IACF,wEAAwE;IACxE,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAeD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,mBAAmB,GAAG,gBAAgB,EAChD,SAAS,EAAE,mBAAmB,GAAG,gBAAgB,EACjD,IAAI,GAAE,gBAAqB,GAC1B,iBAAiB,CAgFnB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bilateral compatibility negotiation between two handshake descriptors (design §7.3/§7.4). Pure
|
|
3
|
+
* logic: given a local and a remote {@link WorkerDescriptor}, compute the negotiated session (highest
|
|
4
|
+
* common protocol major + capability intersection) and classify it into one of three outcomes with a
|
|
5
|
+
* precise, structured reason — never a bare boolean (design §7.6).
|
|
6
|
+
*/
|
|
7
|
+
import { normalizeDescriptor, } from './descriptor.js';
|
|
8
|
+
const rangesOverlap = (a, b) => Math.max(a[0], b[0]) <= Math.min(a[1], b[1]);
|
|
9
|
+
const difference = (a, b) => {
|
|
10
|
+
const bset = new Set(b);
|
|
11
|
+
return [...new Set(a)].filter((x) => !bset.has(x)).sort();
|
|
12
|
+
};
|
|
13
|
+
const intersection = (a, b) => {
|
|
14
|
+
const bset = new Set(b);
|
|
15
|
+
return [...new Set(a)].filter((x) => bset.has(x)).sort();
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Negotiate compatibility between `local` and `remote` (design §7.3/§7.4). Accepts raw/partial
|
|
19
|
+
* descriptors (a missing `protocol`/`capabilities` is normalized to the legacy-v1 baseline, so a
|
|
20
|
+
* legacy peer negotiates as **assume-compatible**, design §7.7).
|
|
21
|
+
*
|
|
22
|
+
* Outcome logic:
|
|
23
|
+
* 1. no protocol-range overlap → `incompatible` (`protocol.incompatible`);
|
|
24
|
+
* 2. else if a `required` capability is missing on the remote → `degraded` (`capability.unavailable`);
|
|
25
|
+
* 3. else if the capability sets differ at all (either direction) → `degraded`;
|
|
26
|
+
* 4. else full parity → `compatible`.
|
|
27
|
+
*
|
|
28
|
+
* **Bilateral-symmetric** on outcome for the parity path: `negotiate(a, b).outcome ===
|
|
29
|
+
* negotiate(b, a).outcome` (the `missingOnLocal`/`missingOnRemote` delta swaps). `required` is the
|
|
30
|
+
* caller's own need and intentionally asymmetric.
|
|
31
|
+
*/
|
|
32
|
+
export function negotiate(localRaw, remoteRaw, opts = {}) {
|
|
33
|
+
const local = normalizeDescriptor(localRaw);
|
|
34
|
+
const remote = normalizeDescriptor(remoteRaw);
|
|
35
|
+
const localRange = local.protocol.range;
|
|
36
|
+
const remoteRange = remote.protocol.range;
|
|
37
|
+
const shared = intersection(local.capabilities, remote.capabilities);
|
|
38
|
+
const missingOnRemote = difference(local.capabilities, remote.capabilities);
|
|
39
|
+
const missingOnLocal = difference(remote.capabilities, local.capabilities);
|
|
40
|
+
const capabilities = { shared, missingOnRemote, missingOnLocal };
|
|
41
|
+
// (1) Protocol range gap → incompatible. This is the ONLY path that blocks dispatch outright.
|
|
42
|
+
if (!rangesOverlap(localRange, remoteRange)) {
|
|
43
|
+
return {
|
|
44
|
+
outcome: 'incompatible',
|
|
45
|
+
negotiatedProtocol: null,
|
|
46
|
+
protocol: { localRange, remoteRange },
|
|
47
|
+
capabilities,
|
|
48
|
+
reason: {
|
|
49
|
+
code: 'protocol.incompatible',
|
|
50
|
+
message: `no common protocol major: local speaks [${localRange[0]}, ${localRange[1]}], ` +
|
|
51
|
+
`remote speaks [${remoteRange[0]}, ${remoteRange[1]}]`,
|
|
52
|
+
detail: { localRange, remoteRange },
|
|
53
|
+
},
|
|
54
|
+
local,
|
|
55
|
+
remote,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const negotiatedProtocol = Math.min(localRange[1], remoteRange[1]);
|
|
59
|
+
// (2) A required capability the remote cannot provide → degraded (router parks the specific run).
|
|
60
|
+
const required = opts.required ?? [];
|
|
61
|
+
const missingRequired = difference(required, remote.capabilities);
|
|
62
|
+
if (missingRequired.length > 0) {
|
|
63
|
+
return {
|
|
64
|
+
outcome: 'degraded',
|
|
65
|
+
negotiatedProtocol,
|
|
66
|
+
protocol: { localRange, remoteRange },
|
|
67
|
+
capabilities,
|
|
68
|
+
reason: {
|
|
69
|
+
code: 'capability.unavailable',
|
|
70
|
+
message: `remote is missing required capability(ies): ${missingRequired.join(', ')}`,
|
|
71
|
+
detail: { missingRequired, missingOnRemote, missingOnLocal },
|
|
72
|
+
},
|
|
73
|
+
local,
|
|
74
|
+
remote,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// (3) Any capability asymmetry → degraded (soft; route capability-requiring work carefully).
|
|
78
|
+
if (missingOnRemote.length > 0 || missingOnLocal.length > 0) {
|
|
79
|
+
const parts = [];
|
|
80
|
+
if (missingOnRemote.length > 0)
|
|
81
|
+
parts.push(`remote lacks ${missingOnRemote.join(', ')}`);
|
|
82
|
+
if (missingOnLocal.length > 0)
|
|
83
|
+
parts.push(`local lacks ${missingOnLocal.join(', ')}`);
|
|
84
|
+
return {
|
|
85
|
+
outcome: 'degraded',
|
|
86
|
+
negotiatedProtocol,
|
|
87
|
+
protocol: { localRange, remoteRange },
|
|
88
|
+
capabilities,
|
|
89
|
+
reason: {
|
|
90
|
+
code: 'capability.unavailable',
|
|
91
|
+
message: `capability mismatch: ${parts.join('; ')}`,
|
|
92
|
+
detail: { missingOnRemote, missingOnLocal },
|
|
93
|
+
},
|
|
94
|
+
local,
|
|
95
|
+
remote,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// (4) Ranges overlap + full capability parity → compatible.
|
|
99
|
+
return {
|
|
100
|
+
outcome: 'compatible',
|
|
101
|
+
negotiatedProtocol,
|
|
102
|
+
protocol: { localRange, remoteRange },
|
|
103
|
+
capabilities,
|
|
104
|
+
local,
|
|
105
|
+
remote,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=negotiate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"negotiate.js","sourceRoot":"","sources":["../../../src/handshake/negotiate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability-aware routing (design §7.5). A workflow/step handler may declare
|
|
3
|
+
* `requires: ['saga', 'search-attr-v2']`; the control-plane dispatches only to workers whose
|
|
4
|
+
* descriptor advertises every required capability. If no live capable worker exists the run must
|
|
5
|
+
* **park as `blocked`** with a precise reason (visible in the dashboard) — never a silent hang.
|
|
6
|
+
*
|
|
7
|
+
* Pure logic: no transport, no store. Consumers (the dispatcher/provider) feed live descriptors in.
|
|
8
|
+
*/
|
|
9
|
+
import { type RawWorkerDescriptor, type WorkerDescriptor } from './descriptor.js';
|
|
10
|
+
/** A handler's capability demand — either a bare list, or anything carrying a `requires` array
|
|
11
|
+
* (a workflow/step ref). An absent/empty list means "runs anywhere" (design §7.5). */
|
|
12
|
+
export type CapabilityRequirement = string[] | {
|
|
13
|
+
requires?: string[];
|
|
14
|
+
};
|
|
15
|
+
/** Normalize the two accepted shapes to a de-duplicated list of required capability names. */
|
|
16
|
+
export declare function requiredCapabilities(handler: CapabilityRequirement): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Can `worker` run a handler with these requirements? True iff the worker's (normalized) descriptor
|
|
19
|
+
* advertises **every** required capability. A legacy worker (no `capabilities` field) is normalized
|
|
20
|
+
* to the v1 baseline first (design §7.7), so it can still run baseline-only work.
|
|
21
|
+
*/
|
|
22
|
+
export declare function canRoute(handler: CapabilityRequirement, worker: RawWorkerDescriptor | WorkerDescriptor): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The capabilities a handler requires that `worker` does NOT advertise — the precise routing delta.
|
|
25
|
+
* Empty iff {@link canRoute} is true.
|
|
26
|
+
*/
|
|
27
|
+
export declare function missingCapabilities(handler: CapabilityRequirement, worker: RawWorkerDescriptor | WorkerDescriptor): string[];
|
|
28
|
+
/** Filter a fleet down to the workers that can run a handler (design §7.5). */
|
|
29
|
+
export declare function capableWorkers<T extends RawWorkerDescriptor | WorkerDescriptor>(handler: CapabilityRequirement, workers: readonly T[]): T[];
|
|
30
|
+
/** A run is dispatchable — at least one live worker can execute the handler. */
|
|
31
|
+
export interface RoutableResolution {
|
|
32
|
+
status: 'routable';
|
|
33
|
+
/** The normalized subset of the fleet that can run it. */
|
|
34
|
+
workers: WorkerDescriptor[];
|
|
35
|
+
requires: string[];
|
|
36
|
+
}
|
|
37
|
+
/** No live capable worker — the run parks as `blocked` with a human reason (design §7.5). */
|
|
38
|
+
export interface BlockedResolution {
|
|
39
|
+
status: 'blocked';
|
|
40
|
+
/** Exactly the dashboard copy: `blocked: no compatible worker (requires <caps>)`. */
|
|
41
|
+
reason: string;
|
|
42
|
+
requires: string[];
|
|
43
|
+
}
|
|
44
|
+
export type RoutingResolution = RoutableResolution | BlockedResolution;
|
|
45
|
+
/**
|
|
46
|
+
* Decide whether a handler can be dispatched to the given live fleet (design §7.5). Returns
|
|
47
|
+
* `routable` with the capable subset, or `blocked` with a precise reason when nobody can run it —
|
|
48
|
+
* so the run parks visibly instead of hanging silently. An empty fleet always blocks.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveRouting(handler: CapabilityRequirement, workers: readonly (RawWorkerDescriptor | WorkerDescriptor)[]): RoutingResolution;
|
|
51
|
+
//# sourceMappingURL=routing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../../src/handshake/routing.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EAEtB,MAAM,iBAAiB,CAAC;AAEzB;uFACuF;AACvF,MAAM,MAAM,qBAAqB,GAAG,MAAM,EAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAEvE,8FAA8F;AAC9F,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,EAAE,CAG7E;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE,mBAAmB,GAAG,gBAAgB,GAC7C,OAAO,CAKT;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE,mBAAmB,GAAG,gBAAgB,GAC7C,MAAM,EAAE,CAGV;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,CAAC,SAAS,mBAAmB,GAAG,gBAAgB,EAC7E,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,SAAS,CAAC,EAAE,GACpB,CAAC,EAAE,CAEL;AAED,gFAAgF;AAChF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,UAAU,CAAC;IACnB,0DAA0D;IAC1D,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,6FAA6F;AAC7F,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,SAAS,CAAC;IAClB,qFAAqF;IACrF,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,GAAG,iBAAiB,CAAC;AAEvE;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,SAAS,CAAC,mBAAmB,GAAG,gBAAgB,CAAC,EAAE,GAC3D,iBAAiB,CAcnB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability-aware routing (design §7.5). A workflow/step handler may declare
|
|
3
|
+
* `requires: ['saga', 'search-attr-v2']`; the control-plane dispatches only to workers whose
|
|
4
|
+
* descriptor advertises every required capability. If no live capable worker exists the run must
|
|
5
|
+
* **park as `blocked`** with a precise reason (visible in the dashboard) — never a silent hang.
|
|
6
|
+
*
|
|
7
|
+
* Pure logic: no transport, no store. Consumers (the dispatcher/provider) feed live descriptors in.
|
|
8
|
+
*/
|
|
9
|
+
import { normalizeDescriptor, } from './descriptor.js';
|
|
10
|
+
/** Normalize the two accepted shapes to a de-duplicated list of required capability names. */
|
|
11
|
+
export function requiredCapabilities(handler) {
|
|
12
|
+
const list = Array.isArray(handler) ? handler : (handler.requires ?? []);
|
|
13
|
+
return [...new Set(list)];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Can `worker` run a handler with these requirements? True iff the worker's (normalized) descriptor
|
|
17
|
+
* advertises **every** required capability. A legacy worker (no `capabilities` field) is normalized
|
|
18
|
+
* to the v1 baseline first (design §7.7), so it can still run baseline-only work.
|
|
19
|
+
*/
|
|
20
|
+
export function canRoute(handler, worker) {
|
|
21
|
+
const required = requiredCapabilities(handler);
|
|
22
|
+
if (required.length === 0)
|
|
23
|
+
return true;
|
|
24
|
+
const advertised = new Set(normalizeDescriptor(worker).capabilities);
|
|
25
|
+
return required.every((cap) => advertised.has(cap));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The capabilities a handler requires that `worker` does NOT advertise — the precise routing delta.
|
|
29
|
+
* Empty iff {@link canRoute} is true.
|
|
30
|
+
*/
|
|
31
|
+
export function missingCapabilities(handler, worker) {
|
|
32
|
+
const advertised = new Set(normalizeDescriptor(worker).capabilities);
|
|
33
|
+
return requiredCapabilities(handler).filter((cap) => !advertised.has(cap));
|
|
34
|
+
}
|
|
35
|
+
/** Filter a fleet down to the workers that can run a handler (design §7.5). */
|
|
36
|
+
export function capableWorkers(handler, workers) {
|
|
37
|
+
return workers.filter((w) => canRoute(handler, w));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Decide whether a handler can be dispatched to the given live fleet (design §7.5). Returns
|
|
41
|
+
* `routable` with the capable subset, or `blocked` with a precise reason when nobody can run it —
|
|
42
|
+
* so the run parks visibly instead of hanging silently. An empty fleet always blocks.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveRouting(handler, workers) {
|
|
45
|
+
const requires = requiredCapabilities(handler);
|
|
46
|
+
const capable = capableWorkers(handler, workers).map(normalizeDescriptor);
|
|
47
|
+
if (capable.length > 0) {
|
|
48
|
+
return { status: 'routable', workers: capable, requires };
|
|
49
|
+
}
|
|
50
|
+
const suffix = requires.length > 0 ? ` (requires ${requires.join(', ')})` : '';
|
|
51
|
+
return {
|
|
52
|
+
status: 'blocked',
|
|
53
|
+
reason: `blocked: no compatible worker${suffix}`,
|
|
54
|
+
requires,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=routing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing.js","sourceRoot":"","sources":["../../../src/handshake/routing.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAGL,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AAMzB,8FAA8F;AAC9F,MAAM,UAAU,oBAAoB,CAAC,OAA8B;IACjE,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CACtB,OAA8B,EAC9B,MAA8C;IAE9C,MAAM,QAAQ,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC/C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IACrE,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AACtD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAA8B,EAC9B,MAA8C;IAE9C,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IACrE,OAAO,oBAAoB,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,cAAc,CAC5B,OAA8B,EAC9B,OAAqB;IAErB,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC;AAoBD;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,OAA8B,EAC9B,OAA4D;IAE5D,MAAM,QAAQ,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAE1E,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IAC5D,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/E,OAAO;QACL,MAAM,EAAE,SAAS;QACjB,MAAM,EAAE,gCAAgC,MAAM,EAAE;QAChD,QAAQ;KACT,CAAC;AACJ,CAAC"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Keep in sync with this package's `version` in package.json. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.11.0";
|
|
3
3
|
export * from './admission.js';
|
|
4
4
|
export * from './control-flow-signal.js';
|
|
5
5
|
export * from './duration.js';
|
|
@@ -11,6 +11,7 @@ export * from './interfaces.js';
|
|
|
11
11
|
export * from './protocol.js';
|
|
12
12
|
export * from './queue.js';
|
|
13
13
|
export * from './remote-workflow-executor.js';
|
|
14
|
+
export * from './workflow-turn.js';
|
|
14
15
|
export * from './tenant-group.js';
|
|
15
16
|
export { DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type StepConfig, type StepRef, stepConfigOf, stepNameOf, } from './step-name-symbol.js';
|
|
16
17
|
export * from './step-ref.js';
|
|
@@ -30,10 +31,11 @@ export { getCurrentWorkflowCtx, workflowAls } from './workflow-als.js';
|
|
|
30
31
|
export { InMemoryStateStore } from './testing/in-memory-state-store.js';
|
|
31
32
|
export { InMemoryTransport } from './testing/in-memory-transport.js';
|
|
32
33
|
export { transports } from './transports/factory.js';
|
|
33
|
-
export type { TransportContext, TransportFactory, MemoryTransportConfig, EventEmitterTransportConfig, QueueTransportConfig, DbTransportConfig, } from './transports/factory.js';
|
|
34
|
+
export type { TransportContext, TransportFactory, MemoryTransportConfig, EventEmitterTransportConfig, QueueTransportConfig, BullMQTransportConfig, DbTransportConfig, } from './transports/factory.js';
|
|
34
35
|
export { EventEmitterTransport, type EventEmitterTransportOptions, } from './transports/event-emitter.js';
|
|
35
36
|
export { QueueTransport, type QueueTransportOptions } from './transports/queue.js';
|
|
36
37
|
export { DbTransport, type DbTransportOptions } from './transports/db.js';
|
|
38
|
+
export { BullMQTransport, type BullMQTransportOptions, type BullMQDeps, createBullMQDeps, } from './transports/bullmq/index.js';
|
|
37
39
|
export { TRANSPORT_TABLES, createDurableTransportTables, dropDurableTransportTables, } from './transports/db-schema.js';
|
|
38
40
|
export { stores } from './stores/factory.js';
|
|
39
41
|
export type { StoreContext, StoreFactory, LucidStoreConfig } from './stores/factory.js';
|
|
@@ -44,4 +46,14 @@ export type { ControlPlaneContext, ControlPlaneFactory, RedisControlPlaneConfig,
|
|
|
44
46
|
export { RedisControlPlane, type RedisControlPlaneOptions, type RedisPubSub, } from './control-plane-redis/redis-control-plane.js';
|
|
45
47
|
export { defineConfig } from './define_config.js';
|
|
46
48
|
export type { DurableConfig } from './define_config.js';
|
|
49
|
+
export type { StandaloneConfig, ControlPlaneConfig, TenantConfig, TenantVerifier, VerifiedTenant, } from './config_types.js';
|
|
50
|
+
export type { RunGateway, DurableTopology, StartRunOptions } from './run-gateway/interface.js';
|
|
51
|
+
export { StoreRunGateway, type RunGatewayEngine, type StoreRunGatewayOptions, } from './run-gateway/store-run-gateway.js';
|
|
52
|
+
export { ProxyRunGateway, type ProxyTransport, type ProxyRunGatewayOptions, } from './run-gateway/proxy-run-gateway.js';
|
|
53
|
+
export { RunRequestResponder, type ResponderTransport, type RunRequestResponderOptions, } from './run-gateway/run-request-responder.js';
|
|
54
|
+
export { signTenantToken, hmacTenantVerifier } from './run-gateway/tenant-auth.js';
|
|
55
|
+
export * from './handshake/descriptor.js';
|
|
56
|
+
export * from './handshake/negotiate.js';
|
|
57
|
+
export * from './handshake/routing.js';
|
|
58
|
+
export * from './dispatch-routing.js';
|
|
47
59
|
//# sourceMappingURL=index.d.ts.map
|