@vaudience/aigentflow-flow-validator 0.22.0 → 0.23.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 CHANGED
@@ -4,7 +4,31 @@ This document maps every rule in this JavaScript validator back to the AIgentFlo
4
4
  Go reference implementation, records the intentional divergences, and defines the
5
5
  discipline for keeping the two in sync.
6
6
 
7
- **Tracks AIgentFlow flow schema: `v2.828.0`** (`SPEC_VERSION` in [`src/spec/aigentflow-spec.json`](./src/spec/aigentflow-spec.json)), which adds the rule `flow_data_key_reserved` (package 0.22.0, below). 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.
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
+
9
+ > package 0.23.0 — **`unknown_function_operation`**, in step with the reference (AIgentFlow
10
+ > v2.831.0; `specVersion` 2.831.0). One new **error**: a step or loop sub-step whose executor
11
+ > is `function://<a>/<b>` where `<a>/<b>` is not one of the 32 registered built-ins. Before
12
+ > this, `function://utils/noop` validated and then failed every run with "function
13
+ > 'utils/noop' not found".
14
+ >
15
+ > | Rule (reference) | Code | Severity | Module |
16
+ > | -------------------------------------------- | ---------------------------- | -------- | ---------------------------------------------------- |
17
+ > | `validateFunctionOperationKnown` (parser.go) | `unknown_function_operation` | error | `validators/executors.ts`, spec `functionOperations` |
18
+ >
19
+ > - **Operation** = authority + `/` + path of the executor URL (so `binary-processor/validate-and-store`).
20
+ > The set is closed and sorted in spec `functionOperations.operations`. Templated executors
21
+ > (containing `{{`), unparsable URLs (`invalid_executor_url` owns them) and other schemes are
22
+ > skipped; the scheme match is exact and case-sensitive.
23
+ > - **Finding.** Field `steps.<id>.executor` (loop sub-step: `steps.<id>.loop.steps[i].executor`,
24
+ > step id `<id>.<sub>`); message `step "<id>" uses function://<op>, which is not a built-in
25
+ function. Available: <sorted list, ", ">`; the suggestion carries the list.
26
+ > - **Divergence 23** (below): the reference tolerates this code on its load door.
27
+ > - **`unknown_executor_scheme` text (still a warning).** For `decide` the message appends
28
+ > `Decision models run through ai://{provider}/decide (for example ai://openai/decide), not
29
+ through a scheme of their own.`; for any other unknown scheme with a known scheme within
30
+ > edit distance 2 (case-insensitive; the closest, ties to the alphabetically first) it
31
+ > appends `Did you mean '<scheme>'?`. No verdict changes.
8
32
 
9
33
  > package 0.22.0 — **`flow_data_key_reserved`**, in step with the reference (AIgentFlow
10
34
  > v2.828.0; `specVersion` 2.828.0). One new **error**: a key of the top-level `data:` map
@@ -1110,6 +1134,7 @@ Comparison contract: **error `code` + `valid` verdict**, not message wording. Th
1110
1134
  | `tool_discovery` vocabulary, mock scenario `delay`, empty `output:` entry | `validateToolDiscoveryVocabulary`, `validateMockScenarioDelays`, `validateOutputParameters` (parser.go) | `validators/saveDoor.ts` | `validate.test.ts`, `conformance.test.ts` |
1111
1135
  | `executor_config` `${NAME}` references outside the key's scope (`executor_config_env_scope`) | `ValidateExecutorConfigEnvScopes` (parser.go) | `validators/executorConfigEnv.ts` | `v0150-save-door.test.ts`, `conformance.test.ts` |
1112
1136
  | A step or loop sub-step `query:` declaring a server-owned key (`server_owned_query_key`) | `validateNoServerOwnedStepQueryKeys` (validation.go), `IsServerOwnedParamKey` | `validators/serverOwnedQueryKeys.ts` | `v0151-save-door.test.ts`, `conformance.test.ts` |
1137
+ | A `function://` operation outside the built-in set (`unknown_function_operation`) | `validateFunctionOperationKnown` (parser.go) | `validators/executors.ts` | `v0230-function-operation.test.ts`, `conformance.test.ts` |
1113
1138
  | A top-level `data:` key naming engine state (`flow_data_key_reserved`) | `FlowDataKeyReservation`, `validateFlowDataKeysAreNotReserved` (validation.flowdata.go) | `validators/flowDataKeys.ts` | `v0220-flow-data-keys.test.ts`, `conformance.test.ts` |
1114
1139
  | An endpoint a server-supplied credential will never be sent to (`credential_endpoint_unpaired`, warning) | `validateCredentialEndpointPairing`, `validateFamilyCredentialEndpointPairing` (validation.go) | `validators/credentialEndpoint.ts`, `validators/goUrl.ts` | `v0152-credential-endpoint.test.ts`, `conformance.test.ts` |
1115
1140
  | The `examples:` block: 58 codes, one walker (ids, inputs, references, expectations, checkpoints, variants) | the examples walker (`ValidateExamples`) | `validators/examples.ts`, `validators/goUrl.ts` | `examples.test.ts`, `conformance.test.ts` |
@@ -1380,6 +1405,11 @@ number their shared divergences alike._
1380
1405
  - The recursion bound of 32 on a nested value is the reference's own and is not in the
1381
1406
  spec.
1382
1407
 
1408
+ 23. **`unknown_function_operation` is tolerated on the reference's load door (0.23.0).** The
1409
+ reference refuses an unknown `function://` operation when a flow is _saved_ but loads
1410
+ (and then fails at run time) a stored flow that already carries one. This validator
1411
+ answers "would this save", so it is an error here, as for every other save-door rule.
1412
+
1383
1413
  ---
1384
1414
 
1385
1415
  ## The examples block
package/README.md CHANGED
@@ -116,7 +116,7 @@ Low-level: parse YAML into `{ flow?, parseErrors, parseWarnings }` without valid
116
116
 
117
117
  ### `SPEC_VERSION: string`
118
118
 
119
- The AIgentFlow flow-schema version this build tracks (e.g. `"2.828.0"`).
119
+ The AIgentFlow flow-schema version this build tracks (e.g. `"2.831.0"`).
120
120
 
121
121
  ### Options
122
122
 
package/dist/cli.js CHANGED
@@ -438,7 +438,7 @@ function validateBasicStructure(flow, issues) {
438
438
  message: "Executor is required for each step",
439
439
  code: "missing_required_field",
440
440
  stepId: stepID,
441
- suggestion: "Specify an executor URL (e.g., 'function://demo/processor')"
441
+ suggestion: "Specify an executor URL (e.g., 'function://utils/log')"
442
442
  });
443
443
  } else if (executorText === null) {
444
444
  issues.error({
@@ -455,7 +455,7 @@ function validateBasicStructure(flow, issues) {
455
455
  // src/spec/aigentflow-spec.json
456
456
  var aigentflow_spec_default = {
457
457
  $comment: "Vendored enum surface mirrored from the AIgentFlow Go reference implementation. Regenerate / review on every flow-YAML schema change. See PARITY.md. Values here are public protocol/type names only.",
458
- specVersion: "2.828.0",
458
+ specVersion: "2.831.0",
459
459
  inputSchemaVersion: 1,
460
460
  executorSchemes: [
461
461
  "a2a",
@@ -965,6 +965,47 @@ var aigentflow_spec_default = {
965
965
  stepFieldFormat: "steps.%s.query.%s",
966
966
  loopStepFieldFormat: "steps.%s.loop.steps.%s.query.%s"
967
967
  },
968
+ functionOperations: {
969
+ $comment: `unknown_function_operation (ERROR): a step or loop sub-step whose executor is function://<authority>/<path> where authority+"/"+path is not in operations. Templated executors (containing templateActionOpen), unparsable URLs (invalid_executor_url owns them) and other schemes are skipped; the scheme match is exact and case-sensitive, as in the reference. The message is messageFormat with the stepId, the operation, then operations joined by ', '. Reference: validateFunctionOperationKnown (parser.go), functionSchemaOperations.`,
970
+ code: "unknown_function_operation",
971
+ scheme: "function",
972
+ messageFormat: "step %s uses function://%s, which is not a built-in function. Available: %s",
973
+ suggestionFormat: "Use one of the built-in function operations: %s. For custom logic use a script://, http:// or ai:// executor instead.",
974
+ operations: [
975
+ "binary-processor/validate-and-store",
976
+ "data/create",
977
+ "data/from-json",
978
+ "data/to-json",
979
+ "data/validate",
980
+ "demo/adult-processor",
981
+ "demo/calculator",
982
+ "demo/data-processor",
983
+ "demo/finalizer",
984
+ "demo/senior-processor",
985
+ "demo/youth-processor",
986
+ "document/finalize",
987
+ "document/prepare",
988
+ "json/validate",
989
+ "math/basic",
990
+ "math/calculate",
991
+ "math/sum",
992
+ "results-compiler/aggregate",
993
+ "system/info",
994
+ "text/analysis",
995
+ "text/count-characters",
996
+ "text/count-lines",
997
+ "text/count-occurrences",
998
+ "text/count-words",
999
+ "text/find-pattern",
1000
+ "text/generate",
1001
+ "text/replace-text",
1002
+ "text/search",
1003
+ "utils/datetime",
1004
+ "utils/format-date",
1005
+ "utils/log",
1006
+ "utils/parse-date"
1007
+ ]
1008
+ },
968
1009
  flowDataReservedKeys: {
969
1010
  $comment: "flow_data_key_reserved (ERROR, no loop demotion): a key of the top-level data: map that names engine state. data: is seeded into the mission's .data namespace, where the engine keeps its own bookkeeping, so a flow declaring such a key would forge it. Matching is exact and case-sensitive. A key is reserved when it is one of exact (each with its reason), or starts with one of prefixes (checked in order, first wins; each with its reason). The finding's field is fieldFormat with the key; the message is messageFormat with the key (JSON-quoted, as Go's %q) then the reason; the suggestion is verbatim. Findings are sorted by key. Reference: FlowDataKeyReservation, validateFlowDataKeysAreNotReserved (aigentflow#247 review).",
970
1011
  code: "flow_data_key_reserved",
@@ -2080,9 +2121,51 @@ var TEMPLATE_DEFAULT_PIPED = {
2080
2121
  messageFormat: aigentflow_spec_default.templateDefaultPiped.messageFormat,
2081
2122
  suggestion: aigentflow_spec_default.templateDefaultPiped.suggestion
2082
2123
  };
2124
+ var FUNCTION_OPERATIONS = {
2125
+ code: aigentflow_spec_default.functionOperations.code,
2126
+ scheme: aigentflow_spec_default.functionOperations.scheme,
2127
+ messageFormat: aigentflow_spec_default.functionOperations.messageFormat,
2128
+ suggestionFormat: aigentflow_spec_default.functionOperations.suggestionFormat,
2129
+ operations: new Set(aigentflow_spec_default.functionOperations.operations),
2130
+ sorted: aigentflow_spec_default.functionOperations.operations
2131
+ };
2083
2132
 
2084
2133
  // src/validators/executors.ts
2085
2134
  var SCHEME_SEPARATOR = "://";
2135
+ var UNKNOWN_SCHEME_BASE = (scheme) => `Unknown executor scheme '${scheme}'. It may be valid in a newer AIgentFlow release, or it could be a typo.`;
2136
+ var DECIDE_SCHEME = "decide";
2137
+ var DECIDE_HINT = "Decision models run through ai://{provider}/decide (for example ai://openai/decide), not through a scheme of their own.";
2138
+ var MAX_SCHEME_EDIT_DISTANCE = 2;
2139
+ function editDistance(a, b) {
2140
+ let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
2141
+ for (let i = 1; i <= a.length; i++) {
2142
+ const cur = [i];
2143
+ for (let j = 1; j <= b.length; j++) {
2144
+ cur[j] = Math.min(
2145
+ prev[j] + 1,
2146
+ cur[j - 1] + 1,
2147
+ prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
2148
+ );
2149
+ }
2150
+ prev = cur;
2151
+ }
2152
+ return prev[b.length];
2153
+ }
2154
+ function unknownSchemeMessage(scheme) {
2155
+ const lower = scheme.toLowerCase();
2156
+ const base = UNKNOWN_SCHEME_BASE(scheme);
2157
+ if (lower === DECIDE_SCHEME) return `${base} ${DECIDE_HINT}`;
2158
+ let best;
2159
+ let bestDist = MAX_SCHEME_EDIT_DISTANCE + 1;
2160
+ for (const known of [...EXECUTOR_SCHEMES].sort()) {
2161
+ const d = editDistance(lower, known);
2162
+ if (d < bestDist) {
2163
+ best = known;
2164
+ bestDist = d;
2165
+ }
2166
+ }
2167
+ return best === void 0 ? base : `${base} Did you mean '${best}'?`;
2168
+ }
2086
2169
  function checkExecutor(executor, field, stepID, issues) {
2087
2170
  const sepIdx = executor.indexOf(SCHEME_SEPARATOR);
2088
2171
  const scheme = sepIdx > 0 ? executor.slice(0, sepIdx) : "";
@@ -2101,11 +2184,28 @@ function checkExecutor(executor, field, stepID, issues) {
2101
2184
  if (!EXECUTOR_SCHEMES.has(scheme.toLowerCase())) {
2102
2185
  issues.warn({
2103
2186
  field,
2104
- message: `Unknown executor scheme '${scheme}'. It may be valid in a newer AIgentFlow release, or it could be a typo.`,
2187
+ message: unknownSchemeMessage(scheme),
2105
2188
  code: "unknown_executor_scheme",
2106
2189
  stepId: stepID
2107
2190
  });
2191
+ return;
2108
2192
  }
2193
+ checkFunctionOperation(executor, field, stepID, templated, issues);
2194
+ }
2195
+ function checkFunctionOperation(executor, field, stepID, templated, issues) {
2196
+ if (templated) return;
2197
+ const m = EXECUTOR_URL_PATTERN.exec(executor);
2198
+ if (m === null || m[1] !== FUNCTION_OPERATIONS.scheme) return;
2199
+ const op = `${m[2]}/${m[3]}`;
2200
+ if (FUNCTION_OPERATIONS.operations.has(op)) return;
2201
+ const list = FUNCTION_OPERATIONS.sorted.join(", ");
2202
+ issues.error({
2203
+ field,
2204
+ message: FUNCTION_OPERATIONS.messageFormat.replace("%s", () => JSON.stringify(stepID)).replace("%s", () => op).replace("%s", () => list),
2205
+ code: FUNCTION_OPERATIONS.code,
2206
+ stepId: stepID,
2207
+ suggestion: FUNCTION_OPERATIONS.suggestionFormat.replace("%s", () => list)
2208
+ });
2109
2209
  }
2110
2210
  function validateExecutors(flow, issues) {
2111
2211
  const steps = flow.steps;
package/dist/index.cjs CHANGED
@@ -435,7 +435,7 @@ function validateBasicStructure(flow, issues) {
435
435
  message: "Executor is required for each step",
436
436
  code: "missing_required_field",
437
437
  stepId: stepID,
438
- suggestion: "Specify an executor URL (e.g., 'function://demo/processor')"
438
+ suggestion: "Specify an executor URL (e.g., 'function://utils/log')"
439
439
  });
440
440
  } else if (executorText === null) {
441
441
  issues.error({
@@ -451,7 +451,7 @@ function validateBasicStructure(flow, issues) {
451
451
 
452
452
  // src/spec/aigentflow-spec.json
453
453
  var aigentflow_spec_default = {
454
- specVersion: "2.828.0",
454
+ specVersion: "2.831.0",
455
455
  inputSchemaVersion: 1,
456
456
  executorSchemes: [
457
457
  "a2a",
@@ -948,6 +948,46 @@ var aigentflow_spec_default = {
948
948
  stepFieldFormat: "steps.%s.query.%s",
949
949
  loopStepFieldFormat: "steps.%s.loop.steps.%s.query.%s"
950
950
  },
951
+ functionOperations: {
952
+ code: "unknown_function_operation",
953
+ scheme: "function",
954
+ messageFormat: "step %s uses function://%s, which is not a built-in function. Available: %s",
955
+ suggestionFormat: "Use one of the built-in function operations: %s. For custom logic use a script://, http:// or ai:// executor instead.",
956
+ operations: [
957
+ "binary-processor/validate-and-store",
958
+ "data/create",
959
+ "data/from-json",
960
+ "data/to-json",
961
+ "data/validate",
962
+ "demo/adult-processor",
963
+ "demo/calculator",
964
+ "demo/data-processor",
965
+ "demo/finalizer",
966
+ "demo/senior-processor",
967
+ "demo/youth-processor",
968
+ "document/finalize",
969
+ "document/prepare",
970
+ "json/validate",
971
+ "math/basic",
972
+ "math/calculate",
973
+ "math/sum",
974
+ "results-compiler/aggregate",
975
+ "system/info",
976
+ "text/analysis",
977
+ "text/count-characters",
978
+ "text/count-lines",
979
+ "text/count-occurrences",
980
+ "text/count-words",
981
+ "text/find-pattern",
982
+ "text/generate",
983
+ "text/replace-text",
984
+ "text/search",
985
+ "utils/datetime",
986
+ "utils/format-date",
987
+ "utils/log",
988
+ "utils/parse-date"
989
+ ]
990
+ },
951
991
  flowDataReservedKeys: {
952
992
  code: "flow_data_key_reserved",
953
993
  fieldFormat: "data.%s",
@@ -1982,9 +2022,51 @@ var TEMPLATE_DEFAULT_PIPED = {
1982
2022
  messageFormat: aigentflow_spec_default.templateDefaultPiped.messageFormat,
1983
2023
  suggestion: aigentflow_spec_default.templateDefaultPiped.suggestion
1984
2024
  };
2025
+ var FUNCTION_OPERATIONS = {
2026
+ code: aigentflow_spec_default.functionOperations.code,
2027
+ scheme: aigentflow_spec_default.functionOperations.scheme,
2028
+ messageFormat: aigentflow_spec_default.functionOperations.messageFormat,
2029
+ suggestionFormat: aigentflow_spec_default.functionOperations.suggestionFormat,
2030
+ operations: new Set(aigentflow_spec_default.functionOperations.operations),
2031
+ sorted: aigentflow_spec_default.functionOperations.operations
2032
+ };
1985
2033
 
1986
2034
  // src/validators/executors.ts
1987
2035
  var SCHEME_SEPARATOR = "://";
2036
+ var UNKNOWN_SCHEME_BASE = (scheme) => `Unknown executor scheme '${scheme}'. It may be valid in a newer AIgentFlow release, or it could be a typo.`;
2037
+ var DECIDE_SCHEME = "decide";
2038
+ var DECIDE_HINT = "Decision models run through ai://{provider}/decide (for example ai://openai/decide), not through a scheme of their own.";
2039
+ var MAX_SCHEME_EDIT_DISTANCE = 2;
2040
+ function editDistance(a, b) {
2041
+ let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
2042
+ for (let i = 1; i <= a.length; i++) {
2043
+ const cur = [i];
2044
+ for (let j = 1; j <= b.length; j++) {
2045
+ cur[j] = Math.min(
2046
+ prev[j] + 1,
2047
+ cur[j - 1] + 1,
2048
+ prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
2049
+ );
2050
+ }
2051
+ prev = cur;
2052
+ }
2053
+ return prev[b.length];
2054
+ }
2055
+ function unknownSchemeMessage(scheme) {
2056
+ const lower = scheme.toLowerCase();
2057
+ const base = UNKNOWN_SCHEME_BASE(scheme);
2058
+ if (lower === DECIDE_SCHEME) return `${base} ${DECIDE_HINT}`;
2059
+ let best;
2060
+ let bestDist = MAX_SCHEME_EDIT_DISTANCE + 1;
2061
+ for (const known of [...EXECUTOR_SCHEMES].sort()) {
2062
+ const d = editDistance(lower, known);
2063
+ if (d < bestDist) {
2064
+ best = known;
2065
+ bestDist = d;
2066
+ }
2067
+ }
2068
+ return best === void 0 ? base : `${base} Did you mean '${best}'?`;
2069
+ }
1988
2070
  function checkExecutor(executor, field, stepID, issues) {
1989
2071
  const sepIdx = executor.indexOf(SCHEME_SEPARATOR);
1990
2072
  const scheme = sepIdx > 0 ? executor.slice(0, sepIdx) : "";
@@ -2003,11 +2085,28 @@ function checkExecutor(executor, field, stepID, issues) {
2003
2085
  if (!EXECUTOR_SCHEMES.has(scheme.toLowerCase())) {
2004
2086
  issues.warn({
2005
2087
  field,
2006
- message: `Unknown executor scheme '${scheme}'. It may be valid in a newer AIgentFlow release, or it could be a typo.`,
2088
+ message: unknownSchemeMessage(scheme),
2007
2089
  code: "unknown_executor_scheme",
2008
2090
  stepId: stepID
2009
2091
  });
2092
+ return;
2010
2093
  }
2094
+ checkFunctionOperation(executor, field, stepID, templated, issues);
2095
+ }
2096
+ function checkFunctionOperation(executor, field, stepID, templated, issues) {
2097
+ if (templated) return;
2098
+ const m = EXECUTOR_URL_PATTERN.exec(executor);
2099
+ if (m === null || m[1] !== FUNCTION_OPERATIONS.scheme) return;
2100
+ const op = `${m[2]}/${m[3]}`;
2101
+ if (FUNCTION_OPERATIONS.operations.has(op)) return;
2102
+ const list = FUNCTION_OPERATIONS.sorted.join(", ");
2103
+ issues.error({
2104
+ field,
2105
+ message: FUNCTION_OPERATIONS.messageFormat.replace("%s", () => JSON.stringify(stepID)).replace("%s", () => op).replace("%s", () => list),
2106
+ code: FUNCTION_OPERATIONS.code,
2107
+ stepId: stepID,
2108
+ suggestion: FUNCTION_OPERATIONS.suggestionFormat.replace("%s", () => list)
2109
+ });
2011
2110
  }
2012
2111
  function validateExecutors(flow, issues) {
2013
2112
  const steps = flow.steps;