footprintjs 3.0.21 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CLAUDE.md +2 -0
  2. package/dist/advanced.js +20 -23
  3. package/dist/esm/advanced.js +2 -3
  4. package/dist/esm/lib/builder/FlowChartBuilder.js +41 -16
  5. package/dist/esm/lib/builder/typedFlowChart.js +10 -20
  6. package/dist/esm/lib/builder/types.js +1 -1
  7. package/dist/esm/lib/contract/defineContract.js +33 -7
  8. package/dist/esm/lib/contract/openapi.js +12 -33
  9. package/dist/esm/lib/contract/types.js +1 -1
  10. package/dist/esm/lib/decide/decide.js +33 -3
  11. package/dist/esm/lib/decide/types.js +1 -1
  12. package/dist/esm/lib/engine/handlers/NodeResolver.js +22 -7
  13. package/dist/esm/lib/engine/handlers/RuntimeStructureManager.js +35 -10
  14. package/dist/esm/lib/engine/handlers/SelectorHandler.js +2 -2
  15. package/dist/esm/lib/engine/handlers/StageRunner.js +2 -2
  16. package/dist/esm/lib/engine/handlers/SubflowInputMapper.js +2 -2
  17. package/dist/esm/lib/engine/index.js +1 -2
  18. package/dist/esm/lib/engine/narrative/CombinedNarrativeBuilder.js +1 -1
  19. package/dist/esm/lib/engine/narrative/CombinedNarrativeRecorder.js +5 -3
  20. package/dist/esm/lib/engine/narrative/FlowRecorderDispatcher.js +41 -32
  21. package/dist/esm/lib/engine/narrative/NarrativeFlowRecorder.js +7 -12
  22. package/dist/esm/lib/engine/narrative/index.js +1 -2
  23. package/dist/esm/lib/engine/traversal/FlowchartTraverser.js +361 -270
  24. package/dist/esm/lib/engine/types.js +1 -1
  25. package/dist/esm/lib/memory/StageContext.js +1 -10
  26. package/dist/esm/lib/memory/pathOps.js +31 -3
  27. package/dist/esm/lib/memory/utils.js +30 -10
  28. package/dist/esm/lib/runner/FlowChartExecutor.js +59 -12
  29. package/dist/esm/lib/runner/RunnableChart.js +25 -1
  30. package/dist/esm/lib/runner/getSubtreeSnapshot.js +17 -27
  31. package/dist/esm/lib/runner/index.js +1 -1
  32. package/dist/esm/lib/scope/ScopeFacade.js +36 -7
  33. package/dist/esm/lib/scope/index.js +1 -1
  34. package/dist/esm/lib/scope/providers/baseStateCompatible.js +2 -2
  35. package/dist/esm/lib/scope/providers/types.js +1 -1
  36. package/dist/esm/lib/scope/recorders/index.js +1 -1
  37. package/dist/esm/lib/scope/types.js +1 -1
  38. package/dist/lib/builder/FlowChartBuilder.js +41 -16
  39. package/dist/lib/builder/typedFlowChart.js +11 -22
  40. package/dist/lib/builder/types.js +1 -1
  41. package/dist/lib/contract/defineContract.js +33 -7
  42. package/dist/lib/contract/openapi.js +12 -33
  43. package/dist/lib/contract/types.js +1 -1
  44. package/dist/lib/decide/decide.js +33 -3
  45. package/dist/lib/decide/types.js +1 -1
  46. package/dist/lib/engine/handlers/NodeResolver.js +22 -7
  47. package/dist/lib/engine/handlers/RuntimeStructureManager.js +35 -10
  48. package/dist/lib/engine/handlers/SelectorHandler.js +2 -2
  49. package/dist/lib/engine/handlers/StageRunner.js +2 -2
  50. package/dist/lib/engine/handlers/SubflowInputMapper.js +2 -2
  51. package/dist/lib/engine/index.js +2 -4
  52. package/dist/lib/engine/narrative/CombinedNarrativeBuilder.js +1 -1
  53. package/dist/lib/engine/narrative/CombinedNarrativeRecorder.js +5 -3
  54. package/dist/lib/engine/narrative/FlowRecorderDispatcher.js +41 -32
  55. package/dist/lib/engine/narrative/NarrativeFlowRecorder.js +7 -12
  56. package/dist/lib/engine/narrative/index.js +2 -4
  57. package/dist/lib/engine/traversal/FlowchartTraverser.js +361 -270
  58. package/dist/lib/engine/types.js +1 -1
  59. package/dist/lib/memory/StageContext.js +1 -10
  60. package/dist/lib/memory/pathOps.js +31 -3
  61. package/dist/lib/memory/utils.js +30 -10
  62. package/dist/lib/runner/FlowChartExecutor.js +59 -12
  63. package/dist/lib/runner/RunnableChart.js +25 -1
  64. package/dist/lib/runner/getSubtreeSnapshot.js +17 -27
  65. package/dist/lib/runner/index.js +1 -1
  66. package/dist/lib/scope/ScopeFacade.js +36 -7
  67. package/dist/lib/scope/index.js +1 -1
  68. package/dist/lib/scope/providers/baseStateCompatible.js +2 -2
  69. package/dist/lib/scope/providers/types.js +1 -1
  70. package/dist/lib/scope/recorders/index.js +1 -1
  71. package/dist/lib/scope/types.js +1 -1
  72. package/dist/types/advanced.d.ts +2 -3
  73. package/dist/types/lib/builder/FlowChartBuilder.d.ts +2 -1
  74. package/dist/types/lib/builder/typedFlowChart.d.ts +9 -18
  75. package/dist/types/lib/builder/types.d.ts +12 -3
  76. package/dist/types/lib/contract/openapi.d.ts +9 -3
  77. package/dist/types/lib/contract/types.d.ts +13 -2
  78. package/dist/types/lib/decide/decide.d.ts +10 -0
  79. package/dist/types/lib/decide/types.d.ts +20 -0
  80. package/dist/types/lib/engine/handlers/NodeResolver.d.ts +10 -3
  81. package/dist/types/lib/engine/handlers/RuntimeStructureManager.d.ts +2 -1
  82. package/dist/types/lib/engine/index.d.ts +0 -1
  83. package/dist/types/lib/engine/narrative/CombinedNarrativeBuilder.d.ts +1 -1
  84. package/dist/types/lib/engine/narrative/FlowRecorderDispatcher.d.ts +3 -2
  85. package/dist/types/lib/engine/narrative/NarrativeFlowRecorder.d.ts +0 -1
  86. package/dist/types/lib/engine/narrative/index.d.ts +0 -1
  87. package/dist/types/lib/engine/traversal/FlowchartTraverser.d.ts +53 -0
  88. package/dist/types/lib/engine/types.d.ts +10 -3
  89. package/dist/types/lib/memory/StageContext.d.ts +0 -3
  90. package/dist/types/lib/memory/pathOps.d.ts +26 -2
  91. package/dist/types/lib/memory/utils.d.ts +16 -3
  92. package/dist/types/lib/runner/FlowChartExecutor.d.ts +78 -7
  93. package/dist/types/lib/runner/RunnableChart.d.ts +2 -0
  94. package/dist/types/lib/runner/index.d.ts +1 -0
  95. package/dist/types/lib/scope/ScopeFacade.d.ts +18 -1
  96. package/dist/types/lib/scope/index.d.ts +0 -1
  97. package/dist/types/lib/scope/providers/types.d.ts +1 -1
  98. package/dist/types/lib/scope/recorders/index.d.ts +0 -1
  99. package/dist/types/lib/scope/types.d.ts +7 -1
  100. package/package.json +1 -1
  101. package/dist/esm/lib/engine/narrative/ControlFlowNarrativeGenerator.js +0 -110
  102. package/dist/esm/lib/scope/recorders/NarrativeRecorder.js +0 -149
  103. package/dist/lib/engine/narrative/ControlFlowNarrativeGenerator.js +0 -114
  104. package/dist/lib/scope/recorders/NarrativeRecorder.js +0 -153
  105. package/dist/types/lib/engine/narrative/ControlFlowNarrativeGenerator.d.ts +0 -31
  106. package/dist/types/lib/scope/recorders/NarrativeRecorder.d.ts +0 -50
@@ -13,7 +13,7 @@ import type { SerializedPipelineStructure } from '../types.js';
13
13
  * Compute the node type from node properties.
14
14
  * Shared by RuntimeStructureManager (serialization) and ExtractorRunner (metadata).
15
15
  */
16
- export declare function computeNodeType(node: StageNode): 'stage' | 'decider' | 'fork' | 'streaming';
16
+ export declare function computeNodeType(node: StageNode): 'stage' | 'decider' | 'selector' | 'fork' | 'streaming' | 'subflow';
17
17
  export declare class RuntimeStructureManager {
18
18
  private runtimePipelineStructure?;
19
19
  private structureNodeMap;
@@ -21,6 +21,7 @@ export declare class RuntimeStructureManager {
21
21
  init(buildTimeStructure?: SerializedPipelineStructure): void;
22
22
  /** Returns the current runtime structure (mutated during execution). */
23
23
  getStructure(): SerializedPipelineStructure | undefined;
24
+ private static readonly MAX_NODE_MAP_DEPTH;
24
25
  /** Recursively registers all nodes in the O(1) lookup map. */
25
26
  private buildNodeMap;
26
27
  /** Convert a runtime StageNode into a SerializedPipelineStructure node. */
@@ -10,7 +10,6 @@ export { isStageNodeReturn } from './graph/StageNode.js';
10
10
  export * from './types.js';
11
11
  export * from './handlers/index.js';
12
12
  export type { CombinedNarrativeEntry, CombinedNarrativeOptions } from './narrative/CombinedNarrativeBuilder.js';
13
- export { ControlFlowNarrativeGenerator } from './narrative/ControlFlowNarrativeGenerator.js';
14
13
  export { NullControlFlowNarrativeGenerator } from './narrative/NullControlFlowNarrativeGenerator.js';
15
14
  export type { IControlFlowNarrative } from './narrative/types.js';
16
15
  export { FlowRecorderDispatcher } from './narrative/FlowRecorderDispatcher.js';
@@ -5,7 +5,7 @@
5
5
  * Use CombinedNarrativeRecorder (auto-attached by setEnableNarrative()) instead.
6
6
  */
7
7
  export interface CombinedNarrativeEntry {
8
- type: 'stage' | 'step' | 'condition' | 'fork' | 'subflow' | 'loop' | 'break' | 'error';
8
+ type: 'stage' | 'step' | 'condition' | 'fork' | 'selector' | 'subflow' | 'loop' | 'break' | 'error';
9
9
  text: string;
10
10
  depth: number;
11
11
  stageName?: string;
@@ -33,8 +33,9 @@ export declare class FlowRecorderDispatcher implements IControlFlowNarrative {
33
33
  onBreak(stageName: string, traversalContext?: TraversalContext): void;
34
34
  onError(stageName: string, errorMessage: string, error: unknown, traversalContext?: TraversalContext): void;
35
35
  /**
36
- * Returns sentences from the first attached recorder that provides them.
37
- * By convention, NarrativeFlowRecorder exposes getSentences().
36
+ * Returns sentences from an attached NarrativeFlowRecorder (looked up by ID).
37
+ * Callers that need sentences should attach a NarrativeFlowRecorder with id 'narrative'
38
+ * and retrieve it directly via getRecorderById() if they need typed access.
38
39
  */
39
40
  getSentences(): string[];
40
41
  }
@@ -14,7 +14,6 @@ export declare class NarrativeFlowRecorder implements FlowRecorder {
14
14
  private sentences;
15
15
  /** Parallel array: the actual stage name that produced each sentence. */
16
16
  private stageNames;
17
- private isFirstStage;
18
17
  constructor(id?: string);
19
18
  onStageExecuted(event: FlowStageEvent): void;
20
19
  onNext(event: FlowNextEvent): void;
@@ -1,7 +1,6 @@
1
1
  export type { CombinedNarrativeEntry, CombinedNarrativeOptions } from './CombinedNarrativeBuilder.js';
2
2
  export type { CombinedNarrativeRecorderOptions } from './CombinedNarrativeRecorder.js';
3
3
  export { CombinedNarrativeRecorder } from './CombinedNarrativeRecorder.js';
4
- export { ControlFlowNarrativeGenerator } from './ControlFlowNarrativeGenerator.js';
5
4
  export { NullControlFlowNarrativeGenerator } from './NullControlFlowNarrativeGenerator.js';
6
5
  export type { IControlFlowNarrative } from './types.js';
7
6
  export { FlowRecorderDispatcher } from './FlowRecorderDispatcher.js';
@@ -45,6 +45,11 @@ export interface TraverserOptions<TOut = any, TScope = any> {
45
45
  signal?: AbortSignal;
46
46
  /** Pre-configured FlowRecorders to attach when narrative is enabled. */
47
47
  flowRecorders?: FlowRecorder[];
48
+ /**
49
+ * Maximum recursive executeNode depth. Defaults to FlowchartTraverser.MAX_EXECUTE_DEPTH (500).
50
+ * Override in tests or unusually deep pipelines.
51
+ */
52
+ maxDepth?: number;
48
53
  }
49
54
  export declare class FlowchartTraverser<TOut = any, TScope = any> {
50
55
  private readonly root;
@@ -65,6 +70,42 @@ export declare class FlowchartTraverser<TOut = any, TScope = any> {
65
70
  private readonly narrativeGenerator;
66
71
  private readonly flowRecorderDispatcher;
67
72
  private subflowResults;
73
+ /**
74
+ * Per-traverser set of lazy subflow IDs that have been resolved by THIS run.
75
+ * Used instead of writing `node.subflowResolver = undefined` back to the shared
76
+ * StageNode graph — avoids a race where a concurrent traverser clears the shared
77
+ * resolver before another traverser has finished using it.
78
+ */
79
+ private readonly resolvedLazySubflows;
80
+ /**
81
+ * Recursion depth counter for executeNode.
82
+ * Each recursive executeNode call increments this; decrements on exit (try/finally).
83
+ * Prevents call-stack overflow on infinite loops or excessively deep stage chains.
84
+ */
85
+ private _executeDepth;
86
+ /**
87
+ * Per-instance maximum depth (set from TraverserOptions.maxDepth or the class default).
88
+ */
89
+ private readonly _maxDepth;
90
+ /**
91
+ * Default maximum recursive executeNode depth before an error is thrown.
92
+ * 500 comfortably covers any realistic pipeline depth (including deeply nested
93
+ * subflows) while preventing call-stack overflow (~10 000 frames in V8).
94
+ *
95
+ * **Note on counting:** the counter increments once per `executeNode` call, not once per
96
+ * logical user stage. Subflow root entry and subflow continuation after return each cost
97
+ * one tick. For pipelines with many nested subflows, budget roughly 2 × (avg stages per
98
+ * subflow) of headroom when computing a custom `maxDepth` via `RunOptions.maxDepth`.
99
+ *
100
+ * **Note on loops:** for `loopTo()` pipelines, this depth guard and `ContinuationResolver`'s
101
+ * iteration limit are independent — the lower one fires first. The default depth guard (500)
102
+ * fires before the default iteration limit (1000) for loop-heavy pipelines.
103
+ *
104
+ * @remarks Not safe for concurrent `.execute()` calls on the same instance — concurrent
105
+ * executions race on `_executeDepth`. Use a separate `FlowchartTraverser` per concurrent
106
+ * execution. `FlowChartExecutor.run()` always creates a fresh traverser per call.
107
+ */
108
+ static readonly MAX_EXECUTE_DEPTH = 500;
68
109
  constructor(opts: TraverserOptions<TOut, TScope>);
69
110
  private createDeps;
70
111
  execute(): Promise<TraversalResult>;
@@ -76,6 +117,12 @@ export declare class FlowchartTraverser<TOut = any, TScope = any> {
76
117
  subflowResults?: Record<string, unknown> | undefined;
77
118
  recorders?: {
78
119
  id: string;
120
+ /**
121
+ * Per-traverser set of lazy subflow IDs that have been resolved by THIS run.
122
+ * Used instead of writing `node.subflowResolver = undefined` back to the shared
123
+ * StageNode graph — avoids a race where a concurrent traverser clears the shared
124
+ * resolver before another traverser has finished using it.
125
+ */
79
126
  name: string;
80
127
  data: unknown;
81
128
  }[] | undefined;
@@ -90,6 +137,12 @@ export declare class FlowchartTraverser<TOut = any, TScope = any> {
90
137
  getNarrative(): string[];
91
138
  /** Returns the FlowRecorderDispatcher, or undefined if narrative is disabled. */
92
139
  getFlowRecorderDispatcher(): FlowRecorderDispatcher | undefined;
140
+ /**
141
+ * Build an O(1) ID→node map from the root graph.
142
+ * Used by NodeResolver to avoid repeated DFS on every loopTo() call.
143
+ * Depth-guarded at MAX_EXECUTE_DEPTH to prevent infinite recursion on cyclic graphs.
144
+ */
145
+ private buildNodeIdMap;
93
146
  private getStageFn;
94
147
  private executeStage;
95
148
  /**
@@ -112,7 +112,7 @@ export interface HandlerDeps<TOut = any, TScope = any> {
112
112
  stageMap: Map<string, StageFunction<TOut, TScope>>;
113
113
  root: StageNode<TOut, TScope>;
114
114
  executionRuntime: IExecutionRuntime;
115
- ScopeFactory: ScopeFactory<TScope>;
115
+ scopeFactory: ScopeFactory<TScope>;
116
116
  subflows?: Record<string, {
117
117
  root: StageNode<TOut, TScope>;
118
118
  }>;
@@ -144,10 +144,17 @@ export interface RunOptions {
144
144
  * Accessible via `scope.getEnv()`. Inherited by subflows automatically.
145
145
  */
146
146
  env?: ExecutionEnv;
147
+ /**
148
+ * Override the maximum recursive `executeNode` depth for this run.
149
+ * Defaults to `FlowchartTraverser.MAX_EXECUTE_DEPTH` (500).
150
+ * Useful when deeply nested subflows or long chains need more headroom.
151
+ * Must be >= 1.
152
+ */
153
+ maxDepth?: number;
147
154
  }
148
155
  export type { FlowControlType, FlowMessage };
149
156
  export interface RuntimeStructureMetadata {
150
- type: 'stage' | 'decider' | 'fork' | 'streaming';
157
+ type: 'stage' | 'decider' | 'selector' | 'fork' | 'streaming' | 'subflow';
151
158
  subflowId?: string;
152
159
  isSubflowRoot?: boolean;
153
160
  subflowName?: string;
@@ -200,7 +207,7 @@ export type TraversalResult = BranchResults | string | Error;
200
207
  export interface SerializedPipelineNode {
201
208
  name: string;
202
209
  id?: string;
203
- type?: 'stage' | 'decider' | 'fork' | 'streaming' | 'loop' | 'user' | 'tool' | 'function' | 'sequence';
210
+ type?: 'stage' | 'decider' | 'selector' | 'fork' | 'streaming' | 'subflow' | 'loop' | 'user' | 'tool' | 'function' | 'sequence';
204
211
  description?: string;
205
212
  children?: SerializedPipelineNode[];
206
213
  next?: SerializedPipelineNode;
@@ -53,11 +53,8 @@ export declare class StageContext {
53
53
  appendToArray(path: string[], key: string, items: unknown[], description?: string): void;
54
54
  mergeObject(path: string[], key: string, obj: Record<string, unknown>, description?: string): void;
55
55
  getValue(path: string[], key?: string, description?: string): any;
56
- get(path: string[], key?: string): any;
57
56
  getRoot(key: string): any;
58
57
  getGlobal(key: string): any;
59
- getFromRoot(key: string): any;
60
- getFromGlobalContext(key: string): any;
61
58
  getScope(): Record<string, unknown>;
62
59
  getRunId(): string;
63
60
  commit(): void;
@@ -1,10 +1,34 @@
1
1
  /**
2
2
  * pathOps.ts — Native nested-path helpers (replaces lodash.get/set/has/mergewith)
3
3
  *
4
- * All functions guard against prototype-pollution attacks.
4
+ * Security contract: all functions guard against prototype-pollution and
5
+ * prototype-chain-read attacks. The DENIED set blocks the three canonical
6
+ * pollution vectors (__proto__, constructor, prototype) on every function.
7
+ *
8
+ * Intentional asymmetry:
9
+ * - nativeSet — DENIED check only at each segment. No hasOwnProperty
10
+ * check is needed because writing always creates an OWN property on `curr`,
11
+ * which cannot pollute the prototype chain.
12
+ * - nativeGet / nativeHas — DENIED check + hasOwnProperty at every step.
13
+ * Reads follow the prototype chain by default (bracket notation), so the
14
+ * hasOwnProperty guard is required to prevent leaking inherited values
15
+ * (e.g. Object.prototype, Object constructor, toString).
16
+ * - mergeContextWins — DENIED check only; Object.keys() is own-enumerable-only
17
+ * by spec so prototype keys never appear in the iteration.
18
+ *
19
+ * Do NOT "fix" the nativeSet asymmetry by adding hasOwnProperty — it is
20
+ * intentional and would break path creation for new intermediate nodes.
21
+ *
5
22
  * Paths may be dot-notation strings or pre-split (string|number)[] arrays.
6
23
  */
7
- /** Get the value at `path` in `obj`, returning `defaultValue` if absent. */
24
+ /**
25
+ * Get the value at `path` in `obj`, returning `defaultValue` if absent.
26
+ *
27
+ * Security: each path segment is checked against the DENIED list and requires
28
+ * an own property at every step, preventing prototype-chain reads.
29
+ * e.g. nativeGet({}, '__proto__') and nativeGet({}, 'constructor') both return
30
+ * `defaultValue` instead of leaking Object.prototype / the Object constructor.
31
+ */
8
32
  export declare function nativeGet(obj: any, path: string | (string | number)[], defaultValue?: any): any;
9
33
  /** Mutate `obj`, setting `value` at `path` (creates intermediate objects). Returns `obj`. */
10
34
  export declare function nativeSet(obj: any, path: string | (string | number)[], value: any): any;
@@ -31,9 +31,18 @@ export declare function setNestedValue<T>(obj: NestedObject, runId: string, _pat
31
31
  export declare function updateNestedValue<T>(obj: any, runId: string | undefined, _path: (string | number)[], field: string | number, value: T, defaultValues?: unknown): any;
32
32
  /**
33
33
  * In-place value update with merge semantics.
34
- * - Arrays: concatenate
35
- * - Objects: shallow merge (spread)
34
+ * - Arrays (non-empty): concatenate onto existing
35
+ * - Arrays (empty): direct replace writing `[]` clears the field
36
+ * - Objects (non-empty): shallow merge (spread)
37
+ * - Objects (empty): direct replace — writing `{}` clears the field
36
38
  * - Primitives: direct assignment
39
+ *
40
+ * Note on empty arrays: both `value && Array.isArray(value)` and
41
+ * `Array.isArray(value)` evaluate the same for arrays — `[]` is truthy in
42
+ * JavaScript, so the `&&` guard was never the issue. The actual bug was the
43
+ * concat path: `[...cur, ...[]]` silently returned `cur` unchanged when `value`
44
+ * was `[]`, making `updateValue(obj, 'tags', [])` a no-op instead of a clear.
45
+ * The fix is the explicit `value.length === 0` early-return branch.
37
46
  */
38
47
  export declare function updateValue(object: any, key: string | number, value: any): void;
39
48
  /**
@@ -50,7 +59,11 @@ export declare function redactPatch(patch: MemoryPatch, redactedSet: Set<string>
50
59
  export declare function normalisePath(path: (string | number)[]): string;
51
60
  /**
52
61
  * Deep union merge helper.
53
- * - Arrays: union without duplicates (encounter order preserved)
62
+ * - Arrays (non-empty): union without duplicates (encounter order preserved)
63
+ * - Arrays (empty): replace — src `[]` clears the destination array.
64
+ * Rationale: writing `scope.tags = []` means "clear tags", not "append nothing".
65
+ * Without this rule, an empty-array write silently becomes a no-op which is
66
+ * impossible to distinguish from a bug.
54
67
  * - Objects: recursive merge
55
68
  * - Primitives: source wins
56
69
  */
@@ -1,11 +1,20 @@
1
1
  /**
2
2
  * FlowChartExecutor — Public API for executing a compiled FlowChart.
3
3
  *
4
- * Wraps FlowchartTraverser. Pairs with FlowChartBuilder:
4
+ * Wraps FlowchartTraverser. Build a chart with flowChart() and pass the result here:
5
+ *
5
6
  * const chart = flowChart('entry', entryFn).addFunction('process', processFn).build();
6
- * const executor = new FlowChartExecutor(chart); // uses default ScopeFacade
7
- * const executor = new FlowChartExecutor(chart, myFactory); // custom scope factory
8
- * const result = await executor.run();
7
+ *
8
+ * // No-options form (uses auto-detected TypedScope factory from the chart):
9
+ * const executor = new FlowChartExecutor(chart);
10
+ *
11
+ * // Options-object form (preferred when you need to customize behavior):
12
+ * const executor = new FlowChartExecutor(chart, { scopeFactory: myFactory, enrichSnapshots: true });
13
+ *
14
+ * // 2-param form (accepts a ScopeFactory directly, for backward compatibility):
15
+ * const executor = new FlowChartExecutor(chart, myFactory);
16
+ *
17
+ * const result = await executor.run({ input: data, env: { traceId: 'req-123' } });
9
18
  */
10
19
  import type { CombinedNarrativeEntry } from '../engine/narrative/CombinedNarrativeBuilder.js';
11
20
  import type { ManifestEntry } from '../engine/narrative/recorders/ManifestFlowRecorder.js';
@@ -14,6 +23,54 @@ import { type ExtractorError, type FlowChart, type RunOptions, type ScopeFactory
14
23
  import type { ScopeProtectionMode } from '../scope/protection/types.js';
15
24
  import type { Recorder, RedactionPolicy, RedactionReport } from '../scope/types.js';
16
25
  import { type RuntimeSnapshot } from './ExecutionRuntime.js';
26
+ /**
27
+ * Options object for `FlowChartExecutor` — preferred over positional params.
28
+ *
29
+ * ```typescript
30
+ * const ex = new FlowChartExecutor(chart, {
31
+ * scopeFactory: myFactory,
32
+ * enrichSnapshots: true,
33
+ * });
34
+ * ```
35
+ *
36
+ * **Sync note for maintainers:** Every field added here must also appear in the
37
+ * `flowChartArgs` private field type and in the constructor's options-resolution
38
+ * block (the `else if` branch that reads from `opts`). Missing any one of the
39
+ * three causes silent omission — the option is accepted but never applied.
40
+ *
41
+ * **TScope inference note:** When using the options-object form with a custom scope,
42
+ * TypeScript cannot infer `TScope` through the options object. Pass the type
43
+ * explicitly: `new FlowChartExecutor<TOut, MyScope>(chart, { scopeFactory })`.
44
+ */
45
+ export interface FlowChartExecutorOptions<TScope = any> {
46
+ /** Custom scope factory. Defaults to TypedScope or ScopeFacade auto-detection. */
47
+ scopeFactory?: ScopeFactory<TScope>;
48
+ /** Whether to enrich snapshots with scope state (enables `getSnapshot()`). */
49
+ enrichSnapshots?: boolean;
50
+ /**
51
+ * Default values pre-populated into the shared context before **each** stage
52
+ * (re-applied every stage, acting as baseline defaults).
53
+ */
54
+ defaultValuesForContext?: unknown;
55
+ /**
56
+ * Initial context values merged into the shared context **once** at startup
57
+ * (applied before the first stage, not repeated on subsequent stages).
58
+ * Distinct from `defaultValuesForContext`, which is re-applied every stage.
59
+ */
60
+ initialContext?: unknown;
61
+ /** Read-only input accessible via `scope.getArgs()` — never tracked or written. */
62
+ readOnlyContext?: unknown;
63
+ /**
64
+ * Custom error classifier for throttling detection. Return `true` if the
65
+ * error represents a rate-limit or backpressure condition (the executor will
66
+ * treat it differently from hard failures). Defaults to no throttling classification.
67
+ */
68
+ throttlingErrorChecker?: (error: unknown) => boolean;
69
+ /** Handlers for streaming stage lifecycle events (see `addStreamingFunction`). */
70
+ streamHandlers?: StreamHandlers;
71
+ /** Scope protection mode for TypedScope direct-assignment detection. */
72
+ scopeProtectionMode?: ScopeProtectionMode;
73
+ }
17
74
  export declare class FlowChartExecutor<TOut = any, TScope = any> {
18
75
  private traverser;
19
76
  private narrativeEnabled;
@@ -24,7 +81,23 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
24
81
  private sharedRedactedKeys;
25
82
  private sharedRedactedFieldsByKey;
26
83
  private readonly flowChartArgs;
27
- constructor(flowChart: FlowChart<TOut, TScope>, scopeFactory?: ScopeFactory<TScope>, defaultValuesForContext?: unknown, initialContext?: unknown, readOnlyContext?: unknown, throttlingErrorChecker?: (error: unknown) => boolean, streamHandlers?: StreamHandlers, scopeProtectionMode?: ScopeProtectionMode, enrichSnapshots?: boolean);
84
+ /**
85
+ * Create a FlowChartExecutor.
86
+ *
87
+ * **Options object form** (preferred):
88
+ * ```typescript
89
+ * new FlowChartExecutor(chart, { scopeFactory, enrichSnapshots: true })
90
+ * ```
91
+ *
92
+ * **2-param form** (also supported):
93
+ * ```typescript
94
+ * new FlowChartExecutor(chart, scopeFactory)
95
+ * ```
96
+ *
97
+ * @param flowChart - The compiled FlowChart returned by `flowChart(...).build()`
98
+ * @param factoryOrOptions - A `ScopeFactory<TScope>` OR a `FlowChartExecutorOptions<TScope>` options object.
99
+ */
100
+ constructor(flowChart: FlowChart<TOut, TScope>, factoryOrOptions?: ScopeFactory<TScope> | FlowChartExecutorOptions<TScope>);
28
101
  private createTraverser;
29
102
  enableNarrative(): void;
30
103
  /**
@@ -100,8 +173,6 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
100
173
  /** @internal */
101
174
  getExtractedResults<TResult = unknown>(): Map<string, TResult>;
102
175
  /** @internal */
103
- getEnrichedResults<TResult = unknown>(): Map<string, TResult>;
104
- /** @internal */
105
176
  getExtractorErrors(): ExtractorError[];
106
177
  /**
107
178
  * Returns the subflow manifest from an attached ManifestFlowRecorder.
@@ -47,6 +47,8 @@ export interface RunnableFlowChart<TOut = any, TScope = any> extends FlowChart<T
47
47
  toOpenAPI(options?: ChartOpenAPIOptions): object;
48
48
  /** Generate MCP tool description from chart metadata. Cached. */
49
49
  toMCPTool(): MCPToolDescription;
50
+ /** Generate a Mermaid flowchart diagram string from the chart's node graph. */
51
+ toMermaid(): string;
50
52
  }
51
53
  /**
52
54
  * Enrich a FlowChart with run + describe methods.
@@ -1,6 +1,7 @@
1
1
  export type { ComposableRunner } from './ComposableRunner.js';
2
2
  export type { NarrativeEntry, RecorderSnapshot, RuntimeSnapshot } from './ExecutionRuntime.js';
3
3
  export { ExecutionRuntime } from './ExecutionRuntime.js';
4
+ export type { FlowChartExecutorOptions } from './FlowChartExecutor.js';
4
5
  export { FlowChartExecutor } from './FlowChartExecutor.js';
5
6
  export type { SubtreeSnapshot } from './getSubtreeSnapshot.js';
6
7
  export { getSubtreeSnapshot, listSubflowPaths } from './getSubtreeSnapshot.js';
@@ -111,8 +111,25 @@ export declare class ScopeFacade {
111
111
  getPipelineId(): string;
112
112
  /** Checks if a key is redacted (explicit _redactedKeys set). */
113
113
  private _isKeyRedacted;
114
- /** Checks if a key should be auto-redacted by the policy (exact keys + patterns). */
114
+ /**
115
+ * Checks if a key should be auto-redacted by the policy (exact keys + patterns).
116
+ *
117
+ * ReDoS guard: pattern testing is capped at MAX_PATTERN_KEY_LEN characters.
118
+ * Scope state keys are always short identifiers; any key exceeding the cap
119
+ * is almost certainly not a legitimate scope key, so skipping pattern matching
120
+ * for it does not risk leaking PII. Exact-key matching (Array.includes) is
121
+ * still applied regardless of length and is not vulnerable to ReDoS.
122
+ */
115
123
  private _isPolicyRedacted;
124
+ /**
125
+ * Maximum key length (characters) that will be tested against regex redaction
126
+ * patterns. Keys longer than this are skipped for pattern matching to prevent
127
+ * ReDoS: a pathological regex tested against an unboundedly long key string
128
+ * can cause catastrophic backtracking.
129
+ *
130
+ * 256 characters comfortably exceeds any realistic scope-state key name.
131
+ */
132
+ private static readonly _MAX_PATTERN_KEY_LEN;
116
133
  /**
117
134
  * Returns a deep-cloned copy with specified fields replaced by '[REDACTED]'.
118
135
  * Supports dot-notation paths (e.g. 'address.zip') for nested objects.
@@ -10,7 +10,6 @@ export type { DebugEntry, DebugRecorderOptions, DebugVerbosity } from './recorde
10
10
  export { DebugRecorder } from './recorders/DebugRecorder.js';
11
11
  export type { AggregatedMetrics, StageMetrics } from './recorders/MetricRecorder.js';
12
12
  export { MetricRecorder } from './recorders/MetricRecorder.js';
13
- export type { NarrativeDetail, NarrativeOperation, NarrativeRecorderOptions, StageNarrativeData, } from './recorders/NarrativeRecorder.js';
14
13
  export type { ScopeProtectionMode, ScopeProtectionOptions } from './protection/index.js';
15
14
  export { createErrorMessage, createProtectedScope } from './protection/index.js';
16
15
  export type { ProviderResolver, ResolveOptions, ScopeFactory, ScopeProvider, StageContextLike, StrictMode, } from './providers/index.js';
@@ -11,7 +11,7 @@ export interface StageContextLike {
11
11
  updateObject(path: string[], key: string, value: unknown, description?: string): void;
12
12
  addLog?(key: string, val: unknown): void;
13
13
  addError?(key: string, val: unknown): void;
14
- getFromGlobalContext?(key: string): unknown;
14
+ getGlobal?(key: string): unknown;
15
15
  setRoot?(key: string, value: unknown): void;
16
16
  setGlobal?(key: string, value: unknown, description?: string): void;
17
17
  pipelineId?: string;
@@ -3,4 +3,3 @@ export type { DebugEntry, DebugRecorderOptions, DebugVerbosity } from './DebugRe
3
3
  export { DebugRecorder } from './DebugRecorder.js';
4
4
  export type { AggregatedMetrics, StageMetrics } from './MetricRecorder.js';
5
5
  export { MetricRecorder } from './MetricRecorder.js';
6
- export type { NarrativeDetail, NarrativeOperation, NarrativeRecorderOptions, StageNarrativeData, } from './NarrativeRecorder.js';
@@ -47,7 +47,13 @@ export interface StageEvent extends RecorderContext {
47
47
  export interface RedactionPolicy {
48
48
  /** Exact key names to always redact (e.g. ['ssn', 'creditCard']). */
49
49
  keys?: string[];
50
- /** Regex patterns — any key matching a pattern is auto-redacted. */
50
+ /**
51
+ * Regex patterns — any key matching a pattern is auto-redacted.
52
+ *
53
+ * Pattern matching is skipped for keys that exceed an internal length cap
54
+ * (designed to prevent ReDoS on pathological patterns). For very long key
55
+ * names, use `keys` (exact match) instead of patterns.
56
+ */
51
57
  patterns?: RegExp[];
52
58
  /** Field-level redaction within objects — key → array of fields to scrub.
53
59
  * Supports dot-notation for nested paths (e.g. 'address.zip'). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "footprintjs",
3
- "version": "3.0.21",
3
+ "version": "4.0.0",
4
4
  "description": "Explainable backend flows — automatic causal traces, decision evidence, and MCP tool generation for AI agents",
5
5
  "license": "MIT",
6
6
  "author": "Sanjay Krishna Anbalagan",
@@ -1,110 +0,0 @@
1
- /**
2
- * ControlFlowNarrativeGenerator — Active implementation of IControlFlowNarrative.
3
- *
4
- * Converts traversal events into plain-English sentences at traversal time.
5
- * Produces a human-readable story as a first-class output, enabling any consumer
6
- * (cheaper LLM, follow-up agent, logging system) to understand what happened
7
- * without parsing technical structures.
8
- *
9
- * This is the FLOW narrative — it captures control flow decisions.
10
- * The DATA narrative comes from scope/recorders/NarrativeRecorder.
11
- * CombinedNarrativeBuilder merges both into one story.
12
- */
13
- export class ControlFlowNarrativeGenerator {
14
- constructor() {
15
- this.sentences = [];
16
- this.isFirstStage = true;
17
- }
18
- onStageExecuted(stageName, description) {
19
- if (this.isFirstStage) {
20
- if (description) {
21
- this.sentences.push(`The process began: ${description}.`);
22
- }
23
- else {
24
- this.sentences.push(`The process began with ${stageName}.`);
25
- }
26
- this.isFirstStage = false;
27
- }
28
- }
29
- onNext(fromStage, toStage, description) {
30
- if (description) {
31
- this.sentences.push(`Next step: ${description}.`);
32
- }
33
- else {
34
- this.sentences.push(`Next, it moved on to ${toStage}.`);
35
- }
36
- }
37
- onDecision(deciderName, chosenBranch, rationale, deciderDescription, _traversalContext, evidence) {
38
- const branchName = chosenBranch;
39
- if (evidence) {
40
- const matchedRule = evidence.rules.find((r) => r.matched);
41
- if (matchedRule) {
42
- const label = matchedRule.label ? ` "${matchedRule.label}"` : '';
43
- if (matchedRule.type === 'filter') {
44
- const parts = matchedRule.conditions.map((c) => `${c.key} ${c.actualSummary} ${c.op} ${JSON.stringify(c.threshold)} ${c.result ? '\u2713' : '\u2717'}`);
45
- this.sentences.push(`It evaluated${label}: ${parts.join(', ')}, and chose ${branchName}.`);
46
- }
47
- else {
48
- const parts = matchedRule.inputs.map((i) => `${i.key}=${i.valueSummary}`);
49
- this.sentences.push(`It examined${label}: ${parts.join(', ')}, and chose ${branchName}.`);
50
- }
51
- }
52
- else {
53
- this.sentences.push(`No rules matched, fell back to default: ${branchName}.`);
54
- }
55
- }
56
- else if (deciderDescription && rationale) {
57
- this.sentences.push(`It ${deciderDescription}: ${rationale}, so it chose ${branchName}.`);
58
- }
59
- else if (deciderDescription) {
60
- this.sentences.push(`It ${deciderDescription} and chose ${branchName}.`);
61
- }
62
- else if (rationale) {
63
- this.sentences.push(`A decision was made: ${rationale}, so the path taken was ${branchName}.`);
64
- }
65
- else {
66
- this.sentences.push(`A decision was made, and the path taken was ${branchName}.`);
67
- }
68
- }
69
- onFork(parentStage, childNames) {
70
- const names = childNames.join(', ');
71
- this.sentences.push(`Forking into ${childNames.length} parallel paths: ${names}.`);
72
- }
73
- onSelected(parentStage, selectedNames, totalCount) {
74
- const names = selectedNames.join(', ');
75
- this.sentences.push(`${selectedNames.length} of ${totalCount} paths were selected: ${names}.`);
76
- }
77
- onSubflowEntry(subflowName, _subflowId, description) {
78
- if (description) {
79
- this.sentences.push(`Entering the ${subflowName} subflow: ${description}.`);
80
- }
81
- else {
82
- this.sentences.push(`Entering the ${subflowName} subflow.`);
83
- }
84
- }
85
- onSubflowExit(subflowName, _subflowId) {
86
- this.sentences.push(`Exiting the ${subflowName} subflow.`);
87
- }
88
- onSubflowRegistered(_subflowId, _name, _description, _specStructure) {
89
- // No narrative output for registration events
90
- }
91
- onLoop(targetStage, iteration, description) {
92
- if (description) {
93
- this.sentences.push(`On pass ${iteration}: ${description} again.`);
94
- }
95
- else {
96
- this.sentences.push(`On pass ${iteration} through ${targetStage}.`);
97
- }
98
- }
99
- onBreak(stageName) {
100
- this.sentences.push(`Execution stopped at ${stageName}.`);
101
- }
102
- onError(stageName, errorMessage, _error) {
103
- this.sentences.push(`An error occurred at ${stageName}: ${errorMessage}.`);
104
- }
105
- /** Returns a defensive copy of accumulated sentences. */
106
- getSentences() {
107
- return [...this.sentences];
108
- }
109
- }
110
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ29udHJvbEZsb3dOYXJyYXRpdmVHZW5lcmF0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvbGliL2VuZ2luZS9uYXJyYXRpdmUvQ29udHJvbEZsb3dOYXJyYXRpdmVHZW5lcmF0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7O0dBV0c7QUFLSCxNQUFNLE9BQU8sNkJBQTZCO0lBQTFDO1FBQ1UsY0FBUyxHQUFhLEVBQUUsQ0FBQztRQUN6QixpQkFBWSxHQUFHLElBQUksQ0FBQztJQXdHOUIsQ0FBQztJQXRHQyxlQUFlLENBQUMsU0FBaUIsRUFBRSxXQUFvQjtRQUNyRCxJQUFJLElBQUksQ0FBQyxZQUFZLEVBQUUsQ0FBQztZQUN0QixJQUFJLFdBQVcsRUFBRSxDQUFDO2dCQUNoQixJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxzQkFBc0IsV0FBVyxHQUFHLENBQUMsQ0FBQztZQUM1RCxDQUFDO2lCQUFNLENBQUM7Z0JBQ04sSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsMEJBQTBCLFNBQVMsR0FBRyxDQUFDLENBQUM7WUFDOUQsQ0FBQztZQUNELElBQUksQ0FBQyxZQUFZLEdBQUcsS0FBSyxDQUFDO1FBQzVCLENBQUM7SUFDSCxDQUFDO0lBRUQsTUFBTSxDQUFDLFNBQWlCLEVBQUUsT0FBZSxFQUFFLFdBQW9CO1FBQzdELElBQUksV0FBVyxFQUFFLENBQUM7WUFDaEIsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsY0FBYyxXQUFXLEdBQUcsQ0FBQyxDQUFDO1FBQ3BELENBQUM7YUFBTSxDQUFDO1lBQ04sSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsd0JBQXdCLE9BQU8sR0FBRyxDQUFDLENBQUM7UUFDMUQsQ0FBQztJQUNILENBQUM7SUFFRCxVQUFVLENBQ1IsV0FBbUIsRUFDbkIsWUFBb0IsRUFDcEIsU0FBa0IsRUFDbEIsa0JBQTJCLEVBQzNC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LENBQUM7SUFFRCxPQUFPLENBQUMsU0FBaUIsRUFBRSxZQUFvQixFQUFFLE1BQWU7UUFDOUQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsd0JBQXdCLFNBQVMsS0FBSyxZQUFZLEdBQUcsQ0FBQyxDQUFDO0lBQzdFLENBQUM7SUFFRCx5REFBeUQ7SUFDekQsWUFBWTtRQUNWLE9BQU8sQ0FBQyxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUM3QixDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvbnRyb2xGbG93TmFycmF0aXZlR2VuZXJhdG9yIOKAlCBBY3RpdmUgaW1wbGVtZW50YXRpb24gb2YgSUNvbnRyb2xGbG93TmFycmF0aXZlLlxuICpcbiAqIENvbnZlcnRzIHRyYXZlcnNhbCBldmVudHMgaW50byBwbGFpbi1FbmdsaXNoIHNlbnRlbmNlcyBhdCB0cmF2ZXJzYWwgdGltZS5cbiAqIFByb2R1Y2VzIGEgaHVtYW4tcmVhZGFibGUgc3RvcnkgYXMgYSBmaXJzdC1jbGFzcyBvdXRwdXQsIGVuYWJsaW5nIGFueSBjb25zdW1lclxuICogKGNoZWFwZXIgTExNLCBmb2xsb3ctdXAgYWdlbnQsIGxvZ2dpbmcgc3lzdGVtKSB0byB1bmRlcnN0YW5kIHdoYXQgaGFwcGVuZWRcbiAqIHdpdGhvdXQgcGFyc2luZyB0ZWNobmljYWwgc3RydWN0dXJlcy5cbiAqXG4gKiBUaGlzIGlzIHRoZSBGTE9XIG5hcnJhdGl2ZSDigJQgaXQgY2FwdHVyZXMgY29udHJvbCBmbG93IGRlY2lzaW9ucy5cbiAqIFRoZSBEQVRBIG5hcnJhdGl2ZSBjb21lcyBmcm9tIHNjb3BlL3JlY29yZGVycy9OYXJyYXRpdmVSZWNvcmRlci5cbiAqIENvbWJpbmVkTmFycmF0aXZlQnVpbGRlciBtZXJnZXMgYm90aCBpbnRvIG9uZSBzdG9yeS5cbiAqL1xuXG5pbXBvcnQgdHlwZSB7IERlY2lzaW9uRXZpZGVuY2UgfSBmcm9tICcuLi8uLi9kZWNpZGUvdHlwZXMuanMnO1xuaW1wb3J0IHR5cGUgeyBJQ29udHJvbEZsb3dOYXJyYXRpdmUgfSBmcm9tICcuL3R5cGVzLmpzJztcblxuZXhwb3J0IGNsYXNzIENvbnRyb2xGbG93TmFycmF0aXZlR2VuZXJhdG9yIGltcGxlbWVudHMgSUNvbnRyb2xGbG93TmFycmF0aXZlIHtcbiAgcHJpdmF0ZSBzZW50ZW5jZXM6IHN0cmluZ1tdID0gW107XG4gIHByaXZhdGUgaXNGaXJzdFN0YWdlID0gdHJ1ZTtcblxuICBvblN0YWdlRXhlY3V0ZWQoc3RhZ2VOYW1lOiBzdHJpbmcsIGRlc2NyaXB0aW9uPzogc3RyaW5nKTogdm9pZCB7XG4gICAgaWYgKHRoaXMuaXNGaXJzdFN0YWdlKSB7XG4gICAgICBpZiAoZGVzY3JpcHRpb24pIHtcbiAgICAgICAgdGhpcy5zZW50ZW5jZXMucHVzaChgVGhlIHByb2Nlc3MgYmVnYW46ICR7ZGVzY3JpcHRpb259LmApO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5zZW50ZW5jZXMucHVzaChgVGhlIHByb2Nlc3MgYmVnYW4gd2l0aCAke3N0YWdlTmFtZX0uYCk7XG4gICAgICB9XG4gICAgICB0aGlzLmlzRmlyc3RTdGFnZSA9IGZhbHNlO1xuICAgIH1cbiAgfVxuXG4gIG9uTmV4dChmcm9tU3RhZ2U6IHN0cmluZywgdG9TdGFnZTogc3RyaW5nLCBkZXNjcmlwdGlvbj86IHN0cmluZyk6IHZvaWQge1xuICAgIGlmIChkZXNjcmlwdGlvbikge1xuICAgICAgdGhpcy5zZW50ZW5jZXMucHVzaChgTmV4dCBzdGVwOiAke2Rlc2NyaXB0aW9ufS5gKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5zZW50ZW5jZXMucHVzaChgTmV4dCwgaXQgbW92ZWQgb24gdG8gJHt0b1N0YWdlfS5gKTtcbiAgICB9XG4gIH1cblxuICBvbkRlY2lzaW9uKFxuICAgIGRlY2lkZXJOYW1lOiBzdHJpbmcsXG4gICAgY2hvc2VuQnJhbmNoOiBzdHJpbmcsXG4gICAgcmF0aW9uYWxlPzogc3RyaW5nLFxuICAgIGRlY2lkZXJEZXNjcmlwdGlvbj86IHN0cmluZyxcbiAgICBfdHJhdmVyc2FsQ29udGV4dD86IHVua25vd24sXG4gICAgZXZpZGVuY2U/OiBEZWNpc2lvbkV2aWRlbmNlLFxuICApOiB2b2lkIHtcbiAgICBjb25zdCBicmFuY2hOYW1lID0gY2hvc2VuQnJhbmNoO1xuICAgIGlmIChldmlkZW5jZSkge1xuICAgICAgY29uc3QgbWF0Y2hlZFJ1bGUgPSBldmlkZW5jZS5ydWxlcy5maW5kKChyKSA9PiByLm1hdGNoZWQpO1xuICAgICAgaWYgKG1hdGNoZWRSdWxlKSB7XG4gICAgICAgIGNvbnN0IGxhYmVsID0gbWF0Y2hlZFJ1bGUubGFiZWwgPyBgIFwiJHttYXRjaGVkUnVsZS5sYWJlbH1cImAgOiAnJztcbiAgICAgICAgaWYgKG1hdGNoZWRSdWxlLnR5cGUgPT09ICdmaWx0ZXInKSB7XG4gICAgICAgICAgY29uc3QgcGFydHMgPSBtYXRjaGVkUnVsZS5jb25kaXRpb25zLm1hcChcbiAgICAgICAgICAgIChjKSA9PlxuICAgICAgICAgICAgICBgJHtjLmtleX0gJHtjLmFjdHVhbFN1bW1hcnl9ICR7Yy5vcH0gJHtKU09OLnN0cmluZ2lmeShjLnRocmVzaG9sZCl9ICR7Yy5yZXN1bHQgPyAnXFx1MjcxMycgOiAnXFx1MjcxNyd9YCxcbiAgICAgICAgICApO1xuICAgICAgICAgIHRoaXMuc2VudGVuY2VzLnB1c2goYEl0IGV2YWx1YXRlZCR7bGFiZWx9OiAke3BhcnRzLmpvaW4oJywgJyl9LCBhbmQgY2hvc2UgJHticmFuY2hOYW1lfS5gKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBjb25zdCBwYXJ0cyA9IG1hdGNoZWRSdWxlLmlucHV0cy5tYXAoKGkpID0+IGAke2kua2V5fT0ke2kudmFsdWVTdW1tYXJ5fWApO1xuICAgICAgICAgIHRoaXMuc2VudGVuY2VzLnB1c2goYEl0IGV4YW1pbmVkJHtsYWJlbH06ICR7cGFydHMuam9pbignLCAnKX0sIGFuZCBjaG9zZSAke2JyYW5jaE5hbWV9LmApO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aGlzLnNlbnRlbmNlcy5wdXNoKGBObyBydWxlcyBtYXRjaGVkLCBmZWxsIGJhY2sgdG8gZGVmYXVsdDogJHticmFuY2hOYW1lfS5gKTtcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKGRlY2lkZXJEZXNjcmlwdGlvbiAmJiByYXRpb25hbGUpIHtcbiAgICAgIHRoaXMuc2VudGVuY2VzLnB1c2goYEl0ICR7ZGVjaWRlckRlc2NyaXB0aW9ufTogJHtyYXRpb25hbGV9LCBzbyBpdCBjaG9zZSAke2JyYW5jaE5hbWV9LmApO1xuICAgIH0gZWxzZSBpZiAoZGVjaWRlckRlc2NyaXB0aW9uKSB7XG4gICAgICB0aGlzLnNlbnRlbmNlcy5wdXNoKGBJdCAke2RlY2lkZXJEZXNjcmlwdGlvbn0gYW5kIGNob3NlICR7YnJhbmNoTmFtZX0uYCk7XG4gICAgfSBlbHNlIGlmIChyYXRpb25hbGUpIHtcbiAgICAgIHRoaXMuc2VudGVuY2VzLnB1c2goYEEgZGVjaXNpb24gd2FzIG1hZGU6ICR7cmF0aW9uYWxlfSwgc28gdGhlIHBhdGggdGFrZW4gd2FzICR7YnJhbmNoTmFtZX0uYCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuc2VudGVuY2VzLnB1c2goYEEgZGVjaXNpb24gd2FzIG1hZGUsIGFuZCB0aGUgcGF0aCB0YWtlbiB3YXMgJHticmFuY2hOYW1lfS5gKTtcbiAgICB9XG4gIH1cblxuICBvbkZvcmsocGFyZW50U3RhZ2U6IHN0cmluZywgY2hpbGROYW1lczogc3RyaW5nW10pOiB2b2lkIHtcbiAgICBjb25zdCBuYW1lcyA9IGNoaWxkTmFtZXMuam9pbignLCAnKTtcbiAgICB0aGlzLnNlbnRlbmNlcy5wdXNoKGBGb3JraW5nIGludG8gJHtjaGlsZE5hbWVzLmxlbmd0aH0gcGFyYWxsZWwgcGF0aHM6ICR7bmFtZXN9LmApO1xuICB9XG5cbiAgb25TZWxlY3RlZChwYXJlbnRTdGFnZTogc3RyaW5nLCBzZWxlY3RlZE5hbWVzOiBzdHJpbmdbXSwgdG90YWxDb3VudDogbnVtYmVyKTogdm9pZCB7XG4gICAgY29uc3QgbmFtZXMgPSBzZWxlY3RlZE5hbWVzLmpvaW4oJywgJyk7XG4gICAgdGhpcy5zZW50ZW5jZXMucHVzaChgJHtzZWxlY3RlZE5hbWVzLmxlbmd0aH0gb2YgJHt0b3RhbENvdW50fSBwYXRocyB3ZXJlIHNlbGVjdGVkOiAke25hbWVzfS5gKTtcbiAgfVxuXG4gIG9uU3ViZmxvd0VudHJ5KHN1YmZsb3dOYW1lOiBzdHJpbmcsIF9zdWJmbG93SWQ/OiBzdHJpbmcsIGRlc2NyaXB0aW9uPzogc3RyaW5nKTogdm9pZCB7XG4gICAgaWYgKGRlc2NyaXB0aW9uKSB7XG4gICAgICB0aGlzLnNlbnRlbmNlcy5wdXNoKGBFbnRlcmluZyB0aGUgJHtzdWJmbG93TmFtZX0gc3ViZmxvdzogJHtkZXNjcmlwdGlvbn0uYCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuc2VudGVuY2VzLnB1c2goYEVudGVyaW5nIHRoZSAke3N1YmZsb3dOYW1lfSBzdWJmbG93LmApO1xuICAgIH1cbiAgfVxuXG4gIG9uU3ViZmxvd0V4aXQoc3ViZmxvd05hbWU6IHN0cmluZywgX3N1YmZsb3dJZD86IHN0cmluZyk6IHZvaWQge1xuICAgIHRoaXMuc2VudGVuY2VzLnB1c2goYEV4aXRpbmcgdGhlICR7c3ViZmxvd05hbWV9IHN1YmZsb3cuYCk7XG4gIH1cblxuICBvblN1YmZsb3dSZWdpc3RlcmVkKF9zdWJmbG93SWQ6IHN0cmluZywgX25hbWU6IHN0cmluZywgX2Rlc2NyaXB0aW9uPzogc3RyaW5nLCBfc3BlY1N0cnVjdHVyZT86IHVua25vd24pOiB2b2lkIHtcbiAgICAvLyBObyBuYXJyYXRpdmUgb3V0cHV0IGZvciByZWdpc3RyYXRpb24gZXZlbnRzXG4gIH1cblxuICBvbkxvb3AodGFyZ2V0U3RhZ2U6IHN0cmluZywgaXRlcmF0aW9uOiBudW1iZXIsIGRlc2NyaXB0aW9uPzogc3RyaW5nKTogdm9pZCB7XG4gICAgaWYgKGRlc2NyaXB0aW9uKSB7XG4gICAgICB0aGlzLnNlbnRlbmNlcy5wdXNoKGBPbiBwYXNzICR7aXRlcmF0aW9ufTogJHtkZXNjcmlwdGlvbn0gYWdhaW4uYCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuc2VudGVuY2VzLnB1c2goYE9uIHBhc3MgJHtpdGVyYXRpb259IHRocm91Z2ggJHt0YXJnZXRTdGFnZX0uYCk7XG4gICAgfVxuICB9XG5cbiAgb25CcmVhayhzdGFnZU5hbWU6IHN0cmluZyk6IHZvaWQge1xuICAgIHRoaXMuc2VudGVuY2VzLnB1c2goYEV4ZWN1dGlvbiBzdG9wcGVkIGF0ICR7c3RhZ2VOYW1lfS5gKTtcbiAgfVxuXG4gIG9uRXJyb3Ioc3RhZ2VOYW1lOiBzdHJpbmcsIGVycm9yTWVzc2FnZTogc3RyaW5nLCBfZXJyb3I6IHVua25vd24pOiB2b2lkIHtcbiAgICB0aGlzLnNlbnRlbmNlcy5wdXNoKGBBbiBlcnJvciBvY2N1cnJlZCBhdCAke3N0YWdlTmFtZX06ICR7ZXJyb3JNZXNzYWdlfS5gKTtcbiAgfVxuXG4gIC8qKiBSZXR1cm5zIGEgZGVmZW5zaXZlIGNvcHkgb2YgYWNjdW11bGF0ZWQgc2VudGVuY2VzLiAqL1xuICBnZXRTZW50ZW5jZXMoKTogc3RyaW5nW10ge1xuICAgIHJldHVybiBbLi4udGhpcy5zZW50ZW5jZXNdO1xuICB9XG59XG4iXX0=