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