@adonis-agora/durable 0.9.1 → 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 +12 -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 +38 -0
- package/dist/providers/dashboard_provider.d.ts.map +1 -1
- package/dist/providers/dashboard_provider.js +103 -10
- 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 +38 -8
- package/dist/src/dashboard/handlers.d.ts.map +1 -1
- package/dist/src/dashboard/handlers.js +17 -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 +129 -3
- package/dist/src/engine.d.ts.map +1 -1
- package/dist/src/engine.js +354 -12
- 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 +184 -7
- 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 +9 -0
- package/dist/src/workflow-ctx.d.ts.map +1 -1
- package/dist/src/workflow-ctx.js +78 -27
- 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,76 @@
|
|
|
1
|
+
import type { WorkerDescriptor } from '../handshake/descriptor.js';
|
|
2
|
+
import type { WorkflowRun } from '../interfaces.js';
|
|
3
|
+
import type { RecordedBlock } from './diagnostics-recorder.js';
|
|
4
|
+
import { type ApiResponse } from './handlers.js';
|
|
5
|
+
/**
|
|
6
|
+
* The data source the {@link compat} health/compat handler reads (design §10). Deliberately a thin,
|
|
7
|
+
* framework-light port so the handler is unit-testable with fakes:
|
|
8
|
+
*
|
|
9
|
+
* - `controlPlaneDescriptor()` — the control plane's own handshake descriptor, negotiated against each
|
|
10
|
+
* worker; `undefined` when nothing has advertised yet (the handler falls back to a current-protocol CP);
|
|
11
|
+
* - `fleet()` — worker descriptors grouped by routing token: the LIVE green fleet enumerated off the
|
|
12
|
+
* transport ({@link enumerateLiveFleet}, so every advertising pod shows — compatible or not, blocked or
|
|
13
|
+
* not), merged with the descriptors {@link BlockedDiagnosticsRecorder} reconstructed from past blocks;
|
|
14
|
+
* - `blockedRuns()` — the runs parked `blocked` (their human reason on `run.error.message`);
|
|
15
|
+
* - `diagnosticsFor(runId)` — the captured loud delta for a blocked run, when one was recorded.
|
|
16
|
+
*
|
|
17
|
+
* The SAME handler serves a store-less `tenant` pod: there `fleet()` is empty and `diagnosticsFor` returns
|
|
18
|
+
* nothing, so the panel still lists blocked runs (reason-only) round-tripped over the wire.
|
|
19
|
+
*/
|
|
20
|
+
export interface CompatSource {
|
|
21
|
+
controlPlaneDescriptor(): WorkerDescriptor | undefined;
|
|
22
|
+
fleet(): Array<{
|
|
23
|
+
token: string;
|
|
24
|
+
workers: WorkerDescriptor[];
|
|
25
|
+
}>;
|
|
26
|
+
blockedRuns(): Promise<WorkflowRun[]>;
|
|
27
|
+
diagnosticsFor(runId: string): RecordedBlock | undefined;
|
|
28
|
+
}
|
|
29
|
+
/** One routing token's live workers — the unit the compat panel negotiates per group. */
|
|
30
|
+
export type FleetGroup = {
|
|
31
|
+
token: string;
|
|
32
|
+
workers: WorkerDescriptor[];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The OPTIONAL transport capability the dashboard uses to enumerate the LIVE green fleet (design §7.2/§10) —
|
|
36
|
+
* every worker currently advertising a descriptor, whether or not it has ever triggered a blocked dispatch.
|
|
37
|
+
* Both methods are optional on the full `Transport` (only broker transports — e.g. bullmq — carry them), so
|
|
38
|
+
* a transport that offers neither degrades the panel to the diagnostics-only view. Mirrors the engine's own
|
|
39
|
+
* `listWorkerGroups`/`listWorkerDescriptors` narrowing.
|
|
40
|
+
*/
|
|
41
|
+
export interface FleetTransport {
|
|
42
|
+
listWorkerGroups?(): Promise<string[]>;
|
|
43
|
+
listWorkerDescriptors?(token: string): Promise<WorkerDescriptor[]>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Enumerate the LIVE fleet across ALL routing tokens: SCAN the heartbeat keyspace for distinct tokens
|
|
47
|
+
* (`listWorkerGroups`), then read each token's advertised descriptors (`listWorkerDescriptors`). This is
|
|
48
|
+
* what surfaces an incompatible worker that has NEVER blocked a dispatch — the diagnostics recorder only
|
|
49
|
+
* knows workers a past block captured, but a green-fleet enumeration sees every live pod (compatible +
|
|
50
|
+
* incompatible), so the panel can negotiate and red-flag it proactively.
|
|
51
|
+
*
|
|
52
|
+
* Degrades to `[]` (never throws): a transport without the capability, an empty keyspace, or a scan error
|
|
53
|
+
* all read as "no live fleet", leaving the panel on its diagnostics-only view. A token whose workers are
|
|
54
|
+
* all legacy (no descriptor published) contributes nothing — legacy is assume-compatible (design §7.7).
|
|
55
|
+
*/
|
|
56
|
+
export declare function enumerateLiveFleet(transport: FleetTransport | null | undefined): Promise<FleetGroup[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Union several fleet snapshots into one, keyed `token` → `instanceId`. Later sources WIN per instance, so
|
|
59
|
+
* pass the freshest last (the provider passes the captured diagnostics fleet first, the live enumeration
|
|
60
|
+
* last — a live descriptor supersedes a stale captured one, and a live-only worker with no prior block is
|
|
61
|
+
* added). The result is what {@link CompatSource.fleet} returns for negotiation.
|
|
62
|
+
*/
|
|
63
|
+
export declare function mergeFleets(...sources: FleetGroup[][]): FleetGroup[];
|
|
64
|
+
/**
|
|
65
|
+
* `GET /compat` — the fleet health / protocol-compatibility panel (design §7.6, §10).
|
|
66
|
+
*
|
|
67
|
+
* Two sections, both LOUD and structured (never a bare boolean):
|
|
68
|
+
* 1. **Per queue/group/pod compatibility** — every live worker descriptor negotiated against the control
|
|
69
|
+
* plane: its protocol version + the negotiated level, plus a red flag + the exact reason when
|
|
70
|
+
* incompatible ("no common protocol major: local speaks [1, 1], remote speaks [2, 2]").
|
|
71
|
+
* 2. **Blocked runs** — runs parked because no worker can run them, each with its human reason and, when
|
|
72
|
+
* captured, the `capability.unavailable` / `protocol.incompatible` diagnostics delta (missing
|
|
73
|
+
* capabilities, the protocol ranges that failed to overlap).
|
|
74
|
+
*/
|
|
75
|
+
export declare function compat(src: CompatSource): Promise<ApiResponse>;
|
|
76
|
+
//# sourceMappingURL=compat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compat.d.ts","sourceRoot":"","sources":["../../../src/dashboard/compat.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,KAAK,WAAW,EAAM,MAAM,eAAe,CAAC;AAErD;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,YAAY;IAC3B,sBAAsB,IAAI,gBAAgB,GAAG,SAAS,CAAC;IACvD,KAAK,IAAI,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,gBAAgB,EAAE,CAAA;KAAE,CAAC,CAAC;IAC/D,WAAW,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IACtC,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAC;CAC1D;AAED,yFAAyF;AACzF,MAAM,MAAM,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAAC;AAExE;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,gBAAgB,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvC,qBAAqB,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;CACpE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,GAC3C,OAAO,CAAC,UAAU,EAAE,CAAC,CAmBvB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,EAAE,GAAG,UAAU,EAAE,CAapE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,MAAM,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAsEpE"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { controlPlaneDescriptor } from '../dispatch-routing.js';
|
|
2
|
+
import { negotiate } from '../handshake/negotiate.js';
|
|
3
|
+
import { ok } from './handlers.js';
|
|
4
|
+
/**
|
|
5
|
+
* Enumerate the LIVE fleet across ALL routing tokens: SCAN the heartbeat keyspace for distinct tokens
|
|
6
|
+
* (`listWorkerGroups`), then read each token's advertised descriptors (`listWorkerDescriptors`). This is
|
|
7
|
+
* what surfaces an incompatible worker that has NEVER blocked a dispatch — the diagnostics recorder only
|
|
8
|
+
* knows workers a past block captured, but a green-fleet enumeration sees every live pod (compatible +
|
|
9
|
+
* incompatible), so the panel can negotiate and red-flag it proactively.
|
|
10
|
+
*
|
|
11
|
+
* Degrades to `[]` (never throws): a transport without the capability, an empty keyspace, or a scan error
|
|
12
|
+
* all read as "no live fleet", leaving the panel on its diagnostics-only view. A token whose workers are
|
|
13
|
+
* all legacy (no descriptor published) contributes nothing — legacy is assume-compatible (design §7.7).
|
|
14
|
+
*/
|
|
15
|
+
export async function enumerateLiveFleet(transport) {
|
|
16
|
+
if (!transport ||
|
|
17
|
+
typeof transport.listWorkerGroups !== 'function' ||
|
|
18
|
+
typeof transport.listWorkerDescriptors !== 'function') {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
const groups = [];
|
|
22
|
+
try {
|
|
23
|
+
for (const token of await transport.listWorkerGroups()) {
|
|
24
|
+
const workers = await transport.listWorkerDescriptors(token);
|
|
25
|
+
if (workers.length > 0)
|
|
26
|
+
groups.push({ token, workers });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// A backend that can't SCAN/GET the descriptor keyspace degrades to whatever was read so far — the
|
|
31
|
+
// panel still renders (diagnostics + any partial live view) rather than 500-ing.
|
|
32
|
+
}
|
|
33
|
+
return groups;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Union several fleet snapshots into one, keyed `token` → `instanceId`. Later sources WIN per instance, so
|
|
37
|
+
* pass the freshest last (the provider passes the captured diagnostics fleet first, the live enumeration
|
|
38
|
+
* last — a live descriptor supersedes a stale captured one, and a live-only worker with no prior block is
|
|
39
|
+
* added). The result is what {@link CompatSource.fleet} returns for negotiation.
|
|
40
|
+
*/
|
|
41
|
+
export function mergeFleets(...sources) {
|
|
42
|
+
const byToken = new Map();
|
|
43
|
+
for (const source of sources) {
|
|
44
|
+
for (const { token, workers } of source) {
|
|
45
|
+
const perInstance = byToken.get(token) ?? new Map();
|
|
46
|
+
for (const worker of workers)
|
|
47
|
+
perInstance.set(worker.instanceId, worker);
|
|
48
|
+
byToken.set(token, perInstance);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return [...byToken].map(([token, perInstance]) => ({
|
|
52
|
+
token,
|
|
53
|
+
workers: [...perInstance.values()],
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* `GET /compat` — the fleet health / protocol-compatibility panel (design §7.6, §10).
|
|
58
|
+
*
|
|
59
|
+
* Two sections, both LOUD and structured (never a bare boolean):
|
|
60
|
+
* 1. **Per queue/group/pod compatibility** — every live worker descriptor negotiated against the control
|
|
61
|
+
* plane: its protocol version + the negotiated level, plus a red flag + the exact reason when
|
|
62
|
+
* incompatible ("no common protocol major: local speaks [1, 1], remote speaks [2, 2]").
|
|
63
|
+
* 2. **Blocked runs** — runs parked because no worker can run them, each with its human reason and, when
|
|
64
|
+
* captured, the `capability.unavailable` / `protocol.incompatible` diagnostics delta (missing
|
|
65
|
+
* capabilities, the protocol ranges that failed to overlap).
|
|
66
|
+
*/
|
|
67
|
+
export async function compat(src) {
|
|
68
|
+
// Fall back to a current-protocol CP descriptor so pods can still be negotiated before the CP has
|
|
69
|
+
// advertised (or on a topology that never publishes one).
|
|
70
|
+
const cp = src.controlPlaneDescriptor() ?? controlPlaneDescriptor({ instanceId: 'control-plane' });
|
|
71
|
+
const groups = src.fleet().map(({ token, workers }) => {
|
|
72
|
+
const pods = workers.map((worker) => {
|
|
73
|
+
const result = negotiate(cp, worker);
|
|
74
|
+
return {
|
|
75
|
+
instanceId: worker.instanceId,
|
|
76
|
+
runtime: worker.runtime,
|
|
77
|
+
sdk: worker.sdk,
|
|
78
|
+
protocol: worker.protocol.version,
|
|
79
|
+
protocolRange: worker.protocol.range,
|
|
80
|
+
capabilities: worker.capabilities,
|
|
81
|
+
outcome: result.outcome,
|
|
82
|
+
negotiatedProtocol: result.negotiatedProtocol,
|
|
83
|
+
incompatible: result.outcome === 'incompatible',
|
|
84
|
+
// The red-flag reason: precise, structured copy for the panel (design §7.6). Absent when compatible.
|
|
85
|
+
reason: result.reason?.message,
|
|
86
|
+
missingOnRemote: result.capabilities.missingOnRemote,
|
|
87
|
+
missingOnLocal: result.capabilities.missingOnLocal,
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
token,
|
|
92
|
+
pods,
|
|
93
|
+
incompatible: pods.some((pod) => pod.incompatible),
|
|
94
|
+
degraded: pods.some((pod) => pod.outcome === 'degraded'),
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
const blocked = (await src.blockedRuns()).map((run) => {
|
|
98
|
+
const recorded = src.diagnosticsFor(run.id);
|
|
99
|
+
return {
|
|
100
|
+
id: run.id,
|
|
101
|
+
workflow: run.workflow,
|
|
102
|
+
namespace: run.namespace,
|
|
103
|
+
status: run.status,
|
|
104
|
+
// Human reason: the persisted run error (always set when parked), or the captured delta's copy.
|
|
105
|
+
reason: run.error?.message ?? recorded?.reason ?? 'blocked',
|
|
106
|
+
code: recorded?.code,
|
|
107
|
+
requires: recorded?.diagnostics.requires ?? [],
|
|
108
|
+
// The structured delta (design §7.6), when a diagnostics event was captured for this run.
|
|
109
|
+
token: recorded?.diagnostics.token,
|
|
110
|
+
missingCapabilities: recorded?.diagnostics.missingCapabilities,
|
|
111
|
+
controlPlaneRange: recorded?.diagnostics.controlPlaneRange,
|
|
112
|
+
workerRanges: recorded?.diagnostics.workerRanges,
|
|
113
|
+
updatedAt: run.updatedAt.toISOString(),
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
const incompatibleCount = groups.reduce((n, group) => n + group.pods.filter((pod) => pod.incompatible).length, 0);
|
|
117
|
+
return ok({
|
|
118
|
+
controlPlane: {
|
|
119
|
+
instanceId: cp.instanceId,
|
|
120
|
+
protocol: cp.protocol.version,
|
|
121
|
+
protocolRange: cp.protocol.range,
|
|
122
|
+
capabilities: cp.capabilities,
|
|
123
|
+
},
|
|
124
|
+
groups,
|
|
125
|
+
blocked,
|
|
126
|
+
incompatibleCount,
|
|
127
|
+
blockedCount: blocked.length,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=compat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compat.js","sourceRoot":"","sources":["../../../src/dashboard/compat.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { DispatchDiagnostics } from '../dispatch-routing.js';
|
|
2
|
+
import type { WorkerDescriptor } from '../handshake/descriptor.js';
|
|
3
|
+
import type { EngineEvent } from '../interfaces.js';
|
|
4
|
+
/** The minimal engine surface the recorder subscribes to — a broad lifecycle-event stream. Both the
|
|
5
|
+
* concrete `WorkflowEngine` and any `DurableEventSource` satisfy it. */
|
|
6
|
+
export interface EngineEventSource {
|
|
7
|
+
subscribe(listener: (event: EngineEvent) => void): () => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A single captured LOUD diagnostics event (design §7.6) — the human reason plus the full structured
|
|
11
|
+
* delta + descriptors the engine attached when it parked a run `blocked`. Enough for the dashboard
|
|
12
|
+
* health panel to render exactly WHY nobody could run the work, never a bare boolean.
|
|
13
|
+
*/
|
|
14
|
+
export interface RecordedBlock {
|
|
15
|
+
/** Which failure fired — a missing capability vs. an unbridgeable protocol gap. */
|
|
16
|
+
code: 'capability.unavailable' | 'protocol.incompatible';
|
|
17
|
+
/** Human one-liner (the run's `error.message`, e.g. `blocked: no compatible worker (requires saga)`). */
|
|
18
|
+
reason: string;
|
|
19
|
+
/** The precise routing delta + both descriptors (`DispatchDiagnostics`). */
|
|
20
|
+
diagnostics: DispatchDiagnostics;
|
|
21
|
+
/** When the block fired (ISO). */
|
|
22
|
+
at: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Captures the engine's `capability.unavailable` / `protocol.incompatible` diagnostics events (design
|
|
26
|
+
* §7.6) into a bounded in-memory index the dashboard health panel reads (design §10, "fed by
|
|
27
|
+
* descriptors + diagnostics events"). Since the loud events carry BOTH descriptors + the precise delta,
|
|
28
|
+
* one subscription feeds the whole panel:
|
|
29
|
+
*
|
|
30
|
+
* - `diagnosticsFor(runId)` joins a `blocked` run to its captured delta (code, missing caps, ranges);
|
|
31
|
+
* - `fleet()` reconstructs the live-fleet compat view — the worker descriptors last seen per routing
|
|
32
|
+
* token — so each pod can be re-negotiated against the control plane;
|
|
33
|
+
* - `controlPlaneDescriptor()` is the CP side the events were negotiated against.
|
|
34
|
+
*
|
|
35
|
+
* Bounded by an LRU-ish cap on the per-run map (oldest evicted) so a long-lived control plane never
|
|
36
|
+
* grows this without bound. Store-role only: a `tenant` pod owns no engine to subscribe to, so its
|
|
37
|
+
* panel shows `blocked` runs (reason from `run.error.message`) without the enriched delta.
|
|
38
|
+
*/
|
|
39
|
+
export declare class BlockedDiagnosticsRecorder {
|
|
40
|
+
#private;
|
|
41
|
+
constructor(opts?: {
|
|
42
|
+
max?: number;
|
|
43
|
+
});
|
|
44
|
+
/** Record one engine event; a no-op for anything but the two diagnostics types (or one missing its
|
|
45
|
+
* `diagnostics` payload). Idempotent per `runId` — a re-block overwrites with the freshest delta. */
|
|
46
|
+
record(event: EngineEvent): void;
|
|
47
|
+
/** Subscribe to `source`'s lifecycle events; returns the unsubscribe fn (call it on shutdown). */
|
|
48
|
+
attach(source: EngineEventSource): () => void;
|
|
49
|
+
/** The captured delta for a `blocked` run, or `undefined` when none was recorded. */
|
|
50
|
+
diagnosticsFor(runId: string): RecordedBlock | undefined;
|
|
51
|
+
/** The control-plane descriptor the recorded events negotiated against, if any has been seen. */
|
|
52
|
+
controlPlaneDescriptor(): WorkerDescriptor | undefined;
|
|
53
|
+
/** The live-fleet compat view: worker descriptors last seen per routing token. */
|
|
54
|
+
fleet(): Array<{
|
|
55
|
+
token: string;
|
|
56
|
+
workers: WorkerDescriptor[];
|
|
57
|
+
}>;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=diagnostics-recorder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics-recorder.d.ts","sourceRoot":"","sources":["../../../src/dashboard/diagnostics-recorder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;yEACyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAC/D;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,mFAAmF;IACnF,IAAI,EAAE,wBAAwB,GAAG,uBAAuB,CAAC;IACzD,yGAAyG;IACzG,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,WAAW,EAAE,mBAAmB,CAAC;IACjC,kCAAkC;IAClC,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,0BAA0B;;gBAOzB,IAAI,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAO;IAIvC;0GACsG;IACtG,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IA4BhC,kGAAkG;IAClG,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,IAAI;IAI7C,qFAAqF;IACrF,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIxD,iGAAiG;IACjG,sBAAsB,IAAI,gBAAgB,GAAG,SAAS;IAItD,kFAAkF;IAClF,KAAK,IAAI,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,gBAAgB,EAAE,CAAA;KAAE,CAAC;CAM/D"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Captures the engine's `capability.unavailable` / `protocol.incompatible` diagnostics events (design
|
|
3
|
+
* §7.6) into a bounded in-memory index the dashboard health panel reads (design §10, "fed by
|
|
4
|
+
* descriptors + diagnostics events"). Since the loud events carry BOTH descriptors + the precise delta,
|
|
5
|
+
* one subscription feeds the whole panel:
|
|
6
|
+
*
|
|
7
|
+
* - `diagnosticsFor(runId)` joins a `blocked` run to its captured delta (code, missing caps, ranges);
|
|
8
|
+
* - `fleet()` reconstructs the live-fleet compat view — the worker descriptors last seen per routing
|
|
9
|
+
* token — so each pod can be re-negotiated against the control plane;
|
|
10
|
+
* - `controlPlaneDescriptor()` is the CP side the events were negotiated against.
|
|
11
|
+
*
|
|
12
|
+
* Bounded by an LRU-ish cap on the per-run map (oldest evicted) so a long-lived control plane never
|
|
13
|
+
* grows this without bound. Store-role only: a `tenant` pod owns no engine to subscribe to, so its
|
|
14
|
+
* panel shows `blocked` runs (reason from `run.error.message`) without the enriched delta.
|
|
15
|
+
*/
|
|
16
|
+
export class BlockedDiagnosticsRecorder {
|
|
17
|
+
#byRun = new Map();
|
|
18
|
+
/** token → (instanceId → latest descriptor). Latest-seen wins per instance. */
|
|
19
|
+
#fleet = new Map();
|
|
20
|
+
#controlPlane;
|
|
21
|
+
#max;
|
|
22
|
+
constructor(opts = {}) {
|
|
23
|
+
this.#max = opts.max ?? 500;
|
|
24
|
+
}
|
|
25
|
+
/** Record one engine event; a no-op for anything but the two diagnostics types (or one missing its
|
|
26
|
+
* `diagnostics` payload). Idempotent per `runId` — a re-block overwrites with the freshest delta. */
|
|
27
|
+
record(event) {
|
|
28
|
+
if (event.type !== 'capability.unavailable' && event.type !== 'protocol.incompatible')
|
|
29
|
+
return;
|
|
30
|
+
const diagnostics = event.diagnostics;
|
|
31
|
+
if (!diagnostics)
|
|
32
|
+
return;
|
|
33
|
+
// Bound the per-run index: evict the oldest insertion when a NEW run would exceed the cap.
|
|
34
|
+
if (!this.#byRun.has(event.runId) && this.#byRun.size >= this.#max) {
|
|
35
|
+
const oldest = this.#byRun.keys().next().value;
|
|
36
|
+
if (oldest !== undefined)
|
|
37
|
+
this.#byRun.delete(oldest);
|
|
38
|
+
}
|
|
39
|
+
// Re-insert at the end (freshest) so eviction stays roughly insertion-ordered.
|
|
40
|
+
this.#byRun.delete(event.runId);
|
|
41
|
+
this.#byRun.set(event.runId, {
|
|
42
|
+
code: event.type,
|
|
43
|
+
// The engine stamps the human reason on `error.message` when it parks the run (design §7.6).
|
|
44
|
+
reason: event.error?.message ?? event.type,
|
|
45
|
+
diagnostics,
|
|
46
|
+
at: (event.at instanceof Date ? event.at : new Date(event.at)).toISOString(),
|
|
47
|
+
});
|
|
48
|
+
// Refresh the live-fleet snapshot for this token from the descriptors the event carried.
|
|
49
|
+
const perToken = this.#fleet.get(diagnostics.token) ?? new Map();
|
|
50
|
+
for (const worker of diagnostics.workers)
|
|
51
|
+
perToken.set(worker.instanceId, worker);
|
|
52
|
+
this.#fleet.set(diagnostics.token, perToken);
|
|
53
|
+
this.#controlPlane = diagnostics.controlPlane;
|
|
54
|
+
}
|
|
55
|
+
/** Subscribe to `source`'s lifecycle events; returns the unsubscribe fn (call it on shutdown). */
|
|
56
|
+
attach(source) {
|
|
57
|
+
return source.subscribe((event) => this.record(event));
|
|
58
|
+
}
|
|
59
|
+
/** The captured delta for a `blocked` run, or `undefined` when none was recorded. */
|
|
60
|
+
diagnosticsFor(runId) {
|
|
61
|
+
return this.#byRun.get(runId);
|
|
62
|
+
}
|
|
63
|
+
/** The control-plane descriptor the recorded events negotiated against, if any has been seen. */
|
|
64
|
+
controlPlaneDescriptor() {
|
|
65
|
+
return this.#controlPlane;
|
|
66
|
+
}
|
|
67
|
+
/** The live-fleet compat view: worker descriptors last seen per routing token. */
|
|
68
|
+
fleet() {
|
|
69
|
+
return [...this.#fleet].map(([token, byInstance]) => ({
|
|
70
|
+
token,
|
|
71
|
+
workers: [...byInstance.values()],
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=diagnostics-recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics-recorder.js","sourceRoot":"","sources":["../../../src/dashboard/diagnostics-recorder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { RunGateway } from '../run-gateway/interface.js';
|
|
2
|
+
import type { DashboardEngine } from './handlers.js';
|
|
3
|
+
/**
|
|
4
|
+
* Adapt a {@link RunGateway} to the {@link DashboardEngine} port the JSON handlers drive, so a
|
|
5
|
+
* **store-less `tenant` pod** serves the SAME dashboard over the wire (design §8) — every read/control
|
|
6
|
+
* verb round-trips through the pod's `ProxyRunGateway` to the control plane, with NO local store or
|
|
7
|
+
* engine. Store presence stays invisible above the handler line.
|
|
8
|
+
*
|
|
9
|
+
* The gateway is a near-superset of the port; two verbs need bridging:
|
|
10
|
+
* - `listCheckpoints` → the gateway's `getCheckpoints` (same shape, §8 naming);
|
|
11
|
+
* - `requeue` → the gateway's `redispatchPending` (the operator recovery verb; the extra
|
|
12
|
+
* `redispatched` count on the result is a harmless superset of `RunResult`).
|
|
13
|
+
*
|
|
14
|
+
* `getRunChildren` (parent→child fan-out) now rides the P4 read surface too (a `getRunChildren`
|
|
15
|
+
* `RunRequestKind` verb), so a store-less tenant pod's run detail view lists children round-tripped over
|
|
16
|
+
* the wire — the responder enforces the tenant-ownership check before answering (anti-IDOR).
|
|
17
|
+
*/
|
|
18
|
+
export declare function gatewayDashboardEngine(gateway: RunGateway): DashboardEngine;
|
|
19
|
+
/** The minimal container surface {@link dashboardEngineForRole} resolves bindings through. */
|
|
20
|
+
export interface DashboardContainer {
|
|
21
|
+
make(key: unknown): Promise<unknown>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the dashboard's read/control port for the active durable `role` (design §5) — the single seam
|
|
25
|
+
* that makes the dashboard store-agnostic:
|
|
26
|
+
*
|
|
27
|
+
* - **`tenant`** (store-less): resolve the {@link DURABLE_RUN_GATEWAY} token (a `ProxyRunGateway`) and
|
|
28
|
+
* adapt it — the engine binding is deliberately ABSENT on a tenant pod (structural isolation), so we
|
|
29
|
+
* MUST NOT reach for `WorkflowEngine` here;
|
|
30
|
+
* - **store roles** (`standalone`/`control-plane`): resolve the concrete `WorkflowEngine` (structurally a
|
|
31
|
+
* {@link DashboardEngine}) exactly as before — behavior byte-for-byte unchanged.
|
|
32
|
+
*
|
|
33
|
+
* `engineToken` is injected (rather than imported) so the module doesn't hard-depend on the engine class
|
|
34
|
+
* for the tenant path — the tenant branch never touches it.
|
|
35
|
+
*/
|
|
36
|
+
export declare function dashboardEngineForRole(role: 'standalone' | 'control-plane' | 'tenant', container: DashboardContainer, engineToken: unknown): Promise<DashboardEngine>;
|
|
37
|
+
//# sourceMappingURL=gateway-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway-adapter.d.ts","sourceRoot":"","sources":["../../../src/dashboard/gateway-adapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,UAAU,GAAG,eAAe,CAW3E;AAED,8FAA8F;AAC9F,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,YAAY,GAAG,eAAe,GAAG,QAAQ,EAC/C,SAAS,EAAE,kBAAkB,EAC7B,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,eAAe,CAAC,CAM1B"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { DURABLE_RUN_GATEWAY } from '../role_bindings.js';
|
|
2
|
+
/**
|
|
3
|
+
* Adapt a {@link RunGateway} to the {@link DashboardEngine} port the JSON handlers drive, so a
|
|
4
|
+
* **store-less `tenant` pod** serves the SAME dashboard over the wire (design §8) — every read/control
|
|
5
|
+
* verb round-trips through the pod's `ProxyRunGateway` to the control plane, with NO local store or
|
|
6
|
+
* engine. Store presence stays invisible above the handler line.
|
|
7
|
+
*
|
|
8
|
+
* The gateway is a near-superset of the port; two verbs need bridging:
|
|
9
|
+
* - `listCheckpoints` → the gateway's `getCheckpoints` (same shape, §8 naming);
|
|
10
|
+
* - `requeue` → the gateway's `redispatchPending` (the operator recovery verb; the extra
|
|
11
|
+
* `redispatched` count on the result is a harmless superset of `RunResult`).
|
|
12
|
+
*
|
|
13
|
+
* `getRunChildren` (parent→child fan-out) now rides the P4 read surface too (a `getRunChildren`
|
|
14
|
+
* `RunRequestKind` verb), so a store-less tenant pod's run detail view lists children round-tripped over
|
|
15
|
+
* the wire — the responder enforces the tenant-ownership check before answering (anti-IDOR).
|
|
16
|
+
*/
|
|
17
|
+
export function gatewayDashboardEngine(gateway) {
|
|
18
|
+
return {
|
|
19
|
+
getRun: (runId) => gateway.getRun(runId),
|
|
20
|
+
listRuns: (query) => gateway.listRuns(query),
|
|
21
|
+
listCheckpoints: (runId) => gateway.getCheckpoints(runId),
|
|
22
|
+
getRunChildren: (runId) => gateway.getRunChildren(runId),
|
|
23
|
+
requeue: (runId) => gateway.redispatchPending(runId),
|
|
24
|
+
redispatchPending: (runId) => gateway.redispatchPending(runId),
|
|
25
|
+
cancel: (runId, opts) => gateway.cancel(runId, opts),
|
|
26
|
+
workerHealth: () => gateway.workerHealth(),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the dashboard's read/control port for the active durable `role` (design §5) — the single seam
|
|
31
|
+
* that makes the dashboard store-agnostic:
|
|
32
|
+
*
|
|
33
|
+
* - **`tenant`** (store-less): resolve the {@link DURABLE_RUN_GATEWAY} token (a `ProxyRunGateway`) and
|
|
34
|
+
* adapt it — the engine binding is deliberately ABSENT on a tenant pod (structural isolation), so we
|
|
35
|
+
* MUST NOT reach for `WorkflowEngine` here;
|
|
36
|
+
* - **store roles** (`standalone`/`control-plane`): resolve the concrete `WorkflowEngine` (structurally a
|
|
37
|
+
* {@link DashboardEngine}) exactly as before — behavior byte-for-byte unchanged.
|
|
38
|
+
*
|
|
39
|
+
* `engineToken` is injected (rather than imported) so the module doesn't hard-depend on the engine class
|
|
40
|
+
* for the tenant path — the tenant branch never touches it.
|
|
41
|
+
*/
|
|
42
|
+
export async function dashboardEngineForRole(role, container, engineToken) {
|
|
43
|
+
if (role === 'tenant') {
|
|
44
|
+
const gateway = (await container.make(DURABLE_RUN_GATEWAY));
|
|
45
|
+
return gatewayDashboardEngine(gateway);
|
|
46
|
+
}
|
|
47
|
+
return (await container.make(engineToken));
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=gateway-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway-adapter.js","sourceRoot":"","sources":["../../../src/dashboard/gateway-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAI1D;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAmB;IACxD,OAAO;QACL,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;QACxC,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC5C,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC;QACzD,cAAc,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC;QACxD,OAAO,EAAE,CAAC,KAAK,EAA6B,EAAE,CAAC,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC;QAC/E,iBAAiB,EAAE,CAAC,KAAK,EAA6B,EAAE,CAAC,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC;QACzF,MAAM,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC;QACpD,YAAY,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE;KAC3C,CAAC;AACJ,CAAC;AAOD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,IAA+C,EAC/C,SAA6B,EAC7B,WAAoB;IAEpB,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,MAAM,OAAO,GAAG,CAAC,MAAM,SAAS,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAe,CAAC;QAC1E,OAAO,sBAAsB,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,CAAC,MAAM,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,CAAoB,CAAC;AAChE,CAAC"}
|
|
@@ -1,20 +1,41 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GroupHealth, RunQuery, RunResult, StepCheckpoint, WorkflowRun } from '../index.js';
|
|
2
2
|
/**
|
|
3
|
-
* Framework-light JSON handlers over a {@link
|
|
3
|
+
* Framework-light JSON handlers over a {@link DashboardEngine}.
|
|
4
4
|
*
|
|
5
|
-
* Each handler takes a {@link Deps} bundle (just the
|
|
6
|
-
* checkpoints are read through
|
|
7
|
-
*
|
|
8
|
-
* dashboard never reaches for
|
|
5
|
+
* Each handler takes a {@link Deps} bundle (just the read/control port — runs
|
|
6
|
+
* and checkpoints are read through its own read API, {@link
|
|
7
|
+
* DashboardEngine.listRuns} / {@link DashboardEngine.listCheckpoints}, so the
|
|
8
|
+
* dashboard never reaches for a private store) plus a plain {@link
|
|
9
9
|
* ApiRequest} (a thin view of the parts of an HTTP request it needs), and
|
|
10
10
|
* returns a plain {@link ApiResponse} (status + JSON body). No AdonisJS types
|
|
11
11
|
* leak in, so the handlers are unit-testable against a real in-memory engine
|
|
12
12
|
* with no HTTP server. The provider adapts an AdonisJS `HttpContext` to these
|
|
13
13
|
* shapes.
|
|
14
14
|
*/
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* The bounded read/control surface the JSON handlers drive — declared STRUCTURALLY (a port), not by
|
|
17
|
+
* importing the concrete `WorkflowEngine` class, so the same handlers serve BOTH durable topologies
|
|
18
|
+
* (design §8): a store role passes the real {@link import('../engine.js').WorkflowEngine} (structurally
|
|
19
|
+
* assignable — it has every method here); a store-less `tenant` pod passes an adapter over its
|
|
20
|
+
* {@link import('../run-gateway/interface.js').RunGateway} (see `gatewayDashboardEngine`). Store presence
|
|
21
|
+
* is therefore invisible to the handlers. Mirrors the `RunGatewayEngine` port pattern already used by
|
|
22
|
+
* `StoreRunGateway`.
|
|
23
|
+
*/
|
|
24
|
+
export interface DashboardEngine {
|
|
25
|
+
getRun(runId: string): Promise<WorkflowRun | null>;
|
|
26
|
+
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
27
|
+
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
28
|
+
getRunChildren(runId: string): Promise<string[]>;
|
|
29
|
+
requeue(runId: string): Promise<RunResult | null>;
|
|
30
|
+
redispatchPending(runId: string): Promise<RunResult | null>;
|
|
31
|
+
cancel(runId: string, opts?: {
|
|
32
|
+
compensate?: boolean;
|
|
33
|
+
}): Promise<RunResult | null>;
|
|
34
|
+
workerHealth(extra?: string[]): Promise<GroupHealth[]>;
|
|
35
|
+
}
|
|
36
|
+
/** The read/control port the handlers operate over (a store engine or a tenant gateway adapter). */
|
|
16
37
|
export interface Deps {
|
|
17
|
-
engine:
|
|
38
|
+
engine: DashboardEngine;
|
|
18
39
|
}
|
|
19
40
|
/** The subset of an HTTP request the handlers read. */
|
|
20
41
|
export interface ApiRequest {
|
|
@@ -30,6 +51,8 @@ export interface ApiResponse {
|
|
|
30
51
|
status: number;
|
|
31
52
|
body: unknown;
|
|
32
53
|
}
|
|
54
|
+
/** A `200 OK` JSON response. Exported so sibling handlers (e.g. `compat`) share one convention. */
|
|
55
|
+
export declare const ok: (body: unknown) => ApiResponse;
|
|
33
56
|
/** `GET /runs` — list runs filtered by status/workflow/tag, paginated. */
|
|
34
57
|
export declare function listRuns(deps: Deps, req: ApiRequest): Promise<ApiResponse>;
|
|
35
58
|
/** `GET /runs/:id` — a run's detail: the run, its step timeline, and child run ids. */
|
|
@@ -40,6 +63,13 @@ export declare function getRun(deps: Deps, req: ApiRequest): Promise<ApiResponse
|
|
|
40
63
|
* state immediately; never blocks on execution.
|
|
41
64
|
*/
|
|
42
65
|
export declare function retryRun(deps: Deps, req: ApiRequest): Promise<ApiResponse>;
|
|
66
|
+
/**
|
|
67
|
+
* `POST /runs/:id/redispatch` — re-enqueue every remote step of a run stuck `pending`, for a run
|
|
68
|
+
* whose dispatched step job was LOST (worker crashed with no result, or the transport dropped the
|
|
69
|
+
* job). The idempotent step re-runs and its result resumes the run. Returns the run's current status
|
|
70
|
+
* and the count re-dispatched; never blocks on execution.
|
|
71
|
+
*/
|
|
72
|
+
export declare function redispatchPendingRun(deps: Deps, req: ApiRequest): Promise<ApiResponse>;
|
|
43
73
|
/** `POST /runs/:id/cancel` — cancel a run. Pass `{ compensate: true }` to undo the saga first. */
|
|
44
74
|
export declare function cancelRun(deps: Deps, req: ApiRequest): Promise<ApiResponse>;
|
|
45
75
|
/** `GET /health` — per-group worker health (queue backlog + live worker heartbeats). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../../src/dashboard/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../../src/dashboard/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EACR,SAAS,EAET,cAAc,EACd,WAAW,EACZ,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;;GAYG;AAEH;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAClD,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAC1D,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAClD,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAC5D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAClF,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;CACxD;AAED,oGAAoG;AACpG,MAAM,WAAW,IAAI;IACnB,MAAM,EAAE,eAAe,CAAC;CACzB;AAED,uDAAuD;AACvD,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC3C,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACrD,2CAA2C;IAC3C,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf;AAED,mGAAmG;AACnG,eAAO,MAAM,EAAE,GAAI,MAAM,OAAO,KAAG,WAAsC,CAAC;AAqC1E,0EAA0E;AAC1E,wBAAsB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAqBhF;AAED,uFAAuF;AACvF,wBAAsB,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAe9E;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAMhF;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAM5F;AAED,kGAAkG;AAClG,wBAAsB,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAUjF;AAED,wFAAwF;AACxF,wBAAsB,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAW7D"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/** A `200 OK` JSON response. Exported so sibling handlers (e.g. `compat`) share one convention. */
|
|
2
|
+
export const ok = (body) => ({ status: 200, body });
|
|
2
3
|
const notFound = (message) => ({
|
|
3
4
|
status: 404,
|
|
4
5
|
body: { error: message },
|
|
@@ -89,6 +90,21 @@ export async function retryRun(deps, req) {
|
|
|
89
90
|
return notFound(`run ${id} not found`);
|
|
90
91
|
return ok({ result });
|
|
91
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* `POST /runs/:id/redispatch` — re-enqueue every remote step of a run stuck `pending`, for a run
|
|
95
|
+
* whose dispatched step job was LOST (worker crashed with no result, or the transport dropped the
|
|
96
|
+
* job). The idempotent step re-runs and its result resumes the run. Returns the run's current status
|
|
97
|
+
* and the count re-dispatched; never blocks on execution.
|
|
98
|
+
*/
|
|
99
|
+
export async function redispatchPendingRun(deps, req) {
|
|
100
|
+
const id = req.params.id;
|
|
101
|
+
if (!id)
|
|
102
|
+
return notFound('run id is required');
|
|
103
|
+
const result = await deps.engine.redispatchPending(id);
|
|
104
|
+
if (!result)
|
|
105
|
+
return notFound(`run ${id} not found`);
|
|
106
|
+
return ok({ result });
|
|
107
|
+
}
|
|
92
108
|
/** `POST /runs/:id/cancel` — cancel a run. Pass `{ compensate: true }` to undo the saga first. */
|
|
93
109
|
export async function cancelRun(deps, req) {
|
|
94
110
|
const id = req.params.id;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../../src/dashboard/handlers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../../src/dashboard/handlers.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,17 @@
|
|
|
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 { defineConfig, defaultAuthorize, resolveConfig } from './define_config.js';
|
|
4
4
|
export type { AuthorizeHook, DurableDashboardConfig, ResolvedDurableDashboardConfig, } from './define_config.js';
|
|
5
|
-
export { listRuns, getRun, retryRun, cancelRun, health, } from './handlers.js';
|
|
6
|
-
export type { ApiRequest, ApiResponse, Deps } from './handlers.js';
|
|
5
|
+
export { listRuns, getRun, retryRun, redispatchPendingRun, cancelRun, health, ok, } from './handlers.js';
|
|
6
|
+
export type { ApiRequest, ApiResponse, Deps, DashboardEngine } from './handlers.js';
|
|
7
7
|
export { renderDashboard } from './html.js';
|
|
8
|
+
export { compat, enumerateLiveFleet, mergeFleets } from './compat.js';
|
|
9
|
+
export type { CompatSource, FleetGroup, FleetTransport } from './compat.js';
|
|
10
|
+
export { BlockedDiagnosticsRecorder } from './diagnostics-recorder.js';
|
|
11
|
+
export type { RecordedBlock, EngineEventSource } from './diagnostics-recorder.js';
|
|
12
|
+
export { outcomeClass, outcomeLabel, formatProtocolRange } from './compat-view.js';
|
|
13
|
+
export { gatewayDashboardEngine, dashboardEngineForRole } from './gateway-adapter.js';
|
|
14
|
+
export type { DashboardContainer } from './gateway-adapter.js';
|
|
8
15
|
export { resolveDashboardAuth, performLogin, readSession, sanitizeReturnTo, SESSION_COOKIE_NAME, } from './auth.js';
|
|
9
16
|
export type { DashboardAuthOptions, ResolvedDashboardAuth, LoginHook, LoginOutcome, } from './auth.js';
|
|
10
17
|
export { signSessionCookie, verifySessionCookie, } from './session_cookie.js';
|