@vaudience/aigentflow-flow-validator 0.15.3 → 0.19.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 +191 -6
- package/README.md +2 -2
- package/dist/cli.js +1592 -89
- package/dist/index.cjs +1526 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +1526 -88
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -436,7 +436,7 @@ function validateBasicStructure(flow, issues) {
|
|
|
436
436
|
// src/spec/aigentflow-spec.json
|
|
437
437
|
var aigentflow_spec_default = {
|
|
438
438
|
$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.",
|
|
439
|
-
specVersion: "2.
|
|
439
|
+
specVersion: "2.820.0",
|
|
440
440
|
inputSchemaVersion: 1,
|
|
441
441
|
executorSchemes: [
|
|
442
442
|
"a2a",
|
|
@@ -467,7 +467,6 @@ var aigentflow_spec_default = {
|
|
|
467
467
|
"messenger",
|
|
468
468
|
"mock",
|
|
469
469
|
"mqtt",
|
|
470
|
-
"nexus",
|
|
471
470
|
"opencorporates",
|
|
472
471
|
"openmeteo",
|
|
473
472
|
"overpass",
|
|
@@ -770,12 +769,6 @@ var aigentflow_spec_default = {
|
|
|
770
769
|
fields: ["api_key", "base_url", "organization"],
|
|
771
770
|
extraKey: "extra",
|
|
772
771
|
scopes: {
|
|
773
|
-
aigentchat: [
|
|
774
|
-
"AIGENTFLOW_AIGENTCHAT_API_KEY",
|
|
775
|
-
"AIGENTFLOW_AIGENTCHAT_BASE_URL",
|
|
776
|
-
"AIGENTFLOW_NEXUS_API_KEY",
|
|
777
|
-
"AIGENTFLOW_NEXUS_BASE_URL"
|
|
778
|
-
],
|
|
779
772
|
anthropic: [
|
|
780
773
|
"AIGENTFLOW_ANTHROPIC_API_KEY",
|
|
781
774
|
"AIGENTFLOW_ANTHROPIC_BASE_URL"
|
|
@@ -883,12 +876,6 @@ var aigentflow_spec_default = {
|
|
|
883
876
|
"AIGENTFLOW_MQTT_PASSWORD",
|
|
884
877
|
"AIGENTFLOW_MQTT_USERNAME"
|
|
885
878
|
],
|
|
886
|
-
nexus: [
|
|
887
|
-
"AIGENTFLOW_AIGENTCHAT_API_KEY",
|
|
888
|
-
"AIGENTFLOW_AIGENTCHAT_BASE_URL",
|
|
889
|
-
"AIGENTFLOW_NEXUS_API_KEY",
|
|
890
|
-
"AIGENTFLOW_NEXUS_BASE_URL"
|
|
891
|
-
],
|
|
892
879
|
ollama: [
|
|
893
880
|
"AIGENTFLOW_OLLAMA_API_KEY",
|
|
894
881
|
"AIGENTFLOW_OLLAMA_BASE_URL"
|
|
@@ -959,7 +946,7 @@ var aigentflow_spec_default = {
|
|
|
959
946
|
loopStepFieldFormat: "steps.%s.loop.steps.%s.query.%s"
|
|
960
947
|
},
|
|
961
948
|
credentialEndpointPairing: {
|
|
962
|
-
$comment: "credential_endpoint_unpaired (WARNING, never an error): the one statically certain shape of AIgentFlow's run-time credential/endpoint pairing refusal. ai: an ai://<provider>/... step (top-level or loop sub-step) whose query holds a non-empty string <provider>+providerBaseUrlParamSuffix while neither its query (genericKeyParam or <provider>+providerKeyParamSuffix, non-empty strings) nor executor_config.<provider>.api_key (a literal, not an env reference) supplies a key; and executor_config.<provider>.base_url for a provider in executorConfigProviders when it is a literal, the block has no literal api_key, and it is not the case that at least one step uses the provider and every such step brings its own query key. keylessProviders are exempt from both. families: a step whose executor resolves (
|
|
949
|
+
$comment: "credential_endpoint_unpaired (WARNING, never an error): the one statically certain shape of AIgentFlow's run-time credential/endpoint pairing refusal. ai: an ai://<provider>/... step (top-level or loop sub-step) whose query holds a non-empty string <provider>+providerBaseUrlParamSuffix while neither its query (genericKeyParam or <provider>+providerKeyParamSuffix, non-empty strings) nor executor_config.<provider>.api_key (a literal, not an env reference) supplies a key; and executor_config.<provider>.base_url for a provider in executorConfigProviders when it is a literal, the block has no literal api_key, and it is not the case that at least one step uses the provider and every such step brings its own query key. keylessProviders are exempt from both. families: a step whose executor resolves (the key protocol+familyKeySeparator+driver, else protocol) to a row. implicitServerCredential rows are never judged. A row warns when its secret (the first non-empty string secretParam in the query, else per configKey in order: executor_config api_key when configApiKeyParam is a secretParam, else the first non-empty string secretParam under extra) is exactly an env reference to a serverEnv name on a row with expandsServerEnvReferences, and its endpoint (the first non-empty string endpointParam in the query, else per configKey: base_url when configBaseUrlParam is an endpointParam, else the first non-empty string endpointParam under extra) is a literal: no templateActionOpen, not an env reference, and not of the same origin as a defaultEndpoint (lower-cased scheme://host:port, defaultPorts filled). serverEnv is the reference's per-family server-variable set, evaluated. Field formats are the reference's. Reference: validateCredentialEndpointPairing (DC-FORGE-231) and validateFamilyCredentialEndpointPairing (DC-FORGE-233).",
|
|
963
950
|
stepFieldFormat: "steps.%s.query.%s",
|
|
964
951
|
loopStepFieldFormat: "steps.%s.loop.steps.%s.query.%s",
|
|
965
952
|
executorConfigBaseUrlFieldFormat: "executor_config.%s.base_url",
|
|
@@ -1010,9 +997,6 @@ var aigentflow_spec_default = {
|
|
|
1010
997
|
]
|
|
1011
998
|
},
|
|
1012
999
|
familyKeySeparator: "/",
|
|
1013
|
-
protocolAliases: {
|
|
1014
|
-
aigentchat: "nexus"
|
|
1015
|
-
},
|
|
1016
1000
|
families: {
|
|
1017
1001
|
db: {
|
|
1018
1002
|
protocol: "db",
|
|
@@ -1171,40 +1155,6 @@ var aigentflow_spec_default = {
|
|
|
1171
1155
|
"AIGENTFLOW_MQTT_PASSWORD"
|
|
1172
1156
|
]
|
|
1173
1157
|
},
|
|
1174
|
-
nexus: {
|
|
1175
|
-
protocol: "nexus",
|
|
1176
|
-
configKeys: [
|
|
1177
|
-
"nexus",
|
|
1178
|
-
"aigentchat"
|
|
1179
|
-
],
|
|
1180
|
-
secretParams: [
|
|
1181
|
-
"aigentchat_api_key",
|
|
1182
|
-
"api_key"
|
|
1183
|
-
],
|
|
1184
|
-
endpointParams: [
|
|
1185
|
-
"aigentchat_base_url",
|
|
1186
|
-
"base_url"
|
|
1187
|
-
],
|
|
1188
|
-
defaultEndpoints: [],
|
|
1189
|
-
configApiKeyParam: "",
|
|
1190
|
-
configBaseUrlParam: "",
|
|
1191
|
-
implicitServerCredential: false,
|
|
1192
|
-
expandsServerEnvReferences: false,
|
|
1193
|
-
serverEnv: [
|
|
1194
|
-
"AIGENTFLOW_NEXUS_BASE_URL",
|
|
1195
|
-
"AIGENTFLOW_NEXUS_API_KEY",
|
|
1196
|
-
"AIGENTFLOW_AIGENTCHAT_API_KEY",
|
|
1197
|
-
"AIGENTFLOW_AIGENTCHAT_BASE_URL"
|
|
1198
|
-
],
|
|
1199
|
-
storedKeyShape: {
|
|
1200
|
-
ownKeyParams: [
|
|
1201
|
-
"aigentchat_api_key",
|
|
1202
|
-
"api_key"
|
|
1203
|
-
],
|
|
1204
|
-
ownCredentialsMapParam: "credentials",
|
|
1205
|
-
provider: "aigentchat"
|
|
1206
|
-
}
|
|
1207
|
-
},
|
|
1208
1158
|
opencorporates: {
|
|
1209
1159
|
protocol: "opencorporates",
|
|
1210
1160
|
configKeys: [
|
|
@@ -1325,6 +1275,160 @@ var aigentflow_spec_default = {
|
|
|
1325
1275
|
}
|
|
1326
1276
|
}
|
|
1327
1277
|
},
|
|
1278
|
+
examples: {
|
|
1279
|
+
$comment: "The examples: block. Derived from the reference's constants, never hand-typed: limits, vocabularies, the credential-shaped-literal pattern set and each code's severity. example_ref_blocked_host is listed for completeness but is raised by the reference implementation only; the ports never raise it. See PARITY.md, 'The examples block'.",
|
|
1280
|
+
limits: {
|
|
1281
|
+
maxPerFlow: 50,
|
|
1282
|
+
maxBlockBytes: 262144,
|
|
1283
|
+
maxInlineInputBytes: 32768,
|
|
1284
|
+
maxReferenceTextBytes: 32768,
|
|
1285
|
+
maxVariants: 10,
|
|
1286
|
+
maxTags: 8,
|
|
1287
|
+
maxMustNot: 12,
|
|
1288
|
+
maxMustNotContain: 20,
|
|
1289
|
+
maxRubricRunes: 4e3,
|
|
1290
|
+
maxGuidanceRunes: 2e3,
|
|
1291
|
+
maxNotesRunes: 2e3,
|
|
1292
|
+
titleMaxRunes: 80,
|
|
1293
|
+
maxMustNotRunes: 300,
|
|
1294
|
+
maxMustNotContainLength: 200,
|
|
1295
|
+
maxProvenanceRunes: 200,
|
|
1296
|
+
maxNoteRunes: 300,
|
|
1297
|
+
maxUrlBytes: 2048,
|
|
1298
|
+
maxPatternLength: 512,
|
|
1299
|
+
weightMax: 10,
|
|
1300
|
+
minScoreMin: 0,
|
|
1301
|
+
minScoreMax: 1
|
|
1302
|
+
},
|
|
1303
|
+
idPattern: "^[a-z][a-z0-9_]{1,47}$",
|
|
1304
|
+
tagPattern: "^[a-z][a-z0-9_-]{0,31}$",
|
|
1305
|
+
sha256Pattern: "^[0-9a-f]{64}$",
|
|
1306
|
+
mediaTypePattern: "^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$",
|
|
1307
|
+
signedUrlQueryKeyPattern: "^(x-amz-signature|signature|sig|token|access_token|key|x-goog-signature|se|sp)$",
|
|
1308
|
+
origins: [
|
|
1309
|
+
"author",
|
|
1310
|
+
"synthetic"
|
|
1311
|
+
],
|
|
1312
|
+
sideEffects: [
|
|
1313
|
+
"refuse",
|
|
1314
|
+
"allow"
|
|
1315
|
+
],
|
|
1316
|
+
statuses: [
|
|
1317
|
+
"completed",
|
|
1318
|
+
"failed",
|
|
1319
|
+
"paused_for_human"
|
|
1320
|
+
],
|
|
1321
|
+
reservedRefSchemes: [
|
|
1322
|
+
"artifact",
|
|
1323
|
+
"trove",
|
|
1324
|
+
"aiv",
|
|
1325
|
+
"s3",
|
|
1326
|
+
"gs"
|
|
1327
|
+
],
|
|
1328
|
+
secretPatterns: [
|
|
1329
|
+
{
|
|
1330
|
+
name: "an API key",
|
|
1331
|
+
expr: "\\bsk-[A-Za-z0-9_-]{20,}",
|
|
1332
|
+
flags: ""
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
name: "a platform API key",
|
|
1336
|
+
expr: "\\bcrn_[A-Za-z0-9]{16,}",
|
|
1337
|
+
flags: ""
|
|
1338
|
+
},
|
|
1339
|
+
{
|
|
1340
|
+
name: "an AWS access key",
|
|
1341
|
+
expr: "\\bAKIA[0-9A-Z]{16}\\b",
|
|
1342
|
+
flags: ""
|
|
1343
|
+
},
|
|
1344
|
+
{
|
|
1345
|
+
name: "a private key",
|
|
1346
|
+
expr: "-----BEGIN [A-Z ]*PRIVATE KEY-----",
|
|
1347
|
+
flags: ""
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
name: "a bearer token",
|
|
1351
|
+
expr: "\\bbearer\\s+[A-Za-z0-9._~+/-]{20,}",
|
|
1352
|
+
flags: "i"
|
|
1353
|
+
},
|
|
1354
|
+
{
|
|
1355
|
+
name: "a Slack token",
|
|
1356
|
+
expr: "\\bxox[abprs]-[A-Za-z0-9-]{10,}",
|
|
1357
|
+
flags: ""
|
|
1358
|
+
},
|
|
1359
|
+
{
|
|
1360
|
+
name: "a GitHub token",
|
|
1361
|
+
expr: "\\bgh[pousr]_[A-Za-z0-9]{30,}",
|
|
1362
|
+
flags: ""
|
|
1363
|
+
},
|
|
1364
|
+
{
|
|
1365
|
+
name: "a retired registry key",
|
|
1366
|
+
expr: "\\baivk_[A-Za-z0-9]{20,}",
|
|
1367
|
+
flags: ""
|
|
1368
|
+
}
|
|
1369
|
+
],
|
|
1370
|
+
codes: {
|
|
1371
|
+
example_checkpoint_status: "error",
|
|
1372
|
+
example_checkpoint_step_composite: "error",
|
|
1373
|
+
example_checkpoint_step_unknown: "error",
|
|
1374
|
+
example_expectation_contradiction: "warning",
|
|
1375
|
+
example_expected_conflict: "error",
|
|
1376
|
+
example_expected_empty: "error",
|
|
1377
|
+
example_expected_field_unchecked: "warning",
|
|
1378
|
+
example_expected_field_unknown: "error",
|
|
1379
|
+
example_expected_missing: "error",
|
|
1380
|
+
example_field_matcher_invalid: "error",
|
|
1381
|
+
example_file_input_needs_ref: "error",
|
|
1382
|
+
example_guidance_missing: "warning",
|
|
1383
|
+
example_guidance_too_long: "error",
|
|
1384
|
+
example_holdout_in_public_flow: "error",
|
|
1385
|
+
example_id_duplicate: "error",
|
|
1386
|
+
example_id_invalid: "error",
|
|
1387
|
+
example_id_missing: "error",
|
|
1388
|
+
example_input_conflict: "error",
|
|
1389
|
+
example_input_invalid: "error",
|
|
1390
|
+
example_input_missing: "error",
|
|
1391
|
+
example_input_too_large: "error",
|
|
1392
|
+
example_input_unvalidated: "warning",
|
|
1393
|
+
example_min_score_range: "error",
|
|
1394
|
+
example_must_not_contain_invalid: "error",
|
|
1395
|
+
example_must_not_invalid: "error",
|
|
1396
|
+
example_notes_too_long: "error",
|
|
1397
|
+
example_ref_blocked_host: "error",
|
|
1398
|
+
example_ref_invalid: "error",
|
|
1399
|
+
example_ref_media_type_invalid: "error",
|
|
1400
|
+
example_ref_scheme: "error",
|
|
1401
|
+
example_ref_scheme_reserved: "error",
|
|
1402
|
+
example_ref_sha256_invalid: "error",
|
|
1403
|
+
example_ref_signed_url: "warning",
|
|
1404
|
+
example_ref_unpinned: "warning",
|
|
1405
|
+
example_reference_binary: "warning",
|
|
1406
|
+
example_reference_invalid: "error",
|
|
1407
|
+
example_rubric_empty: "error",
|
|
1408
|
+
example_rubric_too_long: "error",
|
|
1409
|
+
example_secret_like_value: "error",
|
|
1410
|
+
example_secret_value: "error",
|
|
1411
|
+
example_side_effects_invalid: "error",
|
|
1412
|
+
example_status_invalid: "error",
|
|
1413
|
+
example_tag_invalid: "error",
|
|
1414
|
+
example_title_missing: "error",
|
|
1415
|
+
example_title_too_long: "error",
|
|
1416
|
+
example_variant_empty: "error",
|
|
1417
|
+
example_variant_id_duplicate: "error",
|
|
1418
|
+
example_variant_id_invalid: "error",
|
|
1419
|
+
example_variant_input_conflict: "error",
|
|
1420
|
+
example_variant_input_invalid: "error",
|
|
1421
|
+
example_variant_origin_invalid: "error",
|
|
1422
|
+
example_variant_unchecked: "warning",
|
|
1423
|
+
example_variants_too_many: "error",
|
|
1424
|
+
example_weight_range: "error",
|
|
1425
|
+
examples_all_held_out: "warning",
|
|
1426
|
+
examples_block_too_large: "error",
|
|
1427
|
+
examples_published_with_public_flow: "warning",
|
|
1428
|
+
examples_too_many: "error",
|
|
1429
|
+
examples_unreadable_by_judge: "warning"
|
|
1430
|
+
}
|
|
1431
|
+
},
|
|
1328
1432
|
knownKeys: {
|
|
1329
1433
|
$comment: "Derived by reflection over the reference's Flow type, following yaml.v3's field rules (yaml tag, lowercased field name, inline structs merged). KnownFields(true) refuses any key a struct type does not declare. A shape is scalar | any | map<S> | list<S> | a type name below. any is never inspected (a Go interface, or a type with its own unmarshaller). How it is derived and checked, and how to regenerate it: PARITY.md, 'Unknown keys and value kinds'.",
|
|
1330
1434
|
root: "Flow",
|
|
@@ -1346,6 +1450,17 @@ var aigentflow_spec_default = {
|
|
|
1346
1450
|
on_children_complete: "scalar",
|
|
1347
1451
|
shared_memory: "scalar"
|
|
1348
1452
|
},
|
|
1453
|
+
Checkpoint: {
|
|
1454
|
+
exact: "map<any>",
|
|
1455
|
+
fields: "map<FieldMatcher>",
|
|
1456
|
+
min_score: "scalar",
|
|
1457
|
+
must_not: "list<scalar>",
|
|
1458
|
+
must_not_contain: "list<scalar>",
|
|
1459
|
+
optional: "scalar",
|
|
1460
|
+
reference: "ExampleReference",
|
|
1461
|
+
rubric: "scalar",
|
|
1462
|
+
status: "scalar"
|
|
1463
|
+
},
|
|
1349
1464
|
CheckpointConfig: {
|
|
1350
1465
|
enabled: "scalar",
|
|
1351
1466
|
interval_steps: "scalar",
|
|
@@ -1368,6 +1483,37 @@ var aigentflow_spec_default = {
|
|
|
1368
1483
|
retry_delay: "scalar",
|
|
1369
1484
|
retry_on: "list<scalar>"
|
|
1370
1485
|
},
|
|
1486
|
+
ExampleDefinition: {
|
|
1487
|
+
checkpoints: "map<Checkpoint>",
|
|
1488
|
+
expected: "Expectation",
|
|
1489
|
+
guidance: "scalar",
|
|
1490
|
+
hold_out: "scalar",
|
|
1491
|
+
id: "scalar",
|
|
1492
|
+
input: "map<any>",
|
|
1493
|
+
input_ref: "FileRef",
|
|
1494
|
+
min_score: "scalar",
|
|
1495
|
+
notes: "scalar",
|
|
1496
|
+
side_effects: "scalar",
|
|
1497
|
+
tags: "list<scalar>",
|
|
1498
|
+
title: "scalar",
|
|
1499
|
+
variants: "list<ExampleVariant>",
|
|
1500
|
+
weight: "scalar"
|
|
1501
|
+
},
|
|
1502
|
+
ExampleReference: {
|
|
1503
|
+
ref: "FileRef",
|
|
1504
|
+
text: "scalar"
|
|
1505
|
+
},
|
|
1506
|
+
ExampleVariant: {
|
|
1507
|
+
expected: "Expectation",
|
|
1508
|
+
guidance: "scalar",
|
|
1509
|
+
id: "scalar",
|
|
1510
|
+
input_patch: "map<any>",
|
|
1511
|
+
input_ref: "FileRef",
|
|
1512
|
+
origin: "scalar",
|
|
1513
|
+
provenance: "scalar",
|
|
1514
|
+
title: "scalar",
|
|
1515
|
+
weight: "scalar"
|
|
1516
|
+
},
|
|
1371
1517
|
ExecutorDefaultsDefinition: {
|
|
1372
1518
|
frequency_penalty: "scalar",
|
|
1373
1519
|
max_tokens: "scalar",
|
|
@@ -1390,6 +1536,16 @@ var aigentflow_spec_default = {
|
|
|
1390
1536
|
allowed_models: "list<scalar>",
|
|
1391
1537
|
allowed_providers: "list<scalar>"
|
|
1392
1538
|
},
|
|
1539
|
+
Expectation: {
|
|
1540
|
+
exact: "map<any>",
|
|
1541
|
+
fields: "map<FieldMatcher>",
|
|
1542
|
+
min_score: "scalar",
|
|
1543
|
+
must_not: "list<scalar>",
|
|
1544
|
+
must_not_contain: "list<scalar>",
|
|
1545
|
+
reference: "ExampleReference",
|
|
1546
|
+
rubric: "scalar",
|
|
1547
|
+
status: "scalar"
|
|
1548
|
+
},
|
|
1393
1549
|
FailoverBreakerConfig: {
|
|
1394
1550
|
failure_threshold: "scalar",
|
|
1395
1551
|
open_duration: "scalar",
|
|
@@ -1408,6 +1564,22 @@ var aigentflow_spec_default = {
|
|
|
1408
1564
|
type: "scalar",
|
|
1409
1565
|
validation: "scalar"
|
|
1410
1566
|
},
|
|
1567
|
+
FieldMatcher: {
|
|
1568
|
+
approx: "scalar",
|
|
1569
|
+
contains: "scalar",
|
|
1570
|
+
equals: "any",
|
|
1571
|
+
matches: "scalar",
|
|
1572
|
+
not_contains: "scalar",
|
|
1573
|
+
one_of: "list<any>",
|
|
1574
|
+
present: "scalar",
|
|
1575
|
+
tolerance: "scalar"
|
|
1576
|
+
},
|
|
1577
|
+
FileRef: {
|
|
1578
|
+
media_type: "scalar",
|
|
1579
|
+
note: "scalar",
|
|
1580
|
+
sha256: "scalar",
|
|
1581
|
+
url: "scalar"
|
|
1582
|
+
},
|
|
1411
1583
|
Flow: {
|
|
1412
1584
|
aigentflow_version: "scalar",
|
|
1413
1585
|
billing: "BillingConfig",
|
|
@@ -1421,6 +1593,7 @@ var aigentflow_spec_default = {
|
|
|
1421
1593
|
discoverable_tools: "list<scalar>",
|
|
1422
1594
|
display_name: "scalar",
|
|
1423
1595
|
error_strategy: "ErrorStrategyDefinition",
|
|
1596
|
+
examples: "list<ExampleDefinition>",
|
|
1424
1597
|
executor_config: "map<ExecutorInstanceDefinition>",
|
|
1425
1598
|
exons_uploads: "ExonsUploadsFlowConfig",
|
|
1426
1599
|
expression_functions: "list<map<scalar>>",
|
|
@@ -1498,6 +1671,7 @@ var aigentflow_spec_default = {
|
|
|
1498
1671
|
pattern: "scalar",
|
|
1499
1672
|
required: "scalar",
|
|
1500
1673
|
type: "scalar",
|
|
1674
|
+
untrusted: "scalar",
|
|
1501
1675
|
visible_when: "VisibleWhenPredicate"
|
|
1502
1676
|
},
|
|
1503
1677
|
LoopDefinition: {
|
|
@@ -1507,6 +1681,7 @@ var aigentflow_spec_default = {
|
|
|
1507
1681
|
while: "scalar"
|
|
1508
1682
|
},
|
|
1509
1683
|
LoopStepDefinition: {
|
|
1684
|
+
allow_untrusted: "list<scalar>",
|
|
1510
1685
|
condition: "scalar",
|
|
1511
1686
|
credential: "scalar",
|
|
1512
1687
|
credentials: "map<StepCredentialBinding>",
|
|
@@ -1519,7 +1694,8 @@ var aigentflow_spec_default = {
|
|
|
1519
1694
|
post_processing: "list<any>",
|
|
1520
1695
|
pre_processing: "list<any>",
|
|
1521
1696
|
query: "map<any>",
|
|
1522
|
-
response_evaluation: "scalar"
|
|
1697
|
+
response_evaluation: "scalar",
|
|
1698
|
+
trusted_output: "scalar"
|
|
1523
1699
|
},
|
|
1524
1700
|
MockBinaryAsset: {
|
|
1525
1701
|
filename: "scalar",
|
|
@@ -1550,6 +1726,7 @@ var aigentflow_spec_default = {
|
|
|
1550
1726
|
deny_tools: "list<scalar>",
|
|
1551
1727
|
discoverable_tools: "list<scalar>",
|
|
1552
1728
|
enable_signals: "scalar",
|
|
1729
|
+
enforce_region: "scalar",
|
|
1553
1730
|
engram_scope: "scalar",
|
|
1554
1731
|
exons: "scalar",
|
|
1555
1732
|
human_question_timeout: "scalar",
|
|
@@ -1558,6 +1735,7 @@ var aigentflow_spec_default = {
|
|
|
1558
1735
|
max_tool_turns: "scalar",
|
|
1559
1736
|
max_turns: "scalar",
|
|
1560
1737
|
mode: "scalar",
|
|
1738
|
+
region: "scalar",
|
|
1561
1739
|
require_approval_for: "list<scalar>",
|
|
1562
1740
|
timeout_seconds: "scalar",
|
|
1563
1741
|
tool_discovery: "scalar",
|
|
@@ -1607,6 +1785,7 @@ var aigentflow_spec_default = {
|
|
|
1607
1785
|
properties: "map<PropertyDefinition>",
|
|
1608
1786
|
required: "scalar",
|
|
1609
1787
|
type: "scalar",
|
|
1788
|
+
untrusted: "scalar",
|
|
1610
1789
|
validation: "scalar"
|
|
1611
1790
|
},
|
|
1612
1791
|
StepCredentialBinding: {
|
|
@@ -1614,6 +1793,7 @@ var aigentflow_spec_default = {
|
|
|
1614
1793
|
source: "scalar"
|
|
1615
1794
|
},
|
|
1616
1795
|
StepDefinition: {
|
|
1796
|
+
allow_untrusted: "list<scalar>",
|
|
1617
1797
|
condition: "scalar",
|
|
1618
1798
|
credential: "scalar",
|
|
1619
1799
|
credentials: "map<StepCredentialBinding>",
|
|
@@ -1631,7 +1811,8 @@ var aigentflow_spec_default = {
|
|
|
1631
1811
|
quality_gate: "QualityGateDefinition",
|
|
1632
1812
|
query: "map<any>",
|
|
1633
1813
|
response_evaluation: "scalar",
|
|
1634
|
-
response_expectation: "map<FieldExpectationDefinition>"
|
|
1814
|
+
response_expectation: "map<FieldExpectationDefinition>",
|
|
1815
|
+
trusted_output: "scalar"
|
|
1635
1816
|
},
|
|
1636
1817
|
ThrottleDefinition: {
|
|
1637
1818
|
batch_delay: "scalar",
|
|
@@ -1760,9 +1941,27 @@ var CREDENTIAL_ENDPOINT_PAIRING = {
|
|
|
1760
1941
|
executorConfigProviders: aigentflow_spec_default.credentialEndpointPairing.ai.executorConfigProviders
|
|
1761
1942
|
},
|
|
1762
1943
|
familyKeySeparator: aigentflow_spec_default.credentialEndpointPairing.familyKeySeparator,
|
|
1763
|
-
protocolAliases: aigentflow_spec_default.credentialEndpointPairing.protocolAliases,
|
|
1764
1944
|
families: aigentflow_spec_default.credentialEndpointPairing.families
|
|
1765
1945
|
};
|
|
1946
|
+
var EXAMPLES = {
|
|
1947
|
+
limits: aigentflow_spec_default.examples.limits,
|
|
1948
|
+
idPattern: new RegExp(aigentflow_spec_default.examples.idPattern),
|
|
1949
|
+
tagPattern: new RegExp(aigentflow_spec_default.examples.tagPattern),
|
|
1950
|
+
sha256Pattern: new RegExp(aigentflow_spec_default.examples.sha256Pattern),
|
|
1951
|
+
mediaTypePattern: new RegExp(aigentflow_spec_default.examples.mediaTypePattern),
|
|
1952
|
+
// The reference writes this pattern case-insensitively (`(?i)`); the spec
|
|
1953
|
+
// carries the bare expression, so the flag is applied here.
|
|
1954
|
+
signedUrlQueryKeyPattern: new RegExp(aigentflow_spec_default.examples.signedUrlQueryKeyPattern, "i"),
|
|
1955
|
+
origins: new Set(aigentflow_spec_default.examples.origins),
|
|
1956
|
+
sideEffects: new Set(aigentflow_spec_default.examples.sideEffects),
|
|
1957
|
+
statuses: new Set(aigentflow_spec_default.examples.statuses),
|
|
1958
|
+
reservedRefSchemes: new Set(aigentflow_spec_default.examples.reservedRefSchemes),
|
|
1959
|
+
secretPatterns: aigentflow_spec_default.examples.secretPatterns.map((p) => ({
|
|
1960
|
+
name: p.name,
|
|
1961
|
+
re: new RegExp(p.expr, p.flags)
|
|
1962
|
+
})),
|
|
1963
|
+
codes: aigentflow_spec_default.examples.codes
|
|
1964
|
+
};
|
|
1766
1965
|
|
|
1767
1966
|
// src/validators/executors.ts
|
|
1768
1967
|
var SCHEME_SEPARATOR = "://";
|
|
@@ -2960,6 +3159,17 @@ function validateOrchestratorCampaign(flow, issues, opts) {
|
|
|
2960
3159
|
code: "orchestrator_owner_needs_yield"
|
|
2961
3160
|
});
|
|
2962
3161
|
}
|
|
3162
|
+
if (orch.enforce_region === true) {
|
|
3163
|
+
const region = issues.stringOf(orch, "region", "orchestrator");
|
|
3164
|
+
if (region === null || region.trim() === "") {
|
|
3165
|
+
issues.error({
|
|
3166
|
+
field: "orchestrator.enforce_region",
|
|
3167
|
+
message: "orchestrator: enforce_region requires region (name the region the model must run in, e.g. region: eu)",
|
|
3168
|
+
code: "orchestrator_enforce_region_needs_region",
|
|
3169
|
+
suggestion: "Add `region: eu` (or the region the model must run in), or drop enforce_region"
|
|
3170
|
+
});
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
2963
3173
|
if (Array.isArray(orch.triggers)) {
|
|
2964
3174
|
orch.triggers.forEach((rawTrigger, i) => {
|
|
2965
3175
|
if (!isRecord(rawTrigger)) return;
|
|
@@ -3231,29 +3441,29 @@ function validateField(f, schemaPath, i, seen, issues) {
|
|
|
3231
3441
|
});
|
|
3232
3442
|
}
|
|
3233
3443
|
const stringy = INPUT_SCHEMA.stringTypes.has(type);
|
|
3234
|
-
const
|
|
3444
|
+
const mismatch2 = (constraint) => issues.error({
|
|
3235
3445
|
field: `${base}.${constraint}`,
|
|
3236
3446
|
message: `constraint '${constraint}' is not meaningful for field '${shownName}' of type '${type}'`,
|
|
3237
3447
|
code: "input_schema_constraint_type_mismatch"
|
|
3238
3448
|
});
|
|
3239
3449
|
if (!stringy) {
|
|
3240
|
-
if (f.min_length !== void 0)
|
|
3241
|
-
if (f.max_length !== void 0)
|
|
3242
|
-
if (pattern !== null && pattern !== "")
|
|
3450
|
+
if (f.min_length !== void 0) mismatch2("min_length");
|
|
3451
|
+
if (f.max_length !== void 0) mismatch2("max_length");
|
|
3452
|
+
if (pattern !== null && pattern !== "") mismatch2("pattern");
|
|
3243
3453
|
}
|
|
3244
3454
|
if (type !== TYPE_NUMBER) {
|
|
3245
|
-
if (f.min !== void 0)
|
|
3246
|
-
if (f.max !== void 0)
|
|
3455
|
+
if (f.min !== void 0) mismatch2("min");
|
|
3456
|
+
if (f.max !== void 0) mismatch2("max");
|
|
3247
3457
|
}
|
|
3248
3458
|
if (type !== TYPE_ARRAY_OF_STRINGS) {
|
|
3249
|
-
if (f.min_items !== void 0)
|
|
3250
|
-
if (f.max_items !== void 0)
|
|
3459
|
+
if (f.min_items !== void 0) mismatch2("min_items");
|
|
3460
|
+
if (f.max_items !== void 0) mismatch2("max_items");
|
|
3251
3461
|
}
|
|
3252
3462
|
if (type !== TYPE_FILE) {
|
|
3253
|
-
if (Array.isArray(f.accept) && f.accept.length > 0)
|
|
3254
|
-
if (f.max_size !== void 0)
|
|
3463
|
+
if (Array.isArray(f.accept) && f.accept.length > 0) mismatch2("accept");
|
|
3464
|
+
if (f.max_size !== void 0) mismatch2("max_size");
|
|
3255
3465
|
}
|
|
3256
|
-
if (type !== TYPE_ENUM && Array.isArray(f.enum) && f.enum.length > 0)
|
|
3466
|
+
if (type !== TYPE_ENUM && Array.isArray(f.enum) && f.enum.length > 0) mismatch2("enum");
|
|
3257
3467
|
const cap = (constraint, val) => issues.error({
|
|
3258
3468
|
field: `${base}.${constraint}`,
|
|
3259
3469
|
message: `constraint '${constraint}' (${val}) for field '${shownName}' exceeds the maximum of ${INPUT_SCHEMA.maxConstraintValue}`,
|
|
@@ -4535,7 +4745,8 @@ function parseHost(host) {
|
|
|
4535
4745
|
function parseAuthority(authority) {
|
|
4536
4746
|
const at = authority.lastIndexOf("@");
|
|
4537
4747
|
const host = parseHost(at < 0 ? authority : authority.slice(at + 1));
|
|
4538
|
-
if (host === null
|
|
4748
|
+
if (host === null) return null;
|
|
4749
|
+
if (at < 0) return { host, hasUser: false };
|
|
4539
4750
|
const userinfo = authority.slice(0, at);
|
|
4540
4751
|
if (!validUserinfo(userinfo)) return null;
|
|
4541
4752
|
const colon = userinfo.indexOf(":");
|
|
@@ -4543,7 +4754,7 @@ function parseAuthority(authority) {
|
|
|
4543
4754
|
for (const part of parts) {
|
|
4544
4755
|
if (unescapeText(part, Mode.UserPassword) === null) return null;
|
|
4545
4756
|
}
|
|
4546
|
-
return host;
|
|
4757
|
+
return { host, hasUser: true };
|
|
4547
4758
|
}
|
|
4548
4759
|
function getScheme(raw) {
|
|
4549
4760
|
for (let i = 0; i < raw.length; i++) {
|
|
@@ -4569,28 +4780,37 @@ function containsCTL(s) {
|
|
|
4569
4780
|
return false;
|
|
4570
4781
|
}
|
|
4571
4782
|
function goParseSchemeHost(rawURL) {
|
|
4783
|
+
const u = goParseURLParts(rawURL);
|
|
4784
|
+
return u === null ? null : { scheme: u.scheme, host: u.host };
|
|
4785
|
+
}
|
|
4786
|
+
function goParseURLParts(rawURL) {
|
|
4572
4787
|
const hash = rawURL.indexOf("#");
|
|
4573
4788
|
const main2 = hash < 0 ? rawURL : rawURL.slice(0, hash);
|
|
4574
4789
|
const frag = hash < 0 ? "" : rawURL.slice(hash + 1);
|
|
4575
4790
|
if (containsCTL(main2)) return null;
|
|
4576
|
-
if (main2 === "*")
|
|
4791
|
+
if (main2 === "*") {
|
|
4792
|
+
return { scheme: "", host: "", hasUser: false, rawQuery: "", fragment: frag };
|
|
4793
|
+
}
|
|
4577
4794
|
const split = getScheme(main2);
|
|
4578
4795
|
if (split === null) return null;
|
|
4579
4796
|
const scheme = split.scheme.toLowerCase();
|
|
4580
4797
|
let rest = split.rest;
|
|
4581
4798
|
const questionMarks = rest.split("?").length - 1;
|
|
4799
|
+
let rawQuery = "";
|
|
4582
4800
|
if (rest.endsWith("?") && questionMarks === 1) {
|
|
4583
4801
|
rest = rest.slice(0, -1);
|
|
4584
4802
|
} else if (questionMarks > 0) {
|
|
4803
|
+
rawQuery = rest.slice(rest.indexOf("?") + 1);
|
|
4585
4804
|
rest = rest.slice(0, rest.indexOf("?"));
|
|
4586
4805
|
}
|
|
4587
4806
|
if (!rest.startsWith("/")) {
|
|
4588
|
-
if (scheme !== "") return finish({ scheme, host: "" }, frag);
|
|
4807
|
+
if (scheme !== "") return finish({ scheme, host: "", hasUser: false, rawQuery }, frag);
|
|
4589
4808
|
const slash = rest.indexOf("/");
|
|
4590
4809
|
const segment = slash < 0 ? rest : rest.slice(0, slash);
|
|
4591
4810
|
if (segment.includes(":")) return null;
|
|
4592
4811
|
}
|
|
4593
4812
|
let host = "";
|
|
4813
|
+
let hasUser = false;
|
|
4594
4814
|
if ((scheme !== "" || !rest.startsWith("///")) && rest.startsWith("//")) {
|
|
4595
4815
|
let authority = rest.slice(2);
|
|
4596
4816
|
rest = "";
|
|
@@ -4601,14 +4821,26 @@ function goParseSchemeHost(rawURL) {
|
|
|
4601
4821
|
}
|
|
4602
4822
|
const parsed = parseAuthority(authority);
|
|
4603
4823
|
if (parsed === null) return null;
|
|
4604
|
-
host = parsed;
|
|
4824
|
+
host = parsed.host;
|
|
4825
|
+
hasUser = parsed.hasUser;
|
|
4605
4826
|
}
|
|
4606
4827
|
if (unescapeText(rest, Mode.Path) === null) return null;
|
|
4607
|
-
return finish({ scheme, host }, frag);
|
|
4828
|
+
return finish({ scheme, host, hasUser, rawQuery }, frag);
|
|
4608
4829
|
}
|
|
4609
4830
|
function finish(u, frag) {
|
|
4610
4831
|
if (frag !== "" && unescapeText(frag, Mode.Fragment) === null) return null;
|
|
4611
|
-
return u;
|
|
4832
|
+
return { ...u, fragment: frag };
|
|
4833
|
+
}
|
|
4834
|
+
function goQueryKeys(rawQuery) {
|
|
4835
|
+
const keys = [];
|
|
4836
|
+
for (const pair of rawQuery.split("&")) {
|
|
4837
|
+
if (pair === "" || pair.includes(";")) continue;
|
|
4838
|
+
const eq = pair.indexOf("=");
|
|
4839
|
+
const rawKey = eq < 0 ? pair : pair.slice(0, eq);
|
|
4840
|
+
const key = unescapeText(rawKey.replace(/\+/g, " "), Mode.Path);
|
|
4841
|
+
if (key !== null) keys.push(key);
|
|
4842
|
+
}
|
|
4843
|
+
return keys;
|
|
4612
4844
|
}
|
|
4613
4845
|
function goEndpointOrigin(raw, defaultPorts, protocolSeparator) {
|
|
4614
4846
|
const trimmed = goTrimSpace(raw);
|
|
@@ -4745,12 +4977,10 @@ function warnStoredShape(issues, field, provider) {
|
|
|
4745
4977
|
function familyOf(executor) {
|
|
4746
4978
|
const sep = executor.indexOf(CREDENTIAL_ENDPOINT_PAIRING.protocolSeparator);
|
|
4747
4979
|
if (sep <= 0) return null;
|
|
4748
|
-
|
|
4980
|
+
const protocol = executor.slice(0, sep);
|
|
4749
4981
|
const rest = executor.slice(sep + CREDENTIAL_ENDPOINT_PAIRING.protocolSeparator.length);
|
|
4750
4982
|
const slash = rest.indexOf("/");
|
|
4751
4983
|
const driver = slash < 0 ? rest : rest.slice(0, slash);
|
|
4752
|
-
const alias = own(CREDENTIAL_ENDPOINT_PAIRING.protocolAliases, protocol);
|
|
4753
|
-
if (typeof alias === "string") protocol = alias;
|
|
4754
4984
|
const candidates = driver === "" ? [protocol] : [protocol + CREDENTIAL_ENDPOINT_PAIRING.familyKeySeparator + driver, protocol];
|
|
4755
4985
|
for (const key of candidates) {
|
|
4756
4986
|
const family = own(CREDENTIAL_ENDPOINT_PAIRING.families, key);
|
|
@@ -4830,24 +5060,11 @@ function familyAuthorEndpoint(flow, family, step, issues) {
|
|
|
4830
5060
|
}
|
|
4831
5061
|
return null;
|
|
4832
5062
|
}
|
|
4833
|
-
function storedShapeHasOwnKey(family, query) {
|
|
4834
|
-
const shape = family.storedKeyShape;
|
|
4835
|
-
if (shape === void 0) return false;
|
|
4836
|
-
if (shape.ownKeyParams.some((key) => nonEmptyString(query, key) !== null)) return true;
|
|
4837
|
-
return isRecord(own(query, shape.ownCredentialsMapParam));
|
|
4838
|
-
}
|
|
4839
5063
|
function validateFamilies(flow, steps, issues) {
|
|
4840
5064
|
for (const step of steps) {
|
|
4841
5065
|
const found = familyOf(step.executor);
|
|
4842
5066
|
if (found === null || found.family.implicitServerCredential) continue;
|
|
4843
5067
|
const { key: familyKey, family } = found;
|
|
4844
|
-
if (family.storedKeyShape !== void 0) {
|
|
4845
|
-
if (storedShapeHasOwnKey(family, step.query)) continue;
|
|
4846
|
-
const key = family.endpointParams.find((k) => nonEmptyEndpoint(step.query, k) !== null);
|
|
4847
|
-
if (key !== void 0)
|
|
4848
|
-
warnStoredShape(issues, step.field(key), family.storedKeyShape.provider);
|
|
4849
|
-
continue;
|
|
4850
|
-
}
|
|
4851
5068
|
const secret = familySecret(flow, family, step, issues);
|
|
4852
5069
|
if (secret === null || secret.envName === null) continue;
|
|
4853
5070
|
const endpointField = familyAuthorEndpoint(flow, family, step, issues);
|
|
@@ -5085,6 +5302,1291 @@ function warnOrchestratorToolWithheld(flow, orch, report, issues) {
|
|
|
5085
5302
|
}
|
|
5086
5303
|
}
|
|
5087
5304
|
|
|
5305
|
+
// src/validators/examples.ts
|
|
5306
|
+
var KEY_EXAMPLES = "examples";
|
|
5307
|
+
var VISIBILITY_PUBLIC = "public";
|
|
5308
|
+
var STATUS_COMPLETED = "completed";
|
|
5309
|
+
var STATUS_FAILED = "failed";
|
|
5310
|
+
var STATUS_PAUSED_FOR_HUMAN = "paused_for_human";
|
|
5311
|
+
var ORIGIN_NONE = "";
|
|
5312
|
+
var SIDE_EFFECTS_NONE = "";
|
|
5313
|
+
var SCHEME_HTTPS = "https";
|
|
5314
|
+
var FILE_REF_KEYS = /* @__PURE__ */ new Set(["url", "sha256", "media_type", "note"]);
|
|
5315
|
+
var FILE_REF_KEY = "ref";
|
|
5316
|
+
var TYPE_SECRET = "secret";
|
|
5317
|
+
var TYPE_FILE2 = "file";
|
|
5318
|
+
var TYPE_STRING = "string";
|
|
5319
|
+
var TYPE_MULTILINE = "multiline";
|
|
5320
|
+
var TYPE_NUMBER2 = "number";
|
|
5321
|
+
var TYPE_BOOL = "bool";
|
|
5322
|
+
var TYPE_ENUM2 = "enum";
|
|
5323
|
+
var TYPE_ARRAY_OF_STRINGS2 = "array_of_strings";
|
|
5324
|
+
var TYPE_DATE = "date";
|
|
5325
|
+
var MAX_RECURSION_DEPTH = 32;
|
|
5326
|
+
var TEXTUAL_MEDIA_BASES = /* @__PURE__ */ new Set([
|
|
5327
|
+
"application/json",
|
|
5328
|
+
"application/xml",
|
|
5329
|
+
"application/yaml",
|
|
5330
|
+
"application/x-yaml",
|
|
5331
|
+
"application/markdown"
|
|
5332
|
+
]);
|
|
5333
|
+
var SUGGESTION_ID = "Pick a short stable name such as standard_invoice; it is the key results are filed under, so it never changes once saved";
|
|
5334
|
+
var SUGGESTION_CHECKPOINT = "Did you rename the step? Update the checkpoint to the new name";
|
|
5335
|
+
var SUGGESTION_SECRET_VALUE = "Remove the value; you are asked for it when you run the check";
|
|
5336
|
+
var SUGGESTION_EXPECTED = "Add at least a rubric: one or two sentences on what a good result looks like";
|
|
5337
|
+
var utf8Encoder2 = new TextEncoder();
|
|
5338
|
+
function byteLen(s) {
|
|
5339
|
+
return utf8Encoder2.encode(s).length;
|
|
5340
|
+
}
|
|
5341
|
+
function runeCount(s) {
|
|
5342
|
+
let n = 0;
|
|
5343
|
+
for (const _ of s) n++;
|
|
5344
|
+
return n;
|
|
5345
|
+
}
|
|
5346
|
+
function q(s) {
|
|
5347
|
+
let out = '"';
|
|
5348
|
+
for (const ch of s) {
|
|
5349
|
+
const c = ch.codePointAt(0);
|
|
5350
|
+
if (ch === '"') out += '\\"';
|
|
5351
|
+
else if (ch === "\\") out += "\\\\";
|
|
5352
|
+
else if (c === 7) out += "\\a";
|
|
5353
|
+
else if (c === 8) out += "\\b";
|
|
5354
|
+
else if (c === 12) out += "\\f";
|
|
5355
|
+
else if (c === 10) out += "\\n";
|
|
5356
|
+
else if (c === 13) out += "\\r";
|
|
5357
|
+
else if (c === 9) out += "\\t";
|
|
5358
|
+
else if (c === 11) out += "\\v";
|
|
5359
|
+
else if (c < 32 || c === 127) out += `\\x${c.toString(16).padStart(2, "0")}`;
|
|
5360
|
+
else if (c >= 128 && /[\p{C}\p{Z}]/u.test(ch)) {
|
|
5361
|
+
out += c > 65535 ? `\\U${c.toString(16).padStart(8, "0")}` : `\\u${c.toString(16).padStart(4, "0")}`;
|
|
5362
|
+
} else out += ch;
|
|
5363
|
+
}
|
|
5364
|
+
return `${out}"`;
|
|
5365
|
+
}
|
|
5366
|
+
function num(n) {
|
|
5367
|
+
if (Number.isNaN(n)) return "NaN";
|
|
5368
|
+
if (n === Infinity) return "+Inf";
|
|
5369
|
+
if (n === -Infinity) return "-Inf";
|
|
5370
|
+
if (n === 0) return Object.is(n, -0) ? "-0" : "0";
|
|
5371
|
+
const [mantissa = "", exponent = "0"] = n.toExponential().split("e");
|
|
5372
|
+
const exp = Number(exponent);
|
|
5373
|
+
if (exp < -4 || exp >= 6) {
|
|
5374
|
+
const sign = exp < 0 ? "-" : "+";
|
|
5375
|
+
return `${mantissa}e${sign}${String(Math.abs(exp)).padStart(2, "0")}`;
|
|
5376
|
+
}
|
|
5377
|
+
return String(n);
|
|
5378
|
+
}
|
|
5379
|
+
function has(o, key) {
|
|
5380
|
+
return Object.prototype.hasOwnProperty.call(o, key) && o[key] !== null && o[key] !== void 0;
|
|
5381
|
+
}
|
|
5382
|
+
function own2(o, key) {
|
|
5383
|
+
return Object.prototype.hasOwnProperty.call(o, key) ? o[key] : void 0;
|
|
5384
|
+
}
|
|
5385
|
+
function setOwn(o, key, value) {
|
|
5386
|
+
Object.defineProperty(o, key, {
|
|
5387
|
+
value,
|
|
5388
|
+
enumerable: true,
|
|
5389
|
+
writable: true,
|
|
5390
|
+
configurable: true
|
|
5391
|
+
});
|
|
5392
|
+
}
|
|
5393
|
+
function sortedKeys(o) {
|
|
5394
|
+
return Object.keys(o).sort();
|
|
5395
|
+
}
|
|
5396
|
+
function numberOf(v) {
|
|
5397
|
+
return typeof v === "number" ? v : void 0;
|
|
5398
|
+
}
|
|
5399
|
+
function normalize(v, depth) {
|
|
5400
|
+
if (depth > MAX_RECURSION_DEPTH) return null;
|
|
5401
|
+
if (v instanceof Date) {
|
|
5402
|
+
if (Number.isNaN(v.getTime())) return null;
|
|
5403
|
+
const iso = v.toISOString();
|
|
5404
|
+
if (/T00:00:00\.000Z$/.test(iso)) return iso.slice(0, 10);
|
|
5405
|
+
return iso.replace(/\.000Z$/, "Z");
|
|
5406
|
+
}
|
|
5407
|
+
if (Array.isArray(v)) return v.map((x) => normalize(x, depth + 1));
|
|
5408
|
+
if (isRecord(v)) {
|
|
5409
|
+
const out = {};
|
|
5410
|
+
for (const k of Object.keys(v)) setOwn(out, k, normalize(v[k], depth + 1));
|
|
5411
|
+
return out;
|
|
5412
|
+
}
|
|
5413
|
+
return v;
|
|
5414
|
+
}
|
|
5415
|
+
function deepCopy(v, depth) {
|
|
5416
|
+
if (depth > MAX_RECURSION_DEPTH) return v;
|
|
5417
|
+
if (Array.isArray(v)) return v.map((x) => deepCopy(x, depth + 1));
|
|
5418
|
+
if (isRecord(v)) {
|
|
5419
|
+
const out = {};
|
|
5420
|
+
for (const k of Object.keys(v)) setOwn(out, k, deepCopy(v[k], depth + 1));
|
|
5421
|
+
return out;
|
|
5422
|
+
}
|
|
5423
|
+
return v;
|
|
5424
|
+
}
|
|
5425
|
+
function mergePatch(target, patch, depth) {
|
|
5426
|
+
if (depth > MAX_RECURSION_DEPTH) return;
|
|
5427
|
+
for (const k of Object.keys(patch)) {
|
|
5428
|
+
const pv = patch[k];
|
|
5429
|
+
if (pv === null || pv === void 0) {
|
|
5430
|
+
delete target[k];
|
|
5431
|
+
continue;
|
|
5432
|
+
}
|
|
5433
|
+
if (isRecord(pv)) {
|
|
5434
|
+
const current = own2(target, k);
|
|
5435
|
+
const existing = isRecord(current) ? deepCopy(current, 0) : {};
|
|
5436
|
+
mergePatch(existing, pv, depth + 1);
|
|
5437
|
+
setOwn(target, k, existing);
|
|
5438
|
+
continue;
|
|
5439
|
+
}
|
|
5440
|
+
setOwn(target, k, deepCopy(pv, 0));
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
function applyExamplePatch(base, patch) {
|
|
5444
|
+
const out = deepCopy(base, 0);
|
|
5445
|
+
mergePatch(out, patch, 0);
|
|
5446
|
+
return out;
|
|
5447
|
+
}
|
|
5448
|
+
function asFileRef(v) {
|
|
5449
|
+
if (!isRecord(v)) return null;
|
|
5450
|
+
const keys = Object.keys(v);
|
|
5451
|
+
if (keys.length === 0) return null;
|
|
5452
|
+
for (const k of keys) if (!FILE_REF_KEYS.has(k)) return null;
|
|
5453
|
+
const str = (k) => {
|
|
5454
|
+
const x = own2(v, k);
|
|
5455
|
+
return typeof x === "string" ? x : "";
|
|
5456
|
+
};
|
|
5457
|
+
return {
|
|
5458
|
+
url: str("url"),
|
|
5459
|
+
sha256: str("sha256"),
|
|
5460
|
+
mediaType: str("media_type"),
|
|
5461
|
+
note: str("note")
|
|
5462
|
+
};
|
|
5463
|
+
}
|
|
5464
|
+
function isTextualMediaType(mediaType) {
|
|
5465
|
+
const base = goToLower(goTrimSpace((mediaType.split(";")[0] ?? "").toString()));
|
|
5466
|
+
if (base.startsWith("text/")) return true;
|
|
5467
|
+
if (base.endsWith("+json") || base.endsWith("+xml")) return true;
|
|
5468
|
+
return TEXTUAL_MEDIA_BASES.has(base);
|
|
5469
|
+
}
|
|
5470
|
+
var Walker = class _Walker {
|
|
5471
|
+
constructor(flow, issues) {
|
|
5472
|
+
this.flow = flow;
|
|
5473
|
+
this.issues = issues;
|
|
5474
|
+
this.findings = [];
|
|
5475
|
+
}
|
|
5476
|
+
// -- emit -------------------------------------------------------------
|
|
5477
|
+
add(severity, code, field, message, suggestion) {
|
|
5478
|
+
this.findings.push({
|
|
5479
|
+
code,
|
|
5480
|
+
severity,
|
|
5481
|
+
field,
|
|
5482
|
+
message,
|
|
5483
|
+
...suggestion !== void 0 ? { suggestion } : {}
|
|
5484
|
+
});
|
|
5485
|
+
}
|
|
5486
|
+
err(code, field, message, suggestion) {
|
|
5487
|
+
this.add("error", code, field, message, suggestion);
|
|
5488
|
+
}
|
|
5489
|
+
warn(code, field, message) {
|
|
5490
|
+
this.add("warning", code, field, message);
|
|
5491
|
+
}
|
|
5492
|
+
/** A Go `string` field read by its source text; absent is "". */
|
|
5493
|
+
s(o, key, parentPath) {
|
|
5494
|
+
return this.issues.stringOf(o, key, parentPath) ?? "";
|
|
5495
|
+
}
|
|
5496
|
+
// -- the set ----------------------------------------------------------
|
|
5497
|
+
run(examples) {
|
|
5498
|
+
this.validateSet(examples);
|
|
5499
|
+
const seen = /* @__PURE__ */ new Map();
|
|
5500
|
+
examples.forEach((ex, i) => {
|
|
5501
|
+
if (isRecord(ex)) this.validateExample(i, ex, seen);
|
|
5502
|
+
});
|
|
5503
|
+
}
|
|
5504
|
+
validateSet(examples) {
|
|
5505
|
+
const n = examples.length;
|
|
5506
|
+
const L = EXAMPLES.limits;
|
|
5507
|
+
if (n > L.maxPerFlow) {
|
|
5508
|
+
this.err(
|
|
5509
|
+
"examples_too_many",
|
|
5510
|
+
KEY_EXAMPLES,
|
|
5511
|
+
`flow declares ${n} examples; the limit is ${L.maxPerFlow}`
|
|
5512
|
+
);
|
|
5513
|
+
}
|
|
5514
|
+
const size = blockBytes(examples);
|
|
5515
|
+
if (size !== null && size > L.maxBlockBytes) {
|
|
5516
|
+
this.err(
|
|
5517
|
+
"examples_block_too_large",
|
|
5518
|
+
KEY_EXAMPLES,
|
|
5519
|
+
`the examples block is ${size} bytes; the limit is ${L.maxBlockBytes}. Move large inputs or target outputs to a file reference (https URL).`
|
|
5520
|
+
);
|
|
5521
|
+
}
|
|
5522
|
+
const allHeld = examples.every((e) => isRecord(e) && own2(e, "hold_out") === true);
|
|
5523
|
+
if (allHeld) {
|
|
5524
|
+
this.warn(
|
|
5525
|
+
"examples_all_held_out",
|
|
5526
|
+
KEY_EXAMPLES,
|
|
5527
|
+
"every example is held out, so nothing is left to tune on"
|
|
5528
|
+
);
|
|
5529
|
+
}
|
|
5530
|
+
if (this.isPublic()) {
|
|
5531
|
+
this.warn(
|
|
5532
|
+
"examples_published_with_public_flow",
|
|
5533
|
+
KEY_EXAMPLES,
|
|
5534
|
+
"this flow is public, so everyone who can see it can read its examples, including their expected results. Only publish examples you are happy to show."
|
|
5535
|
+
);
|
|
5536
|
+
}
|
|
5537
|
+
}
|
|
5538
|
+
isPublic() {
|
|
5539
|
+
return this.s(this.flow, "visibility", "") === VISIBILITY_PUBLIC;
|
|
5540
|
+
}
|
|
5541
|
+
// -- one example ------------------------------------------------------
|
|
5542
|
+
validateExample(i, ex, seen) {
|
|
5543
|
+
const L = EXAMPLES.limits;
|
|
5544
|
+
const path = `examples[${i}]`;
|
|
5545
|
+
const id = this.s(ex, "id", path);
|
|
5546
|
+
const label = id === "" ? `#${i + 1}` : id;
|
|
5547
|
+
if (id === "") {
|
|
5548
|
+
this.err("example_id_missing", `${path}.id`, `example #${i + 1} has no id`, SUGGESTION_ID);
|
|
5549
|
+
} else if (!EXAMPLES.idPattern.test(id)) {
|
|
5550
|
+
this.err(
|
|
5551
|
+
"example_id_invalid",
|
|
5552
|
+
`${path}.id`,
|
|
5553
|
+
`example id ${q(id)} is not valid: use lower-case letters, digits and underscores, starting with a letter (2 to 48 characters)`,
|
|
5554
|
+
SUGGESTION_ID
|
|
5555
|
+
);
|
|
5556
|
+
} else {
|
|
5557
|
+
const first = seen.get(id);
|
|
5558
|
+
if (first !== void 0) {
|
|
5559
|
+
this.err(
|
|
5560
|
+
"example_id_duplicate",
|
|
5561
|
+
`${path}.id`,
|
|
5562
|
+
`example id ${q(id)} is used by examples #${first + 1} and #${i + 1}`
|
|
5563
|
+
);
|
|
5564
|
+
} else {
|
|
5565
|
+
seen.set(id, i);
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
const title = this.s(ex, "title", path);
|
|
5569
|
+
if (goTrimSpace(title) === "") {
|
|
5570
|
+
this.err(
|
|
5571
|
+
"example_title_missing",
|
|
5572
|
+
`${path}.title`,
|
|
5573
|
+
`example ${q(label)} has no title; the title is what a person sees in the list of examples`
|
|
5574
|
+
);
|
|
5575
|
+
} else {
|
|
5576
|
+
const n = runeCount(title);
|
|
5577
|
+
if (n > L.titleMaxRunes) {
|
|
5578
|
+
this.err(
|
|
5579
|
+
"example_title_too_long",
|
|
5580
|
+
`${path}.title`,
|
|
5581
|
+
`example ${q(label)} title is ${n} characters; the limit is ${L.titleMaxRunes}`
|
|
5582
|
+
);
|
|
5583
|
+
}
|
|
5584
|
+
}
|
|
5585
|
+
const guidance = this.s(ex, "guidance", path);
|
|
5586
|
+
if (goTrimSpace(guidance) === "") {
|
|
5587
|
+
this.warn(
|
|
5588
|
+
"example_guidance_missing",
|
|
5589
|
+
`${path}.guidance`,
|
|
5590
|
+
`example ${q(label)} has no guidance. One sentence on why this case matters helps the judge and the next author.`
|
|
5591
|
+
);
|
|
5592
|
+
} else {
|
|
5593
|
+
const n = runeCount(guidance);
|
|
5594
|
+
if (n > L.maxGuidanceRunes) {
|
|
5595
|
+
this.err(
|
|
5596
|
+
"example_guidance_too_long",
|
|
5597
|
+
`${path}.guidance`,
|
|
5598
|
+
`example ${q(label)} guidance is ${n} characters; the limit is ${L.maxGuidanceRunes}`
|
|
5599
|
+
);
|
|
5600
|
+
}
|
|
5601
|
+
}
|
|
5602
|
+
const notesN = runeCount(this.s(ex, "notes", path));
|
|
5603
|
+
if (notesN > L.maxNotesRunes) {
|
|
5604
|
+
this.err(
|
|
5605
|
+
"example_notes_too_long",
|
|
5606
|
+
`${path}.notes`,
|
|
5607
|
+
`example ${q(label)} notes are ${notesN} characters; the limit is ${L.maxNotesRunes}`
|
|
5608
|
+
);
|
|
5609
|
+
}
|
|
5610
|
+
this.validateTags(path, label, own2(ex, "tags"));
|
|
5611
|
+
this.validateWeight(`${path}.weight`, label, numberOf(own2(ex, "weight")));
|
|
5612
|
+
const minScore = numberOf(own2(ex, "min_score"));
|
|
5613
|
+
if (minScore !== void 0 && !scoreInRange(minScore)) {
|
|
5614
|
+
this.err(
|
|
5615
|
+
"example_min_score_range",
|
|
5616
|
+
`${path}.min_score`,
|
|
5617
|
+
`example ${q(label)} has min_score ${num(minScore)}; use a number from 0 to 1 (0.7 is 7.3 out of 10)`
|
|
5618
|
+
);
|
|
5619
|
+
}
|
|
5620
|
+
const sideEffects = this.s(ex, "side_effects", path);
|
|
5621
|
+
if (sideEffects !== SIDE_EFFECTS_NONE && !EXAMPLES.sideEffects.has(sideEffects)) {
|
|
5622
|
+
this.err(
|
|
5623
|
+
"example_side_effects_invalid",
|
|
5624
|
+
`${path}.side_effects`,
|
|
5625
|
+
`example ${q(label)} has side_effects ${q(sideEffects)}; use refuse or allow`
|
|
5626
|
+
);
|
|
5627
|
+
}
|
|
5628
|
+
const holdOut = own2(ex, "hold_out") === true;
|
|
5629
|
+
if (holdOut && this.isPublic()) {
|
|
5630
|
+
this.err(
|
|
5631
|
+
"example_holdout_in_public_flow",
|
|
5632
|
+
`${path}.hold_out`,
|
|
5633
|
+
`example ${q(label)} is held out but this flow is public: a held-out example must stay private, and a public flow's definition is readable by everyone. Make the flow private or remove hold_out.`
|
|
5634
|
+
);
|
|
5635
|
+
}
|
|
5636
|
+
const hasInput = has(ex, "input");
|
|
5637
|
+
const hasInputRef = has(ex, "input_ref");
|
|
5638
|
+
if (hasInput && hasInputRef) {
|
|
5639
|
+
this.err(
|
|
5640
|
+
"example_input_conflict",
|
|
5641
|
+
path,
|
|
5642
|
+
`example ${q(label)} sets both input and input_ref; use exactly one`
|
|
5643
|
+
);
|
|
5644
|
+
} else if (!hasInput && !hasInputRef) {
|
|
5645
|
+
this.err(
|
|
5646
|
+
"example_input_missing",
|
|
5647
|
+
path,
|
|
5648
|
+
`example ${q(label)} has neither input nor input_ref; use input: {} for a flow that takes no input`
|
|
5649
|
+
);
|
|
5650
|
+
}
|
|
5651
|
+
const rawInput = own2(ex, "input");
|
|
5652
|
+
const baseInput = isRecord(rawInput) ? rawInput : null;
|
|
5653
|
+
if (baseInput !== null) {
|
|
5654
|
+
this.checkInput(`${path}.input`, label, baseInput, "example_input_invalid");
|
|
5655
|
+
}
|
|
5656
|
+
const rawInputRef = own2(ex, "input_ref");
|
|
5657
|
+
if (isRecord(rawInputRef)) {
|
|
5658
|
+
this.validateFileRef(`${path}.input_ref`, this.fileRefOf(rawInputRef, `${path}.input_ref`));
|
|
5659
|
+
}
|
|
5660
|
+
let binaryOnly = false;
|
|
5661
|
+
const expected = own2(ex, "expected");
|
|
5662
|
+
if (!has(ex, "expected")) {
|
|
5663
|
+
this.err(
|
|
5664
|
+
"example_expected_missing",
|
|
5665
|
+
`${path}.expected`,
|
|
5666
|
+
`example ${q(label)} does not say what a good result is: add a rubric, fields, exact or reference`,
|
|
5667
|
+
SUGGESTION_EXPECTED
|
|
5668
|
+
);
|
|
5669
|
+
} else if (isRecord(expected)) {
|
|
5670
|
+
this.validateExpectation(`${path}.expected`, label, expected, "final", "");
|
|
5671
|
+
binaryOnly = this.expectationIsBinaryReferenceOnly(expected, `${path}.expected`);
|
|
5672
|
+
}
|
|
5673
|
+
if (binaryOnly) {
|
|
5674
|
+
this.warn(
|
|
5675
|
+
"examples_unreadable_by_judge",
|
|
5676
|
+
`${path}.expected`,
|
|
5677
|
+
"this example can be run but not scored"
|
|
5678
|
+
);
|
|
5679
|
+
}
|
|
5680
|
+
this.validateCheckpoints(path, label, own2(ex, "checkpoints"));
|
|
5681
|
+
this.validateVariants(i, path, label, ex, hasInput, baseInput);
|
|
5682
|
+
this.scanSecretLike(i, path, ex);
|
|
5683
|
+
}
|
|
5684
|
+
validateTags(path, label, tags) {
|
|
5685
|
+
if (!Array.isArray(tags)) return;
|
|
5686
|
+
if (tags.length > EXAMPLES.limits.maxTags) {
|
|
5687
|
+
this.err(
|
|
5688
|
+
"example_tag_invalid",
|
|
5689
|
+
`${path}.tags`,
|
|
5690
|
+
`example ${q(label)} has ${tags.length} tags; the limit is ${EXAMPLES.limits.maxTags}`
|
|
5691
|
+
);
|
|
5692
|
+
}
|
|
5693
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5694
|
+
tags.forEach((item, j) => {
|
|
5695
|
+
const field = `${path}.tags[${j}]`;
|
|
5696
|
+
const tag = item === null ? "" : this.issues.stringAt(item, field) ?? null;
|
|
5697
|
+
if (tag === null) return;
|
|
5698
|
+
if (!EXAMPLES.tagPattern.test(tag)) {
|
|
5699
|
+
this.err("example_tag_invalid", field, `example ${q(label)} has an invalid tag ${q(tag)}`);
|
|
5700
|
+
} else if (seen.has(tag)) {
|
|
5701
|
+
this.err(
|
|
5702
|
+
"example_tag_invalid",
|
|
5703
|
+
field,
|
|
5704
|
+
`example ${q(label)} lists the tag ${q(tag)} more than once`
|
|
5705
|
+
);
|
|
5706
|
+
}
|
|
5707
|
+
seen.add(tag);
|
|
5708
|
+
});
|
|
5709
|
+
}
|
|
5710
|
+
validateWeight(field, label, weight) {
|
|
5711
|
+
if (weight === void 0) return;
|
|
5712
|
+
if (Number.isNaN(weight) || weight <= 0 || weight > EXAMPLES.limits.weightMax) {
|
|
5713
|
+
this.err(
|
|
5714
|
+
"example_weight_range",
|
|
5715
|
+
field,
|
|
5716
|
+
`example ${q(label)} has weight ${num(weight)}; use a number above 0 and at most 10`
|
|
5717
|
+
);
|
|
5718
|
+
}
|
|
5719
|
+
}
|
|
5720
|
+
// -- input ------------------------------------------------------------
|
|
5721
|
+
/**
|
|
5722
|
+
* Judge one inline input map (an example's `input`, or a variant's patched
|
|
5723
|
+
* input). NARROWED relative to the reference, which runs its full value
|
|
5724
|
+
* validator: here an unknown field, a missing required field, a value of the
|
|
5725
|
+
* wrong KIND and an enum value outside its list are judged; length, range,
|
|
5726
|
+
* pattern and date-format constraints are not (PARITY.md).
|
|
5727
|
+
*/
|
|
5728
|
+
checkInput(field, label, raw, code) {
|
|
5729
|
+
const input = normalize(raw, 0) ?? {};
|
|
5730
|
+
const L = EXAMPLES.limits;
|
|
5731
|
+
const size = jsonBytes(input);
|
|
5732
|
+
if (size !== null && size > L.maxInlineInputBytes) {
|
|
5733
|
+
this.err(
|
|
5734
|
+
"example_input_too_large",
|
|
5735
|
+
field,
|
|
5736
|
+
`example ${q(label)} input is ${size} bytes; the limit is ${L.maxInlineInputBytes}. Put the large part behind an https file reference.`
|
|
5737
|
+
);
|
|
5738
|
+
return;
|
|
5739
|
+
}
|
|
5740
|
+
const schema = own2(this.flow, "input_schema");
|
|
5741
|
+
const query = own2(this.flow, "query");
|
|
5742
|
+
if (isRecord(schema)) {
|
|
5743
|
+
this.checkInputAgainstSchema(field, label, input, schema, code);
|
|
5744
|
+
} else if (isRecord(query) && Object.keys(query).length > 0) {
|
|
5745
|
+
const declared = sortedKeys(query);
|
|
5746
|
+
for (const k of sortedKeys(input)) {
|
|
5747
|
+
if (!Object.prototype.hasOwnProperty.call(query, k)) {
|
|
5748
|
+
this.err(
|
|
5749
|
+
code,
|
|
5750
|
+
`${field}.${k}`,
|
|
5751
|
+
`example ${q(label)}: ${q(k)} is not a declared input of this flow (declared: ${declared.join(", ")})`
|
|
5752
|
+
);
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
} else {
|
|
5756
|
+
this.warn(
|
|
5757
|
+
"example_input_unvalidated",
|
|
5758
|
+
field,
|
|
5759
|
+
`example ${q(label)} cannot be checked: the flow declares no input_schema and no query parameters.`
|
|
5760
|
+
);
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
checkInputAgainstSchema(field, label, input, schema, code) {
|
|
5764
|
+
const fields = this.schemaFields(schema);
|
|
5765
|
+
const byName = /* @__PURE__ */ new Map();
|
|
5766
|
+
for (const f of fields) byName.set(f.name, f);
|
|
5767
|
+
const runInput = {};
|
|
5768
|
+
for (const name of sortedKeys(input)) {
|
|
5769
|
+
const val = input[name];
|
|
5770
|
+
const f = byName.get(name);
|
|
5771
|
+
const present = val !== null && val !== void 0;
|
|
5772
|
+
if (f !== void 0 && f.type === TYPE_SECRET) {
|
|
5773
|
+
if (present) {
|
|
5774
|
+
this.err(
|
|
5775
|
+
"example_secret_value",
|
|
5776
|
+
`${field}.${name}`,
|
|
5777
|
+
`example ${q(label)} gives a value for ${q(name)}, which is a secret field. Examples are saved inside the flow and are never allowed to hold secrets; remove it. The person running the evaluation is asked for it at run time.`,
|
|
5778
|
+
SUGGESTION_SECRET_VALUE
|
|
5779
|
+
);
|
|
5780
|
+
}
|
|
5781
|
+
continue;
|
|
5782
|
+
}
|
|
5783
|
+
if (f !== void 0 && f.type === TYPE_FILE2 && present) {
|
|
5784
|
+
const ref = asFileRef(val);
|
|
5785
|
+
if (ref === null) {
|
|
5786
|
+
this.err(
|
|
5787
|
+
"example_file_input_needs_ref",
|
|
5788
|
+
`${field}.${name}`,
|
|
5789
|
+
`example ${q(label)}: field ${q(name)} is a file; give it as {url: "https://\u2026"}. A pasted upload id would stop working as soon as the session ends.`
|
|
5790
|
+
);
|
|
5791
|
+
continue;
|
|
5792
|
+
}
|
|
5793
|
+
this.validateFileRef(`${field}.${name}`, ref);
|
|
5794
|
+
setOwn(runInput, name, { [FILE_REF_KEY]: ref.url });
|
|
5795
|
+
continue;
|
|
5796
|
+
}
|
|
5797
|
+
setOwn(runInput, name, val);
|
|
5798
|
+
}
|
|
5799
|
+
for (const failure of this.judgeRun(fields, byName, runInput)) {
|
|
5800
|
+
if (failure.required) {
|
|
5801
|
+
const f = byName.get(failure.field);
|
|
5802
|
+
if (f !== void 0 && f.type === TYPE_SECRET) continue;
|
|
5803
|
+
}
|
|
5804
|
+
const at = failure.field !== "" ? `${field}.${failure.field}` : field;
|
|
5805
|
+
this.err(code, at, `example ${q(label)}: ${failure.message}`);
|
|
5806
|
+
}
|
|
5807
|
+
}
|
|
5808
|
+
schemaFields(schema) {
|
|
5809
|
+
const raw = own2(schema, "fields");
|
|
5810
|
+
if (!Array.isArray(raw)) return [];
|
|
5811
|
+
const out = [];
|
|
5812
|
+
raw.forEach((f, i) => {
|
|
5813
|
+
if (!isRecord(f)) return;
|
|
5814
|
+
const base = `input_schema.fields[${i}]`;
|
|
5815
|
+
const enumRaw = own2(f, "enum");
|
|
5816
|
+
const enumValues = [];
|
|
5817
|
+
if (Array.isArray(enumRaw)) {
|
|
5818
|
+
enumRaw.forEach((e, j) => {
|
|
5819
|
+
enumValues.push(e === null ? "" : this.issues.stringAt(e, `${base}.enum[${j}]`) ?? "");
|
|
5820
|
+
});
|
|
5821
|
+
}
|
|
5822
|
+
const vw = own2(f, "visible_when");
|
|
5823
|
+
out.push({
|
|
5824
|
+
name: this.s(f, "name", base),
|
|
5825
|
+
type: this.s(f, "type", base),
|
|
5826
|
+
required: own2(f, "required") === true,
|
|
5827
|
+
enumValues,
|
|
5828
|
+
visibleWhen: isRecord(vw) ? {
|
|
5829
|
+
field: this.s(vw, "field", `${base}.visible_when`),
|
|
5830
|
+
equals: own2(vw, "equals"),
|
|
5831
|
+
in: Array.isArray(own2(vw, "in")) ? own2(vw, "in") : []
|
|
5832
|
+
} : null
|
|
5833
|
+
});
|
|
5834
|
+
});
|
|
5835
|
+
return out;
|
|
5836
|
+
}
|
|
5837
|
+
/** The narrowed run-time judgement: required, kind, enum, unknown field. */
|
|
5838
|
+
judgeRun(fields, byName, runInput) {
|
|
5839
|
+
const failures = [];
|
|
5840
|
+
for (const f of fields) {
|
|
5841
|
+
if (!isVisible(f, runInput)) continue;
|
|
5842
|
+
const raw = own2(runInput, f.name);
|
|
5843
|
+
if (raw === void 0 || raw === null) {
|
|
5844
|
+
if (f.required) {
|
|
5845
|
+
failures.push({
|
|
5846
|
+
field: f.name,
|
|
5847
|
+
message: `input field ${q(f.name)} is required`,
|
|
5848
|
+
required: true
|
|
5849
|
+
});
|
|
5850
|
+
}
|
|
5851
|
+
continue;
|
|
5852
|
+
}
|
|
5853
|
+
failures.push(...inputValueFailures(f, raw));
|
|
5854
|
+
}
|
|
5855
|
+
for (const k of sortedKeys(runInput)) {
|
|
5856
|
+
if (!byName.has(k)) {
|
|
5857
|
+
failures.push({
|
|
5858
|
+
field: k,
|
|
5859
|
+
message: `input field ${q(k)} is not declared in input_schema`,
|
|
5860
|
+
required: false
|
|
5861
|
+
});
|
|
5862
|
+
}
|
|
5863
|
+
}
|
|
5864
|
+
return failures;
|
|
5865
|
+
}
|
|
5866
|
+
// -- file reference ---------------------------------------------------
|
|
5867
|
+
fileRefOf(rec, path) {
|
|
5868
|
+
return {
|
|
5869
|
+
url: this.s(rec, "url", path),
|
|
5870
|
+
sha256: this.s(rec, "sha256", path),
|
|
5871
|
+
mediaType: this.s(rec, "media_type", path),
|
|
5872
|
+
note: this.s(rec, "note", path)
|
|
5873
|
+
};
|
|
5874
|
+
}
|
|
5875
|
+
validateFileRef(field, ref) {
|
|
5876
|
+
const L = EXAMPLES.limits;
|
|
5877
|
+
const raw = ref.url;
|
|
5878
|
+
const invalid = "the file reference needs a url of at most 2048 characters";
|
|
5879
|
+
if (raw === "" || byteLen(raw) > L.maxUrlBytes) {
|
|
5880
|
+
this.err("example_ref_invalid", field, invalid);
|
|
5881
|
+
return;
|
|
5882
|
+
}
|
|
5883
|
+
const u = goParseURLParts(raw);
|
|
5884
|
+
if (u === null) {
|
|
5885
|
+
this.err("example_ref_invalid", field, invalid);
|
|
5886
|
+
return;
|
|
5887
|
+
}
|
|
5888
|
+
const scheme = u.scheme.toLowerCase();
|
|
5889
|
+
if (EXAMPLES.reservedRefSchemes.has(scheme)) {
|
|
5890
|
+
this.err(
|
|
5891
|
+
"example_ref_scheme_reserved",
|
|
5892
|
+
field,
|
|
5893
|
+
`${scheme}: references are not available yet. Use an https URL, or paste the text into the example.`
|
|
5894
|
+
);
|
|
5895
|
+
return;
|
|
5896
|
+
}
|
|
5897
|
+
if (scheme !== SCHEME_HTTPS) {
|
|
5898
|
+
this.err(
|
|
5899
|
+
"example_ref_scheme",
|
|
5900
|
+
field,
|
|
5901
|
+
`only https:// references are allowed (got ${q(scheme)})`
|
|
5902
|
+
);
|
|
5903
|
+
return;
|
|
5904
|
+
}
|
|
5905
|
+
if (u.hasUser || u.fragment !== "" || raw.includes("#") || u.host === "") {
|
|
5906
|
+
this.err("example_ref_invalid", field, "a reference must not carry a login or a fragment");
|
|
5907
|
+
return;
|
|
5908
|
+
}
|
|
5909
|
+
if (ref.sha256 !== "" && !EXAMPLES.sha256Pattern.test(ref.sha256)) {
|
|
5910
|
+
this.err(
|
|
5911
|
+
"example_ref_sha256_invalid",
|
|
5912
|
+
field,
|
|
5913
|
+
"sha256 must be 64 lowercase hexadecimal characters"
|
|
5914
|
+
);
|
|
5915
|
+
}
|
|
5916
|
+
if (ref.mediaType !== "" && !EXAMPLES.mediaTypePattern.test(ref.mediaType)) {
|
|
5917
|
+
this.err(
|
|
5918
|
+
"example_ref_media_type_invalid",
|
|
5919
|
+
field,
|
|
5920
|
+
`media_type ${q(ref.mediaType)} must look like type/subtype`
|
|
5921
|
+
);
|
|
5922
|
+
}
|
|
5923
|
+
const noteN = runeCount(ref.note);
|
|
5924
|
+
if (noteN > L.maxNoteRunes) {
|
|
5925
|
+
this.err(
|
|
5926
|
+
"example_ref_invalid",
|
|
5927
|
+
field,
|
|
5928
|
+
`the note is ${noteN} characters; the limit is ${L.maxNoteRunes}`
|
|
5929
|
+
);
|
|
5930
|
+
}
|
|
5931
|
+
if (ref.sha256 === "") {
|
|
5932
|
+
this.warn(
|
|
5933
|
+
"example_ref_unpinned",
|
|
5934
|
+
field,
|
|
5935
|
+
"this reference is not pinned. If the file at that address changes, your scores change with it. Add sha256 to freeze it."
|
|
5936
|
+
);
|
|
5937
|
+
}
|
|
5938
|
+
for (const key of goQueryKeys(u.rawQuery)) {
|
|
5939
|
+
if (EXAMPLES.signedUrlQueryKeyPattern.test(key)) {
|
|
5940
|
+
this.warn(
|
|
5941
|
+
"example_ref_signed_url",
|
|
5942
|
+
field,
|
|
5943
|
+
"this address looks like a signed link. Anyone who can read the flow can use it until it expires, and it will stop working. Prefer a stable public address."
|
|
5944
|
+
);
|
|
5945
|
+
break;
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
// -- expectation ------------------------------------------------------
|
|
5950
|
+
/** The names `fields` / `exact` may use, and whether such a list is declared at all. */
|
|
5951
|
+
outputNamesFor(kind, stepId) {
|
|
5952
|
+
if (kind === "final") {
|
|
5953
|
+
const out = own2(this.flow, "output");
|
|
5954
|
+
const names2 = [];
|
|
5955
|
+
if (Array.isArray(out)) {
|
|
5956
|
+
out.forEach((item, i) => {
|
|
5957
|
+
names2.push(item === null ? "" : this.issues.stringAt(item, `output[${i}]`) ?? "");
|
|
5958
|
+
});
|
|
5959
|
+
}
|
|
5960
|
+
return { names: names2, declared: names2.length > 0 };
|
|
5961
|
+
}
|
|
5962
|
+
const steps = own2(this.flow, "steps");
|
|
5963
|
+
const step = isRecord(steps) ? own2(steps, stepId) : void 0;
|
|
5964
|
+
const schema = isRecord(step) ? own2(step, "output_schema") : void 0;
|
|
5965
|
+
const fields = isRecord(schema) ? own2(schema, "fields") : void 0;
|
|
5966
|
+
if (!Array.isArray(fields) || fields.length === 0) return { names: [], declared: false };
|
|
5967
|
+
const names = [];
|
|
5968
|
+
fields.forEach((f, i) => {
|
|
5969
|
+
if (isRecord(f)) names.push(this.s(f, "name", `steps.${stepId}.output_schema.fields[${i}]`));
|
|
5970
|
+
});
|
|
5971
|
+
return { names, declared: true };
|
|
5972
|
+
}
|
|
5973
|
+
validateExpectation(path, label, e, kind, stepId) {
|
|
5974
|
+
const L = EXAMPLES.limits;
|
|
5975
|
+
const status = this.s(e, "status", path);
|
|
5976
|
+
if (status !== "" && !EXAMPLES.statuses.has(status)) {
|
|
5977
|
+
this.err(
|
|
5978
|
+
"example_status_invalid",
|
|
5979
|
+
`${path}.status`,
|
|
5980
|
+
"status must be completed, failed or paused_for_human"
|
|
5981
|
+
);
|
|
5982
|
+
}
|
|
5983
|
+
const noOutput = status === STATUS_FAILED || status === STATUS_PAUSED_FOR_HUMAN;
|
|
5984
|
+
const fieldsRec = own2(e, "fields");
|
|
5985
|
+
const exactRec = own2(e, "exact");
|
|
5986
|
+
const fieldKeys = isRecord(fieldsRec) ? sortedKeys(fieldsRec) : [];
|
|
5987
|
+
const exactKeys = isRecord(exactRec) ? sortedKeys(exactRec) : [];
|
|
5988
|
+
const hasFields = fieldKeys.length > 0;
|
|
5989
|
+
const hasExact = exactKeys.length > 0;
|
|
5990
|
+
if (hasFields && hasExact) {
|
|
5991
|
+
this.err(
|
|
5992
|
+
"example_expected_conflict",
|
|
5993
|
+
path,
|
|
5994
|
+
"use fields (some fields) or exact (the whole result), not both"
|
|
5995
|
+
);
|
|
5996
|
+
} else if (noOutput && (hasFields || hasExact)) {
|
|
5997
|
+
this.err(
|
|
5998
|
+
"example_expected_conflict",
|
|
5999
|
+
path,
|
|
6000
|
+
`a case that ends in ${status} has no output to match; remove fields and exact, or expect status completed`
|
|
6001
|
+
);
|
|
6002
|
+
}
|
|
6003
|
+
const minScore = numberOf(own2(e, "min_score"));
|
|
6004
|
+
if (minScore !== void 0 && !scoreInRange(minScore)) {
|
|
6005
|
+
this.err(
|
|
6006
|
+
"example_min_score_range",
|
|
6007
|
+
`${path}.min_score`,
|
|
6008
|
+
`min_score ${num(minScore)} is out of range; use a number from 0 to 1`
|
|
6009
|
+
);
|
|
6010
|
+
}
|
|
6011
|
+
const rubric = this.s(e, "rubric", path);
|
|
6012
|
+
if (rubric !== "") {
|
|
6013
|
+
if (goTrimSpace(rubric) === "") {
|
|
6014
|
+
this.err(
|
|
6015
|
+
"example_rubric_empty",
|
|
6016
|
+
`${path}.rubric`,
|
|
6017
|
+
"the rubric is empty; write what a good result looks like, or remove it"
|
|
6018
|
+
);
|
|
6019
|
+
} else {
|
|
6020
|
+
const n = runeCount(rubric);
|
|
6021
|
+
if (n > L.maxRubricRunes) {
|
|
6022
|
+
this.err(
|
|
6023
|
+
"example_rubric_too_long",
|
|
6024
|
+
`${path}.rubric`,
|
|
6025
|
+
`the rubric is ${n} characters; the limit is ${L.maxRubricRunes}`
|
|
6026
|
+
);
|
|
6027
|
+
}
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
const reference = own2(e, "reference");
|
|
6031
|
+
if (isRecord(reference)) this.validateReference(`${path}.reference`, reference);
|
|
6032
|
+
const { names, declared } = this.outputNamesFor(kind, stepId);
|
|
6033
|
+
const checkKey = (field, key) => {
|
|
6034
|
+
if (!declared && kind === "final") {
|
|
6035
|
+
this.warn(
|
|
6036
|
+
"example_expected_field_unchecked",
|
|
6037
|
+
field,
|
|
6038
|
+
`this flow declares no output list, so ${q(key)} cannot be checked against it`
|
|
6039
|
+
);
|
|
6040
|
+
} else if (!declared) {
|
|
6041
|
+
this.warn(
|
|
6042
|
+
"example_expected_field_unchecked",
|
|
6043
|
+
field,
|
|
6044
|
+
`this step declares no output_schema, so ${q(key)} cannot be checked against it`
|
|
6045
|
+
);
|
|
6046
|
+
} else if (!names.includes(key)) {
|
|
6047
|
+
this.err(
|
|
6048
|
+
"example_expected_field_unknown",
|
|
6049
|
+
field,
|
|
6050
|
+
kind === "checkpoint" ? `${q(key)} is not a field this step declares in its output_schema (declared: ${names.join(", ")})` : `${q(key)} is not an output of this flow (outputs: ${names.join(", ")})`
|
|
6051
|
+
);
|
|
6052
|
+
}
|
|
6053
|
+
};
|
|
6054
|
+
for (const key of fieldKeys) {
|
|
6055
|
+
const fpath = `${path}.fields.${key}`;
|
|
6056
|
+
checkKey(fpath, key);
|
|
6057
|
+
const matcher = own2(fieldsRec, key);
|
|
6058
|
+
if (isRecord(matcher)) this.validateFieldMatcher(fpath, matcher);
|
|
6059
|
+
}
|
|
6060
|
+
for (const key of exactKeys) checkKey(`${path}.exact.${key}`, key);
|
|
6061
|
+
const mustNot = this.stringList(own2(e, "must_not"), `${path}.must_not`);
|
|
6062
|
+
const mustNotContain = this.stringList(own2(e, "must_not_contain"), `${path}.must_not_contain`);
|
|
6063
|
+
const mustNotMsg = `must_not takes up to ${L.maxMustNot} statements of at most ${L.maxMustNotRunes} characters each, none empty`;
|
|
6064
|
+
if (mustNot.length > L.maxMustNot) {
|
|
6065
|
+
this.err("example_must_not_invalid", `${path}.must_not`, mustNotMsg);
|
|
6066
|
+
}
|
|
6067
|
+
mustNot.forEach((item, j) => {
|
|
6068
|
+
if (goTrimSpace(item) === "" || runeCount(item) > L.maxMustNotRunes) {
|
|
6069
|
+
this.err("example_must_not_invalid", `${path}.must_not[${j}]`, mustNotMsg);
|
|
6070
|
+
}
|
|
6071
|
+
});
|
|
6072
|
+
const mncMsg = `must_not_contain takes up to ${L.maxMustNotContain} literals of 1 to ${L.maxMustNotContainLength} characters each`;
|
|
6073
|
+
if (mustNotContain.length > L.maxMustNotContain) {
|
|
6074
|
+
this.err("example_must_not_contain_invalid", `${path}.must_not_contain`, mncMsg);
|
|
6075
|
+
}
|
|
6076
|
+
mustNotContain.forEach((item, j) => {
|
|
6077
|
+
const n = runeCount(item);
|
|
6078
|
+
if (n === 0 || n > L.maxMustNotContainLength) {
|
|
6079
|
+
this.err("example_must_not_contain_invalid", `${path}.must_not_contain[${j}]`, mncMsg);
|
|
6080
|
+
}
|
|
6081
|
+
});
|
|
6082
|
+
for (const key of fieldKeys) {
|
|
6083
|
+
const matcher = own2(fieldsRec, key);
|
|
6084
|
+
if (!isRecord(matcher)) continue;
|
|
6085
|
+
const eq = own2(matcher, "equals");
|
|
6086
|
+
if (typeof eq !== "string" || eq === "") continue;
|
|
6087
|
+
for (const banned of mustNotContain) {
|
|
6088
|
+
if (banned !== "" && equalFold(banned, eq)) {
|
|
6089
|
+
this.warn(
|
|
6090
|
+
"example_expectation_contradiction",
|
|
6091
|
+
`${path}.fields.${key}`,
|
|
6092
|
+
`${q(banned)} is required by ${q(key)} and forbidden by must_not_contain`
|
|
6093
|
+
);
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
}
|
|
6097
|
+
if (kind === "checkpoint" && status !== "") {
|
|
6098
|
+
this.err(
|
|
6099
|
+
"example_checkpoint_status",
|
|
6100
|
+
`${path}.status`,
|
|
6101
|
+
`example ${q(label)}: a checkpoint cannot set status; status describes how the whole run ends`
|
|
6102
|
+
);
|
|
6103
|
+
}
|
|
6104
|
+
if (rubric === "" && !has(e, "reference") && !hasFields && !hasExact && mustNot.length === 0 && mustNotContain.length === 0 && (status === "" || status === STATUS_COMPLETED)) {
|
|
6105
|
+
this.err(
|
|
6106
|
+
"example_expected_empty",
|
|
6107
|
+
path,
|
|
6108
|
+
"this expectation checks nothing; add a rubric, fields, exact, reference, must_not or must_not_contain",
|
|
6109
|
+
SUGGESTION_EXPECTED
|
|
6110
|
+
);
|
|
6111
|
+
}
|
|
6112
|
+
}
|
|
6113
|
+
/** A Go `[]string` read: scalars by their text, null as "", anything else dropped. */
|
|
6114
|
+
stringList(v, path) {
|
|
6115
|
+
if (!Array.isArray(v)) return [];
|
|
6116
|
+
const out = [];
|
|
6117
|
+
v.forEach((item, j) => {
|
|
6118
|
+
if (item === null) out.push("");
|
|
6119
|
+
else {
|
|
6120
|
+
const t = this.issues.stringAt(item, `${path}[${j}]`);
|
|
6121
|
+
if (t !== null) out.push(t);
|
|
6122
|
+
}
|
|
6123
|
+
});
|
|
6124
|
+
return out;
|
|
6125
|
+
}
|
|
6126
|
+
validateReference(path, r) {
|
|
6127
|
+
const text = this.s(r, "text", path);
|
|
6128
|
+
const hasText = text !== "";
|
|
6129
|
+
const hasRef = has(r, "ref");
|
|
6130
|
+
if (hasText === hasRef) {
|
|
6131
|
+
this.err("example_reference_invalid", path, "a reference needs exactly one of text or ref");
|
|
6132
|
+
return;
|
|
6133
|
+
}
|
|
6134
|
+
if (hasText) {
|
|
6135
|
+
const n = byteLen(text);
|
|
6136
|
+
if (n > EXAMPLES.limits.maxReferenceTextBytes) {
|
|
6137
|
+
this.err(
|
|
6138
|
+
"example_reference_invalid",
|
|
6139
|
+
path,
|
|
6140
|
+
`the reference text is ${n} bytes; the limit is ${EXAMPLES.limits.maxReferenceTextBytes}. Put it behind an https file reference.`
|
|
6141
|
+
);
|
|
6142
|
+
}
|
|
6143
|
+
return;
|
|
6144
|
+
}
|
|
6145
|
+
const refRec = own2(r, "ref");
|
|
6146
|
+
if (!isRecord(refRec)) return;
|
|
6147
|
+
const ref = this.fileRefOf(refRec, `${path}.ref`);
|
|
6148
|
+
this.validateFileRef(`${path}.ref`, ref);
|
|
6149
|
+
if (ref.mediaType !== "" && !isTextualMediaType(ref.mediaType)) {
|
|
6150
|
+
this.warn(
|
|
6151
|
+
"example_reference_binary",
|
|
6152
|
+
path,
|
|
6153
|
+
"this reference is not text, so it cannot be compared in this version; the example will be skipped, not failed."
|
|
6154
|
+
);
|
|
6155
|
+
}
|
|
6156
|
+
}
|
|
6157
|
+
expectationIsBinaryReferenceOnly(e, path) {
|
|
6158
|
+
const reference = own2(e, "reference");
|
|
6159
|
+
const refRec = isRecord(reference) ? own2(reference, "ref") : void 0;
|
|
6160
|
+
if (!isRecord(refRec)) return false;
|
|
6161
|
+
const mediaType = this.s(refRec, "media_type", `${path}.reference.ref`);
|
|
6162
|
+
if (mediaType === "" || isTextualMediaType(mediaType)) return false;
|
|
6163
|
+
const status = this.s(e, "status", path);
|
|
6164
|
+
const fields = own2(e, "fields");
|
|
6165
|
+
const exact = own2(e, "exact");
|
|
6166
|
+
return this.s(e, "rubric", path) === "" && !(isRecord(fields) && Object.keys(fields).length > 0) && !(isRecord(exact) && Object.keys(exact).length > 0) && this.stringList(own2(e, "must_not"), `${path}.must_not`).length === 0 && this.stringList(own2(e, "must_not_contain"), `${path}.must_not_contain`).length === 0 && (status === "" || status === STATUS_COMPLETED);
|
|
6167
|
+
}
|
|
6168
|
+
validateFieldMatcher(path, m) {
|
|
6169
|
+
const L = EXAMPLES.limits;
|
|
6170
|
+
const matches = this.s(m, "matches", path);
|
|
6171
|
+
const oneOf = own2(m, "one_of");
|
|
6172
|
+
const approx = numberOf(own2(m, "approx"));
|
|
6173
|
+
const tolerance = numberOf(own2(m, "tolerance"));
|
|
6174
|
+
let operators = 0;
|
|
6175
|
+
if (has(m, "equals")) operators++;
|
|
6176
|
+
if (this.s(m, "contains", path) !== "") operators++;
|
|
6177
|
+
if (this.s(m, "not_contains", path) !== "") operators++;
|
|
6178
|
+
if (matches !== "") operators++;
|
|
6179
|
+
if (Array.isArray(oneOf) && oneOf.length > 0) operators++;
|
|
6180
|
+
if (approx !== void 0) operators++;
|
|
6181
|
+
if (typeof own2(m, "present") === "boolean") operators++;
|
|
6182
|
+
if (operators !== 1) {
|
|
6183
|
+
this.err(
|
|
6184
|
+
"example_field_matcher_invalid",
|
|
6185
|
+
path,
|
|
6186
|
+
"write exactly one of equals, contains, not_contains, matches, one_of, approx, present"
|
|
6187
|
+
);
|
|
6188
|
+
return;
|
|
6189
|
+
}
|
|
6190
|
+
if (matches !== "" && byteLen(matches) > L.maxPatternLength) {
|
|
6191
|
+
this.err(
|
|
6192
|
+
"example_field_matcher_invalid",
|
|
6193
|
+
path,
|
|
6194
|
+
`the pattern in matches must be a valid regular expression of at most ${L.maxPatternLength} characters`
|
|
6195
|
+
);
|
|
6196
|
+
}
|
|
6197
|
+
if (approx !== void 0 !== (tolerance !== void 0) || tolerance !== void 0 && (Number.isNaN(tolerance) || tolerance < 0)) {
|
|
6198
|
+
this.err(
|
|
6199
|
+
"example_field_matcher_invalid",
|
|
6200
|
+
path,
|
|
6201
|
+
"approx needs a tolerance (a number, zero or more) beside it, and tolerance is only for approx"
|
|
6202
|
+
);
|
|
6203
|
+
}
|
|
6204
|
+
}
|
|
6205
|
+
// -- checkpoints ------------------------------------------------------
|
|
6206
|
+
validateCheckpoints(path, label, cps) {
|
|
6207
|
+
if (!isRecord(cps) || Object.keys(cps).length === 0) return;
|
|
6208
|
+
const stepsRaw = own2(this.flow, "steps");
|
|
6209
|
+
const steps = isRecord(stepsRaw) ? stepsRaw : {};
|
|
6210
|
+
const stepIds = sortedKeys(steps);
|
|
6211
|
+
for (const id of sortedKeys(cps)) {
|
|
6212
|
+
const cpath = `${path}.checkpoints.${id}`;
|
|
6213
|
+
const step = own2(steps, id);
|
|
6214
|
+
if (!isRecord(step)) {
|
|
6215
|
+
this.err(
|
|
6216
|
+
"example_checkpoint_step_unknown",
|
|
6217
|
+
cpath,
|
|
6218
|
+
`example ${q(label)}: checkpoint ${q(id)} is not a step of this flow (steps: ${stepIds.join(", ")})`,
|
|
6219
|
+
SUGGESTION_CHECKPOINT
|
|
6220
|
+
);
|
|
6221
|
+
continue;
|
|
6222
|
+
}
|
|
6223
|
+
const next = own2(step, "next");
|
|
6224
|
+
if (has(step, "for_each") || has(step, "loop") || isRecord(next) && has(next, "parallel")) {
|
|
6225
|
+
this.err(
|
|
6226
|
+
"example_checkpoint_step_composite",
|
|
6227
|
+
cpath,
|
|
6228
|
+
`example ${q(label)}: step ${q(id)} repeats or branches, so there is no single result to check; put the checkpoint on a plain step or check the final output`
|
|
6229
|
+
);
|
|
6230
|
+
continue;
|
|
6231
|
+
}
|
|
6232
|
+
const cp = own2(cps, id);
|
|
6233
|
+
if (cp === null || cp === void 0) {
|
|
6234
|
+
this.validateExpectation(cpath, label, {}, "checkpoint", id);
|
|
6235
|
+
} else if (isRecord(cp)) {
|
|
6236
|
+
this.validateExpectation(cpath, label, cp, "checkpoint", id);
|
|
6237
|
+
}
|
|
6238
|
+
}
|
|
6239
|
+
}
|
|
6240
|
+
// -- variants ---------------------------------------------------------
|
|
6241
|
+
validateVariants(i, path, label, ex, hasInput, baseInput) {
|
|
6242
|
+
const L = EXAMPLES.limits;
|
|
6243
|
+
const variants = own2(ex, "variants");
|
|
6244
|
+
if (!Array.isArray(variants) || variants.length === 0) return;
|
|
6245
|
+
if (variants.length > L.maxVariants) {
|
|
6246
|
+
this.err(
|
|
6247
|
+
"example_variants_too_many",
|
|
6248
|
+
`${path}.variants`,
|
|
6249
|
+
`example ${q(label)} has ${variants.length} variants; the limit is ${L.maxVariants}`
|
|
6250
|
+
);
|
|
6251
|
+
}
|
|
6252
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6253
|
+
variants.forEach((v, j) => {
|
|
6254
|
+
if (!isRecord(v)) return;
|
|
6255
|
+
const vpath = `examples[${i}].variants[${j}]`;
|
|
6256
|
+
const vid = this.s(v, "id", vpath);
|
|
6257
|
+
const vlabel = vid === "" ? `#${j + 1}` : vid;
|
|
6258
|
+
const both = `${label}/${vlabel}`;
|
|
6259
|
+
if (!EXAMPLES.idPattern.test(vid)) {
|
|
6260
|
+
this.err(
|
|
6261
|
+
"example_variant_id_invalid",
|
|
6262
|
+
`${vpath}.id`,
|
|
6263
|
+
`variant id ${q(vid)} of example ${q(label)} is not valid: use lower-case letters, digits and underscores, starting with a letter (2 to 48 characters)`,
|
|
6264
|
+
SUGGESTION_ID
|
|
6265
|
+
);
|
|
6266
|
+
} else if (seen.has(vid)) {
|
|
6267
|
+
this.err(
|
|
6268
|
+
"example_variant_id_duplicate",
|
|
6269
|
+
`${vpath}.id`,
|
|
6270
|
+
`variant id ${q(vid)} is used twice in example ${q(label)}`
|
|
6271
|
+
);
|
|
6272
|
+
}
|
|
6273
|
+
seen.add(vid);
|
|
6274
|
+
const origin = this.s(v, "origin", vpath);
|
|
6275
|
+
if (origin === ORIGIN_NONE || !EXAMPLES.origins.has(origin)) {
|
|
6276
|
+
this.err(
|
|
6277
|
+
"example_variant_origin_invalid",
|
|
6278
|
+
`${vpath}.origin`,
|
|
6279
|
+
`variant ${q(vlabel)} of example ${q(label)} has origin ${q(origin)}; use author (a person wrote or approved it) or synthetic (generated)`
|
|
6280
|
+
);
|
|
6281
|
+
}
|
|
6282
|
+
const vtitleN = runeCount(this.s(v, "title", vpath));
|
|
6283
|
+
if (vtitleN > L.titleMaxRunes) {
|
|
6284
|
+
this.err(
|
|
6285
|
+
"example_title_too_long",
|
|
6286
|
+
`${vpath}.title`,
|
|
6287
|
+
`example ${q(both)} title is ${vtitleN} characters; the limit is ${L.titleMaxRunes}`
|
|
6288
|
+
);
|
|
6289
|
+
}
|
|
6290
|
+
const vguidanceN = runeCount(this.s(v, "guidance", vpath));
|
|
6291
|
+
if (vguidanceN > L.maxGuidanceRunes) {
|
|
6292
|
+
this.err(
|
|
6293
|
+
"example_guidance_too_long",
|
|
6294
|
+
`${vpath}.guidance`,
|
|
6295
|
+
`example ${q(both)} guidance is ${vguidanceN} characters; the limit is ${L.maxGuidanceRunes}`
|
|
6296
|
+
);
|
|
6297
|
+
}
|
|
6298
|
+
const provN = runeCount(this.s(v, "provenance", vpath));
|
|
6299
|
+
if (provN > L.maxProvenanceRunes) {
|
|
6300
|
+
this.err(
|
|
6301
|
+
"example_notes_too_long",
|
|
6302
|
+
`${vpath}.provenance`,
|
|
6303
|
+
`example ${q(both)} notes are ${provN} characters; the limit is ${L.maxProvenanceRunes}`
|
|
6304
|
+
);
|
|
6305
|
+
}
|
|
6306
|
+
this.validateWeight(`${vpath}.weight`, both, numberOf(own2(v, "weight")));
|
|
6307
|
+
const patch = own2(v, "input_patch");
|
|
6308
|
+
const patchRec = isRecord(patch) ? patch : null;
|
|
6309
|
+
const hasPatch = patchRec !== null && Object.keys(patchRec).length > 0;
|
|
6310
|
+
const hasRef = has(v, "input_ref");
|
|
6311
|
+
if (hasPatch && hasRef) {
|
|
6312
|
+
this.err(
|
|
6313
|
+
"example_variant_input_conflict",
|
|
6314
|
+
vpath,
|
|
6315
|
+
`variant ${q(vlabel)} of example ${q(label)} sets both input_patch and input_ref; use one`
|
|
6316
|
+
);
|
|
6317
|
+
} else if (!hasPatch && !hasRef && !has(v, "expected")) {
|
|
6318
|
+
this.err(
|
|
6319
|
+
"example_variant_empty",
|
|
6320
|
+
vpath,
|
|
6321
|
+
`variant ${q(vlabel)} of example ${q(label)} changes neither the input nor the expectation`
|
|
6322
|
+
);
|
|
6323
|
+
}
|
|
6324
|
+
const refRec = own2(v, "input_ref");
|
|
6325
|
+
if (isRecord(refRec)) {
|
|
6326
|
+
this.validateFileRef(`${vpath}.input_ref`, this.fileRefOf(refRec, `${vpath}.input_ref`));
|
|
6327
|
+
}
|
|
6328
|
+
if (hasPatch && patchRec !== null) {
|
|
6329
|
+
if (!hasInput) {
|
|
6330
|
+
this.warn(
|
|
6331
|
+
"example_variant_unchecked",
|
|
6332
|
+
`${vpath}.input_patch`,
|
|
6333
|
+
`variant ${q(vlabel)} of example ${q(label)} cannot be checked: its parent takes its input from input_ref.`
|
|
6334
|
+
);
|
|
6335
|
+
} else if (baseInput !== null) {
|
|
6336
|
+
this.checkVariantPatch(vpath, label, vlabel, baseInput, patchRec);
|
|
6337
|
+
}
|
|
6338
|
+
}
|
|
6339
|
+
const expected = own2(v, "expected");
|
|
6340
|
+
if (isRecord(expected)) {
|
|
6341
|
+
this.validateExpectation(`${vpath}.expected`, both, expected, "final", "");
|
|
6342
|
+
}
|
|
6343
|
+
});
|
|
6344
|
+
}
|
|
6345
|
+
/**
|
|
6346
|
+
* Judge the PATCHED input like a live run, and report a secret the patch
|
|
6347
|
+
* itself supplies. A null patch value deleting a secret is fine.
|
|
6348
|
+
*/
|
|
6349
|
+
checkVariantPatch(vpath, label, vlabel, base, patch) {
|
|
6350
|
+
const schema = own2(this.flow, "input_schema");
|
|
6351
|
+
if (isRecord(schema)) {
|
|
6352
|
+
for (const f of this.schemaFields(schema)) {
|
|
6353
|
+
if (f.type !== TYPE_SECRET) continue;
|
|
6354
|
+
const val = own2(patch, f.name);
|
|
6355
|
+
if (val !== void 0 && val !== null) {
|
|
6356
|
+
this.err(
|
|
6357
|
+
"example_secret_value",
|
|
6358
|
+
`${vpath}.input_patch.${f.name}`,
|
|
6359
|
+
`example ${q(`${label}/${vlabel}`)} gives a value for ${q(f.name)}, which is a secret field. Examples are saved inside the flow and are never allowed to hold secrets; remove it. The person running the evaluation is asked for it at run time.`,
|
|
6360
|
+
SUGGESTION_SECRET_VALUE
|
|
6361
|
+
);
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
}
|
|
6365
|
+
const patched = applyExamplePatch(base, patch);
|
|
6366
|
+
const scratch = new _Walker(this.flow, this.issues);
|
|
6367
|
+
scratch.checkInput(
|
|
6368
|
+
`${vpath}.input_patch`,
|
|
6369
|
+
`${label}/${vlabel}`,
|
|
6370
|
+
patched,
|
|
6371
|
+
"example_variant_input_invalid"
|
|
6372
|
+
);
|
|
6373
|
+
for (const f of scratch.findings) {
|
|
6374
|
+
if (f.code === "example_secret_value") continue;
|
|
6375
|
+
this.findings.push(f);
|
|
6376
|
+
}
|
|
6377
|
+
}
|
|
6378
|
+
// -- credential-shaped literals ----------------------------------------
|
|
6379
|
+
scanSecretLike(i, path, ex) {
|
|
6380
|
+
const emit = (field, s) => {
|
|
6381
|
+
const name = secretMatch(s);
|
|
6382
|
+
if (name !== "") {
|
|
6383
|
+
this.err(
|
|
6384
|
+
"example_secret_like_value",
|
|
6385
|
+
field,
|
|
6386
|
+
`${field} looks like a credential (${name}). Examples are saved in the flow definition and must never contain keys or tokens. Replace it with a made-up value.`,
|
|
6387
|
+
SUGGESTION_SECRET_VALUE
|
|
6388
|
+
);
|
|
6389
|
+
}
|
|
6390
|
+
};
|
|
6391
|
+
emit(`${path}.title`, this.s(ex, "title", path));
|
|
6392
|
+
emit(`${path}.guidance`, this.s(ex, "guidance", path));
|
|
6393
|
+
emit(`${path}.notes`, this.s(ex, "notes", path));
|
|
6394
|
+
const input = own2(ex, "input");
|
|
6395
|
+
if (isRecord(input)) scanLeaves(`${path}.input`, input, 0, emit);
|
|
6396
|
+
const expected = own2(ex, "expected");
|
|
6397
|
+
if (isRecord(expected)) this.scanExpectationLeaves(`${path}.expected`, expected, emit);
|
|
6398
|
+
const cps = own2(ex, "checkpoints");
|
|
6399
|
+
if (isRecord(cps)) {
|
|
6400
|
+
for (const id of sortedKeys(cps)) {
|
|
6401
|
+
const cp = own2(cps, id);
|
|
6402
|
+
if (isRecord(cp)) this.scanExpectationLeaves(`${path}.checkpoints.${id}`, cp, emit);
|
|
6403
|
+
}
|
|
6404
|
+
}
|
|
6405
|
+
const variants = own2(ex, "variants");
|
|
6406
|
+
if (Array.isArray(variants)) {
|
|
6407
|
+
variants.forEach((v, j) => {
|
|
6408
|
+
if (!isRecord(v)) return;
|
|
6409
|
+
const vpath = `examples[${i}].variants[${j}]`;
|
|
6410
|
+
emit(`${vpath}.title`, this.s(v, "title", vpath));
|
|
6411
|
+
emit(`${vpath}.guidance`, this.s(v, "guidance", vpath));
|
|
6412
|
+
emit(`${vpath}.provenance`, this.s(v, "provenance", vpath));
|
|
6413
|
+
scanLeaves(`${vpath}.input_patch`, own2(v, "input_patch"), 0, emit);
|
|
6414
|
+
const vexp = own2(v, "expected");
|
|
6415
|
+
if (isRecord(vexp)) this.scanExpectationLeaves(`${vpath}.expected`, vexp, emit);
|
|
6416
|
+
});
|
|
6417
|
+
}
|
|
6418
|
+
}
|
|
6419
|
+
scanExpectationLeaves(path, e, emit) {
|
|
6420
|
+
emit(`${path}.rubric`, this.s(e, "rubric", path));
|
|
6421
|
+
const reference = own2(e, "reference");
|
|
6422
|
+
if (isRecord(reference)) {
|
|
6423
|
+
emit(`${path}.reference.text`, this.s(reference, "text", `${path}.reference`));
|
|
6424
|
+
}
|
|
6425
|
+
this.stringList(own2(e, "must_not"), `${path}.must_not`).forEach(
|
|
6426
|
+
(s, j) => emit(`${path}.must_not[${j}]`, s)
|
|
6427
|
+
);
|
|
6428
|
+
this.stringList(own2(e, "must_not_contain"), `${path}.must_not_contain`).forEach(
|
|
6429
|
+
(s, j) => emit(`${path}.must_not_contain[${j}]`, s)
|
|
6430
|
+
);
|
|
6431
|
+
const fields = own2(e, "fields");
|
|
6432
|
+
if (isRecord(fields)) {
|
|
6433
|
+
for (const key of sortedKeys(fields)) {
|
|
6434
|
+
const m = own2(fields, key);
|
|
6435
|
+
if (!isRecord(m)) continue;
|
|
6436
|
+
const fpath = `${path}.fields.${key}`;
|
|
6437
|
+
scanLeaves(`${fpath}.equals`, own2(m, "equals"), 0, emit);
|
|
6438
|
+
emit(`${fpath}.contains`, this.s(m, "contains", fpath));
|
|
6439
|
+
emit(`${fpath}.not_contains`, this.s(m, "not_contains", fpath));
|
|
6440
|
+
const oneOf = own2(m, "one_of");
|
|
6441
|
+
if (Array.isArray(oneOf)) {
|
|
6442
|
+
oneOf.forEach(
|
|
6443
|
+
(one, j) => scanLeaves(`${fpath}.one_of[${j}]`, one, 0, emit)
|
|
6444
|
+
);
|
|
6445
|
+
}
|
|
6446
|
+
}
|
|
6447
|
+
}
|
|
6448
|
+
scanLeaves(`${path}.exact`, own2(e, "exact"), 0, emit);
|
|
6449
|
+
}
|
|
6450
|
+
};
|
|
6451
|
+
function scoreInRange(v) {
|
|
6452
|
+
return !Number.isNaN(v) && v >= EXAMPLES.limits.minScoreMin && v <= EXAMPLES.limits.minScoreMax;
|
|
6453
|
+
}
|
|
6454
|
+
function equalFold(a, b) {
|
|
6455
|
+
const ca = [...a];
|
|
6456
|
+
const cb = [...b];
|
|
6457
|
+
if (ca.length !== cb.length) return false;
|
|
6458
|
+
return ca.every((c, i) => {
|
|
6459
|
+
const d = cb[i];
|
|
6460
|
+
return c === d || c.toLowerCase() === d.toLowerCase() || c.toUpperCase() === d.toUpperCase();
|
|
6461
|
+
});
|
|
6462
|
+
}
|
|
6463
|
+
function secretMatch(s) {
|
|
6464
|
+
for (const p of EXAMPLES.secretPatterns) {
|
|
6465
|
+
if (p.re.test(s)) return p.name;
|
|
6466
|
+
}
|
|
6467
|
+
return "";
|
|
6468
|
+
}
|
|
6469
|
+
function scanLeaves(path, v, depth, emit) {
|
|
6470
|
+
if (depth > MAX_RECURSION_DEPTH) return;
|
|
6471
|
+
if (isRecord(v)) {
|
|
6472
|
+
for (const k of sortedKeys(v)) scanLeaves(`${path}.${k}`, v[k], depth + 1, emit);
|
|
6473
|
+
return;
|
|
6474
|
+
}
|
|
6475
|
+
if (typeof v === "string") emit(path, v);
|
|
6476
|
+
else if (Array.isArray(v)) {
|
|
6477
|
+
v.forEach((item, i) => scanLeaves(`${path}[${i}]`, item, depth + 1, emit));
|
|
6478
|
+
}
|
|
6479
|
+
}
|
|
6480
|
+
function jsonBytes(v) {
|
|
6481
|
+
try {
|
|
6482
|
+
const text = JSON.stringify(v);
|
|
6483
|
+
return text === void 0 ? null : byteLen(text);
|
|
6484
|
+
} catch {
|
|
6485
|
+
return null;
|
|
6486
|
+
}
|
|
6487
|
+
}
|
|
6488
|
+
function blockBytes(examples) {
|
|
6489
|
+
try {
|
|
6490
|
+
const text = JSON.stringify(normalize(examples, 0), function(_k, v) {
|
|
6491
|
+
if (Array.isArray(this)) return v;
|
|
6492
|
+
if (v === null || v === "" || v === false) return void 0;
|
|
6493
|
+
if (Array.isArray(v) && v.length === 0) return void 0;
|
|
6494
|
+
return v;
|
|
6495
|
+
});
|
|
6496
|
+
return text === void 0 ? null : byteLen(text);
|
|
6497
|
+
} catch {
|
|
6498
|
+
return null;
|
|
6499
|
+
}
|
|
6500
|
+
}
|
|
6501
|
+
function isVisible(f, input) {
|
|
6502
|
+
const vw = f.visibleWhen;
|
|
6503
|
+
if (vw === null) return true;
|
|
6504
|
+
if (!Object.prototype.hasOwnProperty.call(input, vw.field)) return false;
|
|
6505
|
+
const dep = input[vw.field];
|
|
6506
|
+
if (vw.equals !== void 0 && vw.equals !== null) return valuesEqual(dep, vw.equals);
|
|
6507
|
+
return vw.in.some((candidate) => valuesEqual(dep, candidate));
|
|
6508
|
+
}
|
|
6509
|
+
function valuesEqual(a, b) {
|
|
6510
|
+
const aNil = a === null || a === void 0;
|
|
6511
|
+
const bNil = b === null || b === void 0;
|
|
6512
|
+
if (aNil || bNil) return aNil && bNil;
|
|
6513
|
+
if (typeof a === "number") return typeof b === "number" && a === b;
|
|
6514
|
+
if (typeof a === "boolean") return typeof b === "boolean" && a === b;
|
|
6515
|
+
if (typeof a === "string") return typeof b === "string" && a === b;
|
|
6516
|
+
return false;
|
|
6517
|
+
}
|
|
6518
|
+
function mismatch(f, expected) {
|
|
6519
|
+
return {
|
|
6520
|
+
field: f.name,
|
|
6521
|
+
message: `input field ${q(f.name)} is not a valid ${expected}`,
|
|
6522
|
+
required: false
|
|
6523
|
+
};
|
|
6524
|
+
}
|
|
6525
|
+
function inputValueFailures(f, raw) {
|
|
6526
|
+
switch (f.type) {
|
|
6527
|
+
case TYPE_STRING:
|
|
6528
|
+
case TYPE_MULTILINE:
|
|
6529
|
+
case TYPE_SECRET:
|
|
6530
|
+
return typeof raw === "string" ? [] : [mismatch(f, TYPE_STRING)];
|
|
6531
|
+
case TYPE_NUMBER2:
|
|
6532
|
+
return typeof raw === "number" ? [] : [mismatch(f, TYPE_NUMBER2)];
|
|
6533
|
+
case TYPE_BOOL:
|
|
6534
|
+
return typeof raw === "boolean" ? [] : [mismatch(f, TYPE_BOOL)];
|
|
6535
|
+
case TYPE_ENUM2:
|
|
6536
|
+
if (typeof raw !== "string") return [mismatch(f, TYPE_ENUM2)];
|
|
6537
|
+
if (f.enumValues.includes(raw)) return [];
|
|
6538
|
+
return [
|
|
6539
|
+
{
|
|
6540
|
+
field: f.name,
|
|
6541
|
+
message: `input field ${q(f.name)} value ${q(raw)} is not one of [${f.enumValues.join(",")}]`,
|
|
6542
|
+
required: false
|
|
6543
|
+
}
|
|
6544
|
+
];
|
|
6545
|
+
case TYPE_ARRAY_OF_STRINGS2: {
|
|
6546
|
+
if (!Array.isArray(raw)) return [mismatch(f, TYPE_ARRAY_OF_STRINGS2)];
|
|
6547
|
+
const out = [];
|
|
6548
|
+
raw.forEach((item, i) => {
|
|
6549
|
+
if (typeof item !== "string") {
|
|
6550
|
+
out.push({
|
|
6551
|
+
field: f.name,
|
|
6552
|
+
message: `input field ${q(f.name)} index ${i} is not a string`,
|
|
6553
|
+
required: false
|
|
6554
|
+
});
|
|
6555
|
+
}
|
|
6556
|
+
});
|
|
6557
|
+
return out;
|
|
6558
|
+
}
|
|
6559
|
+
case TYPE_DATE:
|
|
6560
|
+
return typeof raw === "string" ? [] : [mismatch(f, TYPE_DATE)];
|
|
6561
|
+
case TYPE_FILE2: {
|
|
6562
|
+
if (typeof raw === "string") return raw === "" ? [mismatch(f, TYPE_FILE2)] : [];
|
|
6563
|
+
if (isRecord(raw)) {
|
|
6564
|
+
const ref = own2(raw, FILE_REF_KEY);
|
|
6565
|
+
return typeof ref === "string" && ref !== "" ? [] : [mismatch(f, TYPE_FILE2)];
|
|
6566
|
+
}
|
|
6567
|
+
return [mismatch(f, TYPE_FILE2)];
|
|
6568
|
+
}
|
|
6569
|
+
default:
|
|
6570
|
+
return [];
|
|
6571
|
+
}
|
|
6572
|
+
}
|
|
6573
|
+
function validateExamples(flow, issues) {
|
|
6574
|
+
const examples = flow[KEY_EXAMPLES];
|
|
6575
|
+
if (!Array.isArray(examples) || examples.length === 0) return;
|
|
6576
|
+
const walker = new Walker(flow, issues);
|
|
6577
|
+
walker.run(examples);
|
|
6578
|
+
for (const f of walker.findings) {
|
|
6579
|
+
const issue = {
|
|
6580
|
+
field: f.field,
|
|
6581
|
+
message: f.message,
|
|
6582
|
+
code: f.code,
|
|
6583
|
+
...f.suggestion !== void 0 ? { suggestion: f.suggestion } : {}
|
|
6584
|
+
};
|
|
6585
|
+
if (f.severity === "error") issues.error(issue);
|
|
6586
|
+
else issues.warn(issue);
|
|
6587
|
+
}
|
|
6588
|
+
}
|
|
6589
|
+
|
|
5088
6590
|
// src/index.ts
|
|
5089
6591
|
function countStepsWithErrors(errors) {
|
|
5090
6592
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -5137,6 +6639,7 @@ function validateParsed(flow, opts, sources) {
|
|
|
5137
6639
|
validateExecutorConfigEnvScopes(f, issues);
|
|
5138
6640
|
validateServerOwnedQueryKeys(f, issues);
|
|
5139
6641
|
validateCredentialEndpointPairing(f, issues);
|
|
6642
|
+
validateExamples(f, issues);
|
|
5140
6643
|
validateOrchestratorExons(f, issues);
|
|
5141
6644
|
validateUnknownKeys(f, issues);
|
|
5142
6645
|
const templateStats = validateTemplates(f, issues, opts);
|