@riddledc/riddle-proof 0.7.66 → 0.7.67

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/README.md CHANGED
@@ -226,6 +226,25 @@ cycle instead of reusing the final response, for example to repeat a fail-then-
226
226
  success pair across multiple viewports. Repeated sequences also record
227
227
  `sequence_cycle: true` after the first cycle.
228
228
 
229
+ Use `max_hit_count` / `max_hits` when a profile needs to prove a request does
230
+ not run too many times. Use `forbidden: true` as shorthand for
231
+ `max_hit_count: 0` and `required: false`, for example when a chat failure must
232
+ not trigger a downstream build:
233
+
234
+ ```json
235
+ {
236
+ "label": "builder-build-should-not-run",
237
+ "url": "**/api/build",
238
+ "method": "POST",
239
+ "forbidden": true,
240
+ "json": { "previewUrl": "https://cdn.example/should-not-run/index.html" }
241
+ }
242
+ ```
243
+
244
+ The implicit `network_mocks_succeeded` check records `max_hits_by_label` and
245
+ fails with `forbidden_mock_hit` or `mock_hit_count_exceeded` when a cap is
246
+ exceeded.
247
+
229
248
  Set `capture_request_body: true` to include compact request-body evidence on
230
249
  mock hits. Add `request_body_contains` / `request_body_patterns` or
231
250
  `request_body_not_contains` / `request_body_not_patterns` when the request body
@@ -470,6 +470,22 @@ function normalizeNetworkMock(input, index) {
470
470
  if (requiredHitCount !== void 0 && (!Number.isInteger(requiredHitCount) || requiredHitCount < 1)) {
471
471
  throw new Error(`target.network_mocks[${index}].required_hit_count must be a positive integer.`);
472
472
  }
473
+ const forbidden = input.forbidden === true || input.must_not_hit === true || input.mustNotHit === true || input.should_not_run === true || input.shouldNotRun === true;
474
+ const configuredMaxHitCount = numberValue(
475
+ input.max_hit_count ?? input.maxHitCount ?? input.max_hits ?? input.maxHits
476
+ );
477
+ if (configuredMaxHitCount !== void 0 && (!Number.isInteger(configuredMaxHitCount) || configuredMaxHitCount < 0)) {
478
+ throw new Error(`target.network_mocks[${index}].max_hit_count must be a non-negative integer.`);
479
+ }
480
+ if (forbidden && configuredMaxHitCount !== void 0 && configuredMaxHitCount !== 0) {
481
+ throw new Error(`target.network_mocks[${index}].forbidden cannot be combined with max_hit_count greater than 0.`);
482
+ }
483
+ const maxHitCount = forbidden ? 0 : configuredMaxHitCount;
484
+ const required = input.required === false || maxHitCount === 0 && input.required !== true ? false : true;
485
+ const effectiveRequiredHitCount = required ? requiredHitCount ?? (Array.isArray(responses) && responses.length ? responses.length : 1) : 0;
486
+ if (maxHitCount !== void 0 && effectiveRequiredHitCount > maxHitCount) {
487
+ throw new Error(`target.network_mocks[${index}].max_hit_count cannot be less than its required hit count.`);
488
+ }
473
489
  return {
474
490
  ...payload,
475
491
  label: normalizeName(input.label || input.name, `network-mock-${index + 1}`),
@@ -478,7 +494,9 @@ function normalizeNetworkMock(input, index) {
478
494
  responses,
479
495
  repeat_responses: input.repeat_responses === true || input.repeatResponses === true || input.cycle_responses === true || input.cycleResponses === true,
480
496
  required_hit_count: requiredHitCount,
481
- required: input.required === false ? false : true,
497
+ max_hit_count: maxHitCount,
498
+ forbidden,
499
+ required,
482
500
  capture_request_body: requestBody.capture_request_body,
483
501
  request_body_contains: requestBody.request_body_contains,
484
502
  request_body_patterns: requestBody.request_body_patterns,
@@ -1388,8 +1406,9 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
1388
1406
  const events = evidence.network_mocks || [];
1389
1407
  const failed = [];
1390
1408
  const requiredMocks = mocks.filter((mock) => mock.required !== false);
1409
+ const hitCountForMock = (mock) => events.filter((event) => event.label === mock.label && event.ok !== false).length;
1391
1410
  for (const mock of requiredMocks) {
1392
- const hits = events.filter((event) => event.label === mock.label && event.ok !== false).length;
1411
+ const hits = hitCountForMock(mock);
1393
1412
  const requiredHitCount = requiredNetworkMockHitCount(mock);
1394
1413
  if (hits < requiredHitCount) {
1395
1414
  failed.push({
@@ -1402,6 +1421,20 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
1402
1421
  });
1403
1422
  }
1404
1423
  }
1424
+ for (const mock of mocks) {
1425
+ if (mock.max_hit_count === void 0) continue;
1426
+ const hits = hitCountForMock(mock);
1427
+ if (hits > mock.max_hit_count) {
1428
+ failed.push({
1429
+ label: mock.label,
1430
+ url: mock.url,
1431
+ method: mock.method || null,
1432
+ reason: mock.max_hit_count === 0 ? "forbidden_mock_hit" : "mock_hit_count_exceeded",
1433
+ max_hit_count: mock.max_hit_count,
1434
+ hit_count: hits
1435
+ });
1436
+ }
1437
+ }
1405
1438
  for (const event of events) {
1406
1439
  if (event.ok === false) {
1407
1440
  failed.push({
@@ -1436,15 +1469,16 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
1436
1469
  hit_count: events.filter((event) => event.ok !== false).length,
1437
1470
  hits_by_label: Object.fromEntries(mocks.map((mock) => [
1438
1471
  mock.label,
1439
- events.filter((event) => event.label === mock.label && event.ok !== false).length
1472
+ hitCountForMock(mock)
1440
1473
  ])),
1441
1474
  required_hits_by_label: Object.fromEntries(requiredMocks.map((mock) => [
1442
1475
  mock.label,
1443
1476
  requiredNetworkMockHitCount(mock)
1444
1477
  ])),
1478
+ max_hits_by_label: Object.fromEntries(mocks.filter((mock) => mock.max_hit_count !== void 0).map((mock) => [mock.label, mock.max_hit_count ?? null])),
1445
1479
  failed
1446
1480
  },
1447
- message: failed.length ? `Network mocks failed or were not hit for ${failed.length} mock(s).` : void 0
1481
+ message: failed.length ? `Network mocks failed or hit-count contracts failed for ${failed.length} mock(s).` : void 0
1448
1482
  };
1449
1483
  }
1450
1484
  function requiredNetworkMockHitCount(mock) {
@@ -1904,8 +1938,9 @@ function assessProfile(profile, evidence) {
1904
1938
  const events = evidence.network_mocks || [];
1905
1939
  const requiredMocks = profile.target.network_mocks.filter((mock) => mock && mock.required !== false);
1906
1940
  const failed = [];
1941
+ const hitCountForMock = (mock) => events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
1907
1942
  for (const mock of requiredMocks) {
1908
- const hits = events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
1943
+ const hits = hitCountForMock(mock);
1909
1944
  const requiredHitCount = requiredNetworkMockHitCount(mock);
1910
1945
  if (hits < requiredHitCount) {
1911
1946
  failed.push({
@@ -1918,6 +1953,20 @@ function assessProfile(profile, evidence) {
1918
1953
  });
1919
1954
  }
1920
1955
  }
1956
+ for (const mock of profile.target.network_mocks) {
1957
+ if (!mock || mock.max_hit_count === undefined) continue;
1958
+ const hits = hitCountForMock(mock);
1959
+ if (hits > mock.max_hit_count) {
1960
+ failed.push({
1961
+ label: mock.label,
1962
+ url: mock.url,
1963
+ method: mock.method || null,
1964
+ reason: mock.max_hit_count === 0 ? "forbidden_mock_hit" : "mock_hit_count_exceeded",
1965
+ max_hit_count: mock.max_hit_count,
1966
+ hit_count: hits,
1967
+ });
1968
+ }
1969
+ }
1921
1970
  for (const event of events) {
1922
1971
  if (event && event.ok === false) {
1923
1972
  failed.push({
@@ -1944,9 +1993,11 @@ function assessProfile(profile, evidence) {
1944
1993
  }
1945
1994
  const hitsByLabel = {};
1946
1995
  const requiredHitsByLabel = {};
1996
+ const maxHitsByLabel = {};
1947
1997
  for (const mock of profile.target.network_mocks) {
1948
- hitsByLabel[mock.label] = events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
1998
+ hitsByLabel[mock.label] = hitCountForMock(mock);
1949
1999
  if (mock && mock.required !== false) requiredHitsByLabel[mock.label] = requiredNetworkMockHitCount(mock);
2000
+ if (mock && mock.max_hit_count !== undefined) maxHitsByLabel[mock.label] = mock.max_hit_count;
1950
2001
  }
1951
2002
  checks.push({
1952
2003
  type: "network_mocks_succeeded",
@@ -1958,9 +2009,10 @@ function assessProfile(profile, evidence) {
1958
2009
  hit_count: events.filter((event) => event && event.ok !== false).length,
1959
2010
  hits_by_label: hitsByLabel,
1960
2011
  required_hits_by_label: requiredHitsByLabel,
2012
+ max_hits_by_label: maxHitsByLabel,
1961
2013
  failed,
1962
2014
  },
1963
- message: failed.length ? "Network mocks failed or were not hit for " + failed.length + " mock(s)." : undefined,
2015
+ message: failed.length ? "Network mocks failed or hit-count contracts failed for " + failed.length + " mock(s)." : undefined,
1964
2016
  });
1965
2017
  }
1966
2018
  if (profile.target && Array.isArray(profile.target.setup_actions) && profile.target.setup_actions.length) {
package/dist/cli.cjs CHANGED
@@ -7343,6 +7343,22 @@ function normalizeNetworkMock(input, index) {
7343
7343
  if (requiredHitCount !== void 0 && (!Number.isInteger(requiredHitCount) || requiredHitCount < 1)) {
7344
7344
  throw new Error(`target.network_mocks[${index}].required_hit_count must be a positive integer.`);
7345
7345
  }
7346
+ const forbidden = input.forbidden === true || input.must_not_hit === true || input.mustNotHit === true || input.should_not_run === true || input.shouldNotRun === true;
7347
+ const configuredMaxHitCount = numberValue(
7348
+ input.max_hit_count ?? input.maxHitCount ?? input.max_hits ?? input.maxHits
7349
+ );
7350
+ if (configuredMaxHitCount !== void 0 && (!Number.isInteger(configuredMaxHitCount) || configuredMaxHitCount < 0)) {
7351
+ throw new Error(`target.network_mocks[${index}].max_hit_count must be a non-negative integer.`);
7352
+ }
7353
+ if (forbidden && configuredMaxHitCount !== void 0 && configuredMaxHitCount !== 0) {
7354
+ throw new Error(`target.network_mocks[${index}].forbidden cannot be combined with max_hit_count greater than 0.`);
7355
+ }
7356
+ const maxHitCount = forbidden ? 0 : configuredMaxHitCount;
7357
+ const required = input.required === false || maxHitCount === 0 && input.required !== true ? false : true;
7358
+ const effectiveRequiredHitCount = required ? requiredHitCount ?? (Array.isArray(responses) && responses.length ? responses.length : 1) : 0;
7359
+ if (maxHitCount !== void 0 && effectiveRequiredHitCount > maxHitCount) {
7360
+ throw new Error(`target.network_mocks[${index}].max_hit_count cannot be less than its required hit count.`);
7361
+ }
7346
7362
  return {
7347
7363
  ...payload,
7348
7364
  label: normalizeName(input.label || input.name, `network-mock-${index + 1}`),
@@ -7351,7 +7367,9 @@ function normalizeNetworkMock(input, index) {
7351
7367
  responses,
7352
7368
  repeat_responses: input.repeat_responses === true || input.repeatResponses === true || input.cycle_responses === true || input.cycleResponses === true,
7353
7369
  required_hit_count: requiredHitCount,
7354
- required: input.required === false ? false : true,
7370
+ max_hit_count: maxHitCount,
7371
+ forbidden,
7372
+ required,
7355
7373
  capture_request_body: requestBody.capture_request_body,
7356
7374
  request_body_contains: requestBody.request_body_contains,
7357
7375
  request_body_patterns: requestBody.request_body_patterns,
@@ -8261,8 +8279,9 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
8261
8279
  const events = evidence.network_mocks || [];
8262
8280
  const failed = [];
8263
8281
  const requiredMocks = mocks.filter((mock) => mock.required !== false);
8282
+ const hitCountForMock = (mock) => events.filter((event) => event.label === mock.label && event.ok !== false).length;
8264
8283
  for (const mock of requiredMocks) {
8265
- const hits = events.filter((event) => event.label === mock.label && event.ok !== false).length;
8284
+ const hits = hitCountForMock(mock);
8266
8285
  const requiredHitCount = requiredNetworkMockHitCount(mock);
8267
8286
  if (hits < requiredHitCount) {
8268
8287
  failed.push({
@@ -8275,6 +8294,20 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
8275
8294
  });
8276
8295
  }
8277
8296
  }
8297
+ for (const mock of mocks) {
8298
+ if (mock.max_hit_count === void 0) continue;
8299
+ const hits = hitCountForMock(mock);
8300
+ if (hits > mock.max_hit_count) {
8301
+ failed.push({
8302
+ label: mock.label,
8303
+ url: mock.url,
8304
+ method: mock.method || null,
8305
+ reason: mock.max_hit_count === 0 ? "forbidden_mock_hit" : "mock_hit_count_exceeded",
8306
+ max_hit_count: mock.max_hit_count,
8307
+ hit_count: hits
8308
+ });
8309
+ }
8310
+ }
8278
8311
  for (const event of events) {
8279
8312
  if (event.ok === false) {
8280
8313
  failed.push({
@@ -8309,15 +8342,16 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
8309
8342
  hit_count: events.filter((event) => event.ok !== false).length,
8310
8343
  hits_by_label: Object.fromEntries(mocks.map((mock) => [
8311
8344
  mock.label,
8312
- events.filter((event) => event.label === mock.label && event.ok !== false).length
8345
+ hitCountForMock(mock)
8313
8346
  ])),
8314
8347
  required_hits_by_label: Object.fromEntries(requiredMocks.map((mock) => [
8315
8348
  mock.label,
8316
8349
  requiredNetworkMockHitCount(mock)
8317
8350
  ])),
8351
+ max_hits_by_label: Object.fromEntries(mocks.filter((mock) => mock.max_hit_count !== void 0).map((mock) => [mock.label, mock.max_hit_count ?? null])),
8318
8352
  failed
8319
8353
  },
8320
- message: failed.length ? `Network mocks failed or were not hit for ${failed.length} mock(s).` : void 0
8354
+ message: failed.length ? `Network mocks failed or hit-count contracts failed for ${failed.length} mock(s).` : void 0
8321
8355
  };
8322
8356
  }
8323
8357
  function requiredNetworkMockHitCount(mock) {
@@ -8761,8 +8795,9 @@ function assessProfile(profile, evidence) {
8761
8795
  const events = evidence.network_mocks || [];
8762
8796
  const requiredMocks = profile.target.network_mocks.filter((mock) => mock && mock.required !== false);
8763
8797
  const failed = [];
8798
+ const hitCountForMock = (mock) => events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
8764
8799
  for (const mock of requiredMocks) {
8765
- const hits = events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
8800
+ const hits = hitCountForMock(mock);
8766
8801
  const requiredHitCount = requiredNetworkMockHitCount(mock);
8767
8802
  if (hits < requiredHitCount) {
8768
8803
  failed.push({
@@ -8775,6 +8810,20 @@ function assessProfile(profile, evidence) {
8775
8810
  });
8776
8811
  }
8777
8812
  }
8813
+ for (const mock of profile.target.network_mocks) {
8814
+ if (!mock || mock.max_hit_count === undefined) continue;
8815
+ const hits = hitCountForMock(mock);
8816
+ if (hits > mock.max_hit_count) {
8817
+ failed.push({
8818
+ label: mock.label,
8819
+ url: mock.url,
8820
+ method: mock.method || null,
8821
+ reason: mock.max_hit_count === 0 ? "forbidden_mock_hit" : "mock_hit_count_exceeded",
8822
+ max_hit_count: mock.max_hit_count,
8823
+ hit_count: hits,
8824
+ });
8825
+ }
8826
+ }
8778
8827
  for (const event of events) {
8779
8828
  if (event && event.ok === false) {
8780
8829
  failed.push({
@@ -8801,9 +8850,11 @@ function assessProfile(profile, evidence) {
8801
8850
  }
8802
8851
  const hitsByLabel = {};
8803
8852
  const requiredHitsByLabel = {};
8853
+ const maxHitsByLabel = {};
8804
8854
  for (const mock of profile.target.network_mocks) {
8805
- hitsByLabel[mock.label] = events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
8855
+ hitsByLabel[mock.label] = hitCountForMock(mock);
8806
8856
  if (mock && mock.required !== false) requiredHitsByLabel[mock.label] = requiredNetworkMockHitCount(mock);
8857
+ if (mock && mock.max_hit_count !== undefined) maxHitsByLabel[mock.label] = mock.max_hit_count;
8807
8858
  }
8808
8859
  checks.push({
8809
8860
  type: "network_mocks_succeeded",
@@ -8815,9 +8866,10 @@ function assessProfile(profile, evidence) {
8815
8866
  hit_count: events.filter((event) => event && event.ok !== false).length,
8816
8867
  hits_by_label: hitsByLabel,
8817
8868
  required_hits_by_label: requiredHitsByLabel,
8869
+ max_hits_by_label: maxHitsByLabel,
8818
8870
  failed,
8819
8871
  },
8820
- message: failed.length ? "Network mocks failed or were not hit for " + failed.length + " mock(s)." : undefined,
8872
+ message: failed.length ? "Network mocks failed or hit-count contracts failed for " + failed.length + " mock(s)." : undefined,
8821
8873
  });
8822
8874
  }
8823
8875
  if (profile.target && Array.isArray(profile.target.setup_actions) && profile.target.setup_actions.length) {
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  profileStatusExitCode,
11
11
  resolveRiddleProofProfileTargetUrl,
12
12
  resolveRiddleProofProfileTimeoutSec
13
- } from "./chunk-DQU65Z65.js";
13
+ } from "./chunk-376FPGFA.js";
14
14
  import {
15
15
  createRiddleApiClient,
16
16
  parseRiddleViewport
package/dist/index.cjs CHANGED
@@ -9184,6 +9184,22 @@ function normalizeNetworkMock(input, index) {
9184
9184
  if (requiredHitCount !== void 0 && (!Number.isInteger(requiredHitCount) || requiredHitCount < 1)) {
9185
9185
  throw new Error(`target.network_mocks[${index}].required_hit_count must be a positive integer.`);
9186
9186
  }
9187
+ const forbidden = input.forbidden === true || input.must_not_hit === true || input.mustNotHit === true || input.should_not_run === true || input.shouldNotRun === true;
9188
+ const configuredMaxHitCount = numberValue3(
9189
+ input.max_hit_count ?? input.maxHitCount ?? input.max_hits ?? input.maxHits
9190
+ );
9191
+ if (configuredMaxHitCount !== void 0 && (!Number.isInteger(configuredMaxHitCount) || configuredMaxHitCount < 0)) {
9192
+ throw new Error(`target.network_mocks[${index}].max_hit_count must be a non-negative integer.`);
9193
+ }
9194
+ if (forbidden && configuredMaxHitCount !== void 0 && configuredMaxHitCount !== 0) {
9195
+ throw new Error(`target.network_mocks[${index}].forbidden cannot be combined with max_hit_count greater than 0.`);
9196
+ }
9197
+ const maxHitCount = forbidden ? 0 : configuredMaxHitCount;
9198
+ const required = input.required === false || maxHitCount === 0 && input.required !== true ? false : true;
9199
+ const effectiveRequiredHitCount = required ? requiredHitCount ?? (Array.isArray(responses) && responses.length ? responses.length : 1) : 0;
9200
+ if (maxHitCount !== void 0 && effectiveRequiredHitCount > maxHitCount) {
9201
+ throw new Error(`target.network_mocks[${index}].max_hit_count cannot be less than its required hit count.`);
9202
+ }
9187
9203
  return {
9188
9204
  ...payload,
9189
9205
  label: normalizeName(input.label || input.name, `network-mock-${index + 1}`),
@@ -9192,7 +9208,9 @@ function normalizeNetworkMock(input, index) {
9192
9208
  responses,
9193
9209
  repeat_responses: input.repeat_responses === true || input.repeatResponses === true || input.cycle_responses === true || input.cycleResponses === true,
9194
9210
  required_hit_count: requiredHitCount,
9195
- required: input.required === false ? false : true,
9211
+ max_hit_count: maxHitCount,
9212
+ forbidden,
9213
+ required,
9196
9214
  capture_request_body: requestBody.capture_request_body,
9197
9215
  request_body_contains: requestBody.request_body_contains,
9198
9216
  request_body_patterns: requestBody.request_body_patterns,
@@ -10102,8 +10120,9 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
10102
10120
  const events = evidence.network_mocks || [];
10103
10121
  const failed = [];
10104
10122
  const requiredMocks = mocks.filter((mock) => mock.required !== false);
10123
+ const hitCountForMock = (mock) => events.filter((event) => event.label === mock.label && event.ok !== false).length;
10105
10124
  for (const mock of requiredMocks) {
10106
- const hits = events.filter((event) => event.label === mock.label && event.ok !== false).length;
10125
+ const hits = hitCountForMock(mock);
10107
10126
  const requiredHitCount = requiredNetworkMockHitCount(mock);
10108
10127
  if (hits < requiredHitCount) {
10109
10128
  failed.push({
@@ -10116,6 +10135,20 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
10116
10135
  });
10117
10136
  }
10118
10137
  }
10138
+ for (const mock of mocks) {
10139
+ if (mock.max_hit_count === void 0) continue;
10140
+ const hits = hitCountForMock(mock);
10141
+ if (hits > mock.max_hit_count) {
10142
+ failed.push({
10143
+ label: mock.label,
10144
+ url: mock.url,
10145
+ method: mock.method || null,
10146
+ reason: mock.max_hit_count === 0 ? "forbidden_mock_hit" : "mock_hit_count_exceeded",
10147
+ max_hit_count: mock.max_hit_count,
10148
+ hit_count: hits
10149
+ });
10150
+ }
10151
+ }
10119
10152
  for (const event of events) {
10120
10153
  if (event.ok === false) {
10121
10154
  failed.push({
@@ -10150,15 +10183,16 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
10150
10183
  hit_count: events.filter((event) => event.ok !== false).length,
10151
10184
  hits_by_label: Object.fromEntries(mocks.map((mock) => [
10152
10185
  mock.label,
10153
- events.filter((event) => event.label === mock.label && event.ok !== false).length
10186
+ hitCountForMock(mock)
10154
10187
  ])),
10155
10188
  required_hits_by_label: Object.fromEntries(requiredMocks.map((mock) => [
10156
10189
  mock.label,
10157
10190
  requiredNetworkMockHitCount(mock)
10158
10191
  ])),
10192
+ max_hits_by_label: Object.fromEntries(mocks.filter((mock) => mock.max_hit_count !== void 0).map((mock) => [mock.label, mock.max_hit_count ?? null])),
10159
10193
  failed
10160
10194
  },
10161
- message: failed.length ? `Network mocks failed or were not hit for ${failed.length} mock(s).` : void 0
10195
+ message: failed.length ? `Network mocks failed or hit-count contracts failed for ${failed.length} mock(s).` : void 0
10162
10196
  };
10163
10197
  }
10164
10198
  function requiredNetworkMockHitCount(mock) {
@@ -10618,8 +10652,9 @@ function assessProfile(profile, evidence) {
10618
10652
  const events = evidence.network_mocks || [];
10619
10653
  const requiredMocks = profile.target.network_mocks.filter((mock) => mock && mock.required !== false);
10620
10654
  const failed = [];
10655
+ const hitCountForMock = (mock) => events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
10621
10656
  for (const mock of requiredMocks) {
10622
- const hits = events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
10657
+ const hits = hitCountForMock(mock);
10623
10658
  const requiredHitCount = requiredNetworkMockHitCount(mock);
10624
10659
  if (hits < requiredHitCount) {
10625
10660
  failed.push({
@@ -10632,6 +10667,20 @@ function assessProfile(profile, evidence) {
10632
10667
  });
10633
10668
  }
10634
10669
  }
10670
+ for (const mock of profile.target.network_mocks) {
10671
+ if (!mock || mock.max_hit_count === undefined) continue;
10672
+ const hits = hitCountForMock(mock);
10673
+ if (hits > mock.max_hit_count) {
10674
+ failed.push({
10675
+ label: mock.label,
10676
+ url: mock.url,
10677
+ method: mock.method || null,
10678
+ reason: mock.max_hit_count === 0 ? "forbidden_mock_hit" : "mock_hit_count_exceeded",
10679
+ max_hit_count: mock.max_hit_count,
10680
+ hit_count: hits,
10681
+ });
10682
+ }
10683
+ }
10635
10684
  for (const event of events) {
10636
10685
  if (event && event.ok === false) {
10637
10686
  failed.push({
@@ -10658,9 +10707,11 @@ function assessProfile(profile, evidence) {
10658
10707
  }
10659
10708
  const hitsByLabel = {};
10660
10709
  const requiredHitsByLabel = {};
10710
+ const maxHitsByLabel = {};
10661
10711
  for (const mock of profile.target.network_mocks) {
10662
- hitsByLabel[mock.label] = events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
10712
+ hitsByLabel[mock.label] = hitCountForMock(mock);
10663
10713
  if (mock && mock.required !== false) requiredHitsByLabel[mock.label] = requiredNetworkMockHitCount(mock);
10714
+ if (mock && mock.max_hit_count !== undefined) maxHitsByLabel[mock.label] = mock.max_hit_count;
10664
10715
  }
10665
10716
  checks.push({
10666
10717
  type: "network_mocks_succeeded",
@@ -10672,9 +10723,10 @@ function assessProfile(profile, evidence) {
10672
10723
  hit_count: events.filter((event) => event && event.ok !== false).length,
10673
10724
  hits_by_label: hitsByLabel,
10674
10725
  required_hits_by_label: requiredHitsByLabel,
10726
+ max_hits_by_label: maxHitsByLabel,
10675
10727
  failed,
10676
10728
  },
10677
- message: failed.length ? "Network mocks failed or were not hit for " + failed.length + " mock(s)." : undefined,
10729
+ message: failed.length ? "Network mocks failed or hit-count contracts failed for " + failed.length + " mock(s)." : undefined,
10678
10730
  });
10679
10731
  }
10680
10732
  if (profile.target && Array.isArray(profile.target.setup_actions) && profile.target.setup_actions.length) {
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ import {
58
58
  resolveRiddleProofProfileTimeoutSec,
59
59
  slugifyRiddleProofProfileName,
60
60
  summarizeRiddleProofProfileResult
61
- } from "./chunk-DQU65Z65.js";
61
+ } from "./chunk-376FPGFA.js";
62
62
  import {
63
63
  DEFAULT_RIDDLE_API_BASE_URL,
64
64
  DEFAULT_RIDDLE_API_KEY_FILE,
package/dist/profile.cjs CHANGED
@@ -513,6 +513,22 @@ function normalizeNetworkMock(input, index) {
513
513
  if (requiredHitCount !== void 0 && (!Number.isInteger(requiredHitCount) || requiredHitCount < 1)) {
514
514
  throw new Error(`target.network_mocks[${index}].required_hit_count must be a positive integer.`);
515
515
  }
516
+ const forbidden = input.forbidden === true || input.must_not_hit === true || input.mustNotHit === true || input.should_not_run === true || input.shouldNotRun === true;
517
+ const configuredMaxHitCount = numberValue(
518
+ input.max_hit_count ?? input.maxHitCount ?? input.max_hits ?? input.maxHits
519
+ );
520
+ if (configuredMaxHitCount !== void 0 && (!Number.isInteger(configuredMaxHitCount) || configuredMaxHitCount < 0)) {
521
+ throw new Error(`target.network_mocks[${index}].max_hit_count must be a non-negative integer.`);
522
+ }
523
+ if (forbidden && configuredMaxHitCount !== void 0 && configuredMaxHitCount !== 0) {
524
+ throw new Error(`target.network_mocks[${index}].forbidden cannot be combined with max_hit_count greater than 0.`);
525
+ }
526
+ const maxHitCount = forbidden ? 0 : configuredMaxHitCount;
527
+ const required = input.required === false || maxHitCount === 0 && input.required !== true ? false : true;
528
+ const effectiveRequiredHitCount = required ? requiredHitCount ?? (Array.isArray(responses) && responses.length ? responses.length : 1) : 0;
529
+ if (maxHitCount !== void 0 && effectiveRequiredHitCount > maxHitCount) {
530
+ throw new Error(`target.network_mocks[${index}].max_hit_count cannot be less than its required hit count.`);
531
+ }
516
532
  return {
517
533
  ...payload,
518
534
  label: normalizeName(input.label || input.name, `network-mock-${index + 1}`),
@@ -521,7 +537,9 @@ function normalizeNetworkMock(input, index) {
521
537
  responses,
522
538
  repeat_responses: input.repeat_responses === true || input.repeatResponses === true || input.cycle_responses === true || input.cycleResponses === true,
523
539
  required_hit_count: requiredHitCount,
524
- required: input.required === false ? false : true,
540
+ max_hit_count: maxHitCount,
541
+ forbidden,
542
+ required,
525
543
  capture_request_body: requestBody.capture_request_body,
526
544
  request_body_contains: requestBody.request_body_contains,
527
545
  request_body_patterns: requestBody.request_body_patterns,
@@ -1431,8 +1449,9 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
1431
1449
  const events = evidence.network_mocks || [];
1432
1450
  const failed = [];
1433
1451
  const requiredMocks = mocks.filter((mock) => mock.required !== false);
1452
+ const hitCountForMock = (mock) => events.filter((event) => event.label === mock.label && event.ok !== false).length;
1434
1453
  for (const mock of requiredMocks) {
1435
- const hits = events.filter((event) => event.label === mock.label && event.ok !== false).length;
1454
+ const hits = hitCountForMock(mock);
1436
1455
  const requiredHitCount = requiredNetworkMockHitCount(mock);
1437
1456
  if (hits < requiredHitCount) {
1438
1457
  failed.push({
@@ -1445,6 +1464,20 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
1445
1464
  });
1446
1465
  }
1447
1466
  }
1467
+ for (const mock of mocks) {
1468
+ if (mock.max_hit_count === void 0) continue;
1469
+ const hits = hitCountForMock(mock);
1470
+ if (hits > mock.max_hit_count) {
1471
+ failed.push({
1472
+ label: mock.label,
1473
+ url: mock.url,
1474
+ method: mock.method || null,
1475
+ reason: mock.max_hit_count === 0 ? "forbidden_mock_hit" : "mock_hit_count_exceeded",
1476
+ max_hit_count: mock.max_hit_count,
1477
+ hit_count: hits
1478
+ });
1479
+ }
1480
+ }
1448
1481
  for (const event of events) {
1449
1482
  if (event.ok === false) {
1450
1483
  failed.push({
@@ -1479,15 +1512,16 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
1479
1512
  hit_count: events.filter((event) => event.ok !== false).length,
1480
1513
  hits_by_label: Object.fromEntries(mocks.map((mock) => [
1481
1514
  mock.label,
1482
- events.filter((event) => event.label === mock.label && event.ok !== false).length
1515
+ hitCountForMock(mock)
1483
1516
  ])),
1484
1517
  required_hits_by_label: Object.fromEntries(requiredMocks.map((mock) => [
1485
1518
  mock.label,
1486
1519
  requiredNetworkMockHitCount(mock)
1487
1520
  ])),
1521
+ max_hits_by_label: Object.fromEntries(mocks.filter((mock) => mock.max_hit_count !== void 0).map((mock) => [mock.label, mock.max_hit_count ?? null])),
1488
1522
  failed
1489
1523
  },
1490
- message: failed.length ? `Network mocks failed or were not hit for ${failed.length} mock(s).` : void 0
1524
+ message: failed.length ? `Network mocks failed or hit-count contracts failed for ${failed.length} mock(s).` : void 0
1491
1525
  };
1492
1526
  }
1493
1527
  function requiredNetworkMockHitCount(mock) {
@@ -1947,8 +1981,9 @@ function assessProfile(profile, evidence) {
1947
1981
  const events = evidence.network_mocks || [];
1948
1982
  const requiredMocks = profile.target.network_mocks.filter((mock) => mock && mock.required !== false);
1949
1983
  const failed = [];
1984
+ const hitCountForMock = (mock) => events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
1950
1985
  for (const mock of requiredMocks) {
1951
- const hits = events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
1986
+ const hits = hitCountForMock(mock);
1952
1987
  const requiredHitCount = requiredNetworkMockHitCount(mock);
1953
1988
  if (hits < requiredHitCount) {
1954
1989
  failed.push({
@@ -1961,6 +1996,20 @@ function assessProfile(profile, evidence) {
1961
1996
  });
1962
1997
  }
1963
1998
  }
1999
+ for (const mock of profile.target.network_mocks) {
2000
+ if (!mock || mock.max_hit_count === undefined) continue;
2001
+ const hits = hitCountForMock(mock);
2002
+ if (hits > mock.max_hit_count) {
2003
+ failed.push({
2004
+ label: mock.label,
2005
+ url: mock.url,
2006
+ method: mock.method || null,
2007
+ reason: mock.max_hit_count === 0 ? "forbidden_mock_hit" : "mock_hit_count_exceeded",
2008
+ max_hit_count: mock.max_hit_count,
2009
+ hit_count: hits,
2010
+ });
2011
+ }
2012
+ }
1964
2013
  for (const event of events) {
1965
2014
  if (event && event.ok === false) {
1966
2015
  failed.push({
@@ -1987,9 +2036,11 @@ function assessProfile(profile, evidence) {
1987
2036
  }
1988
2037
  const hitsByLabel = {};
1989
2038
  const requiredHitsByLabel = {};
2039
+ const maxHitsByLabel = {};
1990
2040
  for (const mock of profile.target.network_mocks) {
1991
- hitsByLabel[mock.label] = events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
2041
+ hitsByLabel[mock.label] = hitCountForMock(mock);
1992
2042
  if (mock && mock.required !== false) requiredHitsByLabel[mock.label] = requiredNetworkMockHitCount(mock);
2043
+ if (mock && mock.max_hit_count !== undefined) maxHitsByLabel[mock.label] = mock.max_hit_count;
1993
2044
  }
1994
2045
  checks.push({
1995
2046
  type: "network_mocks_succeeded",
@@ -2001,9 +2052,10 @@ function assessProfile(profile, evidence) {
2001
2052
  hit_count: events.filter((event) => event && event.ok !== false).length,
2002
2053
  hits_by_label: hitsByLabel,
2003
2054
  required_hits_by_label: requiredHitsByLabel,
2055
+ max_hits_by_label: maxHitsByLabel,
2004
2056
  failed,
2005
2057
  },
2006
- message: failed.length ? "Network mocks failed or were not hit for " + failed.length + " mock(s)." : undefined,
2058
+ message: failed.length ? "Network mocks failed or hit-count contracts failed for " + failed.length + " mock(s)." : undefined,
2007
2059
  });
2008
2060
  }
2009
2061
  if (profile.target && Array.isArray(profile.target.setup_actions) && profile.target.setup_actions.length) {
@@ -74,6 +74,8 @@ interface RiddleProofProfileNetworkMock extends RiddleProofProfileNetworkMockRes
74
74
  responses?: RiddleProofProfileNetworkMockResponse[];
75
75
  repeat_responses?: boolean;
76
76
  required_hit_count?: number;
77
+ max_hit_count?: number;
78
+ forbidden?: boolean;
77
79
  required?: boolean;
78
80
  capture_request_body?: boolean;
79
81
  request_body_contains?: string[];
package/dist/profile.d.ts CHANGED
@@ -74,6 +74,8 @@ interface RiddleProofProfileNetworkMock extends RiddleProofProfileNetworkMockRes
74
74
  responses?: RiddleProofProfileNetworkMockResponse[];
75
75
  repeat_responses?: boolean;
76
76
  required_hit_count?: number;
77
+ max_hit_count?: number;
78
+ forbidden?: boolean;
77
79
  required?: boolean;
78
80
  capture_request_body?: boolean;
79
81
  request_body_contains?: string[];
package/dist/profile.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  resolveRiddleProofProfileTimeoutSec,
20
20
  slugifyRiddleProofProfileName,
21
21
  summarizeRiddleProofProfileResult
22
- } from "./chunk-DQU65Z65.js";
22
+ } from "./chunk-376FPGFA.js";
23
23
  export {
24
24
  RIDDLE_PROOF_PROFILE_CHECK_TYPES,
25
25
  RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.7.66",
3
+ "version": "0.7.67",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",