@griffin-app/griffin-plan-executor 0.1.13 → 0.1.14
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/README.md +14 -14
- package/dist/events/adapters/in-memory.test.js +22 -22
- package/dist/events/adapters/in-memory.test.js.map +1 -1
- package/dist/events/adapters/kinesis.test.js +13 -13
- package/dist/events/adapters/kinesis.test.js.map +1 -1
- package/dist/events/emitter.test.js +14 -14
- package/dist/events/emitter.test.js.map +1 -1
- package/dist/events/types.d.ts +9 -9
- package/dist/events/types.d.ts.map +1 -1
- package/dist/events/types.js +1 -1
- package/dist/executor.d.ts +2 -2
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +40 -40
- package/dist/executor.js.map +1 -1
- package/dist/executor.test.js +99 -99
- package/dist/executor.test.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/secrets/index.d.ts +3 -3
- package/dist/secrets/index.d.ts.map +1 -1
- package/dist/secrets/index.js +3 -3
- package/dist/secrets/index.js.map +1 -1
- package/dist/secrets/resolver.d.ts +12 -12
- package/dist/secrets/resolver.d.ts.map +1 -1
- package/dist/secrets/resolver.js +19 -19
- package/dist/secrets/resolver.js.map +1 -1
- package/dist/secrets/secrets.test.js +54 -54
- package/dist/secrets/secrets.test.js.map +1 -1
- package/dist/secrets/types.d.ts +2 -2
- package/dist/types.d.ts +2 -2
- package/package.json +4 -4
- package/src/events/adapters/README.md +7 -7
- package/src/events/adapters/in-memory.test.ts +22 -22
- package/src/events/adapters/kinesis.test.ts +13 -13
- package/src/events/emitter.test.ts +14 -14
- package/src/events/types.ts +10 -10
- package/src/executor.test.ts +100 -100
- package/src/executor.ts +41 -41
- package/src/index.ts +7 -7
- package/src/secrets/index.ts +4 -4
- package/src/secrets/resolver.ts +24 -24
- package/src/secrets/secrets.test.ts +57 -57
- package/src/secrets/types.ts +2 -2
- package/src/{test-plan-types.ts → test-monitor-types.ts} +1 -1
- package/src/types.ts +2 -2
package/src/executor.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
UnaryPredicate,
|
|
5
5
|
Assertions,
|
|
6
6
|
Node,
|
|
7
|
-
|
|
7
|
+
MonitorV1,
|
|
8
8
|
Wait,
|
|
9
9
|
HttpRequest,
|
|
10
10
|
} from "@griffin-app/griffin-hub-sdk";
|
|
@@ -23,14 +23,14 @@ import { createStateGraph, graphStore, StateGraphRegistry } from "ts-edge";
|
|
|
23
23
|
import type { ExecutionEvent, BaseEvent } from "./events/index.js";
|
|
24
24
|
import { randomUUID } from "crypto";
|
|
25
25
|
import {
|
|
26
|
-
|
|
26
|
+
resolveSecretsInMonitor,
|
|
27
27
|
planHasSecrets,
|
|
28
28
|
SecretResolutionError,
|
|
29
29
|
} from "./secrets/index.js";
|
|
30
30
|
import { utcNow } from "./utils/dates.js";
|
|
31
31
|
import {
|
|
32
32
|
migrateToLatest,
|
|
33
|
-
|
|
33
|
+
CURRENT_MONITOR_VERSION,
|
|
34
34
|
isSupportedVersion,
|
|
35
35
|
} from "@griffin-app/griffin-ts";
|
|
36
36
|
|
|
@@ -41,7 +41,7 @@ interface NodeExecuteContext {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Execution context that tracks event emission state throughout a
|
|
44
|
+
* Execution context that tracks event emission state throughout a monitor execution.
|
|
45
45
|
* Maintains sequence counter and provides event creation helpers.
|
|
46
46
|
*/
|
|
47
47
|
class ExecutionContext {
|
|
@@ -49,7 +49,7 @@ class ExecutionContext {
|
|
|
49
49
|
|
|
50
50
|
constructor(
|
|
51
51
|
public readonly executionId: string,
|
|
52
|
-
public readonly
|
|
52
|
+
public readonly monitor: MonitorV1,
|
|
53
53
|
public readonly organizationId: string,
|
|
54
54
|
private readonly emitter?: ExecutionOptions["eventEmitter"],
|
|
55
55
|
) {}
|
|
@@ -62,7 +62,7 @@ class ExecutionContext {
|
|
|
62
62
|
eventId: randomUUID(),
|
|
63
63
|
seq: this.seq++,
|
|
64
64
|
timestamp: Date.now(),
|
|
65
|
-
planId: this.
|
|
65
|
+
planId: this.monitor.id,
|
|
66
66
|
executionId: this.executionId,
|
|
67
67
|
organizationId: this.organizationId,
|
|
68
68
|
};
|
|
@@ -121,7 +121,7 @@ interface ExecutionState {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
function buildNode(
|
|
124
|
-
|
|
124
|
+
monitor: MonitorV1,
|
|
125
125
|
node: Node,
|
|
126
126
|
options: ExecutionOptions,
|
|
127
127
|
): {
|
|
@@ -294,7 +294,7 @@ function buildNode(
|
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
function buildGraph(
|
|
297
|
-
|
|
297
|
+
monitor: MonitorV1,
|
|
298
298
|
options: ExecutionOptions,
|
|
299
299
|
executionContext: ExecutionContext,
|
|
300
300
|
): DynamicStateGraph {
|
|
@@ -317,23 +317,23 @@ function buildGraph(
|
|
|
317
317
|
}) as DynamicStateGraph;
|
|
318
318
|
|
|
319
319
|
// Add all nodes - cast back to DynamicStateGraph to maintain our dynamic type
|
|
320
|
-
const graphWithNodes =
|
|
321
|
-
(g, node) => g.addNode(buildNode(
|
|
320
|
+
const graphWithNodes = monitor.nodes.reduce<DynamicStateGraph>(
|
|
321
|
+
(g, node) => g.addNode(buildNode(monitor, node, options)) as DynamicStateGraph,
|
|
322
322
|
graph,
|
|
323
323
|
);
|
|
324
324
|
|
|
325
325
|
// Add all edges
|
|
326
326
|
// Cast the edge method to accept string arguments since ts-edge expects literal types
|
|
327
|
-
// but we have runtime strings from the
|
|
328
|
-
const graphWithEdges =
|
|
327
|
+
// but we have runtime strings from the monitor
|
|
328
|
+
const graphWithEdges = monitor.edges.reduce<DynamicStateGraph>((g, edge) => {
|
|
329
329
|
const addEdge = g.edge as (from: string, to: string) => DynamicStateGraph;
|
|
330
330
|
return addEdge(edge.from, edge.to);
|
|
331
331
|
}, graphWithNodes);
|
|
332
332
|
|
|
333
333
|
return graphWithEdges;
|
|
334
334
|
}
|
|
335
|
-
export async function
|
|
336
|
-
|
|
335
|
+
export async function executeMonitorV1(
|
|
336
|
+
monitor: MonitorV1,
|
|
337
337
|
organizationId: string,
|
|
338
338
|
options: ExecutionOptions,
|
|
339
339
|
): Promise<ExecutionResult> {
|
|
@@ -342,33 +342,33 @@ export async function executePlanV1(
|
|
|
342
342
|
// Generate or use provided executionId
|
|
343
343
|
const executionId = options.executionId || randomUUID();
|
|
344
344
|
|
|
345
|
-
// Migrate
|
|
346
|
-
let
|
|
347
|
-
if (
|
|
348
|
-
if (!isSupportedVersion(
|
|
345
|
+
// Migrate monitor to latest version if needed
|
|
346
|
+
let migratedMonitor = monitor;
|
|
347
|
+
if (monitor.version !== CURRENT_MONITOR_VERSION) {
|
|
348
|
+
if (!isSupportedVersion(monitor.version)) {
|
|
349
349
|
throw new Error(
|
|
350
|
-
`Unsupported
|
|
350
|
+
`Unsupported monitor version: ${monitor.version}. Supported versions: ${CURRENT_MONITOR_VERSION}`,
|
|
351
351
|
);
|
|
352
352
|
}
|
|
353
353
|
// Migrate to latest version
|
|
354
|
-
|
|
354
|
+
migratedMonitor = migrateToLatest(monitor as any) as MonitorV1;
|
|
355
355
|
}
|
|
356
356
|
|
|
357
357
|
// Create execution context for event emission
|
|
358
358
|
const executionContext = new ExecutionContext(
|
|
359
359
|
executionId,
|
|
360
|
-
|
|
360
|
+
migratedMonitor,
|
|
361
361
|
organizationId,
|
|
362
362
|
options.eventEmitter,
|
|
363
363
|
);
|
|
364
364
|
|
|
365
365
|
try {
|
|
366
|
-
// Resolve secrets if the
|
|
367
|
-
let
|
|
368
|
-
if (planHasSecrets(
|
|
366
|
+
// Resolve secrets if the monitor contains any
|
|
367
|
+
let resolvedMonitor = migratedMonitor;
|
|
368
|
+
if (planHasSecrets(monitor)) {
|
|
369
369
|
if (!options.secretRegistry) {
|
|
370
370
|
throw new SecretResolutionError(
|
|
371
|
-
"
|
|
371
|
+
"Monitor contains secret references but no secret registry was provided",
|
|
372
372
|
{ provider: "unknown", ref: "unknown" },
|
|
373
373
|
);
|
|
374
374
|
}
|
|
@@ -380,7 +380,7 @@ export async function executePlanV1(
|
|
|
380
380
|
});
|
|
381
381
|
|
|
382
382
|
try {
|
|
383
|
-
|
|
383
|
+
resolvedMonitor = await resolveSecretsInMonitor(monitor, options.secretRegistry);
|
|
384
384
|
|
|
385
385
|
executionContext.emit({
|
|
386
386
|
type: "NODE_END",
|
|
@@ -402,13 +402,13 @@ export async function executePlanV1(
|
|
|
402
402
|
}
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
-
// Emit
|
|
405
|
+
// Emit MONITOR_START event
|
|
406
406
|
executionContext.emit({
|
|
407
|
-
type: "
|
|
408
|
-
planName:
|
|
409
|
-
planVersion:
|
|
410
|
-
nodeCount:
|
|
411
|
-
edgeCount:
|
|
407
|
+
type: "MONITOR_START",
|
|
408
|
+
planName: resolvedMonitor.name,
|
|
409
|
+
planVersion: resolvedMonitor.version,
|
|
410
|
+
nodeCount: resolvedMonitor.nodes.length,
|
|
411
|
+
edgeCount: resolvedMonitor.edges.length,
|
|
412
412
|
});
|
|
413
413
|
|
|
414
414
|
// Call onStart callback if provided
|
|
@@ -422,7 +422,7 @@ export async function executePlanV1(
|
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
// Build execution graph (state-based)
|
|
425
|
-
const graph = buildGraph(
|
|
425
|
+
const graph = buildGraph(resolvedMonitor, options, executionContext);
|
|
426
426
|
|
|
427
427
|
// Compile and run the state graph
|
|
428
428
|
const app = graph.compile(START, END);
|
|
@@ -436,9 +436,9 @@ export async function executePlanV1(
|
|
|
436
436
|
const finalResults = graphResult.output?.results || [];
|
|
437
437
|
const finalErrors = graphResult.output?.errors || [errorMessage];
|
|
438
438
|
|
|
439
|
-
// Emit
|
|
439
|
+
// Emit MONITOR_END event
|
|
440
440
|
executionContext.emit({
|
|
441
|
-
type: "
|
|
441
|
+
type: "MONITOR_END",
|
|
442
442
|
success: false,
|
|
443
443
|
totalDuration_ms: Date.now() - startTime,
|
|
444
444
|
nodeResultCount: finalResults.length,
|
|
@@ -477,9 +477,9 @@ export async function executePlanV1(
|
|
|
477
477
|
const success = finalState.errors.length === 0;
|
|
478
478
|
const duration = Date.now() - startTime;
|
|
479
479
|
|
|
480
|
-
// Emit
|
|
480
|
+
// Emit MONITOR_END event
|
|
481
481
|
executionContext.emit({
|
|
482
|
-
type: "
|
|
482
|
+
type: "MONITOR_END",
|
|
483
483
|
success,
|
|
484
484
|
totalDuration_ms: duration,
|
|
485
485
|
nodeResultCount: finalState.results.length,
|
|
@@ -519,9 +519,9 @@ export async function executePlanV1(
|
|
|
519
519
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
520
520
|
const duration = Date.now() - startTime;
|
|
521
521
|
|
|
522
|
-
// Emit
|
|
522
|
+
// Emit MONITOR_END event
|
|
523
523
|
executionContext.emit({
|
|
524
|
-
type: "
|
|
524
|
+
type: "MONITOR_END",
|
|
525
525
|
success: false,
|
|
526
526
|
totalDuration_ms: duration,
|
|
527
527
|
nodeResultCount: 0,
|
|
@@ -572,12 +572,12 @@ async function executeHttpRequest(
|
|
|
572
572
|
const path = endpoint.path;
|
|
573
573
|
const url = `${baseUrl}${path}`;
|
|
574
574
|
|
|
575
|
-
// TODO: Add retry configuration from
|
|
575
|
+
// TODO: Add retry configuration from monitor (node-level or monitor-level)
|
|
576
576
|
// For now, we always attempt once (attempt: 1)
|
|
577
577
|
const attempt = 1;
|
|
578
578
|
|
|
579
579
|
// After secret resolution, headers are guaranteed to be plain strings
|
|
580
|
-
// Cast is safe because
|
|
580
|
+
// Cast is safe because resolveSecretsInMonitor substitutes all SecretRefs
|
|
581
581
|
const resolvedHeaders = endpoint.headers as
|
|
582
582
|
| Record<string, string>
|
|
583
583
|
| undefined;
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { executeMonitorV1 } from "./executor.js";
|
|
2
2
|
export type {
|
|
3
3
|
ExecutionOptions,
|
|
4
4
|
ExecutionResult,
|
|
@@ -10,12 +10,12 @@ export type {
|
|
|
10
10
|
StatusCallbacks,
|
|
11
11
|
} from "./types.js";
|
|
12
12
|
export type {
|
|
13
|
-
|
|
13
|
+
TestMonitor,
|
|
14
14
|
HttpRequest as HttpRequestNode,
|
|
15
15
|
WaitNode,
|
|
16
16
|
AssertionNode,
|
|
17
17
|
Edge,
|
|
18
|
-
} from "./test-
|
|
18
|
+
} from "./test-monitor-types.js";
|
|
19
19
|
export {
|
|
20
20
|
AxiosAdapter,
|
|
21
21
|
StubAdapter,
|
|
@@ -31,8 +31,8 @@ export {
|
|
|
31
31
|
export type {
|
|
32
32
|
ExecutionEvent,
|
|
33
33
|
BaseEvent,
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
MonitorStartEvent,
|
|
35
|
+
MonitorEndEvent,
|
|
36
36
|
NodeStartEvent,
|
|
37
37
|
NodeEndEvent,
|
|
38
38
|
HttpRequestEvent,
|
|
@@ -62,8 +62,8 @@ export {
|
|
|
62
62
|
// Registry
|
|
63
63
|
SecretProviderRegistry,
|
|
64
64
|
// Resolution utilities
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
resolveSecretsInMonitor,
|
|
66
|
+
collectSecretsFromMonitor,
|
|
67
67
|
planHasSecrets,
|
|
68
68
|
// Providers
|
|
69
69
|
EnvSecretProvider,
|
package/src/secrets/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Secret management for griffin
|
|
2
|
+
* Secret management for griffin monitor executor.
|
|
3
3
|
*
|
|
4
4
|
* This module provides:
|
|
5
5
|
* - SecretProvider interface for implementing custom providers
|
|
6
6
|
* - SecretProviderRegistry for managing multiple providers
|
|
7
|
-
* - Secret resolution utilities for test
|
|
7
|
+
* - Secret resolution utilities for test monitors
|
|
8
8
|
* - Built-in providers: env, aws, vault
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -24,8 +24,8 @@ export { SecretProviderRegistry } from "./registry.js";
|
|
|
24
24
|
|
|
25
25
|
// Resolution utilities
|
|
26
26
|
export {
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
resolveSecretsInMonitor,
|
|
28
|
+
collectSecretsFromMonitor,
|
|
29
29
|
planHasSecrets,
|
|
30
30
|
} from "./resolver.js";
|
|
31
31
|
|
package/src/secrets/resolver.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Secret resolution utilities for test
|
|
2
|
+
* Secret resolution utilities for test monitors.
|
|
3
3
|
*/
|
|
4
|
-
import { type
|
|
4
|
+
import { type MonitorV1 } from "@griffin-app/griffin-hub-sdk";
|
|
5
5
|
import type { SecretProviderRegistry } from "./registry.js";
|
|
6
6
|
import type { SecretRef, SecretRefData } from "./types.js";
|
|
7
7
|
import { isSecretRef, isStringLiteral } from "./types.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Collected secret references and literals from a
|
|
10
|
+
* Collected secret references and literals from a monitor.
|
|
11
11
|
*/
|
|
12
12
|
interface CollectedSecrets {
|
|
13
13
|
/** All unique secret references found */
|
|
@@ -71,18 +71,18 @@ function collectSecretsFromValue(
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* Collect all secret references and string literals from a test
|
|
74
|
+
* Collect all secret references and string literals from a test monitor.
|
|
75
75
|
* Scans endpoint headers and bodies for $secret markers and $literal wrappers.
|
|
76
76
|
*/
|
|
77
|
-
export function
|
|
77
|
+
export function collectSecretsFromMonitor(monitor: MonitorV1): CollectedSecrets {
|
|
78
78
|
const collected: CollectedSecrets = {
|
|
79
79
|
refs: [],
|
|
80
80
|
paths: [],
|
|
81
81
|
literalPaths: [],
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
for (let nodeIndex = 0; nodeIndex <
|
|
85
|
-
const node =
|
|
84
|
+
for (let nodeIndex = 0; nodeIndex < monitor.nodes.length; nodeIndex++) {
|
|
85
|
+
const node = monitor.nodes[nodeIndex];
|
|
86
86
|
|
|
87
87
|
// Only endpoints can have secrets (in headers and body)
|
|
88
88
|
if (node.type !== "HTTP_REQUEST") {
|
|
@@ -165,24 +165,24 @@ function deepClone<T>(value: T): T {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
/**
|
|
168
|
-
* Resolve all secrets and unwrap string literals in a
|
|
169
|
-
* The original
|
|
168
|
+
* Resolve all secrets and unwrap string literals in a monitor and return a new monitor with substituted values.
|
|
169
|
+
* The original monitor is not modified.
|
|
170
170
|
*
|
|
171
|
-
* @param
|
|
171
|
+
* @param monitor - The test monitor containing secret references and string literals
|
|
172
172
|
* @param registry - The secret provider registry
|
|
173
|
-
* @returns A new
|
|
173
|
+
* @returns A new monitor with all secrets resolved to their values and literals unwrapped
|
|
174
174
|
* @throws SecretResolutionError if any secret cannot be resolved (fail-fast)
|
|
175
175
|
*/
|
|
176
|
-
export async function
|
|
177
|
-
|
|
176
|
+
export async function resolveSecretsInMonitor(
|
|
177
|
+
monitor: MonitorV1,
|
|
178
178
|
registry: SecretProviderRegistry,
|
|
179
|
-
): Promise<
|
|
179
|
+
): Promise<MonitorV1> {
|
|
180
180
|
// Collect all secret references and string literals
|
|
181
|
-
const collected =
|
|
181
|
+
const collected = collectSecretsFromMonitor(monitor);
|
|
182
182
|
|
|
183
183
|
if (collected.refs.length === 0 && collected.literalPaths.length === 0) {
|
|
184
184
|
// No secrets or literals to resolve
|
|
185
|
-
return
|
|
185
|
+
return monitor;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
// Resolve all secrets (fail-fast on any error)
|
|
@@ -191,8 +191,8 @@ export async function resolveSecretsInPlan(
|
|
|
191
191
|
? await registry.resolveMany(collected.refs)
|
|
192
192
|
: new Map();
|
|
193
193
|
|
|
194
|
-
// Clone the
|
|
195
|
-
const
|
|
194
|
+
// Clone the monitor for modification
|
|
195
|
+
const resolvedMonitor = deepClone(monitor);
|
|
196
196
|
|
|
197
197
|
// Substitute resolved secret values at each path
|
|
198
198
|
for (const { path, secretRef } of collected.paths) {
|
|
@@ -206,23 +206,23 @@ export async function resolveSecretsInPlan(
|
|
|
206
206
|
);
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
setAtPath(
|
|
209
|
+
setAtPath(resolvedMonitor, path, value);
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
// Unwrap string literals at each path
|
|
213
213
|
for (const { path, value } of collected.literalPaths) {
|
|
214
|
-
setAtPath(
|
|
214
|
+
setAtPath(resolvedMonitor, path, value);
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
return
|
|
217
|
+
return resolvedMonitor;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
/**
|
|
221
|
-
* Check if a
|
|
221
|
+
* Check if a monitor contains any secret references or string literals that need resolution.
|
|
222
222
|
* Useful for short-circuiting resolution when no secrets or literals are present.
|
|
223
223
|
*/
|
|
224
|
-
export function planHasSecrets(
|
|
225
|
-
for (const node of
|
|
224
|
+
export function planHasSecrets(monitor: MonitorV1): boolean {
|
|
225
|
+
for (const node of monitor.nodes) {
|
|
226
226
|
if (node.type !== "HTTP_REQUEST") {
|
|
227
227
|
continue;
|
|
228
228
|
}
|