@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/index.js
CHANGED
|
@@ -430,7 +430,7 @@ function validateBasicStructure(flow, issues) {
|
|
|
430
430
|
|
|
431
431
|
// src/spec/aigentflow-spec.json
|
|
432
432
|
var aigentflow_spec_default = {
|
|
433
|
-
specVersion: "2.
|
|
433
|
+
specVersion: "2.820.0",
|
|
434
434
|
inputSchemaVersion: 1,
|
|
435
435
|
executorSchemes: [
|
|
436
436
|
"a2a",
|
|
@@ -461,7 +461,6 @@ var aigentflow_spec_default = {
|
|
|
461
461
|
"messenger",
|
|
462
462
|
"mock",
|
|
463
463
|
"mqtt",
|
|
464
|
-
"nexus",
|
|
465
464
|
"opencorporates",
|
|
466
465
|
"openmeteo",
|
|
467
466
|
"overpass",
|
|
@@ -752,12 +751,6 @@ var aigentflow_spec_default = {
|
|
|
752
751
|
fields: ["api_key", "base_url", "organization"],
|
|
753
752
|
extraKey: "extra",
|
|
754
753
|
scopes: {
|
|
755
|
-
aigentchat: [
|
|
756
|
-
"AIGENTFLOW_AIGENTCHAT_API_KEY",
|
|
757
|
-
"AIGENTFLOW_AIGENTCHAT_BASE_URL",
|
|
758
|
-
"AIGENTFLOW_NEXUS_API_KEY",
|
|
759
|
-
"AIGENTFLOW_NEXUS_BASE_URL"
|
|
760
|
-
],
|
|
761
754
|
anthropic: [
|
|
762
755
|
"AIGENTFLOW_ANTHROPIC_API_KEY",
|
|
763
756
|
"AIGENTFLOW_ANTHROPIC_BASE_URL"
|
|
@@ -865,12 +858,6 @@ var aigentflow_spec_default = {
|
|
|
865
858
|
"AIGENTFLOW_MQTT_PASSWORD",
|
|
866
859
|
"AIGENTFLOW_MQTT_USERNAME"
|
|
867
860
|
],
|
|
868
|
-
nexus: [
|
|
869
|
-
"AIGENTFLOW_AIGENTCHAT_API_KEY",
|
|
870
|
-
"AIGENTFLOW_AIGENTCHAT_BASE_URL",
|
|
871
|
-
"AIGENTFLOW_NEXUS_API_KEY",
|
|
872
|
-
"AIGENTFLOW_NEXUS_BASE_URL"
|
|
873
|
-
],
|
|
874
861
|
ollama: [
|
|
875
862
|
"AIGENTFLOW_OLLAMA_API_KEY",
|
|
876
863
|
"AIGENTFLOW_OLLAMA_BASE_URL"
|
|
@@ -990,9 +977,6 @@ var aigentflow_spec_default = {
|
|
|
990
977
|
]
|
|
991
978
|
},
|
|
992
979
|
familyKeySeparator: "/",
|
|
993
|
-
protocolAliases: {
|
|
994
|
-
aigentchat: "nexus"
|
|
995
|
-
},
|
|
996
980
|
families: {
|
|
997
981
|
db: {
|
|
998
982
|
protocol: "db",
|
|
@@ -1151,40 +1135,6 @@ var aigentflow_spec_default = {
|
|
|
1151
1135
|
"AIGENTFLOW_MQTT_PASSWORD"
|
|
1152
1136
|
]
|
|
1153
1137
|
},
|
|
1154
|
-
nexus: {
|
|
1155
|
-
protocol: "nexus",
|
|
1156
|
-
configKeys: [
|
|
1157
|
-
"nexus",
|
|
1158
|
-
"aigentchat"
|
|
1159
|
-
],
|
|
1160
|
-
secretParams: [
|
|
1161
|
-
"aigentchat_api_key",
|
|
1162
|
-
"api_key"
|
|
1163
|
-
],
|
|
1164
|
-
endpointParams: [
|
|
1165
|
-
"aigentchat_base_url",
|
|
1166
|
-
"base_url"
|
|
1167
|
-
],
|
|
1168
|
-
defaultEndpoints: [],
|
|
1169
|
-
configApiKeyParam: "",
|
|
1170
|
-
configBaseUrlParam: "",
|
|
1171
|
-
implicitServerCredential: false,
|
|
1172
|
-
expandsServerEnvReferences: false,
|
|
1173
|
-
serverEnv: [
|
|
1174
|
-
"AIGENTFLOW_NEXUS_BASE_URL",
|
|
1175
|
-
"AIGENTFLOW_NEXUS_API_KEY",
|
|
1176
|
-
"AIGENTFLOW_AIGENTCHAT_API_KEY",
|
|
1177
|
-
"AIGENTFLOW_AIGENTCHAT_BASE_URL"
|
|
1178
|
-
],
|
|
1179
|
-
storedKeyShape: {
|
|
1180
|
-
ownKeyParams: [
|
|
1181
|
-
"aigentchat_api_key",
|
|
1182
|
-
"api_key"
|
|
1183
|
-
],
|
|
1184
|
-
ownCredentialsMapParam: "credentials",
|
|
1185
|
-
provider: "aigentchat"
|
|
1186
|
-
}
|
|
1187
|
-
},
|
|
1188
1138
|
opencorporates: {
|
|
1189
1139
|
protocol: "opencorporates",
|
|
1190
1140
|
configKeys: [
|
|
@@ -1305,6 +1255,97 @@ var aigentflow_spec_default = {
|
|
|
1305
1255
|
}
|
|
1306
1256
|
}
|
|
1307
1257
|
},
|
|
1258
|
+
examples: {
|
|
1259
|
+
limits: {
|
|
1260
|
+
maxPerFlow: 50,
|
|
1261
|
+
maxBlockBytes: 262144,
|
|
1262
|
+
maxInlineInputBytes: 32768,
|
|
1263
|
+
maxReferenceTextBytes: 32768,
|
|
1264
|
+
maxVariants: 10,
|
|
1265
|
+
maxTags: 8,
|
|
1266
|
+
maxMustNot: 12,
|
|
1267
|
+
maxMustNotContain: 20,
|
|
1268
|
+
maxRubricRunes: 4e3,
|
|
1269
|
+
maxGuidanceRunes: 2e3,
|
|
1270
|
+
maxNotesRunes: 2e3,
|
|
1271
|
+
titleMaxRunes: 80,
|
|
1272
|
+
maxMustNotRunes: 300,
|
|
1273
|
+
maxMustNotContainLength: 200,
|
|
1274
|
+
maxProvenanceRunes: 200,
|
|
1275
|
+
maxNoteRunes: 300,
|
|
1276
|
+
maxUrlBytes: 2048,
|
|
1277
|
+
maxPatternLength: 512,
|
|
1278
|
+
weightMax: 10,
|
|
1279
|
+
minScoreMin: 0,
|
|
1280
|
+
minScoreMax: 1
|
|
1281
|
+
},
|
|
1282
|
+
idPattern: "^[a-z][a-z0-9_]{1,47}$",
|
|
1283
|
+
tagPattern: "^[a-z][a-z0-9_-]{0,31}$",
|
|
1284
|
+
sha256Pattern: "^[0-9a-f]{64}$",
|
|
1285
|
+
mediaTypePattern: "^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$",
|
|
1286
|
+
signedUrlQueryKeyPattern: "^(x-amz-signature|signature|sig|token|access_token|key|x-goog-signature|se|sp)$",
|
|
1287
|
+
origins: [
|
|
1288
|
+
"author",
|
|
1289
|
+
"synthetic"
|
|
1290
|
+
],
|
|
1291
|
+
sideEffects: [
|
|
1292
|
+
"refuse",
|
|
1293
|
+
"allow"
|
|
1294
|
+
],
|
|
1295
|
+
statuses: [
|
|
1296
|
+
"completed",
|
|
1297
|
+
"failed",
|
|
1298
|
+
"paused_for_human"
|
|
1299
|
+
],
|
|
1300
|
+
reservedRefSchemes: [
|
|
1301
|
+
"artifact",
|
|
1302
|
+
"trove",
|
|
1303
|
+
"aiv",
|
|
1304
|
+
"s3",
|
|
1305
|
+
"gs"
|
|
1306
|
+
],
|
|
1307
|
+
secretPatterns: [
|
|
1308
|
+
{
|
|
1309
|
+
name: "an API key",
|
|
1310
|
+
expr: "\\bsk-[A-Za-z0-9_-]{20,}",
|
|
1311
|
+
flags: ""
|
|
1312
|
+
},
|
|
1313
|
+
{
|
|
1314
|
+
name: "a platform API key",
|
|
1315
|
+
expr: "\\bcrn_[A-Za-z0-9]{16,}",
|
|
1316
|
+
flags: ""
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
name: "an AWS access key",
|
|
1320
|
+
expr: "\\bAKIA[0-9A-Z]{16}\\b",
|
|
1321
|
+
flags: ""
|
|
1322
|
+
},
|
|
1323
|
+
{
|
|
1324
|
+
name: "a private key",
|
|
1325
|
+
expr: "-----BEGIN [A-Z ]*PRIVATE KEY-----",
|
|
1326
|
+
flags: ""
|
|
1327
|
+
},
|
|
1328
|
+
{
|
|
1329
|
+
name: "a bearer token",
|
|
1330
|
+
expr: "\\bbearer\\s+[A-Za-z0-9._~+/-]{20,}",
|
|
1331
|
+
flags: "i"
|
|
1332
|
+
},
|
|
1333
|
+
{
|
|
1334
|
+
name: "a Slack token",
|
|
1335
|
+
expr: "\\bxox[abprs]-[A-Za-z0-9-]{10,}",
|
|
1336
|
+
flags: ""
|
|
1337
|
+
},
|
|
1338
|
+
{
|
|
1339
|
+
name: "a GitHub token",
|
|
1340
|
+
expr: "\\bgh[pousr]_[A-Za-z0-9]{30,}",
|
|
1341
|
+
flags: ""
|
|
1342
|
+
},
|
|
1343
|
+
{
|
|
1344
|
+
name: "a retired registry key",
|
|
1345
|
+
expr: "\\baivk_[A-Za-z0-9]{20,}",
|
|
1346
|
+
flags: ""
|
|
1347
|
+
}
|
|
1348
|
+
]},
|
|
1308
1349
|
knownKeys: {
|
|
1309
1350
|
root: "Flow",
|
|
1310
1351
|
types: {
|
|
@@ -1325,6 +1366,17 @@ var aigentflow_spec_default = {
|
|
|
1325
1366
|
on_children_complete: "scalar",
|
|
1326
1367
|
shared_memory: "scalar"
|
|
1327
1368
|
},
|
|
1369
|
+
Checkpoint: {
|
|
1370
|
+
exact: "map<any>",
|
|
1371
|
+
fields: "map<FieldMatcher>",
|
|
1372
|
+
min_score: "scalar",
|
|
1373
|
+
must_not: "list<scalar>",
|
|
1374
|
+
must_not_contain: "list<scalar>",
|
|
1375
|
+
optional: "scalar",
|
|
1376
|
+
reference: "ExampleReference",
|
|
1377
|
+
rubric: "scalar",
|
|
1378
|
+
status: "scalar"
|
|
1379
|
+
},
|
|
1328
1380
|
CheckpointConfig: {
|
|
1329
1381
|
enabled: "scalar",
|
|
1330
1382
|
interval_steps: "scalar",
|
|
@@ -1347,6 +1399,37 @@ var aigentflow_spec_default = {
|
|
|
1347
1399
|
retry_delay: "scalar",
|
|
1348
1400
|
retry_on: "list<scalar>"
|
|
1349
1401
|
},
|
|
1402
|
+
ExampleDefinition: {
|
|
1403
|
+
checkpoints: "map<Checkpoint>",
|
|
1404
|
+
expected: "Expectation",
|
|
1405
|
+
guidance: "scalar",
|
|
1406
|
+
hold_out: "scalar",
|
|
1407
|
+
id: "scalar",
|
|
1408
|
+
input: "map<any>",
|
|
1409
|
+
input_ref: "FileRef",
|
|
1410
|
+
min_score: "scalar",
|
|
1411
|
+
notes: "scalar",
|
|
1412
|
+
side_effects: "scalar",
|
|
1413
|
+
tags: "list<scalar>",
|
|
1414
|
+
title: "scalar",
|
|
1415
|
+
variants: "list<ExampleVariant>",
|
|
1416
|
+
weight: "scalar"
|
|
1417
|
+
},
|
|
1418
|
+
ExampleReference: {
|
|
1419
|
+
ref: "FileRef",
|
|
1420
|
+
text: "scalar"
|
|
1421
|
+
},
|
|
1422
|
+
ExampleVariant: {
|
|
1423
|
+
expected: "Expectation",
|
|
1424
|
+
guidance: "scalar",
|
|
1425
|
+
id: "scalar",
|
|
1426
|
+
input_patch: "map<any>",
|
|
1427
|
+
input_ref: "FileRef",
|
|
1428
|
+
origin: "scalar",
|
|
1429
|
+
provenance: "scalar",
|
|
1430
|
+
title: "scalar",
|
|
1431
|
+
weight: "scalar"
|
|
1432
|
+
},
|
|
1350
1433
|
ExecutorDefaultsDefinition: {
|
|
1351
1434
|
frequency_penalty: "scalar",
|
|
1352
1435
|
max_tokens: "scalar",
|
|
@@ -1369,6 +1452,16 @@ var aigentflow_spec_default = {
|
|
|
1369
1452
|
allowed_models: "list<scalar>",
|
|
1370
1453
|
allowed_providers: "list<scalar>"
|
|
1371
1454
|
},
|
|
1455
|
+
Expectation: {
|
|
1456
|
+
exact: "map<any>",
|
|
1457
|
+
fields: "map<FieldMatcher>",
|
|
1458
|
+
min_score: "scalar",
|
|
1459
|
+
must_not: "list<scalar>",
|
|
1460
|
+
must_not_contain: "list<scalar>",
|
|
1461
|
+
reference: "ExampleReference",
|
|
1462
|
+
rubric: "scalar",
|
|
1463
|
+
status: "scalar"
|
|
1464
|
+
},
|
|
1372
1465
|
FailoverBreakerConfig: {
|
|
1373
1466
|
failure_threshold: "scalar",
|
|
1374
1467
|
open_duration: "scalar",
|
|
@@ -1387,6 +1480,22 @@ var aigentflow_spec_default = {
|
|
|
1387
1480
|
type: "scalar",
|
|
1388
1481
|
validation: "scalar"
|
|
1389
1482
|
},
|
|
1483
|
+
FieldMatcher: {
|
|
1484
|
+
approx: "scalar",
|
|
1485
|
+
contains: "scalar",
|
|
1486
|
+
equals: "any",
|
|
1487
|
+
matches: "scalar",
|
|
1488
|
+
not_contains: "scalar",
|
|
1489
|
+
one_of: "list<any>",
|
|
1490
|
+
present: "scalar",
|
|
1491
|
+
tolerance: "scalar"
|
|
1492
|
+
},
|
|
1493
|
+
FileRef: {
|
|
1494
|
+
media_type: "scalar",
|
|
1495
|
+
note: "scalar",
|
|
1496
|
+
sha256: "scalar",
|
|
1497
|
+
url: "scalar"
|
|
1498
|
+
},
|
|
1390
1499
|
Flow: {
|
|
1391
1500
|
aigentflow_version: "scalar",
|
|
1392
1501
|
billing: "BillingConfig",
|
|
@@ -1400,6 +1509,7 @@ var aigentflow_spec_default = {
|
|
|
1400
1509
|
discoverable_tools: "list<scalar>",
|
|
1401
1510
|
display_name: "scalar",
|
|
1402
1511
|
error_strategy: "ErrorStrategyDefinition",
|
|
1512
|
+
examples: "list<ExampleDefinition>",
|
|
1403
1513
|
executor_config: "map<ExecutorInstanceDefinition>",
|
|
1404
1514
|
exons_uploads: "ExonsUploadsFlowConfig",
|
|
1405
1515
|
expression_functions: "list<map<scalar>>",
|
|
@@ -1477,6 +1587,7 @@ var aigentflow_spec_default = {
|
|
|
1477
1587
|
pattern: "scalar",
|
|
1478
1588
|
required: "scalar",
|
|
1479
1589
|
type: "scalar",
|
|
1590
|
+
untrusted: "scalar",
|
|
1480
1591
|
visible_when: "VisibleWhenPredicate"
|
|
1481
1592
|
},
|
|
1482
1593
|
LoopDefinition: {
|
|
@@ -1486,6 +1597,7 @@ var aigentflow_spec_default = {
|
|
|
1486
1597
|
while: "scalar"
|
|
1487
1598
|
},
|
|
1488
1599
|
LoopStepDefinition: {
|
|
1600
|
+
allow_untrusted: "list<scalar>",
|
|
1489
1601
|
condition: "scalar",
|
|
1490
1602
|
credential: "scalar",
|
|
1491
1603
|
credentials: "map<StepCredentialBinding>",
|
|
@@ -1498,7 +1610,8 @@ var aigentflow_spec_default = {
|
|
|
1498
1610
|
post_processing: "list<any>",
|
|
1499
1611
|
pre_processing: "list<any>",
|
|
1500
1612
|
query: "map<any>",
|
|
1501
|
-
response_evaluation: "scalar"
|
|
1613
|
+
response_evaluation: "scalar",
|
|
1614
|
+
trusted_output: "scalar"
|
|
1502
1615
|
},
|
|
1503
1616
|
MockBinaryAsset: {
|
|
1504
1617
|
filename: "scalar",
|
|
@@ -1529,6 +1642,7 @@ var aigentflow_spec_default = {
|
|
|
1529
1642
|
deny_tools: "list<scalar>",
|
|
1530
1643
|
discoverable_tools: "list<scalar>",
|
|
1531
1644
|
enable_signals: "scalar",
|
|
1645
|
+
enforce_region: "scalar",
|
|
1532
1646
|
engram_scope: "scalar",
|
|
1533
1647
|
exons: "scalar",
|
|
1534
1648
|
human_question_timeout: "scalar",
|
|
@@ -1537,6 +1651,7 @@ var aigentflow_spec_default = {
|
|
|
1537
1651
|
max_tool_turns: "scalar",
|
|
1538
1652
|
max_turns: "scalar",
|
|
1539
1653
|
mode: "scalar",
|
|
1654
|
+
region: "scalar",
|
|
1540
1655
|
require_approval_for: "list<scalar>",
|
|
1541
1656
|
timeout_seconds: "scalar",
|
|
1542
1657
|
tool_discovery: "scalar",
|
|
@@ -1586,6 +1701,7 @@ var aigentflow_spec_default = {
|
|
|
1586
1701
|
properties: "map<PropertyDefinition>",
|
|
1587
1702
|
required: "scalar",
|
|
1588
1703
|
type: "scalar",
|
|
1704
|
+
untrusted: "scalar",
|
|
1589
1705
|
validation: "scalar"
|
|
1590
1706
|
},
|
|
1591
1707
|
StepCredentialBinding: {
|
|
@@ -1593,6 +1709,7 @@ var aigentflow_spec_default = {
|
|
|
1593
1709
|
source: "scalar"
|
|
1594
1710
|
},
|
|
1595
1711
|
StepDefinition: {
|
|
1712
|
+
allow_untrusted: "list<scalar>",
|
|
1596
1713
|
condition: "scalar",
|
|
1597
1714
|
credential: "scalar",
|
|
1598
1715
|
credentials: "map<StepCredentialBinding>",
|
|
@@ -1610,7 +1727,8 @@ var aigentflow_spec_default = {
|
|
|
1610
1727
|
quality_gate: "QualityGateDefinition",
|
|
1611
1728
|
query: "map<any>",
|
|
1612
1729
|
response_evaluation: "scalar",
|
|
1613
|
-
response_expectation: "map<FieldExpectationDefinition>"
|
|
1730
|
+
response_expectation: "map<FieldExpectationDefinition>",
|
|
1731
|
+
trusted_output: "scalar"
|
|
1614
1732
|
},
|
|
1615
1733
|
ThrottleDefinition: {
|
|
1616
1734
|
batch_delay: "scalar",
|
|
@@ -1730,9 +1848,25 @@ var CREDENTIAL_ENDPOINT_PAIRING = {
|
|
|
1730
1848
|
executorConfigProviders: aigentflow_spec_default.credentialEndpointPairing.ai.executorConfigProviders
|
|
1731
1849
|
},
|
|
1732
1850
|
familyKeySeparator: aigentflow_spec_default.credentialEndpointPairing.familyKeySeparator,
|
|
1733
|
-
protocolAliases: aigentflow_spec_default.credentialEndpointPairing.protocolAliases,
|
|
1734
1851
|
families: aigentflow_spec_default.credentialEndpointPairing.families
|
|
1735
1852
|
};
|
|
1853
|
+
var EXAMPLES = {
|
|
1854
|
+
limits: aigentflow_spec_default.examples.limits,
|
|
1855
|
+
idPattern: new RegExp(aigentflow_spec_default.examples.idPattern),
|
|
1856
|
+
tagPattern: new RegExp(aigentflow_spec_default.examples.tagPattern),
|
|
1857
|
+
sha256Pattern: new RegExp(aigentflow_spec_default.examples.sha256Pattern),
|
|
1858
|
+
mediaTypePattern: new RegExp(aigentflow_spec_default.examples.mediaTypePattern),
|
|
1859
|
+
// The reference writes this pattern case-insensitively (`(?i)`); the spec
|
|
1860
|
+
// carries the bare expression, so the flag is applied here.
|
|
1861
|
+
signedUrlQueryKeyPattern: new RegExp(aigentflow_spec_default.examples.signedUrlQueryKeyPattern, "i"),
|
|
1862
|
+
origins: new Set(aigentflow_spec_default.examples.origins),
|
|
1863
|
+
sideEffects: new Set(aigentflow_spec_default.examples.sideEffects),
|
|
1864
|
+
statuses: new Set(aigentflow_spec_default.examples.statuses),
|
|
1865
|
+
reservedRefSchemes: new Set(aigentflow_spec_default.examples.reservedRefSchemes),
|
|
1866
|
+
secretPatterns: aigentflow_spec_default.examples.secretPatterns.map((p) => ({
|
|
1867
|
+
name: p.name,
|
|
1868
|
+
re: new RegExp(p.expr, p.flags)
|
|
1869
|
+
}))};
|
|
1736
1870
|
|
|
1737
1871
|
// src/validators/executors.ts
|
|
1738
1872
|
var SCHEME_SEPARATOR = "://";
|
|
@@ -2930,6 +3064,17 @@ function validateOrchestratorCampaign(flow, issues, opts) {
|
|
|
2930
3064
|
code: "orchestrator_owner_needs_yield"
|
|
2931
3065
|
});
|
|
2932
3066
|
}
|
|
3067
|
+
if (orch.enforce_region === true) {
|
|
3068
|
+
const region = issues.stringOf(orch, "region", "orchestrator");
|
|
3069
|
+
if (region === null || region.trim() === "") {
|
|
3070
|
+
issues.error({
|
|
3071
|
+
field: "orchestrator.enforce_region",
|
|
3072
|
+
message: "orchestrator: enforce_region requires region (name the region the model must run in, e.g. region: eu)",
|
|
3073
|
+
code: "orchestrator_enforce_region_needs_region",
|
|
3074
|
+
suggestion: "Add `region: eu` (or the region the model must run in), or drop enforce_region"
|
|
3075
|
+
});
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
2933
3078
|
if (Array.isArray(orch.triggers)) {
|
|
2934
3079
|
orch.triggers.forEach((rawTrigger, i) => {
|
|
2935
3080
|
if (!isRecord(rawTrigger)) return;
|
|
@@ -3201,29 +3346,29 @@ function validateField(f, schemaPath, i, seen, issues) {
|
|
|
3201
3346
|
});
|
|
3202
3347
|
}
|
|
3203
3348
|
const stringy = INPUT_SCHEMA.stringTypes.has(type);
|
|
3204
|
-
const
|
|
3349
|
+
const mismatch2 = (constraint) => issues.error({
|
|
3205
3350
|
field: `${base}.${constraint}`,
|
|
3206
3351
|
message: `constraint '${constraint}' is not meaningful for field '${shownName}' of type '${type}'`,
|
|
3207
3352
|
code: "input_schema_constraint_type_mismatch"
|
|
3208
3353
|
});
|
|
3209
3354
|
if (!stringy) {
|
|
3210
|
-
if (f.min_length !== void 0)
|
|
3211
|
-
if (f.max_length !== void 0)
|
|
3212
|
-
if (pattern !== null && pattern !== "")
|
|
3355
|
+
if (f.min_length !== void 0) mismatch2("min_length");
|
|
3356
|
+
if (f.max_length !== void 0) mismatch2("max_length");
|
|
3357
|
+
if (pattern !== null && pattern !== "") mismatch2("pattern");
|
|
3213
3358
|
}
|
|
3214
3359
|
if (type !== TYPE_NUMBER) {
|
|
3215
|
-
if (f.min !== void 0)
|
|
3216
|
-
if (f.max !== void 0)
|
|
3360
|
+
if (f.min !== void 0) mismatch2("min");
|
|
3361
|
+
if (f.max !== void 0) mismatch2("max");
|
|
3217
3362
|
}
|
|
3218
3363
|
if (type !== TYPE_ARRAY_OF_STRINGS) {
|
|
3219
|
-
if (f.min_items !== void 0)
|
|
3220
|
-
if (f.max_items !== void 0)
|
|
3364
|
+
if (f.min_items !== void 0) mismatch2("min_items");
|
|
3365
|
+
if (f.max_items !== void 0) mismatch2("max_items");
|
|
3221
3366
|
}
|
|
3222
3367
|
if (type !== TYPE_FILE) {
|
|
3223
|
-
if (Array.isArray(f.accept) && f.accept.length > 0)
|
|
3224
|
-
if (f.max_size !== void 0)
|
|
3368
|
+
if (Array.isArray(f.accept) && f.accept.length > 0) mismatch2("accept");
|
|
3369
|
+
if (f.max_size !== void 0) mismatch2("max_size");
|
|
3225
3370
|
}
|
|
3226
|
-
if (type !== TYPE_ENUM && Array.isArray(f.enum) && f.enum.length > 0)
|
|
3371
|
+
if (type !== TYPE_ENUM && Array.isArray(f.enum) && f.enum.length > 0) mismatch2("enum");
|
|
3227
3372
|
const cap = (constraint, val) => issues.error({
|
|
3228
3373
|
field: `${base}.${constraint}`,
|
|
3229
3374
|
message: `constraint '${constraint}' (${val}) for field '${shownName}' exceeds the maximum of ${INPUT_SCHEMA.maxConstraintValue}`,
|
|
@@ -4503,7 +4648,8 @@ function parseHost(host) {
|
|
|
4503
4648
|
function parseAuthority(authority) {
|
|
4504
4649
|
const at = authority.lastIndexOf("@");
|
|
4505
4650
|
const host = parseHost(at < 0 ? authority : authority.slice(at + 1));
|
|
4506
|
-
if (host === null
|
|
4651
|
+
if (host === null) return null;
|
|
4652
|
+
if (at < 0) return { host, hasUser: false };
|
|
4507
4653
|
const userinfo = authority.slice(0, at);
|
|
4508
4654
|
if (!validUserinfo(userinfo)) return null;
|
|
4509
4655
|
const colon = userinfo.indexOf(":");
|
|
@@ -4511,7 +4657,7 @@ function parseAuthority(authority) {
|
|
|
4511
4657
|
for (const part of parts) {
|
|
4512
4658
|
if (unescapeText(part, Mode.UserPassword) === null) return null;
|
|
4513
4659
|
}
|
|
4514
|
-
return host;
|
|
4660
|
+
return { host, hasUser: true };
|
|
4515
4661
|
}
|
|
4516
4662
|
function getScheme(raw) {
|
|
4517
4663
|
for (let i = 0; i < raw.length; i++) {
|
|
@@ -4537,28 +4683,37 @@ function containsCTL(s) {
|
|
|
4537
4683
|
return false;
|
|
4538
4684
|
}
|
|
4539
4685
|
function goParseSchemeHost(rawURL) {
|
|
4686
|
+
const u = goParseURLParts(rawURL);
|
|
4687
|
+
return u === null ? null : { scheme: u.scheme, host: u.host };
|
|
4688
|
+
}
|
|
4689
|
+
function goParseURLParts(rawURL) {
|
|
4540
4690
|
const hash = rawURL.indexOf("#");
|
|
4541
4691
|
const main = hash < 0 ? rawURL : rawURL.slice(0, hash);
|
|
4542
4692
|
const frag = hash < 0 ? "" : rawURL.slice(hash + 1);
|
|
4543
4693
|
if (containsCTL(main)) return null;
|
|
4544
|
-
if (main === "*")
|
|
4694
|
+
if (main === "*") {
|
|
4695
|
+
return { scheme: "", host: "", hasUser: false, rawQuery: "", fragment: frag };
|
|
4696
|
+
}
|
|
4545
4697
|
const split = getScheme(main);
|
|
4546
4698
|
if (split === null) return null;
|
|
4547
4699
|
const scheme = split.scheme.toLowerCase();
|
|
4548
4700
|
let rest = split.rest;
|
|
4549
4701
|
const questionMarks = rest.split("?").length - 1;
|
|
4702
|
+
let rawQuery = "";
|
|
4550
4703
|
if (rest.endsWith("?") && questionMarks === 1) {
|
|
4551
4704
|
rest = rest.slice(0, -1);
|
|
4552
4705
|
} else if (questionMarks > 0) {
|
|
4706
|
+
rawQuery = rest.slice(rest.indexOf("?") + 1);
|
|
4553
4707
|
rest = rest.slice(0, rest.indexOf("?"));
|
|
4554
4708
|
}
|
|
4555
4709
|
if (!rest.startsWith("/")) {
|
|
4556
|
-
if (scheme !== "") return finish({ scheme, host: "" }, frag);
|
|
4710
|
+
if (scheme !== "") return finish({ scheme, host: "", hasUser: false, rawQuery }, frag);
|
|
4557
4711
|
const slash = rest.indexOf("/");
|
|
4558
4712
|
const segment = slash < 0 ? rest : rest.slice(0, slash);
|
|
4559
4713
|
if (segment.includes(":")) return null;
|
|
4560
4714
|
}
|
|
4561
4715
|
let host = "";
|
|
4716
|
+
let hasUser = false;
|
|
4562
4717
|
if ((scheme !== "" || !rest.startsWith("///")) && rest.startsWith("//")) {
|
|
4563
4718
|
let authority = rest.slice(2);
|
|
4564
4719
|
rest = "";
|
|
@@ -4569,14 +4724,26 @@ function goParseSchemeHost(rawURL) {
|
|
|
4569
4724
|
}
|
|
4570
4725
|
const parsed = parseAuthority(authority);
|
|
4571
4726
|
if (parsed === null) return null;
|
|
4572
|
-
host = parsed;
|
|
4727
|
+
host = parsed.host;
|
|
4728
|
+
hasUser = parsed.hasUser;
|
|
4573
4729
|
}
|
|
4574
4730
|
if (unescapeText(rest, Mode.Path) === null) return null;
|
|
4575
|
-
return finish({ scheme, host }, frag);
|
|
4731
|
+
return finish({ scheme, host, hasUser, rawQuery }, frag);
|
|
4576
4732
|
}
|
|
4577
4733
|
function finish(u, frag) {
|
|
4578
4734
|
if (frag !== "" && unescapeText(frag, Mode.Fragment) === null) return null;
|
|
4579
|
-
return u;
|
|
4735
|
+
return { ...u, fragment: frag };
|
|
4736
|
+
}
|
|
4737
|
+
function goQueryKeys(rawQuery) {
|
|
4738
|
+
const keys = [];
|
|
4739
|
+
for (const pair of rawQuery.split("&")) {
|
|
4740
|
+
if (pair === "" || pair.includes(";")) continue;
|
|
4741
|
+
const eq = pair.indexOf("=");
|
|
4742
|
+
const rawKey = eq < 0 ? pair : pair.slice(0, eq);
|
|
4743
|
+
const key = unescapeText(rawKey.replace(/\+/g, " "), Mode.Path);
|
|
4744
|
+
if (key !== null) keys.push(key);
|
|
4745
|
+
}
|
|
4746
|
+
return keys;
|
|
4580
4747
|
}
|
|
4581
4748
|
function goEndpointOrigin(raw, defaultPorts, protocolSeparator) {
|
|
4582
4749
|
const trimmed = goTrimSpace(raw);
|
|
@@ -4713,12 +4880,10 @@ function warnStoredShape(issues, field, provider) {
|
|
|
4713
4880
|
function familyOf(executor) {
|
|
4714
4881
|
const sep = executor.indexOf(CREDENTIAL_ENDPOINT_PAIRING.protocolSeparator);
|
|
4715
4882
|
if (sep <= 0) return null;
|
|
4716
|
-
|
|
4883
|
+
const protocol = executor.slice(0, sep);
|
|
4717
4884
|
const rest = executor.slice(sep + CREDENTIAL_ENDPOINT_PAIRING.protocolSeparator.length);
|
|
4718
4885
|
const slash = rest.indexOf("/");
|
|
4719
4886
|
const driver = slash < 0 ? rest : rest.slice(0, slash);
|
|
4720
|
-
const alias = own(CREDENTIAL_ENDPOINT_PAIRING.protocolAliases, protocol);
|
|
4721
|
-
if (typeof alias === "string") protocol = alias;
|
|
4722
4887
|
const candidates = driver === "" ? [protocol] : [protocol + CREDENTIAL_ENDPOINT_PAIRING.familyKeySeparator + driver, protocol];
|
|
4723
4888
|
for (const key of candidates) {
|
|
4724
4889
|
const family = own(CREDENTIAL_ENDPOINT_PAIRING.families, key);
|
|
@@ -4798,24 +4963,11 @@ function familyAuthorEndpoint(flow, family, step, issues) {
|
|
|
4798
4963
|
}
|
|
4799
4964
|
return null;
|
|
4800
4965
|
}
|
|
4801
|
-
function storedShapeHasOwnKey(family, query) {
|
|
4802
|
-
const shape = family.storedKeyShape;
|
|
4803
|
-
if (shape === void 0) return false;
|
|
4804
|
-
if (shape.ownKeyParams.some((key) => nonEmptyString(query, key) !== null)) return true;
|
|
4805
|
-
return isRecord(own(query, shape.ownCredentialsMapParam));
|
|
4806
|
-
}
|
|
4807
4966
|
function validateFamilies(flow, steps, issues) {
|
|
4808
4967
|
for (const step of steps) {
|
|
4809
4968
|
const found = familyOf(step.executor);
|
|
4810
4969
|
if (found === null || found.family.implicitServerCredential) continue;
|
|
4811
4970
|
const { key: familyKey, family } = found;
|
|
4812
|
-
if (family.storedKeyShape !== void 0) {
|
|
4813
|
-
if (storedShapeHasOwnKey(family, step.query)) continue;
|
|
4814
|
-
const key = family.endpointParams.find((k) => nonEmptyEndpoint(step.query, k) !== null);
|
|
4815
|
-
if (key !== void 0)
|
|
4816
|
-
warnStoredShape(issues, step.field(key), family.storedKeyShape.provider);
|
|
4817
|
-
continue;
|
|
4818
|
-
}
|
|
4819
4971
|
const secret = familySecret(flow, family, step, issues);
|
|
4820
4972
|
if (secret === null || secret.envName === null) continue;
|
|
4821
4973
|
const endpointField = familyAuthorEndpoint(flow, family, step, issues);
|
|
@@ -5050,6 +5202,1291 @@ function warnOrchestratorToolWithheld(flow, orch, report, issues) {
|
|
|
5050
5202
|
}
|
|
5051
5203
|
}
|
|
5052
5204
|
|
|
5205
|
+
// src/validators/examples.ts
|
|
5206
|
+
var KEY_EXAMPLES = "examples";
|
|
5207
|
+
var VISIBILITY_PUBLIC = "public";
|
|
5208
|
+
var STATUS_COMPLETED = "completed";
|
|
5209
|
+
var STATUS_FAILED = "failed";
|
|
5210
|
+
var STATUS_PAUSED_FOR_HUMAN = "paused_for_human";
|
|
5211
|
+
var ORIGIN_NONE = "";
|
|
5212
|
+
var SIDE_EFFECTS_NONE = "";
|
|
5213
|
+
var SCHEME_HTTPS = "https";
|
|
5214
|
+
var FILE_REF_KEYS = /* @__PURE__ */ new Set(["url", "sha256", "media_type", "note"]);
|
|
5215
|
+
var FILE_REF_KEY = "ref";
|
|
5216
|
+
var TYPE_SECRET = "secret";
|
|
5217
|
+
var TYPE_FILE2 = "file";
|
|
5218
|
+
var TYPE_STRING = "string";
|
|
5219
|
+
var TYPE_MULTILINE = "multiline";
|
|
5220
|
+
var TYPE_NUMBER2 = "number";
|
|
5221
|
+
var TYPE_BOOL = "bool";
|
|
5222
|
+
var TYPE_ENUM2 = "enum";
|
|
5223
|
+
var TYPE_ARRAY_OF_STRINGS2 = "array_of_strings";
|
|
5224
|
+
var TYPE_DATE = "date";
|
|
5225
|
+
var MAX_RECURSION_DEPTH = 32;
|
|
5226
|
+
var TEXTUAL_MEDIA_BASES = /* @__PURE__ */ new Set([
|
|
5227
|
+
"application/json",
|
|
5228
|
+
"application/xml",
|
|
5229
|
+
"application/yaml",
|
|
5230
|
+
"application/x-yaml",
|
|
5231
|
+
"application/markdown"
|
|
5232
|
+
]);
|
|
5233
|
+
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";
|
|
5234
|
+
var SUGGESTION_CHECKPOINT = "Did you rename the step? Update the checkpoint to the new name";
|
|
5235
|
+
var SUGGESTION_SECRET_VALUE = "Remove the value; you are asked for it when you run the check";
|
|
5236
|
+
var SUGGESTION_EXPECTED = "Add at least a rubric: one or two sentences on what a good result looks like";
|
|
5237
|
+
var utf8Encoder2 = new TextEncoder();
|
|
5238
|
+
function byteLen(s) {
|
|
5239
|
+
return utf8Encoder2.encode(s).length;
|
|
5240
|
+
}
|
|
5241
|
+
function runeCount(s) {
|
|
5242
|
+
let n = 0;
|
|
5243
|
+
for (const _ of s) n++;
|
|
5244
|
+
return n;
|
|
5245
|
+
}
|
|
5246
|
+
function q(s) {
|
|
5247
|
+
let out = '"';
|
|
5248
|
+
for (const ch of s) {
|
|
5249
|
+
const c = ch.codePointAt(0);
|
|
5250
|
+
if (ch === '"') out += '\\"';
|
|
5251
|
+
else if (ch === "\\") out += "\\\\";
|
|
5252
|
+
else if (c === 7) out += "\\a";
|
|
5253
|
+
else if (c === 8) out += "\\b";
|
|
5254
|
+
else if (c === 12) out += "\\f";
|
|
5255
|
+
else if (c === 10) out += "\\n";
|
|
5256
|
+
else if (c === 13) out += "\\r";
|
|
5257
|
+
else if (c === 9) out += "\\t";
|
|
5258
|
+
else if (c === 11) out += "\\v";
|
|
5259
|
+
else if (c < 32 || c === 127) out += `\\x${c.toString(16).padStart(2, "0")}`;
|
|
5260
|
+
else if (c >= 128 && /[\p{C}\p{Z}]/u.test(ch)) {
|
|
5261
|
+
out += c > 65535 ? `\\U${c.toString(16).padStart(8, "0")}` : `\\u${c.toString(16).padStart(4, "0")}`;
|
|
5262
|
+
} else out += ch;
|
|
5263
|
+
}
|
|
5264
|
+
return `${out}"`;
|
|
5265
|
+
}
|
|
5266
|
+
function num(n) {
|
|
5267
|
+
if (Number.isNaN(n)) return "NaN";
|
|
5268
|
+
if (n === Infinity) return "+Inf";
|
|
5269
|
+
if (n === -Infinity) return "-Inf";
|
|
5270
|
+
if (n === 0) return Object.is(n, -0) ? "-0" : "0";
|
|
5271
|
+
const [mantissa = "", exponent = "0"] = n.toExponential().split("e");
|
|
5272
|
+
const exp = Number(exponent);
|
|
5273
|
+
if (exp < -4 || exp >= 6) {
|
|
5274
|
+
const sign = exp < 0 ? "-" : "+";
|
|
5275
|
+
return `${mantissa}e${sign}${String(Math.abs(exp)).padStart(2, "0")}`;
|
|
5276
|
+
}
|
|
5277
|
+
return String(n);
|
|
5278
|
+
}
|
|
5279
|
+
function has(o, key) {
|
|
5280
|
+
return Object.prototype.hasOwnProperty.call(o, key) && o[key] !== null && o[key] !== void 0;
|
|
5281
|
+
}
|
|
5282
|
+
function own2(o, key) {
|
|
5283
|
+
return Object.prototype.hasOwnProperty.call(o, key) ? o[key] : void 0;
|
|
5284
|
+
}
|
|
5285
|
+
function setOwn(o, key, value) {
|
|
5286
|
+
Object.defineProperty(o, key, {
|
|
5287
|
+
value,
|
|
5288
|
+
enumerable: true,
|
|
5289
|
+
writable: true,
|
|
5290
|
+
configurable: true
|
|
5291
|
+
});
|
|
5292
|
+
}
|
|
5293
|
+
function sortedKeys(o) {
|
|
5294
|
+
return Object.keys(o).sort();
|
|
5295
|
+
}
|
|
5296
|
+
function numberOf(v) {
|
|
5297
|
+
return typeof v === "number" ? v : void 0;
|
|
5298
|
+
}
|
|
5299
|
+
function normalize(v, depth) {
|
|
5300
|
+
if (depth > MAX_RECURSION_DEPTH) return null;
|
|
5301
|
+
if (v instanceof Date) {
|
|
5302
|
+
if (Number.isNaN(v.getTime())) return null;
|
|
5303
|
+
const iso = v.toISOString();
|
|
5304
|
+
if (/T00:00:00\.000Z$/.test(iso)) return iso.slice(0, 10);
|
|
5305
|
+
return iso.replace(/\.000Z$/, "Z");
|
|
5306
|
+
}
|
|
5307
|
+
if (Array.isArray(v)) return v.map((x) => normalize(x, depth + 1));
|
|
5308
|
+
if (isRecord(v)) {
|
|
5309
|
+
const out = {};
|
|
5310
|
+
for (const k of Object.keys(v)) setOwn(out, k, normalize(v[k], depth + 1));
|
|
5311
|
+
return out;
|
|
5312
|
+
}
|
|
5313
|
+
return v;
|
|
5314
|
+
}
|
|
5315
|
+
function deepCopy(v, depth) {
|
|
5316
|
+
if (depth > MAX_RECURSION_DEPTH) return v;
|
|
5317
|
+
if (Array.isArray(v)) return v.map((x) => deepCopy(x, depth + 1));
|
|
5318
|
+
if (isRecord(v)) {
|
|
5319
|
+
const out = {};
|
|
5320
|
+
for (const k of Object.keys(v)) setOwn(out, k, deepCopy(v[k], depth + 1));
|
|
5321
|
+
return out;
|
|
5322
|
+
}
|
|
5323
|
+
return v;
|
|
5324
|
+
}
|
|
5325
|
+
function mergePatch(target, patch, depth) {
|
|
5326
|
+
if (depth > MAX_RECURSION_DEPTH) return;
|
|
5327
|
+
for (const k of Object.keys(patch)) {
|
|
5328
|
+
const pv = patch[k];
|
|
5329
|
+
if (pv === null || pv === void 0) {
|
|
5330
|
+
delete target[k];
|
|
5331
|
+
continue;
|
|
5332
|
+
}
|
|
5333
|
+
if (isRecord(pv)) {
|
|
5334
|
+
const current = own2(target, k);
|
|
5335
|
+
const existing = isRecord(current) ? deepCopy(current, 0) : {};
|
|
5336
|
+
mergePatch(existing, pv, depth + 1);
|
|
5337
|
+
setOwn(target, k, existing);
|
|
5338
|
+
continue;
|
|
5339
|
+
}
|
|
5340
|
+
setOwn(target, k, deepCopy(pv, 0));
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
function applyExamplePatch(base, patch) {
|
|
5344
|
+
const out = deepCopy(base, 0);
|
|
5345
|
+
mergePatch(out, patch, 0);
|
|
5346
|
+
return out;
|
|
5347
|
+
}
|
|
5348
|
+
function asFileRef(v) {
|
|
5349
|
+
if (!isRecord(v)) return null;
|
|
5350
|
+
const keys = Object.keys(v);
|
|
5351
|
+
if (keys.length === 0) return null;
|
|
5352
|
+
for (const k of keys) if (!FILE_REF_KEYS.has(k)) return null;
|
|
5353
|
+
const str = (k) => {
|
|
5354
|
+
const x = own2(v, k);
|
|
5355
|
+
return typeof x === "string" ? x : "";
|
|
5356
|
+
};
|
|
5357
|
+
return {
|
|
5358
|
+
url: str("url"),
|
|
5359
|
+
sha256: str("sha256"),
|
|
5360
|
+
mediaType: str("media_type"),
|
|
5361
|
+
note: str("note")
|
|
5362
|
+
};
|
|
5363
|
+
}
|
|
5364
|
+
function isTextualMediaType(mediaType) {
|
|
5365
|
+
const base = goToLower(goTrimSpace((mediaType.split(";")[0] ?? "").toString()));
|
|
5366
|
+
if (base.startsWith("text/")) return true;
|
|
5367
|
+
if (base.endsWith("+json") || base.endsWith("+xml")) return true;
|
|
5368
|
+
return TEXTUAL_MEDIA_BASES.has(base);
|
|
5369
|
+
}
|
|
5370
|
+
var Walker = class _Walker {
|
|
5371
|
+
constructor(flow, issues) {
|
|
5372
|
+
this.flow = flow;
|
|
5373
|
+
this.issues = issues;
|
|
5374
|
+
this.findings = [];
|
|
5375
|
+
}
|
|
5376
|
+
// -- emit -------------------------------------------------------------
|
|
5377
|
+
add(severity, code, field, message, suggestion) {
|
|
5378
|
+
this.findings.push({
|
|
5379
|
+
code,
|
|
5380
|
+
severity,
|
|
5381
|
+
field,
|
|
5382
|
+
message,
|
|
5383
|
+
...suggestion !== void 0 ? { suggestion } : {}
|
|
5384
|
+
});
|
|
5385
|
+
}
|
|
5386
|
+
err(code, field, message, suggestion) {
|
|
5387
|
+
this.add("error", code, field, message, suggestion);
|
|
5388
|
+
}
|
|
5389
|
+
warn(code, field, message) {
|
|
5390
|
+
this.add("warning", code, field, message);
|
|
5391
|
+
}
|
|
5392
|
+
/** A Go `string` field read by its source text; absent is "". */
|
|
5393
|
+
s(o, key, parentPath) {
|
|
5394
|
+
return this.issues.stringOf(o, key, parentPath) ?? "";
|
|
5395
|
+
}
|
|
5396
|
+
// -- the set ----------------------------------------------------------
|
|
5397
|
+
run(examples) {
|
|
5398
|
+
this.validateSet(examples);
|
|
5399
|
+
const seen = /* @__PURE__ */ new Map();
|
|
5400
|
+
examples.forEach((ex, i) => {
|
|
5401
|
+
if (isRecord(ex)) this.validateExample(i, ex, seen);
|
|
5402
|
+
});
|
|
5403
|
+
}
|
|
5404
|
+
validateSet(examples) {
|
|
5405
|
+
const n = examples.length;
|
|
5406
|
+
const L = EXAMPLES.limits;
|
|
5407
|
+
if (n > L.maxPerFlow) {
|
|
5408
|
+
this.err(
|
|
5409
|
+
"examples_too_many",
|
|
5410
|
+
KEY_EXAMPLES,
|
|
5411
|
+
`flow declares ${n} examples; the limit is ${L.maxPerFlow}`
|
|
5412
|
+
);
|
|
5413
|
+
}
|
|
5414
|
+
const size = blockBytes(examples);
|
|
5415
|
+
if (size !== null && size > L.maxBlockBytes) {
|
|
5416
|
+
this.err(
|
|
5417
|
+
"examples_block_too_large",
|
|
5418
|
+
KEY_EXAMPLES,
|
|
5419
|
+
`the examples block is ${size} bytes; the limit is ${L.maxBlockBytes}. Move large inputs or target outputs to a file reference (https URL).`
|
|
5420
|
+
);
|
|
5421
|
+
}
|
|
5422
|
+
const allHeld = examples.every((e) => isRecord(e) && own2(e, "hold_out") === true);
|
|
5423
|
+
if (allHeld) {
|
|
5424
|
+
this.warn(
|
|
5425
|
+
"examples_all_held_out",
|
|
5426
|
+
KEY_EXAMPLES,
|
|
5427
|
+
"every example is held out, so nothing is left to tune on"
|
|
5428
|
+
);
|
|
5429
|
+
}
|
|
5430
|
+
if (this.isPublic()) {
|
|
5431
|
+
this.warn(
|
|
5432
|
+
"examples_published_with_public_flow",
|
|
5433
|
+
KEY_EXAMPLES,
|
|
5434
|
+
"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."
|
|
5435
|
+
);
|
|
5436
|
+
}
|
|
5437
|
+
}
|
|
5438
|
+
isPublic() {
|
|
5439
|
+
return this.s(this.flow, "visibility", "") === VISIBILITY_PUBLIC;
|
|
5440
|
+
}
|
|
5441
|
+
// -- one example ------------------------------------------------------
|
|
5442
|
+
validateExample(i, ex, seen) {
|
|
5443
|
+
const L = EXAMPLES.limits;
|
|
5444
|
+
const path = `examples[${i}]`;
|
|
5445
|
+
const id = this.s(ex, "id", path);
|
|
5446
|
+
const label = id === "" ? `#${i + 1}` : id;
|
|
5447
|
+
if (id === "") {
|
|
5448
|
+
this.err("example_id_missing", `${path}.id`, `example #${i + 1} has no id`, SUGGESTION_ID);
|
|
5449
|
+
} else if (!EXAMPLES.idPattern.test(id)) {
|
|
5450
|
+
this.err(
|
|
5451
|
+
"example_id_invalid",
|
|
5452
|
+
`${path}.id`,
|
|
5453
|
+
`example id ${q(id)} is not valid: use lower-case letters, digits and underscores, starting with a letter (2 to 48 characters)`,
|
|
5454
|
+
SUGGESTION_ID
|
|
5455
|
+
);
|
|
5456
|
+
} else {
|
|
5457
|
+
const first = seen.get(id);
|
|
5458
|
+
if (first !== void 0) {
|
|
5459
|
+
this.err(
|
|
5460
|
+
"example_id_duplicate",
|
|
5461
|
+
`${path}.id`,
|
|
5462
|
+
`example id ${q(id)} is used by examples #${first + 1} and #${i + 1}`
|
|
5463
|
+
);
|
|
5464
|
+
} else {
|
|
5465
|
+
seen.set(id, i);
|
|
5466
|
+
}
|
|
5467
|
+
}
|
|
5468
|
+
const title = this.s(ex, "title", path);
|
|
5469
|
+
if (goTrimSpace(title) === "") {
|
|
5470
|
+
this.err(
|
|
5471
|
+
"example_title_missing",
|
|
5472
|
+
`${path}.title`,
|
|
5473
|
+
`example ${q(label)} has no title; the title is what a person sees in the list of examples`
|
|
5474
|
+
);
|
|
5475
|
+
} else {
|
|
5476
|
+
const n = runeCount(title);
|
|
5477
|
+
if (n > L.titleMaxRunes) {
|
|
5478
|
+
this.err(
|
|
5479
|
+
"example_title_too_long",
|
|
5480
|
+
`${path}.title`,
|
|
5481
|
+
`example ${q(label)} title is ${n} characters; the limit is ${L.titleMaxRunes}`
|
|
5482
|
+
);
|
|
5483
|
+
}
|
|
5484
|
+
}
|
|
5485
|
+
const guidance = this.s(ex, "guidance", path);
|
|
5486
|
+
if (goTrimSpace(guidance) === "") {
|
|
5487
|
+
this.warn(
|
|
5488
|
+
"example_guidance_missing",
|
|
5489
|
+
`${path}.guidance`,
|
|
5490
|
+
`example ${q(label)} has no guidance. One sentence on why this case matters helps the judge and the next author.`
|
|
5491
|
+
);
|
|
5492
|
+
} else {
|
|
5493
|
+
const n = runeCount(guidance);
|
|
5494
|
+
if (n > L.maxGuidanceRunes) {
|
|
5495
|
+
this.err(
|
|
5496
|
+
"example_guidance_too_long",
|
|
5497
|
+
`${path}.guidance`,
|
|
5498
|
+
`example ${q(label)} guidance is ${n} characters; the limit is ${L.maxGuidanceRunes}`
|
|
5499
|
+
);
|
|
5500
|
+
}
|
|
5501
|
+
}
|
|
5502
|
+
const notesN = runeCount(this.s(ex, "notes", path));
|
|
5503
|
+
if (notesN > L.maxNotesRunes) {
|
|
5504
|
+
this.err(
|
|
5505
|
+
"example_notes_too_long",
|
|
5506
|
+
`${path}.notes`,
|
|
5507
|
+
`example ${q(label)} notes are ${notesN} characters; the limit is ${L.maxNotesRunes}`
|
|
5508
|
+
);
|
|
5509
|
+
}
|
|
5510
|
+
this.validateTags(path, label, own2(ex, "tags"));
|
|
5511
|
+
this.validateWeight(`${path}.weight`, label, numberOf(own2(ex, "weight")));
|
|
5512
|
+
const minScore = numberOf(own2(ex, "min_score"));
|
|
5513
|
+
if (minScore !== void 0 && !scoreInRange(minScore)) {
|
|
5514
|
+
this.err(
|
|
5515
|
+
"example_min_score_range",
|
|
5516
|
+
`${path}.min_score`,
|
|
5517
|
+
`example ${q(label)} has min_score ${num(minScore)}; use a number from 0 to 1 (0.7 is 7.3 out of 10)`
|
|
5518
|
+
);
|
|
5519
|
+
}
|
|
5520
|
+
const sideEffects = this.s(ex, "side_effects", path);
|
|
5521
|
+
if (sideEffects !== SIDE_EFFECTS_NONE && !EXAMPLES.sideEffects.has(sideEffects)) {
|
|
5522
|
+
this.err(
|
|
5523
|
+
"example_side_effects_invalid",
|
|
5524
|
+
`${path}.side_effects`,
|
|
5525
|
+
`example ${q(label)} has side_effects ${q(sideEffects)}; use refuse or allow`
|
|
5526
|
+
);
|
|
5527
|
+
}
|
|
5528
|
+
const holdOut = own2(ex, "hold_out") === true;
|
|
5529
|
+
if (holdOut && this.isPublic()) {
|
|
5530
|
+
this.err(
|
|
5531
|
+
"example_holdout_in_public_flow",
|
|
5532
|
+
`${path}.hold_out`,
|
|
5533
|
+
`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.`
|
|
5534
|
+
);
|
|
5535
|
+
}
|
|
5536
|
+
const hasInput = has(ex, "input");
|
|
5537
|
+
const hasInputRef = has(ex, "input_ref");
|
|
5538
|
+
if (hasInput && hasInputRef) {
|
|
5539
|
+
this.err(
|
|
5540
|
+
"example_input_conflict",
|
|
5541
|
+
path,
|
|
5542
|
+
`example ${q(label)} sets both input and input_ref; use exactly one`
|
|
5543
|
+
);
|
|
5544
|
+
} else if (!hasInput && !hasInputRef) {
|
|
5545
|
+
this.err(
|
|
5546
|
+
"example_input_missing",
|
|
5547
|
+
path,
|
|
5548
|
+
`example ${q(label)} has neither input nor input_ref; use input: {} for a flow that takes no input`
|
|
5549
|
+
);
|
|
5550
|
+
}
|
|
5551
|
+
const rawInput = own2(ex, "input");
|
|
5552
|
+
const baseInput = isRecord(rawInput) ? rawInput : null;
|
|
5553
|
+
if (baseInput !== null) {
|
|
5554
|
+
this.checkInput(`${path}.input`, label, baseInput, "example_input_invalid");
|
|
5555
|
+
}
|
|
5556
|
+
const rawInputRef = own2(ex, "input_ref");
|
|
5557
|
+
if (isRecord(rawInputRef)) {
|
|
5558
|
+
this.validateFileRef(`${path}.input_ref`, this.fileRefOf(rawInputRef, `${path}.input_ref`));
|
|
5559
|
+
}
|
|
5560
|
+
let binaryOnly = false;
|
|
5561
|
+
const expected = own2(ex, "expected");
|
|
5562
|
+
if (!has(ex, "expected")) {
|
|
5563
|
+
this.err(
|
|
5564
|
+
"example_expected_missing",
|
|
5565
|
+
`${path}.expected`,
|
|
5566
|
+
`example ${q(label)} does not say what a good result is: add a rubric, fields, exact or reference`,
|
|
5567
|
+
SUGGESTION_EXPECTED
|
|
5568
|
+
);
|
|
5569
|
+
} else if (isRecord(expected)) {
|
|
5570
|
+
this.validateExpectation(`${path}.expected`, label, expected, "final", "");
|
|
5571
|
+
binaryOnly = this.expectationIsBinaryReferenceOnly(expected, `${path}.expected`);
|
|
5572
|
+
}
|
|
5573
|
+
if (binaryOnly) {
|
|
5574
|
+
this.warn(
|
|
5575
|
+
"examples_unreadable_by_judge",
|
|
5576
|
+
`${path}.expected`,
|
|
5577
|
+
"this example can be run but not scored"
|
|
5578
|
+
);
|
|
5579
|
+
}
|
|
5580
|
+
this.validateCheckpoints(path, label, own2(ex, "checkpoints"));
|
|
5581
|
+
this.validateVariants(i, path, label, ex, hasInput, baseInput);
|
|
5582
|
+
this.scanSecretLike(i, path, ex);
|
|
5583
|
+
}
|
|
5584
|
+
validateTags(path, label, tags) {
|
|
5585
|
+
if (!Array.isArray(tags)) return;
|
|
5586
|
+
if (tags.length > EXAMPLES.limits.maxTags) {
|
|
5587
|
+
this.err(
|
|
5588
|
+
"example_tag_invalid",
|
|
5589
|
+
`${path}.tags`,
|
|
5590
|
+
`example ${q(label)} has ${tags.length} tags; the limit is ${EXAMPLES.limits.maxTags}`
|
|
5591
|
+
);
|
|
5592
|
+
}
|
|
5593
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5594
|
+
tags.forEach((item, j) => {
|
|
5595
|
+
const field = `${path}.tags[${j}]`;
|
|
5596
|
+
const tag = item === null ? "" : this.issues.stringAt(item, field) ?? null;
|
|
5597
|
+
if (tag === null) return;
|
|
5598
|
+
if (!EXAMPLES.tagPattern.test(tag)) {
|
|
5599
|
+
this.err("example_tag_invalid", field, `example ${q(label)} has an invalid tag ${q(tag)}`);
|
|
5600
|
+
} else if (seen.has(tag)) {
|
|
5601
|
+
this.err(
|
|
5602
|
+
"example_tag_invalid",
|
|
5603
|
+
field,
|
|
5604
|
+
`example ${q(label)} lists the tag ${q(tag)} more than once`
|
|
5605
|
+
);
|
|
5606
|
+
}
|
|
5607
|
+
seen.add(tag);
|
|
5608
|
+
});
|
|
5609
|
+
}
|
|
5610
|
+
validateWeight(field, label, weight) {
|
|
5611
|
+
if (weight === void 0) return;
|
|
5612
|
+
if (Number.isNaN(weight) || weight <= 0 || weight > EXAMPLES.limits.weightMax) {
|
|
5613
|
+
this.err(
|
|
5614
|
+
"example_weight_range",
|
|
5615
|
+
field,
|
|
5616
|
+
`example ${q(label)} has weight ${num(weight)}; use a number above 0 and at most 10`
|
|
5617
|
+
);
|
|
5618
|
+
}
|
|
5619
|
+
}
|
|
5620
|
+
// -- input ------------------------------------------------------------
|
|
5621
|
+
/**
|
|
5622
|
+
* Judge one inline input map (an example's `input`, or a variant's patched
|
|
5623
|
+
* input). NARROWED relative to the reference, which runs its full value
|
|
5624
|
+
* validator: here an unknown field, a missing required field, a value of the
|
|
5625
|
+
* wrong KIND and an enum value outside its list are judged; length, range,
|
|
5626
|
+
* pattern and date-format constraints are not (PARITY.md).
|
|
5627
|
+
*/
|
|
5628
|
+
checkInput(field, label, raw, code) {
|
|
5629
|
+
const input = normalize(raw, 0) ?? {};
|
|
5630
|
+
const L = EXAMPLES.limits;
|
|
5631
|
+
const size = jsonBytes(input);
|
|
5632
|
+
if (size !== null && size > L.maxInlineInputBytes) {
|
|
5633
|
+
this.err(
|
|
5634
|
+
"example_input_too_large",
|
|
5635
|
+
field,
|
|
5636
|
+
`example ${q(label)} input is ${size} bytes; the limit is ${L.maxInlineInputBytes}. Put the large part behind an https file reference.`
|
|
5637
|
+
);
|
|
5638
|
+
return;
|
|
5639
|
+
}
|
|
5640
|
+
const schema = own2(this.flow, "input_schema");
|
|
5641
|
+
const query = own2(this.flow, "query");
|
|
5642
|
+
if (isRecord(schema)) {
|
|
5643
|
+
this.checkInputAgainstSchema(field, label, input, schema, code);
|
|
5644
|
+
} else if (isRecord(query) && Object.keys(query).length > 0) {
|
|
5645
|
+
const declared = sortedKeys(query);
|
|
5646
|
+
for (const k of sortedKeys(input)) {
|
|
5647
|
+
if (!Object.prototype.hasOwnProperty.call(query, k)) {
|
|
5648
|
+
this.err(
|
|
5649
|
+
code,
|
|
5650
|
+
`${field}.${k}`,
|
|
5651
|
+
`example ${q(label)}: ${q(k)} is not a declared input of this flow (declared: ${declared.join(", ")})`
|
|
5652
|
+
);
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
} else {
|
|
5656
|
+
this.warn(
|
|
5657
|
+
"example_input_unvalidated",
|
|
5658
|
+
field,
|
|
5659
|
+
`example ${q(label)} cannot be checked: the flow declares no input_schema and no query parameters.`
|
|
5660
|
+
);
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5663
|
+
checkInputAgainstSchema(field, label, input, schema, code) {
|
|
5664
|
+
const fields = this.schemaFields(schema);
|
|
5665
|
+
const byName = /* @__PURE__ */ new Map();
|
|
5666
|
+
for (const f of fields) byName.set(f.name, f);
|
|
5667
|
+
const runInput = {};
|
|
5668
|
+
for (const name of sortedKeys(input)) {
|
|
5669
|
+
const val = input[name];
|
|
5670
|
+
const f = byName.get(name);
|
|
5671
|
+
const present = val !== null && val !== void 0;
|
|
5672
|
+
if (f !== void 0 && f.type === TYPE_SECRET) {
|
|
5673
|
+
if (present) {
|
|
5674
|
+
this.err(
|
|
5675
|
+
"example_secret_value",
|
|
5676
|
+
`${field}.${name}`,
|
|
5677
|
+
`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.`,
|
|
5678
|
+
SUGGESTION_SECRET_VALUE
|
|
5679
|
+
);
|
|
5680
|
+
}
|
|
5681
|
+
continue;
|
|
5682
|
+
}
|
|
5683
|
+
if (f !== void 0 && f.type === TYPE_FILE2 && present) {
|
|
5684
|
+
const ref = asFileRef(val);
|
|
5685
|
+
if (ref === null) {
|
|
5686
|
+
this.err(
|
|
5687
|
+
"example_file_input_needs_ref",
|
|
5688
|
+
`${field}.${name}`,
|
|
5689
|
+
`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.`
|
|
5690
|
+
);
|
|
5691
|
+
continue;
|
|
5692
|
+
}
|
|
5693
|
+
this.validateFileRef(`${field}.${name}`, ref);
|
|
5694
|
+
setOwn(runInput, name, { [FILE_REF_KEY]: ref.url });
|
|
5695
|
+
continue;
|
|
5696
|
+
}
|
|
5697
|
+
setOwn(runInput, name, val);
|
|
5698
|
+
}
|
|
5699
|
+
for (const failure of this.judgeRun(fields, byName, runInput)) {
|
|
5700
|
+
if (failure.required) {
|
|
5701
|
+
const f = byName.get(failure.field);
|
|
5702
|
+
if (f !== void 0 && f.type === TYPE_SECRET) continue;
|
|
5703
|
+
}
|
|
5704
|
+
const at = failure.field !== "" ? `${field}.${failure.field}` : field;
|
|
5705
|
+
this.err(code, at, `example ${q(label)}: ${failure.message}`);
|
|
5706
|
+
}
|
|
5707
|
+
}
|
|
5708
|
+
schemaFields(schema) {
|
|
5709
|
+
const raw = own2(schema, "fields");
|
|
5710
|
+
if (!Array.isArray(raw)) return [];
|
|
5711
|
+
const out = [];
|
|
5712
|
+
raw.forEach((f, i) => {
|
|
5713
|
+
if (!isRecord(f)) return;
|
|
5714
|
+
const base = `input_schema.fields[${i}]`;
|
|
5715
|
+
const enumRaw = own2(f, "enum");
|
|
5716
|
+
const enumValues = [];
|
|
5717
|
+
if (Array.isArray(enumRaw)) {
|
|
5718
|
+
enumRaw.forEach((e, j) => {
|
|
5719
|
+
enumValues.push(e === null ? "" : this.issues.stringAt(e, `${base}.enum[${j}]`) ?? "");
|
|
5720
|
+
});
|
|
5721
|
+
}
|
|
5722
|
+
const vw = own2(f, "visible_when");
|
|
5723
|
+
out.push({
|
|
5724
|
+
name: this.s(f, "name", base),
|
|
5725
|
+
type: this.s(f, "type", base),
|
|
5726
|
+
required: own2(f, "required") === true,
|
|
5727
|
+
enumValues,
|
|
5728
|
+
visibleWhen: isRecord(vw) ? {
|
|
5729
|
+
field: this.s(vw, "field", `${base}.visible_when`),
|
|
5730
|
+
equals: own2(vw, "equals"),
|
|
5731
|
+
in: Array.isArray(own2(vw, "in")) ? own2(vw, "in") : []
|
|
5732
|
+
} : null
|
|
5733
|
+
});
|
|
5734
|
+
});
|
|
5735
|
+
return out;
|
|
5736
|
+
}
|
|
5737
|
+
/** The narrowed run-time judgement: required, kind, enum, unknown field. */
|
|
5738
|
+
judgeRun(fields, byName, runInput) {
|
|
5739
|
+
const failures = [];
|
|
5740
|
+
for (const f of fields) {
|
|
5741
|
+
if (!isVisible(f, runInput)) continue;
|
|
5742
|
+
const raw = own2(runInput, f.name);
|
|
5743
|
+
if (raw === void 0 || raw === null) {
|
|
5744
|
+
if (f.required) {
|
|
5745
|
+
failures.push({
|
|
5746
|
+
field: f.name,
|
|
5747
|
+
message: `input field ${q(f.name)} is required`,
|
|
5748
|
+
required: true
|
|
5749
|
+
});
|
|
5750
|
+
}
|
|
5751
|
+
continue;
|
|
5752
|
+
}
|
|
5753
|
+
failures.push(...inputValueFailures(f, raw));
|
|
5754
|
+
}
|
|
5755
|
+
for (const k of sortedKeys(runInput)) {
|
|
5756
|
+
if (!byName.has(k)) {
|
|
5757
|
+
failures.push({
|
|
5758
|
+
field: k,
|
|
5759
|
+
message: `input field ${q(k)} is not declared in input_schema`,
|
|
5760
|
+
required: false
|
|
5761
|
+
});
|
|
5762
|
+
}
|
|
5763
|
+
}
|
|
5764
|
+
return failures;
|
|
5765
|
+
}
|
|
5766
|
+
// -- file reference ---------------------------------------------------
|
|
5767
|
+
fileRefOf(rec, path) {
|
|
5768
|
+
return {
|
|
5769
|
+
url: this.s(rec, "url", path),
|
|
5770
|
+
sha256: this.s(rec, "sha256", path),
|
|
5771
|
+
mediaType: this.s(rec, "media_type", path),
|
|
5772
|
+
note: this.s(rec, "note", path)
|
|
5773
|
+
};
|
|
5774
|
+
}
|
|
5775
|
+
validateFileRef(field, ref) {
|
|
5776
|
+
const L = EXAMPLES.limits;
|
|
5777
|
+
const raw = ref.url;
|
|
5778
|
+
const invalid = "the file reference needs a url of at most 2048 characters";
|
|
5779
|
+
if (raw === "" || byteLen(raw) > L.maxUrlBytes) {
|
|
5780
|
+
this.err("example_ref_invalid", field, invalid);
|
|
5781
|
+
return;
|
|
5782
|
+
}
|
|
5783
|
+
const u = goParseURLParts(raw);
|
|
5784
|
+
if (u === null) {
|
|
5785
|
+
this.err("example_ref_invalid", field, invalid);
|
|
5786
|
+
return;
|
|
5787
|
+
}
|
|
5788
|
+
const scheme = u.scheme.toLowerCase();
|
|
5789
|
+
if (EXAMPLES.reservedRefSchemes.has(scheme)) {
|
|
5790
|
+
this.err(
|
|
5791
|
+
"example_ref_scheme_reserved",
|
|
5792
|
+
field,
|
|
5793
|
+
`${scheme}: references are not available yet. Use an https URL, or paste the text into the example.`
|
|
5794
|
+
);
|
|
5795
|
+
return;
|
|
5796
|
+
}
|
|
5797
|
+
if (scheme !== SCHEME_HTTPS) {
|
|
5798
|
+
this.err(
|
|
5799
|
+
"example_ref_scheme",
|
|
5800
|
+
field,
|
|
5801
|
+
`only https:// references are allowed (got ${q(scheme)})`
|
|
5802
|
+
);
|
|
5803
|
+
return;
|
|
5804
|
+
}
|
|
5805
|
+
if (u.hasUser || u.fragment !== "" || raw.includes("#") || u.host === "") {
|
|
5806
|
+
this.err("example_ref_invalid", field, "a reference must not carry a login or a fragment");
|
|
5807
|
+
return;
|
|
5808
|
+
}
|
|
5809
|
+
if (ref.sha256 !== "" && !EXAMPLES.sha256Pattern.test(ref.sha256)) {
|
|
5810
|
+
this.err(
|
|
5811
|
+
"example_ref_sha256_invalid",
|
|
5812
|
+
field,
|
|
5813
|
+
"sha256 must be 64 lowercase hexadecimal characters"
|
|
5814
|
+
);
|
|
5815
|
+
}
|
|
5816
|
+
if (ref.mediaType !== "" && !EXAMPLES.mediaTypePattern.test(ref.mediaType)) {
|
|
5817
|
+
this.err(
|
|
5818
|
+
"example_ref_media_type_invalid",
|
|
5819
|
+
field,
|
|
5820
|
+
`media_type ${q(ref.mediaType)} must look like type/subtype`
|
|
5821
|
+
);
|
|
5822
|
+
}
|
|
5823
|
+
const noteN = runeCount(ref.note);
|
|
5824
|
+
if (noteN > L.maxNoteRunes) {
|
|
5825
|
+
this.err(
|
|
5826
|
+
"example_ref_invalid",
|
|
5827
|
+
field,
|
|
5828
|
+
`the note is ${noteN} characters; the limit is ${L.maxNoteRunes}`
|
|
5829
|
+
);
|
|
5830
|
+
}
|
|
5831
|
+
if (ref.sha256 === "") {
|
|
5832
|
+
this.warn(
|
|
5833
|
+
"example_ref_unpinned",
|
|
5834
|
+
field,
|
|
5835
|
+
"this reference is not pinned. If the file at that address changes, your scores change with it. Add sha256 to freeze it."
|
|
5836
|
+
);
|
|
5837
|
+
}
|
|
5838
|
+
for (const key of goQueryKeys(u.rawQuery)) {
|
|
5839
|
+
if (EXAMPLES.signedUrlQueryKeyPattern.test(key)) {
|
|
5840
|
+
this.warn(
|
|
5841
|
+
"example_ref_signed_url",
|
|
5842
|
+
field,
|
|
5843
|
+
"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."
|
|
5844
|
+
);
|
|
5845
|
+
break;
|
|
5846
|
+
}
|
|
5847
|
+
}
|
|
5848
|
+
}
|
|
5849
|
+
// -- expectation ------------------------------------------------------
|
|
5850
|
+
/** The names `fields` / `exact` may use, and whether such a list is declared at all. */
|
|
5851
|
+
outputNamesFor(kind, stepId) {
|
|
5852
|
+
if (kind === "final") {
|
|
5853
|
+
const out = own2(this.flow, "output");
|
|
5854
|
+
const names2 = [];
|
|
5855
|
+
if (Array.isArray(out)) {
|
|
5856
|
+
out.forEach((item, i) => {
|
|
5857
|
+
names2.push(item === null ? "" : this.issues.stringAt(item, `output[${i}]`) ?? "");
|
|
5858
|
+
});
|
|
5859
|
+
}
|
|
5860
|
+
return { names: names2, declared: names2.length > 0 };
|
|
5861
|
+
}
|
|
5862
|
+
const steps = own2(this.flow, "steps");
|
|
5863
|
+
const step = isRecord(steps) ? own2(steps, stepId) : void 0;
|
|
5864
|
+
const schema = isRecord(step) ? own2(step, "output_schema") : void 0;
|
|
5865
|
+
const fields = isRecord(schema) ? own2(schema, "fields") : void 0;
|
|
5866
|
+
if (!Array.isArray(fields) || fields.length === 0) return { names: [], declared: false };
|
|
5867
|
+
const names = [];
|
|
5868
|
+
fields.forEach((f, i) => {
|
|
5869
|
+
if (isRecord(f)) names.push(this.s(f, "name", `steps.${stepId}.output_schema.fields[${i}]`));
|
|
5870
|
+
});
|
|
5871
|
+
return { names, declared: true };
|
|
5872
|
+
}
|
|
5873
|
+
validateExpectation(path, label, e, kind, stepId) {
|
|
5874
|
+
const L = EXAMPLES.limits;
|
|
5875
|
+
const status = this.s(e, "status", path);
|
|
5876
|
+
if (status !== "" && !EXAMPLES.statuses.has(status)) {
|
|
5877
|
+
this.err(
|
|
5878
|
+
"example_status_invalid",
|
|
5879
|
+
`${path}.status`,
|
|
5880
|
+
"status must be completed, failed or paused_for_human"
|
|
5881
|
+
);
|
|
5882
|
+
}
|
|
5883
|
+
const noOutput = status === STATUS_FAILED || status === STATUS_PAUSED_FOR_HUMAN;
|
|
5884
|
+
const fieldsRec = own2(e, "fields");
|
|
5885
|
+
const exactRec = own2(e, "exact");
|
|
5886
|
+
const fieldKeys = isRecord(fieldsRec) ? sortedKeys(fieldsRec) : [];
|
|
5887
|
+
const exactKeys = isRecord(exactRec) ? sortedKeys(exactRec) : [];
|
|
5888
|
+
const hasFields = fieldKeys.length > 0;
|
|
5889
|
+
const hasExact = exactKeys.length > 0;
|
|
5890
|
+
if (hasFields && hasExact) {
|
|
5891
|
+
this.err(
|
|
5892
|
+
"example_expected_conflict",
|
|
5893
|
+
path,
|
|
5894
|
+
"use fields (some fields) or exact (the whole result), not both"
|
|
5895
|
+
);
|
|
5896
|
+
} else if (noOutput && (hasFields || hasExact)) {
|
|
5897
|
+
this.err(
|
|
5898
|
+
"example_expected_conflict",
|
|
5899
|
+
path,
|
|
5900
|
+
`a case that ends in ${status} has no output to match; remove fields and exact, or expect status completed`
|
|
5901
|
+
);
|
|
5902
|
+
}
|
|
5903
|
+
const minScore = numberOf(own2(e, "min_score"));
|
|
5904
|
+
if (minScore !== void 0 && !scoreInRange(minScore)) {
|
|
5905
|
+
this.err(
|
|
5906
|
+
"example_min_score_range",
|
|
5907
|
+
`${path}.min_score`,
|
|
5908
|
+
`min_score ${num(minScore)} is out of range; use a number from 0 to 1`
|
|
5909
|
+
);
|
|
5910
|
+
}
|
|
5911
|
+
const rubric = this.s(e, "rubric", path);
|
|
5912
|
+
if (rubric !== "") {
|
|
5913
|
+
if (goTrimSpace(rubric) === "") {
|
|
5914
|
+
this.err(
|
|
5915
|
+
"example_rubric_empty",
|
|
5916
|
+
`${path}.rubric`,
|
|
5917
|
+
"the rubric is empty; write what a good result looks like, or remove it"
|
|
5918
|
+
);
|
|
5919
|
+
} else {
|
|
5920
|
+
const n = runeCount(rubric);
|
|
5921
|
+
if (n > L.maxRubricRunes) {
|
|
5922
|
+
this.err(
|
|
5923
|
+
"example_rubric_too_long",
|
|
5924
|
+
`${path}.rubric`,
|
|
5925
|
+
`the rubric is ${n} characters; the limit is ${L.maxRubricRunes}`
|
|
5926
|
+
);
|
|
5927
|
+
}
|
|
5928
|
+
}
|
|
5929
|
+
}
|
|
5930
|
+
const reference = own2(e, "reference");
|
|
5931
|
+
if (isRecord(reference)) this.validateReference(`${path}.reference`, reference);
|
|
5932
|
+
const { names, declared } = this.outputNamesFor(kind, stepId);
|
|
5933
|
+
const checkKey = (field, key) => {
|
|
5934
|
+
if (!declared && kind === "final") {
|
|
5935
|
+
this.warn(
|
|
5936
|
+
"example_expected_field_unchecked",
|
|
5937
|
+
field,
|
|
5938
|
+
`this flow declares no output list, so ${q(key)} cannot be checked against it`
|
|
5939
|
+
);
|
|
5940
|
+
} else if (!declared) {
|
|
5941
|
+
this.warn(
|
|
5942
|
+
"example_expected_field_unchecked",
|
|
5943
|
+
field,
|
|
5944
|
+
`this step declares no output_schema, so ${q(key)} cannot be checked against it`
|
|
5945
|
+
);
|
|
5946
|
+
} else if (!names.includes(key)) {
|
|
5947
|
+
this.err(
|
|
5948
|
+
"example_expected_field_unknown",
|
|
5949
|
+
field,
|
|
5950
|
+
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(", ")})`
|
|
5951
|
+
);
|
|
5952
|
+
}
|
|
5953
|
+
};
|
|
5954
|
+
for (const key of fieldKeys) {
|
|
5955
|
+
const fpath = `${path}.fields.${key}`;
|
|
5956
|
+
checkKey(fpath, key);
|
|
5957
|
+
const matcher = own2(fieldsRec, key);
|
|
5958
|
+
if (isRecord(matcher)) this.validateFieldMatcher(fpath, matcher);
|
|
5959
|
+
}
|
|
5960
|
+
for (const key of exactKeys) checkKey(`${path}.exact.${key}`, key);
|
|
5961
|
+
const mustNot = this.stringList(own2(e, "must_not"), `${path}.must_not`);
|
|
5962
|
+
const mustNotContain = this.stringList(own2(e, "must_not_contain"), `${path}.must_not_contain`);
|
|
5963
|
+
const mustNotMsg = `must_not takes up to ${L.maxMustNot} statements of at most ${L.maxMustNotRunes} characters each, none empty`;
|
|
5964
|
+
if (mustNot.length > L.maxMustNot) {
|
|
5965
|
+
this.err("example_must_not_invalid", `${path}.must_not`, mustNotMsg);
|
|
5966
|
+
}
|
|
5967
|
+
mustNot.forEach((item, j) => {
|
|
5968
|
+
if (goTrimSpace(item) === "" || runeCount(item) > L.maxMustNotRunes) {
|
|
5969
|
+
this.err("example_must_not_invalid", `${path}.must_not[${j}]`, mustNotMsg);
|
|
5970
|
+
}
|
|
5971
|
+
});
|
|
5972
|
+
const mncMsg = `must_not_contain takes up to ${L.maxMustNotContain} literals of 1 to ${L.maxMustNotContainLength} characters each`;
|
|
5973
|
+
if (mustNotContain.length > L.maxMustNotContain) {
|
|
5974
|
+
this.err("example_must_not_contain_invalid", `${path}.must_not_contain`, mncMsg);
|
|
5975
|
+
}
|
|
5976
|
+
mustNotContain.forEach((item, j) => {
|
|
5977
|
+
const n = runeCount(item);
|
|
5978
|
+
if (n === 0 || n > L.maxMustNotContainLength) {
|
|
5979
|
+
this.err("example_must_not_contain_invalid", `${path}.must_not_contain[${j}]`, mncMsg);
|
|
5980
|
+
}
|
|
5981
|
+
});
|
|
5982
|
+
for (const key of fieldKeys) {
|
|
5983
|
+
const matcher = own2(fieldsRec, key);
|
|
5984
|
+
if (!isRecord(matcher)) continue;
|
|
5985
|
+
const eq = own2(matcher, "equals");
|
|
5986
|
+
if (typeof eq !== "string" || eq === "") continue;
|
|
5987
|
+
for (const banned of mustNotContain) {
|
|
5988
|
+
if (banned !== "" && equalFold(banned, eq)) {
|
|
5989
|
+
this.warn(
|
|
5990
|
+
"example_expectation_contradiction",
|
|
5991
|
+
`${path}.fields.${key}`,
|
|
5992
|
+
`${q(banned)} is required by ${q(key)} and forbidden by must_not_contain`
|
|
5993
|
+
);
|
|
5994
|
+
}
|
|
5995
|
+
}
|
|
5996
|
+
}
|
|
5997
|
+
if (kind === "checkpoint" && status !== "") {
|
|
5998
|
+
this.err(
|
|
5999
|
+
"example_checkpoint_status",
|
|
6000
|
+
`${path}.status`,
|
|
6001
|
+
`example ${q(label)}: a checkpoint cannot set status; status describes how the whole run ends`
|
|
6002
|
+
);
|
|
6003
|
+
}
|
|
6004
|
+
if (rubric === "" && !has(e, "reference") && !hasFields && !hasExact && mustNot.length === 0 && mustNotContain.length === 0 && (status === "" || status === STATUS_COMPLETED)) {
|
|
6005
|
+
this.err(
|
|
6006
|
+
"example_expected_empty",
|
|
6007
|
+
path,
|
|
6008
|
+
"this expectation checks nothing; add a rubric, fields, exact, reference, must_not or must_not_contain",
|
|
6009
|
+
SUGGESTION_EXPECTED
|
|
6010
|
+
);
|
|
6011
|
+
}
|
|
6012
|
+
}
|
|
6013
|
+
/** A Go `[]string` read: scalars by their text, null as "", anything else dropped. */
|
|
6014
|
+
stringList(v, path) {
|
|
6015
|
+
if (!Array.isArray(v)) return [];
|
|
6016
|
+
const out = [];
|
|
6017
|
+
v.forEach((item, j) => {
|
|
6018
|
+
if (item === null) out.push("");
|
|
6019
|
+
else {
|
|
6020
|
+
const t = this.issues.stringAt(item, `${path}[${j}]`);
|
|
6021
|
+
if (t !== null) out.push(t);
|
|
6022
|
+
}
|
|
6023
|
+
});
|
|
6024
|
+
return out;
|
|
6025
|
+
}
|
|
6026
|
+
validateReference(path, r) {
|
|
6027
|
+
const text = this.s(r, "text", path);
|
|
6028
|
+
const hasText = text !== "";
|
|
6029
|
+
const hasRef = has(r, "ref");
|
|
6030
|
+
if (hasText === hasRef) {
|
|
6031
|
+
this.err("example_reference_invalid", path, "a reference needs exactly one of text or ref");
|
|
6032
|
+
return;
|
|
6033
|
+
}
|
|
6034
|
+
if (hasText) {
|
|
6035
|
+
const n = byteLen(text);
|
|
6036
|
+
if (n > EXAMPLES.limits.maxReferenceTextBytes) {
|
|
6037
|
+
this.err(
|
|
6038
|
+
"example_reference_invalid",
|
|
6039
|
+
path,
|
|
6040
|
+
`the reference text is ${n} bytes; the limit is ${EXAMPLES.limits.maxReferenceTextBytes}. Put it behind an https file reference.`
|
|
6041
|
+
);
|
|
6042
|
+
}
|
|
6043
|
+
return;
|
|
6044
|
+
}
|
|
6045
|
+
const refRec = own2(r, "ref");
|
|
6046
|
+
if (!isRecord(refRec)) return;
|
|
6047
|
+
const ref = this.fileRefOf(refRec, `${path}.ref`);
|
|
6048
|
+
this.validateFileRef(`${path}.ref`, ref);
|
|
6049
|
+
if (ref.mediaType !== "" && !isTextualMediaType(ref.mediaType)) {
|
|
6050
|
+
this.warn(
|
|
6051
|
+
"example_reference_binary",
|
|
6052
|
+
path,
|
|
6053
|
+
"this reference is not text, so it cannot be compared in this version; the example will be skipped, not failed."
|
|
6054
|
+
);
|
|
6055
|
+
}
|
|
6056
|
+
}
|
|
6057
|
+
expectationIsBinaryReferenceOnly(e, path) {
|
|
6058
|
+
const reference = own2(e, "reference");
|
|
6059
|
+
const refRec = isRecord(reference) ? own2(reference, "ref") : void 0;
|
|
6060
|
+
if (!isRecord(refRec)) return false;
|
|
6061
|
+
const mediaType = this.s(refRec, "media_type", `${path}.reference.ref`);
|
|
6062
|
+
if (mediaType === "" || isTextualMediaType(mediaType)) return false;
|
|
6063
|
+
const status = this.s(e, "status", path);
|
|
6064
|
+
const fields = own2(e, "fields");
|
|
6065
|
+
const exact = own2(e, "exact");
|
|
6066
|
+
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);
|
|
6067
|
+
}
|
|
6068
|
+
validateFieldMatcher(path, m) {
|
|
6069
|
+
const L = EXAMPLES.limits;
|
|
6070
|
+
const matches = this.s(m, "matches", path);
|
|
6071
|
+
const oneOf = own2(m, "one_of");
|
|
6072
|
+
const approx = numberOf(own2(m, "approx"));
|
|
6073
|
+
const tolerance = numberOf(own2(m, "tolerance"));
|
|
6074
|
+
let operators = 0;
|
|
6075
|
+
if (has(m, "equals")) operators++;
|
|
6076
|
+
if (this.s(m, "contains", path) !== "") operators++;
|
|
6077
|
+
if (this.s(m, "not_contains", path) !== "") operators++;
|
|
6078
|
+
if (matches !== "") operators++;
|
|
6079
|
+
if (Array.isArray(oneOf) && oneOf.length > 0) operators++;
|
|
6080
|
+
if (approx !== void 0) operators++;
|
|
6081
|
+
if (typeof own2(m, "present") === "boolean") operators++;
|
|
6082
|
+
if (operators !== 1) {
|
|
6083
|
+
this.err(
|
|
6084
|
+
"example_field_matcher_invalid",
|
|
6085
|
+
path,
|
|
6086
|
+
"write exactly one of equals, contains, not_contains, matches, one_of, approx, present"
|
|
6087
|
+
);
|
|
6088
|
+
return;
|
|
6089
|
+
}
|
|
6090
|
+
if (matches !== "" && byteLen(matches) > L.maxPatternLength) {
|
|
6091
|
+
this.err(
|
|
6092
|
+
"example_field_matcher_invalid",
|
|
6093
|
+
path,
|
|
6094
|
+
`the pattern in matches must be a valid regular expression of at most ${L.maxPatternLength} characters`
|
|
6095
|
+
);
|
|
6096
|
+
}
|
|
6097
|
+
if (approx !== void 0 !== (tolerance !== void 0) || tolerance !== void 0 && (Number.isNaN(tolerance) || tolerance < 0)) {
|
|
6098
|
+
this.err(
|
|
6099
|
+
"example_field_matcher_invalid",
|
|
6100
|
+
path,
|
|
6101
|
+
"approx needs a tolerance (a number, zero or more) beside it, and tolerance is only for approx"
|
|
6102
|
+
);
|
|
6103
|
+
}
|
|
6104
|
+
}
|
|
6105
|
+
// -- checkpoints ------------------------------------------------------
|
|
6106
|
+
validateCheckpoints(path, label, cps) {
|
|
6107
|
+
if (!isRecord(cps) || Object.keys(cps).length === 0) return;
|
|
6108
|
+
const stepsRaw = own2(this.flow, "steps");
|
|
6109
|
+
const steps = isRecord(stepsRaw) ? stepsRaw : {};
|
|
6110
|
+
const stepIds = sortedKeys(steps);
|
|
6111
|
+
for (const id of sortedKeys(cps)) {
|
|
6112
|
+
const cpath = `${path}.checkpoints.${id}`;
|
|
6113
|
+
const step = own2(steps, id);
|
|
6114
|
+
if (!isRecord(step)) {
|
|
6115
|
+
this.err(
|
|
6116
|
+
"example_checkpoint_step_unknown",
|
|
6117
|
+
cpath,
|
|
6118
|
+
`example ${q(label)}: checkpoint ${q(id)} is not a step of this flow (steps: ${stepIds.join(", ")})`,
|
|
6119
|
+
SUGGESTION_CHECKPOINT
|
|
6120
|
+
);
|
|
6121
|
+
continue;
|
|
6122
|
+
}
|
|
6123
|
+
const next = own2(step, "next");
|
|
6124
|
+
if (has(step, "for_each") || has(step, "loop") || isRecord(next) && has(next, "parallel")) {
|
|
6125
|
+
this.err(
|
|
6126
|
+
"example_checkpoint_step_composite",
|
|
6127
|
+
cpath,
|
|
6128
|
+
`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`
|
|
6129
|
+
);
|
|
6130
|
+
continue;
|
|
6131
|
+
}
|
|
6132
|
+
const cp = own2(cps, id);
|
|
6133
|
+
if (cp === null || cp === void 0) {
|
|
6134
|
+
this.validateExpectation(cpath, label, {}, "checkpoint", id);
|
|
6135
|
+
} else if (isRecord(cp)) {
|
|
6136
|
+
this.validateExpectation(cpath, label, cp, "checkpoint", id);
|
|
6137
|
+
}
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
// -- variants ---------------------------------------------------------
|
|
6141
|
+
validateVariants(i, path, label, ex, hasInput, baseInput) {
|
|
6142
|
+
const L = EXAMPLES.limits;
|
|
6143
|
+
const variants = own2(ex, "variants");
|
|
6144
|
+
if (!Array.isArray(variants) || variants.length === 0) return;
|
|
6145
|
+
if (variants.length > L.maxVariants) {
|
|
6146
|
+
this.err(
|
|
6147
|
+
"example_variants_too_many",
|
|
6148
|
+
`${path}.variants`,
|
|
6149
|
+
`example ${q(label)} has ${variants.length} variants; the limit is ${L.maxVariants}`
|
|
6150
|
+
);
|
|
6151
|
+
}
|
|
6152
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6153
|
+
variants.forEach((v, j) => {
|
|
6154
|
+
if (!isRecord(v)) return;
|
|
6155
|
+
const vpath = `examples[${i}].variants[${j}]`;
|
|
6156
|
+
const vid = this.s(v, "id", vpath);
|
|
6157
|
+
const vlabel = vid === "" ? `#${j + 1}` : vid;
|
|
6158
|
+
const both = `${label}/${vlabel}`;
|
|
6159
|
+
if (!EXAMPLES.idPattern.test(vid)) {
|
|
6160
|
+
this.err(
|
|
6161
|
+
"example_variant_id_invalid",
|
|
6162
|
+
`${vpath}.id`,
|
|
6163
|
+
`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)`,
|
|
6164
|
+
SUGGESTION_ID
|
|
6165
|
+
);
|
|
6166
|
+
} else if (seen.has(vid)) {
|
|
6167
|
+
this.err(
|
|
6168
|
+
"example_variant_id_duplicate",
|
|
6169
|
+
`${vpath}.id`,
|
|
6170
|
+
`variant id ${q(vid)} is used twice in example ${q(label)}`
|
|
6171
|
+
);
|
|
6172
|
+
}
|
|
6173
|
+
seen.add(vid);
|
|
6174
|
+
const origin = this.s(v, "origin", vpath);
|
|
6175
|
+
if (origin === ORIGIN_NONE || !EXAMPLES.origins.has(origin)) {
|
|
6176
|
+
this.err(
|
|
6177
|
+
"example_variant_origin_invalid",
|
|
6178
|
+
`${vpath}.origin`,
|
|
6179
|
+
`variant ${q(vlabel)} of example ${q(label)} has origin ${q(origin)}; use author (a person wrote or approved it) or synthetic (generated)`
|
|
6180
|
+
);
|
|
6181
|
+
}
|
|
6182
|
+
const vtitleN = runeCount(this.s(v, "title", vpath));
|
|
6183
|
+
if (vtitleN > L.titleMaxRunes) {
|
|
6184
|
+
this.err(
|
|
6185
|
+
"example_title_too_long",
|
|
6186
|
+
`${vpath}.title`,
|
|
6187
|
+
`example ${q(both)} title is ${vtitleN} characters; the limit is ${L.titleMaxRunes}`
|
|
6188
|
+
);
|
|
6189
|
+
}
|
|
6190
|
+
const vguidanceN = runeCount(this.s(v, "guidance", vpath));
|
|
6191
|
+
if (vguidanceN > L.maxGuidanceRunes) {
|
|
6192
|
+
this.err(
|
|
6193
|
+
"example_guidance_too_long",
|
|
6194
|
+
`${vpath}.guidance`,
|
|
6195
|
+
`example ${q(both)} guidance is ${vguidanceN} characters; the limit is ${L.maxGuidanceRunes}`
|
|
6196
|
+
);
|
|
6197
|
+
}
|
|
6198
|
+
const provN = runeCount(this.s(v, "provenance", vpath));
|
|
6199
|
+
if (provN > L.maxProvenanceRunes) {
|
|
6200
|
+
this.err(
|
|
6201
|
+
"example_notes_too_long",
|
|
6202
|
+
`${vpath}.provenance`,
|
|
6203
|
+
`example ${q(both)} notes are ${provN} characters; the limit is ${L.maxProvenanceRunes}`
|
|
6204
|
+
);
|
|
6205
|
+
}
|
|
6206
|
+
this.validateWeight(`${vpath}.weight`, both, numberOf(own2(v, "weight")));
|
|
6207
|
+
const patch = own2(v, "input_patch");
|
|
6208
|
+
const patchRec = isRecord(patch) ? patch : null;
|
|
6209
|
+
const hasPatch = patchRec !== null && Object.keys(patchRec).length > 0;
|
|
6210
|
+
const hasRef = has(v, "input_ref");
|
|
6211
|
+
if (hasPatch && hasRef) {
|
|
6212
|
+
this.err(
|
|
6213
|
+
"example_variant_input_conflict",
|
|
6214
|
+
vpath,
|
|
6215
|
+
`variant ${q(vlabel)} of example ${q(label)} sets both input_patch and input_ref; use one`
|
|
6216
|
+
);
|
|
6217
|
+
} else if (!hasPatch && !hasRef && !has(v, "expected")) {
|
|
6218
|
+
this.err(
|
|
6219
|
+
"example_variant_empty",
|
|
6220
|
+
vpath,
|
|
6221
|
+
`variant ${q(vlabel)} of example ${q(label)} changes neither the input nor the expectation`
|
|
6222
|
+
);
|
|
6223
|
+
}
|
|
6224
|
+
const refRec = own2(v, "input_ref");
|
|
6225
|
+
if (isRecord(refRec)) {
|
|
6226
|
+
this.validateFileRef(`${vpath}.input_ref`, this.fileRefOf(refRec, `${vpath}.input_ref`));
|
|
6227
|
+
}
|
|
6228
|
+
if (hasPatch && patchRec !== null) {
|
|
6229
|
+
if (!hasInput) {
|
|
6230
|
+
this.warn(
|
|
6231
|
+
"example_variant_unchecked",
|
|
6232
|
+
`${vpath}.input_patch`,
|
|
6233
|
+
`variant ${q(vlabel)} of example ${q(label)} cannot be checked: its parent takes its input from input_ref.`
|
|
6234
|
+
);
|
|
6235
|
+
} else if (baseInput !== null) {
|
|
6236
|
+
this.checkVariantPatch(vpath, label, vlabel, baseInput, patchRec);
|
|
6237
|
+
}
|
|
6238
|
+
}
|
|
6239
|
+
const expected = own2(v, "expected");
|
|
6240
|
+
if (isRecord(expected)) {
|
|
6241
|
+
this.validateExpectation(`${vpath}.expected`, both, expected, "final", "");
|
|
6242
|
+
}
|
|
6243
|
+
});
|
|
6244
|
+
}
|
|
6245
|
+
/**
|
|
6246
|
+
* Judge the PATCHED input like a live run, and report a secret the patch
|
|
6247
|
+
* itself supplies. A null patch value deleting a secret is fine.
|
|
6248
|
+
*/
|
|
6249
|
+
checkVariantPatch(vpath, label, vlabel, base, patch) {
|
|
6250
|
+
const schema = own2(this.flow, "input_schema");
|
|
6251
|
+
if (isRecord(schema)) {
|
|
6252
|
+
for (const f of this.schemaFields(schema)) {
|
|
6253
|
+
if (f.type !== TYPE_SECRET) continue;
|
|
6254
|
+
const val = own2(patch, f.name);
|
|
6255
|
+
if (val !== void 0 && val !== null) {
|
|
6256
|
+
this.err(
|
|
6257
|
+
"example_secret_value",
|
|
6258
|
+
`${vpath}.input_patch.${f.name}`,
|
|
6259
|
+
`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.`,
|
|
6260
|
+
SUGGESTION_SECRET_VALUE
|
|
6261
|
+
);
|
|
6262
|
+
}
|
|
6263
|
+
}
|
|
6264
|
+
}
|
|
6265
|
+
const patched = applyExamplePatch(base, patch);
|
|
6266
|
+
const scratch = new _Walker(this.flow, this.issues);
|
|
6267
|
+
scratch.checkInput(
|
|
6268
|
+
`${vpath}.input_patch`,
|
|
6269
|
+
`${label}/${vlabel}`,
|
|
6270
|
+
patched,
|
|
6271
|
+
"example_variant_input_invalid"
|
|
6272
|
+
);
|
|
6273
|
+
for (const f of scratch.findings) {
|
|
6274
|
+
if (f.code === "example_secret_value") continue;
|
|
6275
|
+
this.findings.push(f);
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
// -- credential-shaped literals ----------------------------------------
|
|
6279
|
+
scanSecretLike(i, path, ex) {
|
|
6280
|
+
const emit = (field, s) => {
|
|
6281
|
+
const name = secretMatch(s);
|
|
6282
|
+
if (name !== "") {
|
|
6283
|
+
this.err(
|
|
6284
|
+
"example_secret_like_value",
|
|
6285
|
+
field,
|
|
6286
|
+
`${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.`,
|
|
6287
|
+
SUGGESTION_SECRET_VALUE
|
|
6288
|
+
);
|
|
6289
|
+
}
|
|
6290
|
+
};
|
|
6291
|
+
emit(`${path}.title`, this.s(ex, "title", path));
|
|
6292
|
+
emit(`${path}.guidance`, this.s(ex, "guidance", path));
|
|
6293
|
+
emit(`${path}.notes`, this.s(ex, "notes", path));
|
|
6294
|
+
const input = own2(ex, "input");
|
|
6295
|
+
if (isRecord(input)) scanLeaves(`${path}.input`, input, 0, emit);
|
|
6296
|
+
const expected = own2(ex, "expected");
|
|
6297
|
+
if (isRecord(expected)) this.scanExpectationLeaves(`${path}.expected`, expected, emit);
|
|
6298
|
+
const cps = own2(ex, "checkpoints");
|
|
6299
|
+
if (isRecord(cps)) {
|
|
6300
|
+
for (const id of sortedKeys(cps)) {
|
|
6301
|
+
const cp = own2(cps, id);
|
|
6302
|
+
if (isRecord(cp)) this.scanExpectationLeaves(`${path}.checkpoints.${id}`, cp, emit);
|
|
6303
|
+
}
|
|
6304
|
+
}
|
|
6305
|
+
const variants = own2(ex, "variants");
|
|
6306
|
+
if (Array.isArray(variants)) {
|
|
6307
|
+
variants.forEach((v, j) => {
|
|
6308
|
+
if (!isRecord(v)) return;
|
|
6309
|
+
const vpath = `examples[${i}].variants[${j}]`;
|
|
6310
|
+
emit(`${vpath}.title`, this.s(v, "title", vpath));
|
|
6311
|
+
emit(`${vpath}.guidance`, this.s(v, "guidance", vpath));
|
|
6312
|
+
emit(`${vpath}.provenance`, this.s(v, "provenance", vpath));
|
|
6313
|
+
scanLeaves(`${vpath}.input_patch`, own2(v, "input_patch"), 0, emit);
|
|
6314
|
+
const vexp = own2(v, "expected");
|
|
6315
|
+
if (isRecord(vexp)) this.scanExpectationLeaves(`${vpath}.expected`, vexp, emit);
|
|
6316
|
+
});
|
|
6317
|
+
}
|
|
6318
|
+
}
|
|
6319
|
+
scanExpectationLeaves(path, e, emit) {
|
|
6320
|
+
emit(`${path}.rubric`, this.s(e, "rubric", path));
|
|
6321
|
+
const reference = own2(e, "reference");
|
|
6322
|
+
if (isRecord(reference)) {
|
|
6323
|
+
emit(`${path}.reference.text`, this.s(reference, "text", `${path}.reference`));
|
|
6324
|
+
}
|
|
6325
|
+
this.stringList(own2(e, "must_not"), `${path}.must_not`).forEach(
|
|
6326
|
+
(s, j) => emit(`${path}.must_not[${j}]`, s)
|
|
6327
|
+
);
|
|
6328
|
+
this.stringList(own2(e, "must_not_contain"), `${path}.must_not_contain`).forEach(
|
|
6329
|
+
(s, j) => emit(`${path}.must_not_contain[${j}]`, s)
|
|
6330
|
+
);
|
|
6331
|
+
const fields = own2(e, "fields");
|
|
6332
|
+
if (isRecord(fields)) {
|
|
6333
|
+
for (const key of sortedKeys(fields)) {
|
|
6334
|
+
const m = own2(fields, key);
|
|
6335
|
+
if (!isRecord(m)) continue;
|
|
6336
|
+
const fpath = `${path}.fields.${key}`;
|
|
6337
|
+
scanLeaves(`${fpath}.equals`, own2(m, "equals"), 0, emit);
|
|
6338
|
+
emit(`${fpath}.contains`, this.s(m, "contains", fpath));
|
|
6339
|
+
emit(`${fpath}.not_contains`, this.s(m, "not_contains", fpath));
|
|
6340
|
+
const oneOf = own2(m, "one_of");
|
|
6341
|
+
if (Array.isArray(oneOf)) {
|
|
6342
|
+
oneOf.forEach(
|
|
6343
|
+
(one, j) => scanLeaves(`${fpath}.one_of[${j}]`, one, 0, emit)
|
|
6344
|
+
);
|
|
6345
|
+
}
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
scanLeaves(`${path}.exact`, own2(e, "exact"), 0, emit);
|
|
6349
|
+
}
|
|
6350
|
+
};
|
|
6351
|
+
function scoreInRange(v) {
|
|
6352
|
+
return !Number.isNaN(v) && v >= EXAMPLES.limits.minScoreMin && v <= EXAMPLES.limits.minScoreMax;
|
|
6353
|
+
}
|
|
6354
|
+
function equalFold(a, b) {
|
|
6355
|
+
const ca = [...a];
|
|
6356
|
+
const cb = [...b];
|
|
6357
|
+
if (ca.length !== cb.length) return false;
|
|
6358
|
+
return ca.every((c, i) => {
|
|
6359
|
+
const d = cb[i];
|
|
6360
|
+
return c === d || c.toLowerCase() === d.toLowerCase() || c.toUpperCase() === d.toUpperCase();
|
|
6361
|
+
});
|
|
6362
|
+
}
|
|
6363
|
+
function secretMatch(s) {
|
|
6364
|
+
for (const p of EXAMPLES.secretPatterns) {
|
|
6365
|
+
if (p.re.test(s)) return p.name;
|
|
6366
|
+
}
|
|
6367
|
+
return "";
|
|
6368
|
+
}
|
|
6369
|
+
function scanLeaves(path, v, depth, emit) {
|
|
6370
|
+
if (depth > MAX_RECURSION_DEPTH) return;
|
|
6371
|
+
if (isRecord(v)) {
|
|
6372
|
+
for (const k of sortedKeys(v)) scanLeaves(`${path}.${k}`, v[k], depth + 1, emit);
|
|
6373
|
+
return;
|
|
6374
|
+
}
|
|
6375
|
+
if (typeof v === "string") emit(path, v);
|
|
6376
|
+
else if (Array.isArray(v)) {
|
|
6377
|
+
v.forEach((item, i) => scanLeaves(`${path}[${i}]`, item, depth + 1, emit));
|
|
6378
|
+
}
|
|
6379
|
+
}
|
|
6380
|
+
function jsonBytes(v) {
|
|
6381
|
+
try {
|
|
6382
|
+
const text = JSON.stringify(v);
|
|
6383
|
+
return text === void 0 ? null : byteLen(text);
|
|
6384
|
+
} catch {
|
|
6385
|
+
return null;
|
|
6386
|
+
}
|
|
6387
|
+
}
|
|
6388
|
+
function blockBytes(examples) {
|
|
6389
|
+
try {
|
|
6390
|
+
const text = JSON.stringify(normalize(examples, 0), function(_k, v) {
|
|
6391
|
+
if (Array.isArray(this)) return v;
|
|
6392
|
+
if (v === null || v === "" || v === false) return void 0;
|
|
6393
|
+
if (Array.isArray(v) && v.length === 0) return void 0;
|
|
6394
|
+
return v;
|
|
6395
|
+
});
|
|
6396
|
+
return text === void 0 ? null : byteLen(text);
|
|
6397
|
+
} catch {
|
|
6398
|
+
return null;
|
|
6399
|
+
}
|
|
6400
|
+
}
|
|
6401
|
+
function isVisible(f, input) {
|
|
6402
|
+
const vw = f.visibleWhen;
|
|
6403
|
+
if (vw === null) return true;
|
|
6404
|
+
if (!Object.prototype.hasOwnProperty.call(input, vw.field)) return false;
|
|
6405
|
+
const dep = input[vw.field];
|
|
6406
|
+
if (vw.equals !== void 0 && vw.equals !== null) return valuesEqual(dep, vw.equals);
|
|
6407
|
+
return vw.in.some((candidate) => valuesEqual(dep, candidate));
|
|
6408
|
+
}
|
|
6409
|
+
function valuesEqual(a, b) {
|
|
6410
|
+
const aNil = a === null || a === void 0;
|
|
6411
|
+
const bNil = b === null || b === void 0;
|
|
6412
|
+
if (aNil || bNil) return aNil && bNil;
|
|
6413
|
+
if (typeof a === "number") return typeof b === "number" && a === b;
|
|
6414
|
+
if (typeof a === "boolean") return typeof b === "boolean" && a === b;
|
|
6415
|
+
if (typeof a === "string") return typeof b === "string" && a === b;
|
|
6416
|
+
return false;
|
|
6417
|
+
}
|
|
6418
|
+
function mismatch(f, expected) {
|
|
6419
|
+
return {
|
|
6420
|
+
field: f.name,
|
|
6421
|
+
message: `input field ${q(f.name)} is not a valid ${expected}`,
|
|
6422
|
+
required: false
|
|
6423
|
+
};
|
|
6424
|
+
}
|
|
6425
|
+
function inputValueFailures(f, raw) {
|
|
6426
|
+
switch (f.type) {
|
|
6427
|
+
case TYPE_STRING:
|
|
6428
|
+
case TYPE_MULTILINE:
|
|
6429
|
+
case TYPE_SECRET:
|
|
6430
|
+
return typeof raw === "string" ? [] : [mismatch(f, TYPE_STRING)];
|
|
6431
|
+
case TYPE_NUMBER2:
|
|
6432
|
+
return typeof raw === "number" ? [] : [mismatch(f, TYPE_NUMBER2)];
|
|
6433
|
+
case TYPE_BOOL:
|
|
6434
|
+
return typeof raw === "boolean" ? [] : [mismatch(f, TYPE_BOOL)];
|
|
6435
|
+
case TYPE_ENUM2:
|
|
6436
|
+
if (typeof raw !== "string") return [mismatch(f, TYPE_ENUM2)];
|
|
6437
|
+
if (f.enumValues.includes(raw)) return [];
|
|
6438
|
+
return [
|
|
6439
|
+
{
|
|
6440
|
+
field: f.name,
|
|
6441
|
+
message: `input field ${q(f.name)} value ${q(raw)} is not one of [${f.enumValues.join(",")}]`,
|
|
6442
|
+
required: false
|
|
6443
|
+
}
|
|
6444
|
+
];
|
|
6445
|
+
case TYPE_ARRAY_OF_STRINGS2: {
|
|
6446
|
+
if (!Array.isArray(raw)) return [mismatch(f, TYPE_ARRAY_OF_STRINGS2)];
|
|
6447
|
+
const out = [];
|
|
6448
|
+
raw.forEach((item, i) => {
|
|
6449
|
+
if (typeof item !== "string") {
|
|
6450
|
+
out.push({
|
|
6451
|
+
field: f.name,
|
|
6452
|
+
message: `input field ${q(f.name)} index ${i} is not a string`,
|
|
6453
|
+
required: false
|
|
6454
|
+
});
|
|
6455
|
+
}
|
|
6456
|
+
});
|
|
6457
|
+
return out;
|
|
6458
|
+
}
|
|
6459
|
+
case TYPE_DATE:
|
|
6460
|
+
return typeof raw === "string" ? [] : [mismatch(f, TYPE_DATE)];
|
|
6461
|
+
case TYPE_FILE2: {
|
|
6462
|
+
if (typeof raw === "string") return raw === "" ? [mismatch(f, TYPE_FILE2)] : [];
|
|
6463
|
+
if (isRecord(raw)) {
|
|
6464
|
+
const ref = own2(raw, FILE_REF_KEY);
|
|
6465
|
+
return typeof ref === "string" && ref !== "" ? [] : [mismatch(f, TYPE_FILE2)];
|
|
6466
|
+
}
|
|
6467
|
+
return [mismatch(f, TYPE_FILE2)];
|
|
6468
|
+
}
|
|
6469
|
+
default:
|
|
6470
|
+
return [];
|
|
6471
|
+
}
|
|
6472
|
+
}
|
|
6473
|
+
function validateExamples(flow, issues) {
|
|
6474
|
+
const examples = flow[KEY_EXAMPLES];
|
|
6475
|
+
if (!Array.isArray(examples) || examples.length === 0) return;
|
|
6476
|
+
const walker = new Walker(flow, issues);
|
|
6477
|
+
walker.run(examples);
|
|
6478
|
+
for (const f of walker.findings) {
|
|
6479
|
+
const issue = {
|
|
6480
|
+
field: f.field,
|
|
6481
|
+
message: f.message,
|
|
6482
|
+
code: f.code,
|
|
6483
|
+
...f.suggestion !== void 0 ? { suggestion: f.suggestion } : {}
|
|
6484
|
+
};
|
|
6485
|
+
if (f.severity === "error") issues.error(issue);
|
|
6486
|
+
else issues.warn(issue);
|
|
6487
|
+
}
|
|
6488
|
+
}
|
|
6489
|
+
|
|
5053
6490
|
// src/index.ts
|
|
5054
6491
|
function countStepsWithErrors(errors) {
|
|
5055
6492
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -5105,6 +6542,7 @@ function validateParsed(flow, opts, sources) {
|
|
|
5105
6542
|
validateExecutorConfigEnvScopes(f, issues);
|
|
5106
6543
|
validateServerOwnedQueryKeys(f, issues);
|
|
5107
6544
|
validateCredentialEndpointPairing(f, issues);
|
|
6545
|
+
validateExamples(f, issues);
|
|
5108
6546
|
validateOrchestratorExons(f, issues);
|
|
5109
6547
|
validateUnknownKeys(f, issues);
|
|
5110
6548
|
const templateStats = validateTemplates(f, issues, opts);
|