@vertesia/common 1.6.0-dev.20260912.110338Z → 1.6.0-dev.20260918.061604Z

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.
Files changed (63) hide show
  1. package/lib/access-control-values.d.ts +12 -0
  2. package/lib/access-control-values.d.ts.map +1 -1
  3. package/lib/access-control-values.js +23 -0
  4. package/lib/access-control-values.js.map +1 -1
  5. package/lib/api-contract/components.generated.json +361 -5
  6. package/lib/api-schemas/account.d.ts +3 -10
  7. package/lib/api-schemas/account.d.ts.map +1 -1
  8. package/lib/api-schemas/account.js +5 -9
  9. package/lib/api-schemas/account.js.map +1 -1
  10. package/lib/api-schemas/agent-runs.d.ts +15 -0
  11. package/lib/api-schemas/agent-runs.d.ts.map +1 -1
  12. package/lib/api-schemas/agent-runs.js +24 -1
  13. package/lib/api-schemas/agent-runs.js.map +1 -1
  14. package/lib/api-schemas/apikey.d.ts +62 -1
  15. package/lib/api-schemas/apikey.d.ts.map +1 -1
  16. package/lib/api-schemas/apikey.js +40 -1
  17. package/lib/api-schemas/apikey.js.map +1 -1
  18. package/lib/api-schemas/project-configuration.d.ts +5 -0
  19. package/lib/api-schemas/project-configuration.d.ts.map +1 -1
  20. package/lib/api-schemas/project-configuration.js +3 -2
  21. package/lib/api-schemas/project-configuration.js.map +1 -1
  22. package/lib/api-schemas/project.d.ts +3 -0
  23. package/lib/api-schemas/project.d.ts.map +1 -1
  24. package/lib/api-schemas/registry.d.ts +81 -1
  25. package/lib/api-schemas/registry.d.ts.map +1 -1
  26. package/lib/api-schemas/registry.js +10 -1
  27. package/lib/api-schemas/registry.js.map +1 -1
  28. package/lib/apikey.d.ts +7 -0
  29. package/lib/apikey.d.ts.map +1 -1
  30. package/lib/apikey.js.map +1 -1
  31. package/lib/oauth-scopes.d.ts.map +1 -1
  32. package/lib/oauth-scopes.js +2 -1
  33. package/lib/oauth-scopes.js.map +1 -1
  34. package/lib/project-values.d.ts +1 -0
  35. package/lib/project-values.d.ts.map +1 -1
  36. package/lib/project-values.js +1 -0
  37. package/lib/project-values.js.map +1 -1
  38. package/lib/vertesia-common.js +2 -2
  39. package/lib/vertesia-common.js.map +1 -1
  40. package/lib/workflow-analytics.d.ts +35 -3
  41. package/lib/workflow-analytics.d.ts.map +1 -1
  42. package/lib/workflow-analytics.js +2 -0
  43. package/lib/workflow-analytics.js.map +1 -1
  44. package/package.json +5 -5
  45. package/src/access-control-values.ts +25 -0
  46. package/src/api-contract/components.contract.test.ts +2 -2
  47. package/src/api-contract/components.generated.json +361 -5
  48. package/src/api-schemas/account-apikey.test.ts +60 -0
  49. package/src/api-schemas/account.contract.test.ts +2 -1
  50. package/src/api-schemas/account.ts +5 -9
  51. package/src/api-schemas/agent-runs.contract.test.ts +37 -0
  52. package/src/api-schemas/agent-runs.ts +26 -1
  53. package/src/api-schemas/apikey.ts +42 -1
  54. package/src/api-schemas/bundle-isolation.test.ts +5 -41
  55. package/src/api-schemas/project-configuration.ts +3 -2
  56. package/src/api-schemas/project.contract.test.ts +15 -0
  57. package/src/api-schemas/registry.ts +16 -0
  58. package/src/api-schemas/store.contract.test.ts +4 -126
  59. package/src/apikey.ts +12 -0
  60. package/src/oauth-scopes.ts +2 -1
  61. package/src/project-values.ts +1 -0
  62. package/src/roles.test.ts +1 -0
  63. package/src/workflow-analytics.ts +38 -2
@@ -289,7 +289,15 @@
289
289
  "const": "stripe"
290
290
  },
291
291
  "portal_url": {
292
- "type": "string"
292
+ "anyOf": [
293
+ {
294
+ "type": "string"
295
+ },
296
+ {
297
+ "type": "null"
298
+ }
299
+ ],
300
+ "description": "Interactive billing portal URL; null for account API keys."
293
301
  }
294
302
  },
295
303
  "required": [
@@ -1303,6 +1311,16 @@
1303
1311
  "Permission": {
1304
1312
  "type": "string",
1305
1313
  "enum": [
1314
+ "account:user:read",
1315
+ "account:user:manage",
1316
+ "account:group:read",
1317
+ "account:group:manage",
1318
+ "account:membership:read",
1319
+ "account:membership:manage",
1320
+ "account:api_key:read",
1321
+ "account:api_key:manage",
1322
+ "account:billing:status_read",
1323
+ "account:billing:portal_create",
1306
1324
  "schedule:delegate",
1307
1325
  "interaction:read",
1308
1326
  "interaction:write",
@@ -1374,6 +1392,10 @@
1374
1392
  "enabled": {
1375
1393
  "type": "boolean"
1376
1394
  },
1395
+ "scim_provisioning": {
1396
+ "type": "boolean",
1397
+ "description": "Organization-wide SCIM provisioning credential. Only account administrators may create or manage these keys. May be enabled by account administrators on existing keys; disable or delete to revoke access."
1398
+ },
1377
1399
  "created_by": {
1378
1400
  "type": "string"
1379
1401
  },
@@ -1433,6 +1455,7 @@
1433
1455
  "enum": [
1434
1456
  "owner",
1435
1457
  "admin",
1458
+ "app_admin",
1436
1459
  "manager",
1437
1460
  "developer",
1438
1461
  "application",
@@ -1485,6 +1508,10 @@
1485
1508
  "enabled": {
1486
1509
  "type": "boolean"
1487
1510
  },
1511
+ "scim_provisioning": {
1512
+ "type": "boolean",
1513
+ "description": "Organization-wide SCIM provisioning credential. Only account administrators may create or manage these keys. May be enabled by account administrators on existing keys; disable or delete to revoke access."
1514
+ },
1488
1515
  "created_by": {
1489
1516
  "type": "string"
1490
1517
  },
@@ -1539,6 +1566,174 @@
1539
1566
  ],
1540
1567
  "additionalProperties": false
1541
1568
  },
1569
+ "AccountApiKey": {
1570
+ "type": "object",
1571
+ "properties": {
1572
+ "id": {
1573
+ "type": "string"
1574
+ },
1575
+ "name": {
1576
+ "type": "string"
1577
+ },
1578
+ "account": {
1579
+ "type": "string"
1580
+ },
1581
+ "enabled": {
1582
+ "type": "boolean"
1583
+ },
1584
+ "maskedValue": {
1585
+ "type": "string"
1586
+ },
1587
+ "created_by": {
1588
+ "type": "string"
1589
+ },
1590
+ "updated_by": {
1591
+ "type": "string"
1592
+ },
1593
+ "created_at": {
1594
+ "type": "string",
1595
+ "format": "date-time"
1596
+ },
1597
+ "updated_at": {
1598
+ "type": "string",
1599
+ "format": "date-time"
1600
+ },
1601
+ "expires_at": {
1602
+ "type": "string",
1603
+ "format": "date-time"
1604
+ },
1605
+ "scope": {
1606
+ "type": "string",
1607
+ "const": "account"
1608
+ },
1609
+ "profile": {
1610
+ "type": "string",
1611
+ "const": "account_admin_v1"
1612
+ },
1613
+ "last_used_at": {
1614
+ "type": "string",
1615
+ "format": "date-time"
1616
+ }
1617
+ },
1618
+ "required": [
1619
+ "id",
1620
+ "name",
1621
+ "account",
1622
+ "enabled",
1623
+ "created_by",
1624
+ "updated_by",
1625
+ "created_at",
1626
+ "updated_at",
1627
+ "scope",
1628
+ "profile"
1629
+ ],
1630
+ "additionalProperties": false
1631
+ },
1632
+ "AccountApiKeyWithValue": {
1633
+ "type": "object",
1634
+ "properties": {
1635
+ "id": {
1636
+ "type": "string"
1637
+ },
1638
+ "name": {
1639
+ "type": "string"
1640
+ },
1641
+ "account": {
1642
+ "type": "string"
1643
+ },
1644
+ "enabled": {
1645
+ "type": "boolean"
1646
+ },
1647
+ "maskedValue": {
1648
+ "type": "string"
1649
+ },
1650
+ "created_by": {
1651
+ "type": "string"
1652
+ },
1653
+ "updated_by": {
1654
+ "type": "string"
1655
+ },
1656
+ "created_at": {
1657
+ "type": "string",
1658
+ "format": "date-time"
1659
+ },
1660
+ "updated_at": {
1661
+ "type": "string",
1662
+ "format": "date-time"
1663
+ },
1664
+ "expires_at": {
1665
+ "type": "string",
1666
+ "format": "date-time"
1667
+ },
1668
+ "scope": {
1669
+ "type": "string",
1670
+ "const": "account"
1671
+ },
1672
+ "profile": {
1673
+ "type": "string",
1674
+ "const": "account_admin_v1"
1675
+ },
1676
+ "last_used_at": {
1677
+ "type": "string",
1678
+ "format": "date-time"
1679
+ },
1680
+ "value": {
1681
+ "type": "string"
1682
+ }
1683
+ },
1684
+ "required": [
1685
+ "id",
1686
+ "name",
1687
+ "account",
1688
+ "enabled",
1689
+ "created_by",
1690
+ "updated_by",
1691
+ "created_at",
1692
+ "updated_at",
1693
+ "scope",
1694
+ "profile",
1695
+ "value"
1696
+ ],
1697
+ "additionalProperties": false
1698
+ },
1699
+ "AccountApiKeyArray": {
1700
+ "type": "array",
1701
+ "items": {
1702
+ "$ref": "#/components/schemas/AccountApiKey"
1703
+ }
1704
+ },
1705
+ "CreateAccountApiKeyPayload": {
1706
+ "type": "object",
1707
+ "properties": {
1708
+ "name": {
1709
+ "type": "string",
1710
+ "minLength": 1,
1711
+ "pattern": "\\S"
1712
+ },
1713
+ "expires_at": {
1714
+ "type": "string",
1715
+ "format": "date-time"
1716
+ }
1717
+ },
1718
+ "required": [
1719
+ "name"
1720
+ ],
1721
+ "additionalProperties": false
1722
+ },
1723
+ "UpdateAccountApiKeyPayload": {
1724
+ "type": "object",
1725
+ "properties": {
1726
+ "name": {
1727
+ "type": "string",
1728
+ "minLength": 1,
1729
+ "pattern": "\\S"
1730
+ },
1731
+ "enabled": {
1732
+ "type": "boolean"
1733
+ }
1734
+ },
1735
+ "additionalProperties": false
1736
+ },
1542
1737
  "ApiKeyReadResponse": {
1543
1738
  "type": "object",
1544
1739
  "properties": {
@@ -1569,6 +1764,10 @@
1569
1764
  "enabled": {
1570
1765
  "type": "boolean"
1571
1766
  },
1767
+ "scim_provisioning": {
1768
+ "type": "boolean",
1769
+ "description": "Organization-wide SCIM provisioning credential. Only account administrators may create or manage these keys. May be enabled by account administrators on existing keys; disable or delete to revoke access."
1770
+ },
1572
1771
  "created_by": {
1573
1772
  "type": "string"
1574
1773
  },
@@ -1647,6 +1846,10 @@
1647
1846
  "type": "string",
1648
1847
  "format": "date-time"
1649
1848
  },
1849
+ "scim_provisioning": {
1850
+ "type": "boolean",
1851
+ "description": "Organization-wide SCIM provisioning credential. Only account administrators may create or manage these keys. May be enabled by account administrators on existing keys; disable or delete to revoke access."
1852
+ },
1650
1853
  "properties": {
1651
1854
  "type": "object",
1652
1855
  "additionalProperties": true,
@@ -1682,6 +1885,10 @@
1682
1885
  "enabled": {
1683
1886
  "type": "boolean"
1684
1887
  },
1888
+ "scim_provisioning": {
1889
+ "type": "boolean",
1890
+ "description": "Organization-wide SCIM provisioning credential. Only account administrators may create or manage these keys. May be enabled by account administrators on existing keys; disable or delete to revoke access."
1891
+ },
1685
1892
  "properties": {
1686
1893
  "type": "object",
1687
1894
  "additionalProperties": true,
@@ -2339,9 +2546,10 @@
2339
2546
  "long",
2340
2547
  "double",
2341
2548
  "date",
2342
- "geo_point"
2549
+ "geo_point",
2550
+ "nested"
2343
2551
  ],
2344
- "description": "Elasticsearch field types that may be explicitly assigned to content-object properties. Paths are relative to the object's `properties` field."
2552
+ "description": "Elasticsearch field types that may be explicitly assigned to content-object properties. Declare nested object-array paths with type `nested` and their children as separate dotted paths. Paths are relative to the object's `properties` field."
2345
2553
  },
2346
2554
  "ProjectConfigurationEmbedding": {
2347
2555
  "type": "object",
@@ -32884,6 +33092,11 @@
32884
33092
  "type": "integer",
32885
33093
  "minimum": -9007199254740991,
32886
33094
  "maximum": 9007199254740991
33095
+ },
33096
+ "stall_trips": {
33097
+ "type": "integer",
33098
+ "minimum": -9007199254740991,
33099
+ "maximum": 9007199254740991
32887
33100
  }
32888
33101
  },
32889
33102
  "required": [
@@ -32945,7 +33158,8 @@
32945
33158
  "high_gather",
32946
33159
  "overhead",
32947
33160
  "followup_after_answer",
32948
- "approval_denied"
33161
+ "approval_denied",
33162
+ "circuit_breaker"
32949
33163
  ],
32950
33164
  "description": "Reason behind an evaluation severity."
32951
33165
  },
@@ -32986,7 +33200,7 @@
32986
33200
  "down"
32987
33201
  ],
32988
33202
  "additionalProperties": false,
32989
- "description": "Ratings over the retained feedback entries."
33203
+ "description": "Ratings over the retained feedback entries, one vote per rater: a user who rated the run and one of its answers counts once, by their latest rating."
32990
33204
  },
32991
33205
  "AgentRunFeedbackRating": {
32992
33206
  "type": "string",
@@ -35490,6 +35704,16 @@
35490
35704
  "minimum": -9007199254740991,
35491
35705
  "maximum": 9007199254740991
35492
35706
  },
35707
+ "stallCorrectives": {
35708
+ "type": "integer",
35709
+ "minimum": -9007199254740991,
35710
+ "maximum": 9007199254740991
35711
+ },
35712
+ "stallTrips": {
35713
+ "type": "integer",
35714
+ "minimum": -9007199254740991,
35715
+ "maximum": 9007199254740991
35716
+ },
35493
35717
  "followupAfterAnswer": {
35494
35718
  "type": "boolean"
35495
35719
  },
@@ -35856,6 +36080,138 @@
35856
36080
  "promptVersion"
35857
36081
  ],
35858
36082
  "additionalProperties": false
36083
+ },
36084
+ {
36085
+ "type": "object",
36086
+ "properties": {
36087
+ "timestamp": {
36088
+ "type": "string"
36089
+ },
36090
+ "runId": {
36091
+ "type": "string"
36092
+ },
36093
+ "agentRunId": {
36094
+ "type": "string"
36095
+ },
36096
+ "model": {
36097
+ "type": "string"
36098
+ },
36099
+ "environmentId": {
36100
+ "type": "string"
36101
+ },
36102
+ "environmentType": {
36103
+ "type": "string"
36104
+ },
36105
+ "interactionId": {
36106
+ "type": "string"
36107
+ },
36108
+ "parentRunId": {
36109
+ "type": "string"
36110
+ },
36111
+ "ancestorRunIds": {
36112
+ "type": "array",
36113
+ "items": {
36114
+ "type": "string"
36115
+ }
36116
+ },
36117
+ "eventId": {
36118
+ "type": "string"
36119
+ },
36120
+ "deployment": {
36121
+ "type": "object",
36122
+ "properties": {
36123
+ "env": {
36124
+ "type": "string"
36125
+ },
36126
+ "group": {
36127
+ "type": "string"
36128
+ },
36129
+ "version": {
36130
+ "type": "string"
36131
+ },
36132
+ "region": {
36133
+ "type": "string"
36134
+ }
36135
+ },
36136
+ "required": [
36137
+ "env",
36138
+ "group",
36139
+ "version"
36140
+ ],
36141
+ "additionalProperties": false
36142
+ },
36143
+ "producer": {
36144
+ "type": "object",
36145
+ "properties": {
36146
+ "version": {
36147
+ "type": "string"
36148
+ }
36149
+ },
36150
+ "required": [
36151
+ "version"
36152
+ ],
36153
+ "additionalProperties": false
36154
+ },
36155
+ "eventType": {
36156
+ "type": "string",
36157
+ "const": "stall_breaker"
36158
+ },
36159
+ "action": {
36160
+ "type": "string",
36161
+ "enum": [
36162
+ "corrective",
36163
+ "trip"
36164
+ ]
36165
+ },
36166
+ "toolNames": {
36167
+ "type": "array",
36168
+ "items": {
36169
+ "type": "string"
36170
+ }
36171
+ },
36172
+ "repeatCount": {
36173
+ "type": "integer",
36174
+ "minimum": -9007199254740991,
36175
+ "maximum": 9007199254740991
36176
+ },
36177
+ "stallMeasure": {
36178
+ "type": "integer",
36179
+ "minimum": -9007199254740991,
36180
+ "maximum": 9007199254740991
36181
+ },
36182
+ "allErrored": {
36183
+ "type": "boolean"
36184
+ },
36185
+ "iteration": {
36186
+ "type": "integer",
36187
+ "minimum": -9007199254740991,
36188
+ "maximum": 9007199254740991
36189
+ },
36190
+ "interactive": {
36191
+ "type": "boolean"
36192
+ },
36193
+ "workstreamId": {
36194
+ "type": "string"
36195
+ }
36196
+ },
36197
+ "required": [
36198
+ "timestamp",
36199
+ "runId",
36200
+ "model",
36201
+ "environmentId",
36202
+ "environmentType",
36203
+ "interactionId",
36204
+ "eventType",
36205
+ "action",
36206
+ "toolNames",
36207
+ "repeatCount",
36208
+ "stallMeasure",
36209
+ "allErrored",
36210
+ "iteration",
36211
+ "interactive",
36212
+ "workstreamId"
36213
+ ],
36214
+ "additionalProperties": false
35859
36215
  }
35860
36216
  ]
35861
36217
  },
@@ -0,0 +1,60 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { ApiSchemaComponents } from '../api-contract/index.js';
3
+ import {
4
+ AccountApiKeySchema,
5
+ AccountApiKeyWithValueSchema,
6
+ ApiKeySchema,
7
+ CreateAccountApiKeyPayloadSchema,
8
+ UpdateAccountApiKeyPayloadSchema,
9
+ } from './apikey.js';
10
+ import { validateApiRequest } from './registry.js';
11
+
12
+ describe('separate account-key wire contract', () => {
13
+ const metadata = {
14
+ id: 'key',
15
+ name: 'IdP',
16
+ account: 'account',
17
+ scope: 'account',
18
+ profile: 'account_admin_v1',
19
+ enabled: true,
20
+ maskedValue: 'sk-…',
21
+ created_at: '2026-09-15T00:00:00Z',
22
+ updated_at: '2026-09-15T00:00:00Z',
23
+ created_by: 'user:admin',
24
+ updated_by: 'user:admin',
25
+ };
26
+ it('requires account scope/profile without weakening the project-key contract', () => {
27
+ expect(AccountApiKeySchema.safeParse(metadata).success).toBe(true);
28
+ expect(AccountApiKeySchema.safeParse({ ...metadata, profile: 'other' }).success).toBe(false);
29
+ expect(ApiKeySchema.safeParse(metadata).success).toBe(false);
30
+ expect(AccountApiKeyWithValueSchema.safeParse(metadata).success).toBe(false);
31
+ expect(AccountApiKeyWithValueSchema.safeParse({ ...metadata, value: 'test-secret' }).success).toBe(true);
32
+ });
33
+ it('publishes strict management payloads and metadata without a value or project property', () => {
34
+ const components = ApiSchemaComponents as Record<
35
+ string,
36
+ { additionalProperties?: boolean; properties?: Record<string, unknown>; required?: string[] }
37
+ >;
38
+ for (const name of ['AccountApiKey', 'CreateAccountApiKeyPayload', 'UpdateAccountApiKeyPayload']) {
39
+ expect(components[name].additionalProperties).toBe(false);
40
+ expect(components[name].properties).not.toHaveProperty('value');
41
+ expect(components[name].properties).not.toHaveProperty('project');
42
+ }
43
+ expect(components.ApiKey.required).toContain('project');
44
+ });
45
+ });
46
+
47
+ // AJV enforces emitted JSON Schema; Zod trimming alone cannot reject whitespace at the API boundary.
48
+ describe.each([
49
+ ['CreateAccountApiKeyPayload', CreateAccountApiKeyPayloadSchema],
50
+ ['UpdateAccountApiKeyPayload', UpdateAccountApiKeyPayloadSchema],
51
+ ] as const)('%s name validation', (component, schema) => {
52
+ it.each(['', ' ', '\t\n ', '\u00a0'])('rejects blank name %j in Zod and AJV', (name) => {
53
+ expect(schema.safeParse({ name }).success).toBe(false);
54
+ expect(validateApiRequest(component, { name }).valid).toBe(false);
55
+ });
56
+ it.each(['IdP provisioning', ' IdP provisioning '])('accepts nonblank name %j in both validators', (name) => {
57
+ expect(schema.safeParse({ name }).success).toBe(true);
58
+ expect(validateApiRequest(component, { name }).valid).toBe(true);
59
+ });
60
+ });
@@ -74,7 +74,7 @@ describe('gate 1 — the schema is the single source of truth for the public typ
74
74
  portal_url: 'https://billing.stripe.com/p/session_1',
75
75
  };
76
76
  if (enabled.status === 'enabled') {
77
- assertType<Equals<typeof enabled.portal_url, string>>(true);
77
+ assertType<Equals<typeof enabled.portal_url, string | null>>(true);
78
78
  expect(enabled.portal_url).toBe('https://billing.stripe.com/p/session_1');
79
79
  }
80
80
 
@@ -146,6 +146,7 @@ describe('gate 3 — AJV validates the same canonical objects that are published
146
146
  it('enforces the discriminated union per branch', () => {
147
147
  const validate = compile('StripeBillingStatusResponse');
148
148
  expect(validate({ status: 'enabled', billing_method: 'stripe', portal_url: 'https://x' })).toBe(true);
149
+ expect(validate({ status: 'enabled', billing_method: 'stripe', portal_url: null })).toBe(true);
149
150
  expect(validate({ status: 'disabled', billing_method: null, reason: 'No billing method' })).toBe(true);
150
151
  // The flattened interface allowed this; a real discriminated union does not.
151
152
  expect(validate({ status: 'enabled', billing_method: 'stripe', reason: 'nope' })).toBe(false);
@@ -111,14 +111,7 @@ export const UpdateAccountPayloadSchema = z
111
111
  })
112
112
  .meta({ id: 'UpdateAccountPayload' });
113
113
 
114
- /**
115
- * Stripe billing status, modelled as a real discriminated union.
116
- *
117
- * The hand-written `StripeBillingStatusResponse` in `meters.ts` flattens this into one object
118
- * with `portal_url?` and `reason?` both optional, so a generated Java/Go client gets two
119
- * unrelated optionals and no way to know which is populated. The server only ever sets
120
- * `portal_url` when enabled and `reason` when disabled.
121
- */
114
+ /** Stripe billing status. Account API keys receive status without a billing portal session. */
122
115
  export const StripeBillingEnabledSchema = z
123
116
  .object({
124
117
  status: z.literal('enabled'),
@@ -128,7 +121,10 @@ export const StripeBillingEnabledSchema = z
128
121
  * would describe states the server cannot produce.
129
122
  */
130
123
  billing_method: z.literal(BillingMethod.stripe),
131
- portal_url: z.string(),
124
+ portal_url: z
125
+ .string()
126
+ .nullable()
127
+ .meta({ description: 'Interactive billing portal URL; null for account API keys.' }),
132
128
  })
133
129
  .meta({ id: 'StripeBillingEnabled' });
134
130
 
@@ -59,6 +59,8 @@ const turnEvaluation: TurnEvaluationEvent = {
59
59
  approvalsRequested: 1,
60
60
  approvalsDenied: 0,
61
61
  stopRequests: 0,
62
+ stallCorrectives: 0,
63
+ stallTrips: 0,
62
64
  followupAfterAnswer: false,
63
65
  severity: 'none',
64
66
  flags: [],
@@ -98,6 +100,11 @@ describe('agent run evaluation API contracts', () => {
98
100
  expect(validateApiRequest('IngestAgentEventsPayload', { events: [turnEvaluation] }).valid).toBe(true);
99
101
  });
100
102
 
103
+ it('accepts a turn_evaluation event from a producer that predates the stall counters', () => {
104
+ const { stallCorrectives: _c, stallTrips: _t, ...legacy } = turnEvaluation;
105
+ expect(validateApiRequest('IngestAgentEventsPayload', { events: [legacy] }).valid).toBe(true);
106
+ });
107
+
101
108
  it('rejects a turn_evaluation event with an undeclared field', () => {
102
109
  const events = [{ ...turnEvaluation, verdict: 'success' }];
103
110
  expect(validateApiRequest('IngestAgentEventsPayload', { events }).valid).toBe(false);
@@ -138,6 +145,18 @@ describe('agent run evaluation API contracts', () => {
138
145
  score: 0.2,
139
146
  promptVersion: 'v1',
140
147
  },
148
+ {
149
+ ...base,
150
+ eventType: 'stall_breaker',
151
+ action: 'trip',
152
+ toolNames: ['fetch_document'],
153
+ repeatCount: 4,
154
+ stallMeasure: 4,
155
+ allErrored: false,
156
+ iteration: 7,
157
+ interactive: true,
158
+ workstreamId: 'main',
159
+ },
141
160
  ];
142
161
  expect(validateApiRequest('IngestAgentEventsPayload', { events }).valid).toBe(true);
143
162
  });
@@ -175,10 +194,28 @@ describe('agent run evaluation API contracts', () => {
175
194
  approvals_requested: 0,
176
195
  approvals_denied: 0,
177
196
  stop_requests: 0,
197
+ stall_trips: 0,
178
198
  },
179
199
  updated_at: turnEvaluation.timestamp,
180
200
  };
181
201
  expect(validateApiRequest('UpdateAgentRunStatusPayload', { evaluation_rollup: rollup }).valid).toBe(true);
202
+ // Rollups stored or sent before the stall counter existed keep validating.
203
+ const { stall_trips: _s, ...legacyTotals } = rollup.totals;
204
+ expect(
205
+ validateApiRequest('UpdateAgentRunStatusPayload', {
206
+ evaluation_rollup: { ...rollup, totals: legacyTotals },
207
+ }).valid,
208
+ ).toBe(true);
209
+ expect(
210
+ validateApiResponse('AgentRunEvaluation', {
211
+ rev: 1,
212
+ rollup: { ...rollup, totals: legacyTotals },
213
+ severity: 'medium',
214
+ flags: ['unrecovered_tool'],
215
+ contradicted: false,
216
+ updated_at: turnEvaluation.timestamp,
217
+ }).valid,
218
+ ).toBe(true);
182
219
  expect(
183
220
  validateApiRequest('UpdateAgentRunStatusPayload', {
184
221
  evaluation_rollup: { ...rollup, feedback_counts: { up: 1, down: 0 } },