@vaudience/aigentflow-flow-validator 0.23.0 → 0.24.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/PARITY.md +16 -0
- package/dist/cli.js +70 -20
- package/dist/index.cjs +70 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +70 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/PARITY.md
CHANGED
|
@@ -6,6 +6,22 @@ discipline for keeping the two in sync.
|
|
|
6
6
|
|
|
7
7
|
**Tracks AIgentFlow flow schema: `v2.831.0`** (`SPEC_VERSION` in [`src/spec/aigentflow-spec.json`](./src/spec/aigentflow-spec.json)), which adds the rule `unknown_function_operation` and a clearer `unknown_executor_scheme` message (package 0.23.0, below). Earlier: the rule `flow_data_key_reserved` (package 0.22.0). Earlier: the `here` executor scheme, the `strict_templates` flow key and the rule `template_default_piped` (package 0.21.0). Earlier: the warning `compliance_residency_unenforced` (package 0.20.0). `orchestrator.region` / `orchestrator.enforce_region` (package 0.19.0). Earlier: the AigentChat executor was removed (package 0.18.0). Earlier additions: the `examples:` block (package 0.17.0), `server_owned_query_key` (package 0.15.1) from the AIgentFlow release after v2.790.0, `credential_endpoint_unpaired` (package 0.15.2) from AIgentFlow v2.793.0 and the release after it, and `display_name_too_long` (package 0.15.3) from AIgentFlow v2.802.0.
|
|
8
8
|
|
|
9
|
+
> package 0.24.0 — **`potential_infinite_loop` fires only on a cycle with no way out**, in step
|
|
10
|
+
> with the reference. It used to fire on every cycle reachable from the start step, so a deliberate
|
|
11
|
+
> ask-again loop (a step that routes back until a condition sends it onward) carried a warning that
|
|
12
|
+
> told the author nothing. The reference now computes the strongly connected sets (Tarjan) over
|
|
13
|
+
> `next.default` and `next.conditions[].goto` edges reachable from start (markers `null` /
|
|
14
|
+
> `orchestrator` and unknown steps are not followed; `end` is an ordinary step) and warns once when
|
|
15
|
+
> a cyclic set (more than one step, or a self-loop) has no exit. A member leaves the set when it has
|
|
16
|
+
> no `next`, its `next` has `parallel` (a fan-out is treated as an exit, not guessed at), its
|
|
17
|
+
> `next.default` is empty (when no condition matches the flow ends), or any default or condition
|
|
18
|
+
> target is `null`, `orchestrator` or a step outside the set. Code and severity are unchanged; the
|
|
19
|
+
> message now ends "a cycle of steps has no way out" and the suggestion asks for a `next.conditions`
|
|
20
|
+
> branch that leaves the cycle. It cannot prove an exit ever fires; that is undecidable from the
|
|
21
|
+
> YAML. Module: `validators/connectivity.ts`. Gates: ten `valid-cycle-*` / `warn-cycle-*` fixtures
|
|
22
|
+
> (the valid ones use `forbidWarningCodes`, the only way to assert a false-positive risk), shared
|
|
23
|
+
> byte for byte with the Go port. On the 227 bundled example flows both ports report 0 findings.
|
|
24
|
+
|
|
9
25
|
> package 0.23.0 — **`unknown_function_operation`**, in step with the reference (AIgentFlow
|
|
10
26
|
> v2.831.0; `specVersion` 2.831.0). One new **error**: a step or loop sub-step whose executor
|
|
11
27
|
> is `function://<a>/<b>` where `<a>/<b>` is not one of the 32 registered built-ins. Before
|
package/dist/cli.js
CHANGED
|
@@ -2732,31 +2732,81 @@ function validateConnectivity(flow, issues) {
|
|
|
2732
2732
|
});
|
|
2733
2733
|
}
|
|
2734
2734
|
}
|
|
2735
|
-
|
|
2736
|
-
const recStack = /* @__PURE__ */ new Set();
|
|
2737
|
-
const hasCycle = (stepID) => {
|
|
2738
|
-
if (recStack.has(stepID)) return true;
|
|
2739
|
-
if (visited.has(stepID)) return false;
|
|
2740
|
-
visited.add(stepID);
|
|
2741
|
-
recStack.add(stepID);
|
|
2742
|
-
const step = steps[stepID];
|
|
2743
|
-
if (isRecord(step) && isRecord(step.next)) {
|
|
2744
|
-
const next = step.next;
|
|
2745
|
-
for (const target of cycleTargets(next, `steps.${stepID}.next`, issues)) {
|
|
2746
|
-
if (hasCycle(target)) return true;
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2749
|
-
recStack.delete(stepID);
|
|
2750
|
-
return false;
|
|
2751
|
-
};
|
|
2752
|
-
if (hasCycle(start)) {
|
|
2735
|
+
if (cycleWithoutExit(steps, start, issues)) {
|
|
2753
2736
|
issues.warn({
|
|
2754
2737
|
field: "steps",
|
|
2755
|
-
message: "Potential infinite loop detected in step flow",
|
|
2738
|
+
message: "Potential infinite loop detected in step flow: a cycle of steps has no way out",
|
|
2756
2739
|
code: "potential_infinite_loop",
|
|
2757
|
-
suggestion: "
|
|
2740
|
+
suggestion: "Give one step in the cycle a `next.conditions` branch that leaves it (or end the flow there)"
|
|
2741
|
+
});
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
function cycleEdges(step, stepID, issues) {
|
|
2745
|
+
if (!isRecord(step) || !isRecord(step.next)) return [];
|
|
2746
|
+
const next = step.next;
|
|
2747
|
+
const nextPath = `steps.${stepID}.next`;
|
|
2748
|
+
const out = [];
|
|
2749
|
+
const def = issues.stringOf(next, "default", nextPath);
|
|
2750
|
+
if (def !== null && def !== "") out.push(def);
|
|
2751
|
+
if (Array.isArray(next.conditions)) {
|
|
2752
|
+
next.conditions.forEach((cond, i) => {
|
|
2753
|
+
if (!isRecord(cond)) return;
|
|
2754
|
+
const goto = issues.stringOf(cond, "goto", `${nextPath}.conditions[${i}]`);
|
|
2755
|
+
if (goto !== null && goto !== "") out.push(goto);
|
|
2758
2756
|
});
|
|
2759
2757
|
}
|
|
2758
|
+
return out;
|
|
2759
|
+
}
|
|
2760
|
+
function stepLeavesCycle(steps, stepID, inCycle, issues) {
|
|
2761
|
+
const step = steps[stepID];
|
|
2762
|
+
if (!isRecord(step) || !isRecord(step.next)) return true;
|
|
2763
|
+
const next = step.next;
|
|
2764
|
+
if (next.parallel !== void 0 && next.parallel !== null) return true;
|
|
2765
|
+
const def = issues.stringOf(next, "default", `steps.${stepID}.next`);
|
|
2766
|
+
if (def === null || def === "") return true;
|
|
2767
|
+
for (const target of cycleEdges(step, stepID, issues)) {
|
|
2768
|
+
if (isMarker(target) || !inCycle.has(target)) return true;
|
|
2769
|
+
}
|
|
2770
|
+
return false;
|
|
2771
|
+
}
|
|
2772
|
+
function cycleWithoutExit(steps, start, issues) {
|
|
2773
|
+
let index = 0;
|
|
2774
|
+
const indices = /* @__PURE__ */ new Map();
|
|
2775
|
+
const low = /* @__PURE__ */ new Map();
|
|
2776
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
2777
|
+
const stack = [];
|
|
2778
|
+
let found = false;
|
|
2779
|
+
const strong = (id) => {
|
|
2780
|
+
indices.set(id, index);
|
|
2781
|
+
low.set(id, index);
|
|
2782
|
+
index++;
|
|
2783
|
+
stack.push(id);
|
|
2784
|
+
onStack.add(id);
|
|
2785
|
+
for (const target of cycleEdges(steps[id], id, issues)) {
|
|
2786
|
+
if (isMarker(target) || !Object.prototype.hasOwnProperty.call(steps, target)) continue;
|
|
2787
|
+
if (!indices.has(target)) {
|
|
2788
|
+
strong(target);
|
|
2789
|
+
low.set(id, Math.min(low.get(id), low.get(target)));
|
|
2790
|
+
} else if (onStack.has(target)) {
|
|
2791
|
+
low.set(id, Math.min(low.get(id), indices.get(target)));
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
if (low.get(id) !== indices.get(id)) return;
|
|
2795
|
+
const component = [];
|
|
2796
|
+
for (; ; ) {
|
|
2797
|
+
const top = stack.pop();
|
|
2798
|
+
onStack.delete(top);
|
|
2799
|
+
component.push(top);
|
|
2800
|
+
if (top === id) break;
|
|
2801
|
+
}
|
|
2802
|
+
const inCycle = new Set(component);
|
|
2803
|
+
const cyclic = component.length > 1 || cycleEdges(steps[id], id, issues).includes(id);
|
|
2804
|
+
if (!cyclic) return;
|
|
2805
|
+
if (component.some((member) => stepLeavesCycle(steps, member, inCycle, issues))) return;
|
|
2806
|
+
found = true;
|
|
2807
|
+
};
|
|
2808
|
+
if (Object.prototype.hasOwnProperty.call(steps, start)) strong(start);
|
|
2809
|
+
return found;
|
|
2760
2810
|
}
|
|
2761
2811
|
|
|
2762
2812
|
// src/validators/nextLogic.ts
|
package/dist/index.cjs
CHANGED
|
@@ -2633,31 +2633,81 @@ function validateConnectivity(flow, issues) {
|
|
|
2633
2633
|
});
|
|
2634
2634
|
}
|
|
2635
2635
|
}
|
|
2636
|
-
|
|
2637
|
-
const recStack = /* @__PURE__ */ new Set();
|
|
2638
|
-
const hasCycle = (stepID) => {
|
|
2639
|
-
if (recStack.has(stepID)) return true;
|
|
2640
|
-
if (visited.has(stepID)) return false;
|
|
2641
|
-
visited.add(stepID);
|
|
2642
|
-
recStack.add(stepID);
|
|
2643
|
-
const step = steps[stepID];
|
|
2644
|
-
if (isRecord(step) && isRecord(step.next)) {
|
|
2645
|
-
const next = step.next;
|
|
2646
|
-
for (const target of cycleTargets(next, `steps.${stepID}.next`, issues)) {
|
|
2647
|
-
if (hasCycle(target)) return true;
|
|
2648
|
-
}
|
|
2649
|
-
}
|
|
2650
|
-
recStack.delete(stepID);
|
|
2651
|
-
return false;
|
|
2652
|
-
};
|
|
2653
|
-
if (hasCycle(start)) {
|
|
2636
|
+
if (cycleWithoutExit(steps, start, issues)) {
|
|
2654
2637
|
issues.warn({
|
|
2655
2638
|
field: "steps",
|
|
2656
|
-
message: "Potential infinite loop detected in step flow",
|
|
2639
|
+
message: "Potential infinite loop detected in step flow: a cycle of steps has no way out",
|
|
2657
2640
|
code: "potential_infinite_loop",
|
|
2658
|
-
suggestion: "
|
|
2641
|
+
suggestion: "Give one step in the cycle a `next.conditions` branch that leaves it (or end the flow there)"
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
function cycleEdges(step, stepID, issues) {
|
|
2646
|
+
if (!isRecord(step) || !isRecord(step.next)) return [];
|
|
2647
|
+
const next = step.next;
|
|
2648
|
+
const nextPath = `steps.${stepID}.next`;
|
|
2649
|
+
const out = [];
|
|
2650
|
+
const def = issues.stringOf(next, "default", nextPath);
|
|
2651
|
+
if (def !== null && def !== "") out.push(def);
|
|
2652
|
+
if (Array.isArray(next.conditions)) {
|
|
2653
|
+
next.conditions.forEach((cond, i) => {
|
|
2654
|
+
if (!isRecord(cond)) return;
|
|
2655
|
+
const goto = issues.stringOf(cond, "goto", `${nextPath}.conditions[${i}]`);
|
|
2656
|
+
if (goto !== null && goto !== "") out.push(goto);
|
|
2659
2657
|
});
|
|
2660
2658
|
}
|
|
2659
|
+
return out;
|
|
2660
|
+
}
|
|
2661
|
+
function stepLeavesCycle(steps, stepID, inCycle, issues) {
|
|
2662
|
+
const step = steps[stepID];
|
|
2663
|
+
if (!isRecord(step) || !isRecord(step.next)) return true;
|
|
2664
|
+
const next = step.next;
|
|
2665
|
+
if (next.parallel !== void 0 && next.parallel !== null) return true;
|
|
2666
|
+
const def = issues.stringOf(next, "default", `steps.${stepID}.next`);
|
|
2667
|
+
if (def === null || def === "") return true;
|
|
2668
|
+
for (const target of cycleEdges(step, stepID, issues)) {
|
|
2669
|
+
if (isMarker(target) || !inCycle.has(target)) return true;
|
|
2670
|
+
}
|
|
2671
|
+
return false;
|
|
2672
|
+
}
|
|
2673
|
+
function cycleWithoutExit(steps, start, issues) {
|
|
2674
|
+
let index = 0;
|
|
2675
|
+
const indices = /* @__PURE__ */ new Map();
|
|
2676
|
+
const low = /* @__PURE__ */ new Map();
|
|
2677
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
2678
|
+
const stack = [];
|
|
2679
|
+
let found = false;
|
|
2680
|
+
const strong = (id) => {
|
|
2681
|
+
indices.set(id, index);
|
|
2682
|
+
low.set(id, index);
|
|
2683
|
+
index++;
|
|
2684
|
+
stack.push(id);
|
|
2685
|
+
onStack.add(id);
|
|
2686
|
+
for (const target of cycleEdges(steps[id], id, issues)) {
|
|
2687
|
+
if (isMarker(target) || !Object.prototype.hasOwnProperty.call(steps, target)) continue;
|
|
2688
|
+
if (!indices.has(target)) {
|
|
2689
|
+
strong(target);
|
|
2690
|
+
low.set(id, Math.min(low.get(id), low.get(target)));
|
|
2691
|
+
} else if (onStack.has(target)) {
|
|
2692
|
+
low.set(id, Math.min(low.get(id), indices.get(target)));
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
if (low.get(id) !== indices.get(id)) return;
|
|
2696
|
+
const component = [];
|
|
2697
|
+
for (; ; ) {
|
|
2698
|
+
const top = stack.pop();
|
|
2699
|
+
onStack.delete(top);
|
|
2700
|
+
component.push(top);
|
|
2701
|
+
if (top === id) break;
|
|
2702
|
+
}
|
|
2703
|
+
const inCycle = new Set(component);
|
|
2704
|
+
const cyclic = component.length > 1 || cycleEdges(steps[id], id, issues).includes(id);
|
|
2705
|
+
if (!cyclic) return;
|
|
2706
|
+
if (component.some((member) => stepLeavesCycle(steps, member, inCycle, issues))) return;
|
|
2707
|
+
found = true;
|
|
2708
|
+
};
|
|
2709
|
+
if (Object.prototype.hasOwnProperty.call(steps, start)) strong(start);
|
|
2710
|
+
return found;
|
|
2661
2711
|
}
|
|
2662
2712
|
|
|
2663
2713
|
// src/validators/nextLogic.ts
|