@vertesia/common 1.5.0-dev.20260804.124748Z → 1.5.0-dev.20260807.073259Z

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 (179) hide show
  1. package/lib/Progress.d.ts.map +1 -1
  2. package/lib/Progress.js.map +1 -1
  3. package/lib/access-control-values.d.ts +2 -2
  4. package/lib/access-control-values.d.ts.map +1 -1
  5. package/lib/account-values.d.ts +1 -1
  6. package/lib/account-values.d.ts.map +1 -1
  7. package/lib/api-schemas/access-control.d.ts +19 -19
  8. package/lib/api-schemas/adapter.d.ts +1 -0
  9. package/lib/api-schemas/adapter.d.ts.map +1 -1
  10. package/lib/api-schemas/adapter.js +1 -1
  11. package/lib/api-schemas/adapter.js.map +1 -1
  12. package/lib/api-schemas/agent-runs.d.ts +660 -515
  13. package/lib/api-schemas/agent-runs.d.ts.map +1 -1
  14. package/lib/api-schemas/agent-runs.js +11 -2
  15. package/lib/api-schemas/agent-runs.js.map +1 -1
  16. package/lib/api-schemas/analytics.d.ts +12 -12
  17. package/lib/api-schemas/apikey.d.ts +10 -10
  18. package/lib/api-schemas/app-lifecycle.d.ts +186 -152
  19. package/lib/api-schemas/app-lifecycle.d.ts.map +1 -1
  20. package/lib/api-schemas/app-lifecycle.js +51 -0
  21. package/lib/api-schemas/app-lifecycle.js.map +1 -1
  22. package/lib/api-schemas/app-runtime.d.ts +4493 -2122
  23. package/lib/api-schemas/app-runtime.d.ts.map +1 -1
  24. package/lib/api-schemas/app-runtime.js +93 -1
  25. package/lib/api-schemas/app-runtime.js.map +1 -1
  26. package/lib/api-schemas/apps.d.ts +43 -19
  27. package/lib/api-schemas/apps.d.ts.map +1 -1
  28. package/lib/api-schemas/apps.js +63 -3
  29. package/lib/api-schemas/apps.js.map +1 -1
  30. package/lib/api-schemas/audit-trail.d.ts +129 -129
  31. package/lib/api-schemas/bulk-operation.d.ts +10 -10
  32. package/lib/api-schemas/content.d.ts +542 -398
  33. package/lib/api-schemas/content.d.ts.map +1 -1
  34. package/lib/api-schemas/content.js +12 -0
  35. package/lib/api-schemas/content.js.map +1 -1
  36. package/lib/api-schemas/cost-analytics.d.ts +12 -12
  37. package/lib/api-schemas/dashboard.d.ts +10 -10
  38. package/lib/api-schemas/data-store.d.ts +556 -556
  39. package/lib/api-schemas/document-processing.d.ts +280 -196
  40. package/lib/api-schemas/document-processing.d.ts.map +1 -1
  41. package/lib/api-schemas/embeddings.d.ts +12 -12
  42. package/lib/api-schemas/environment.d.ts +20 -18
  43. package/lib/api-schemas/environment.d.ts.map +1 -1
  44. package/lib/api-schemas/events.d.ts +988 -698
  45. package/lib/api-schemas/events.d.ts.map +1 -1
  46. package/lib/api-schemas/events.js +59 -3
  47. package/lib/api-schemas/events.js.map +1 -1
  48. package/lib/api-schemas/files.d.ts +1 -1
  49. package/lib/api-schemas/group.d.ts +3 -3
  50. package/lib/api-schemas/indexing.d.ts +3 -3
  51. package/lib/api-schemas/integrations.d.ts +4 -4
  52. package/lib/api-schemas/interaction.d.ts +3894 -2752
  53. package/lib/api-schemas/interaction.d.ts.map +1 -1
  54. package/lib/api-schemas/interaction.js +6 -0
  55. package/lib/api-schemas/interaction.js.map +1 -1
  56. package/lib/api-schemas/invites.d.ts +9 -9
  57. package/lib/api-schemas/oauth-server.d.ts +36 -36
  58. package/lib/api-schemas/parameters.js.map +1 -1
  59. package/lib/api-schemas/process.d.ts +47 -47
  60. package/lib/api-schemas/project-configuration.d.ts +944 -664
  61. package/lib/api-schemas/project-configuration.d.ts.map +1 -1
  62. package/lib/api-schemas/project.d.ts +1403 -983
  63. package/lib/api-schemas/project.d.ts.map +1 -1
  64. package/lib/api-schemas/registry.d.ts +19119 -13557
  65. package/lib/api-schemas/registry.d.ts.map +1 -1
  66. package/lib/api-schemas/registry.js +170 -18
  67. package/lib/api-schemas/registry.js.map +1 -1
  68. package/lib/api-schemas/secrets.d.ts +4 -4
  69. package/lib/api-schemas/store.d.ts +4624 -3224
  70. package/lib/api-schemas/store.d.ts.map +1 -1
  71. package/lib/api-schemas/sts.d.ts +46 -36
  72. package/lib/api-schemas/sts.d.ts.map +1 -1
  73. package/lib/api-schemas/sts.js +11 -0
  74. package/lib/api-schemas/sts.js.map +1 -1
  75. package/lib/api-schemas/task.d.ts +30 -30
  76. package/lib/api-schemas/tools.d.ts +9 -9
  77. package/lib/api-schemas/user.d.ts +6 -6
  78. package/lib/api-schemas/view-execution.d.ts +626 -570
  79. package/lib/api-schemas/view-execution.d.ts.map +1 -1
  80. package/lib/api-schemas/views.d.ts +1293 -1125
  81. package/lib/api-schemas/views.d.ts.map +1 -1
  82. package/lib/api-schemas/workflow-runs.d.ts +325 -268
  83. package/lib/api-schemas/workflow-runs.d.ts.map +1 -1
  84. package/lib/apps.d.ts +38 -24
  85. package/lib/apps.d.ts.map +1 -1
  86. package/lib/apps.js +4 -0
  87. package/lib/apps.js.map +1 -1
  88. package/lib/audit-trail.d.ts +3 -3
  89. package/lib/audit-trail.d.ts.map +1 -1
  90. package/lib/data-platform.d.ts +31 -31
  91. package/lib/data-platform.d.ts.map +1 -1
  92. package/lib/environment.d.ts +3 -3
  93. package/lib/index.d.ts +1 -0
  94. package/lib/index.d.ts.map +1 -1
  95. package/lib/index.js +1 -0
  96. package/lib/index.js.map +1 -1
  97. package/lib/platform-event-validation.d.ts.map +1 -1
  98. package/lib/platform-event-validation.js +15 -2
  99. package/lib/platform-event-validation.js.map +1 -1
  100. package/lib/platform-event.d.ts +31 -3
  101. package/lib/platform-event.d.ts.map +1 -1
  102. package/lib/platform-event.js.map +1 -1
  103. package/lib/project.d.ts +1 -1
  104. package/lib/schema-for-extraction.js.map +1 -1
  105. package/lib/store/agent-approval.d.ts +1 -1
  106. package/lib/store/agent-approval.d.ts.map +1 -1
  107. package/lib/store/intake-policy-schema.generated.d.ts.map +1 -1
  108. package/lib/store/intake-policy-schema.generated.js +36 -0
  109. package/lib/store/intake-policy-schema.generated.js.map +1 -1
  110. package/lib/store/process-schema.d.ts +1 -1
  111. package/lib/store/process-schema.d.ts.map +1 -1
  112. package/lib/store/process-validation.js.map +1 -1
  113. package/lib/store/schedule.d.ts +6 -6
  114. package/lib/store/schedule.d.ts.map +1 -1
  115. package/lib/store/store.d.ts +3 -3
  116. package/lib/store/store.d.ts.map +1 -1
  117. package/lib/store/workflow.d.ts +6 -0
  118. package/lib/store/workflow.d.ts.map +1 -1
  119. package/lib/store/workflow.js +8 -0
  120. package/lib/store/workflow.js.map +1 -1
  121. package/lib/sts-errors.d.ts +7 -0
  122. package/lib/sts-errors.d.ts.map +1 -0
  123. package/lib/sts-errors.js +11 -0
  124. package/lib/sts-errors.js.map +1 -0
  125. package/lib/sts-token-types.d.ts +2 -1
  126. package/lib/sts-token-types.d.ts.map +1 -1
  127. package/lib/sts-token-types.js.map +1 -1
  128. package/lib/user.d.ts +1 -1
  129. package/lib/user.d.ts.map +1 -1
  130. package/lib/user.js +1 -1
  131. package/lib/user.js.map +1 -1
  132. package/lib/versions.d.ts +10 -0
  133. package/lib/versions.d.ts.map +1 -1
  134. package/lib/versions.js +10 -0
  135. package/lib/versions.js.map +1 -1
  136. package/lib/vertesia-common.js +3 -3
  137. package/lib/vertesia-common.js.map +1 -1
  138. package/lib/view-configuration-validation.js.map +1 -1
  139. package/lib/view-query-validation.js.map +1 -1
  140. package/lib/view-validation-helpers.d.ts +1 -1
  141. package/lib/view-validation-helpers.d.ts.map +1 -1
  142. package/lib/views-schema.d.ts +51 -51
  143. package/lib/views-schema.d.ts.map +1 -1
  144. package/lib/views-validation.js.map +1 -1
  145. package/lib/views.d.ts +2 -2
  146. package/lib/views.d.ts.map +1 -1
  147. package/lib/workflow-analytics.d.ts +10 -3
  148. package/lib/workflow-analytics.d.ts.map +1 -1
  149. package/package.json +4 -4
  150. package/src/api-schemas/adapter.ts +1 -1
  151. package/src/api-schemas/agent-runs.ts +12 -2
  152. package/src/api-schemas/api-discriminators.test.ts +84 -0
  153. package/src/api-schemas/app-delete-summary.contract.test.ts +67 -0
  154. package/src/api-schemas/app-lifecycle.ts +59 -0
  155. package/src/api-schemas/app-runtime.ts +103 -0
  156. package/src/api-schemas/apps.ts +72 -3
  157. package/src/api-schemas/content.ts +13 -0
  158. package/src/api-schemas/events.ts +63 -3
  159. package/src/api-schemas/interaction.contract.test.ts +27 -0
  160. package/src/api-schemas/interaction.ts +7 -0
  161. package/src/api-schemas/mcp-api-key.contract.test.ts +86 -0
  162. package/src/api-schemas/registry.ts +200 -18
  163. package/src/api-schemas/store.contract.test.ts +21 -0
  164. package/src/api-schemas/sts.contract.test.ts +33 -1
  165. package/src/api-schemas/sts.ts +13 -0
  166. package/src/apps.test.ts +82 -0
  167. package/src/apps.ts +50 -16
  168. package/src/index.ts +1 -0
  169. package/src/platform-event-validation.test.ts +26 -0
  170. package/src/platform-event-validation.ts +15 -2
  171. package/src/platform-event.ts +43 -0
  172. package/src/store/intake-policy-schema.generated.ts +36 -0
  173. package/src/store/workflow.ts +9 -0
  174. package/src/sts-errors.ts +13 -0
  175. package/src/sts-token-types.ts +2 -0
  176. package/src/user.ts +1 -1
  177. package/src/versions.test.ts +13 -0
  178. package/src/versions.ts +12 -0
  179. package/src/workflow-analytics.ts +10 -3
@@ -0,0 +1,86 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { McpApiKeyStatus } from '../apps.js';
3
+ import { AppInstallationPayloadSchema } from './app-lifecycle.js';
4
+ import { McpApiKeyStatusSchema, SetMcpApiKeyRequestSchema } from './apps.js';
5
+ import { validateApiRequest } from './registry.js';
6
+
7
+ /** Exact type identity — `extends` in both directions is too weak (any/unknown slip through). */
8
+ type Equals<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
9
+ function assertType<T extends true>(_ok: T): void {}
10
+
11
+ /**
12
+ * Request bodies are enforced by AJV against the EMITTED JSON Schema, not by parsing with Zod. A
13
+ * Zod-only assertion would pass on transforms (`.trim()`) that leave no trace in the emission and
14
+ * therefore never run at the endpoint — so the blank-key cases below go through `validateApiRequest`,
15
+ * which is the path `validatedBody` actually takes.
16
+ */
17
+ describe('SetMcpApiKeyRequest — as enforced at the endpoint (AJV)', () => {
18
+ it('accepts a real key', () => {
19
+ expect(validateApiRequest('SetMcpApiKeyRequest', { api_key: 'sk-live-abcdef' }).valid).toBe(true);
20
+ });
21
+
22
+ it('rejects a whitespace-only key', () => {
23
+ expect(validateApiRequest('SetMcpApiKeyRequest', { api_key: ' ' }).valid).toBe(false);
24
+ });
25
+
26
+ it('rejects a tab/newline-only key', () => {
27
+ expect(validateApiRequest('SetMcpApiKeyRequest', { api_key: '\t\n ' }).valid).toBe(false);
28
+ });
29
+
30
+ it('rejects an empty key', () => {
31
+ expect(validateApiRequest('SetMcpApiKeyRequest', { api_key: '' }).valid).toBe(false);
32
+ });
33
+
34
+ it('rejects a missing key', () => {
35
+ expect(validateApiRequest('SetMcpApiKeyRequest', {}).valid).toBe(false);
36
+ });
37
+
38
+ it('rejects unknown properties', () => {
39
+ expect(validateApiRequest('SetMcpApiKeyRequest', { api_key: 'sk-live', collection_id: 'x' }).valid).toBe(false);
40
+ });
41
+
42
+ it('still normalizes surrounding whitespace for Zod consumers', () => {
43
+ expect(SetMcpApiKeyRequestSchema.parse({ api_key: ' sk-live-abcdef ' }).api_key).toBe('sk-live-abcdef');
44
+ });
45
+ });
46
+
47
+ describe('AppInstallationPayload.api_key_params — as enforced at the endpoint (AJV)', () => {
48
+ const APP_ID = '68b1779130afe5403a1589bc';
49
+
50
+ it('accepts a key per collection', () => {
51
+ const result = validateApiRequest('AppInstallationPayload', {
52
+ app_id: APP_ID,
53
+ api_key_params: { acme_mcp: { api_key: 'sk-install-abcdef' } },
54
+ });
55
+ expect(result.valid).toBe(true);
56
+ });
57
+
58
+ it('rejects a whitespace-only key on the install path too', () => {
59
+ const result = validateApiRequest('AppInstallationPayload', {
60
+ app_id: APP_ID,
61
+ api_key_params: { acme_mcp: { api_key: ' ' } },
62
+ });
63
+ expect(result.valid).toBe(false);
64
+ });
65
+
66
+ it('stays optional — installs with no api_key collections send nothing', () => {
67
+ expect(validateApiRequest('AppInstallationPayload', { app_id: APP_ID }).valid).toBe(true);
68
+ expect(AppInstallationPayloadSchema.parse({ app_id: APP_ID }).api_key_params).toBeUndefined();
69
+ });
70
+ });
71
+
72
+ describe('McpApiKeyStatus', () => {
73
+ it('publishes hint as required-but-nullable, not optional', () => {
74
+ // Every response sets `hint`, so the field must not be optional in the generated clients.
75
+ assertType<Equals<McpApiKeyStatus, { configured: boolean; hint: string | null }>>(true);
76
+ expect(McpApiKeyStatusSchema.safeParse({ configured: false }).success).toBe(false);
77
+ expect(McpApiKeyStatusSchema.safeParse({ configured: false, hint: null }).success).toBe(true);
78
+ expect(McpApiKeyStatusSchema.safeParse({ configured: true, hint: 'sk-l...3456' }).success).toBe(true);
79
+ });
80
+
81
+ it('never carries the key itself', () => {
82
+ expect(McpApiKeyStatusSchema.safeParse({ configured: true, hint: null, api_key: 'sk-live' }).success).toBe(
83
+ false,
84
+ );
85
+ });
86
+ });
@@ -80,7 +80,7 @@ import {
80
80
  SystemRoleDefinitionArraySchema,
81
81
  } from './access-control.js';
82
82
  import { AccountSchema, StripeBillingStatusResponseSchema, UpdateAccountPayloadSchema } from './account.js';
83
- import { findUnprunablePaths, type JsonObject, pruneToSchema, toOpenApiComponents } from './adapter.js';
83
+ import { findUnprunablePaths, isPlainObject, type JsonObject, pruneToSchema, toOpenApiComponents } from './adapter.js';
84
84
  import * as AgentRunSchemas from './agent-runs.js';
85
85
  import {
86
86
  AnalyticsAxisSchema,
@@ -109,9 +109,11 @@ import {
109
109
  AgentRunTypeSchema,
110
110
  AgentToolApprovalClassSchema,
111
111
  AgentToolDefinitionSchema,
112
+ AppApiKeyCollectionParamsSchema,
112
113
  AppBuildProgressSchema,
113
114
  AppBuildProgressStatusSchema,
114
115
  AppBuildTriggerSchema,
116
+ AppDeleteSummarySchema,
115
117
  AppDevelopmentTaskDetailsSchema,
116
118
  AppDevelopmentTaskListSchema,
117
119
  AppDevelopmentTaskSchema,
@@ -155,6 +157,7 @@ import {
155
157
  EventRefSchema,
156
158
  Extract_AppVersionGitRefType_branch_tag_commitSchema,
157
159
  InCodeTypeRefSchema,
160
+ McpApiKeyCredentialSchema,
158
161
  OAuthClientCredentialsMapSchema,
159
162
  OAuthClientCredentialsSchema,
160
163
  RunKindSchema,
@@ -178,6 +181,7 @@ import {
178
181
  AppSourceConfigSchema,
179
182
  AppUIConfigSchema,
180
183
  MCPToolAnnotationsSchema,
184
+ McpApiKeyStatusSchema,
181
185
  McpOAuthConnectResponseSchema,
182
186
  McpOAuthDisconnectResponseSchema,
183
187
  McpOAuthTokenRequestSchema,
@@ -186,6 +190,7 @@ import {
186
190
  OAuthAuthStatusArraySchema,
187
191
  OAuthAuthStatusSchema,
188
192
  OAuthMetadataResponseSchema,
193
+ SetMcpApiKeyRequestSchema,
189
194
  ToolCollectionObjectSchema,
190
195
  } from './apps.js';
191
196
  import {
@@ -1585,17 +1590,21 @@ const EVENT_DELIVERY_SCHEMAS = {
1585
1590
  EventOutboxStatus: EventSchemas.EventOutboxStatusSchema,
1586
1591
  EventDeliverySortField: EventSchemas.EventDeliverySortFieldSchema,
1587
1592
  WebhookEventDeliveryTarget: EventSchemas.WebhookEventDeliveryTargetSchema,
1593
+ AppEventDeliveryTarget: EventSchemas.AppEventDeliveryTargetSchema,
1588
1594
  WorkflowEventDeliveryTarget: EventSchemas.WorkflowEventDeliveryTargetSchema,
1589
1595
  EventDeliveryQueueFailureSummary: EventSchemas.EventDeliveryQueueFailureSummarySchema,
1590
1596
  EventOutboxQueueSummary: EventSchemas.EventOutboxQueueSummarySchema,
1591
1597
  EventDeliveryQueueSortField: EventSchemas.EventDeliveryQueueSortFieldSchema,
1592
1598
  AgentEventDeliveryTarget: EventSchemas.AgentEventDeliveryTargetSchema,
1593
1599
  WebhookEventDeliveryTargetInput: EventSchemas.WebhookEventDeliveryTargetInputSchema,
1600
+ AppEventDeliveryTargetInput: EventSchemas.AppEventDeliveryTargetInputSchema,
1594
1601
  WorkflowEventDeliveryTargetInput: EventSchemas.WorkflowEventDeliveryTargetInputSchema,
1595
1602
  SemanticEvaluator: EventSchemas.SemanticEvaluatorSchema,
1596
1603
  SemanticEvaluationRecord: EventSchemas.SemanticEvaluationRecordSchema,
1597
1604
  EventDeliveryQueueSubscriptionSummary: EventSchemas.EventDeliveryQueueSubscriptionSummarySchema,
1598
1605
  EventDeliveryQueueSummaryPayload: EventSchemas.EventDeliveryQueueSummaryPayloadSchema,
1606
+ CancelEventDeliveryIntentsPayload: EventSchemas.CancelEventDeliveryIntentsPayloadSchema,
1607
+ CancelEventDeliveryIntentsResponse: EventSchemas.CancelEventDeliveryIntentsResponseSchema,
1599
1608
  EventSemanticCondition: EventSchemas.EventSemanticConditionSchema,
1600
1609
  EventDeliveryIntentSummary: EventSchemas.EventDeliveryIntentSummarySchema,
1601
1610
  EventDeliveryQueueSummaryResponse: EventSchemas.EventDeliveryQueueSummaryResponseSchema,
@@ -1806,6 +1815,10 @@ const REMOTE_MCP_SCHEMAS = {
1806
1815
  McpOAuthConnectResponse: McpOAuthConnectResponseSchema,
1807
1816
  OAuthAuthorizeResponse: OAuthAuthorizeResponseSchema,
1808
1817
  OAuthAuthStatusArray: OAuthAuthStatusArraySchema,
1818
+ // The static-key alternative to that handshake: the key itself is write-only, so only the
1819
+ // setter payload and the configured/hint projection are published.
1820
+ SetMcpApiKeyRequest: SetMcpApiKeyRequestSchema,
1821
+ McpApiKeyStatus: McpApiKeyStatusSchema,
1809
1822
  } as const satisfies Record<string, z.ZodType>;
1810
1823
 
1811
1824
  const AUDIT_TRAIL_SCHEMAS = {
@@ -1919,6 +1932,7 @@ const APP_LIFECYCLE_SCHEMAS = {
1919
1932
  RunType: RunTypeSchema,
1920
1933
  AppBuildProgressStatus: AppBuildProgressStatusSchema,
1921
1934
  DeleteAppVersionResponse: DeleteAppVersionResponseSchema,
1935
+ AppDeleteSummary: AppDeleteSummarySchema,
1922
1936
  AppRepoBranch: AppRepoBranchSchema,
1923
1937
  AppRepoDocumentCommit: AppRepoDocumentCommitSchema,
1924
1938
  AppVersionGitSource: AppVersionGitSourceSchema,
@@ -1928,6 +1942,8 @@ const APP_LIFECYCLE_SCHEMAS = {
1928
1942
  AppDevelopmentTaskList: AppDevelopmentTaskListSchema,
1929
1943
  OAuthClientCredentialsMap: OAuthClientCredentialsMapSchema,
1930
1944
  AppOAuthCollectionParams: AppOAuthCollectionParamsSchema,
1945
+ McpApiKeyCredential: McpApiKeyCredentialSchema,
1946
+ AppApiKeyCollectionParams: AppApiKeyCollectionParamsSchema,
1931
1947
  AppInspectionIssue: AppInspectionIssueSchema,
1932
1948
  AppScaffoldProgress: AppScaffoldProgressSchema,
1933
1949
  AppRepoTree: AppRepoTreeSchema,
@@ -2077,6 +2093,7 @@ const STS_SCHEMAS = {
2077
2093
  ServiceAccountTokenRequest: StsSchemas.ServiceAccountTokenRequestSchema,
2078
2094
  IssueTokenRequest: StsSchemas.IssueTokenRequestSchema,
2079
2095
  IssueTokenResponse: StsSchemas.IssueTokenResponseSchema,
2096
+ IssueTokenForbiddenResponse: StsSchemas.IssueTokenForbiddenResponseSchema,
2080
2097
  IssueTokenUnavailableResponse: StsSchemas.IssueTokenUnavailableResponseSchema,
2081
2098
  } as const satisfies Record<string, z.ZodType>;
2082
2099
 
@@ -2224,6 +2241,7 @@ const STRICT_COMPONENTS: ReadonlySet<string> = new Set<string>([
2224
2241
  'AgentTokenRequest',
2225
2242
  'ServiceAccountTokenRequest',
2226
2243
  'IssueTokenResponse',
2244
+ 'IssueTokenForbiddenResponse',
2227
2245
  'IssueTokenUnavailableResponse',
2228
2246
  'ApiKeyListQuery',
2229
2247
  // These payloads previously referenced closed generated utility components. They now own their
@@ -2371,6 +2389,7 @@ const STRICT_COMPONENTS: ReadonlySet<string> = new Set<string>([
2371
2389
  'AppUIConfig',
2372
2390
  'AppUINavItem',
2373
2391
  'MCPOAuthConfig',
2392
+ 'MCPApiKeyConfig',
2374
2393
  'MCPToolCollectionObject',
2375
2394
  'VertesiaSDKToolCollectionObject',
2376
2395
  // File, task, content-type and command components. Every one is published closed
@@ -2662,6 +2681,8 @@ const STRICT_COMPONENTS: ReadonlySet<string> = new Set<string>([
2662
2681
  'ListWorkflowRunsResponse',
2663
2682
  'EventDeliveryQueueSubscriptionSummary',
2664
2683
  'EventDeliveryQueueSummaryPayload',
2684
+ 'CancelEventDeliveryIntentsPayload',
2685
+ 'CancelEventDeliveryIntentsResponse',
2665
2686
  'ContentObjectExportStatusResponse',
2666
2687
  'TimerTask',
2667
2688
  'SignalTask',
@@ -2929,6 +2950,8 @@ const STRICT_COMPONENTS: ReadonlySet<string> = new Set<string>([
2929
2950
  'McpOAuthDisconnectResponse',
2930
2951
  'McpOAuthConnectResponse',
2931
2952
  'OAuthAuthorizeResponse',
2953
+ 'SetMcpApiKeyRequest',
2954
+ 'McpApiKeyStatus',
2932
2955
  'AuditMeter',
2933
2956
  'AuditAggregationDimensionMap',
2934
2957
  'AuditAggregationRow',
@@ -2982,6 +3005,7 @@ const STRICT_COMPONENTS: ReadonlySet<string> = new Set<string>([
2982
3005
  'AppInstallationProviderBinding',
2983
3006
  'AppInstallationOAuthBinding',
2984
3007
  'OAuthClientCredentials',
3008
+ 'McpApiKeyCredential',
2985
3009
  'AppInspectionCapabilityReport',
2986
3010
  'AppRepoTreeEntry',
2987
3011
  'AppRepoRef',
@@ -2990,6 +3014,7 @@ const STRICT_COMPONENTS: ReadonlySet<string> = new Set<string>([
2990
3014
  'InCodeTypeRef',
2991
3015
  'StoredTypeRef',
2992
3016
  'DeleteAppVersionResponse',
3017
+ 'AppDeleteSummary',
2993
3018
  'AppRepoBranch',
2994
3019
  'AppRepoDocumentCommit',
2995
3020
  'AppVersionGitSource',
@@ -3331,14 +3356,88 @@ const validators = new Map<string, ValidateFunction>();
3331
3356
  */
3332
3357
  let ajvInstance: Ajv2020 | undefined;
3333
3358
 
3359
+ /**
3360
+ * The published components as AJV can consume them: the same schemas, with the two adjustments
3361
+ * AJV's discriminator support requires and the OpenAPI document must not have.
3362
+ *
3363
+ * `discriminator: true` below is what makes AJV validate a discriminated union through the branch
3364
+ * its tag names, instead of trying every branch and reporting all of their failures at once. The
3365
+ * difference is not cosmetic — a legacy MCP tool collection missing `id` used to report the real
3366
+ * error alongside two impossible ones from the branch it was never meant to match:
3367
+ *
3368
+ * / must have required property 'id' <- the real one
3369
+ * / must NOT have additional properties: oauth_app <- vertesia_sdk branch
3370
+ * /type must be equal to constant <- vertesia_sdk branch
3371
+ * / must match exactly one schema in oneOf
3372
+ *
3373
+ * AJV rejects `mapping` outright ("discriminator: mapping is not supported"), because it derives
3374
+ * the tag-to-branch map from each branch's own `const`/`enum` instead. The OpenAPI document still
3375
+ * needs the mapping — a generated Java or Go client reads it to pick a concrete subtype — so both
3376
+ * fixups here apply to AJV's copy only, and `ApiSchemaComponents` is published exactly as built.
3377
+ *
3378
+ * AJV's remaining requirements — the tag required in every branch, carrying `const` or a
3379
+ * single-valued `enum`, with the union node typed as an object — are what `synthesizeDiscriminator`
3380
+ * in the adapter already checks before it emits a discriminator at all, and what the hand-written
3381
+ * `.meta({ discriminator })` declarations restate. `api-discriminators.test.ts` compiles every
3382
+ * registered component so a union satisfying neither fails the build rather than the request.
3383
+ */
3384
+ function toAjvComponents(): Record<string, JsonObject> {
3385
+ const schemas = structuredClone(ApiSchemaComponents) as Record<string, JsonObject>;
3386
+ const resolve = (value: unknown): JsonObject | undefined => {
3387
+ if (!isPlainObject(value)) return undefined;
3388
+ const ref = value.$ref;
3389
+ if (typeof ref !== 'string' || !ref.startsWith(COMPONENT_REF_PREFIX)) return value;
3390
+ return schemas[ref.slice(COMPONENT_REF_PREFIX.length)];
3391
+ };
3392
+
3393
+ visitSchemaNodes(schemas, (node) => {
3394
+ const discriminator = node.discriminator;
3395
+ if (!isPlainObject(discriminator) || !Array.isArray(node.oneOf)) return;
3396
+ // AJV derives the tag-to-branch map from each branch's own literal, so `mapping` is both
3397
+ // redundant and rejected.
3398
+ delete discriminator.mapping;
3399
+
3400
+ const tag = discriminator.propertyName;
3401
+ if (typeof tag !== 'string') return;
3402
+ for (const member of node.oneOf) {
3403
+ const branch = resolve(member);
3404
+ const properties = branch && isPlainObject(branch.properties) ? branch.properties : undefined;
3405
+ const tagSchema = properties?.[tag];
3406
+ if (!isPlainObject(tagSchema) || tagSchema.const !== undefined || tagSchema.enum !== undefined) continue;
3407
+ // The tag is a `$ref` to a named literal component — `TaskType_ACTIVITY`,
3408
+ // `SupportedIntegrations_gladia` — because the literal carries its own `.meta({ id })`.
3409
+ // AJV reads `properties/<tag>` looking for `const` or `enum` and does not follow a
3410
+ // `$ref` to find one, so it refuses to compile the union at all. Restating the resolved
3411
+ // literal ALONGSIDE the `$ref` is validation-neutral: `$ref` has no special precedence
3412
+ // in 2020-12, both keywords apply, and they carry the same value by construction.
3413
+ const target = resolve(tagSchema);
3414
+ if (!target) continue;
3415
+ if (target.const !== undefined) tagSchema.const = target.const;
3416
+ else if (Array.isArray(target.enum)) tagSchema.enum = target.enum;
3417
+ }
3418
+ });
3419
+ return schemas;
3420
+ }
3421
+
3422
+ /** Every object node in the component graph, parents before children. */
3423
+ function visitSchemaNodes(value: unknown, visit: (node: JsonObject) => void): void {
3424
+ if (Array.isArray(value)) {
3425
+ for (const item of value) visitSchemaNodes(item, visit);
3426
+ return;
3427
+ }
3428
+ if (!isPlainObject(value)) return;
3429
+ visit(value);
3430
+ for (const item of Object.values(value)) visitSchemaNodes(item, visit);
3431
+ }
3432
+
3334
3433
  function getAjv(): Ajv2020 {
3335
3434
  if (ajvInstance) return ajvInstance;
3336
- const ajv = new Ajv2020({ strictSchema: false, allErrors: true });
3435
+ const ajv = new Ajv2020({ strictSchema: false, allErrors: true, discriminator: true });
3337
3436
  // Without this, AJV treats `format` as an annotation and ignores it, so a `date-time` property
3338
3437
  // would document a constraint nothing checks — the exact spec/enforcement gap this design is
3339
3438
  // meant to close.
3340
3439
  addFormats(ajv);
3341
- ajv.addSchema({ $id: 'vertesia://openapi', components: { schemas: ApiSchemaComponents } });
3440
+ ajv.addSchema({ $id: 'vertesia://openapi', components: { schemas: toAjvComponents() } });
3342
3441
  ajvInstance = ajv;
3343
3442
  return ajv;
3344
3443
  }
@@ -3351,20 +3450,101 @@ function getValidator(name: ApiComponentName): ValidateFunction {
3351
3450
  return validate;
3352
3451
  }
3353
3452
 
3354
- function formatErrors(validate: ValidateFunction): string[] {
3355
- return (validate.errors ?? []).map((error) => {
3453
+ function additionalProperty(error: { params?: unknown }): string | undefined {
3454
+ // AJV writes "must NOT have additional properties" and puts the offending key in `params`,
3455
+ // so the message alone says a body is wrong without saying which property made it wrong.
3456
+ // Every other keyword names its subject already — `required` quotes the missing property,
3457
+ // `enum` and `type` describe the value in place — and this is the one a caller most often
3458
+ // trips, so it is the one worth spelling out rather than reformatting all of them.
3459
+ return (error.params as { additionalProperty?: string } | undefined)?.additionalProperty;
3460
+ }
3461
+
3462
+ /**
3463
+ * One validation failure, with the undeclared property names kept AS NAMES.
3464
+ *
3465
+ * The structure exists because the rendered line cannot be taken apart again. Property names are
3466
+ * arbitrary JSON strings: `{"customer secret token": 1}` and `{"a, b": 1}` are both valid, so
3467
+ * neither a space nor a comma is a reliable separator once the names have been joined. Any consumer
3468
+ * that needs to shorten the list — the HTTP boundary does, the log does not — has to do it here,
3469
+ * on the array, where a name can be dropped whole.
3470
+ */
3471
+ export interface ApiValidationIssue {
3472
+ /**
3473
+ * AJV's `instancePath` for the failing value, with `'/'` substituted for the root.
3474
+ *
3475
+ * Not quite a JSON Pointer: the pointer for a document root is the empty string, which reads as
3476
+ * a missing path in a message. `'/'` is a display sentinel, so treat this as human-facing rather
3477
+ * than as something to resolve against the payload.
3478
+ */
3479
+ path: string;
3480
+ /** AJV's own message, e.g. `must NOT have additional properties`. */
3481
+ message: string;
3482
+ /** Undeclared property names at {@link path}, in the order AJV reported them. */
3483
+ undeclared?: string[];
3484
+ }
3485
+
3486
+ /**
3487
+ * The failures, with all undeclared properties at a given path gathered into ONE issue.
3488
+ *
3489
+ * AJV reports `additionalProperties` per property, so a value carrying a whole foreign object —
3490
+ * a Mongoose document that reached the response mapper unmapped, say — produces one error per own
3491
+ * key and buries every other failure in the payload. Gathering is lossless: every name is kept, in
3492
+ * the order AJV found it, on the issue for the path it belongs to.
3493
+ */
3494
+ function collectIssues(validate: ValidateFunction): ApiValidationIssue[] {
3495
+ const errors = validate.errors ?? [];
3496
+ const undeclaredByPath = new Map<string, Set<string>>();
3497
+ for (const error of errors) {
3498
+ const additional = additionalProperty(error);
3499
+ if (additional === undefined) continue;
3356
3500
  const where = error.instancePath || '/';
3357
- // AJV writes "must NOT have additional properties" and puts the offending key in `params`,
3358
- // so the message alone says a body is wrong without saying which property made it wrong.
3359
- // Every other keyword names its subject already — `required` quotes the missing property,
3360
- // `enum` and `type` describe the value in place — and this is the one a caller most often
3361
- // trips, so it is the one worth spelling out rather than reformatting all of them.
3362
- const additional = (error.params as { additionalProperty?: string } | undefined)?.additionalProperty;
3363
- return additional ? `${where} ${error.message}: ${additional}` : `${where} ${error.message}`;
3364
- });
3501
+ const names = undeclaredByPath.get(where);
3502
+ // A union whose branches all reject the same property reports it once per branch. The name
3503
+ // is the same fact each time, so it is listed once.
3504
+ if (names) names.add(additional);
3505
+ else undeclaredByPath.set(where, new Set([additional]));
3506
+ }
3507
+
3508
+ const gathered = new Set<string>();
3509
+ const issues: ApiValidationIssue[] = [];
3510
+ for (const error of errors) {
3511
+ const path = error.instancePath || '/';
3512
+ const message = error.message ?? 'is invalid';
3513
+ if (additionalProperty(error) === undefined) {
3514
+ issues.push({ path, message });
3515
+ continue;
3516
+ }
3517
+ if (gathered.has(path)) continue;
3518
+ gathered.add(path);
3519
+ issues.push({ path, message, undeclared: [...(undeclaredByPath.get(path) ?? [])] });
3520
+ }
3521
+ return issues;
3522
+ }
3523
+
3524
+ /**
3525
+ * The complete rendering of one issue — every name, no budget.
3526
+ *
3527
+ * This is what gets logged. A caller-facing message renders from {@link ApiValidationIssue} itself
3528
+ * with a length limit rather than shortening this string; see `reportableErrors` in the enforcer.
3529
+ */
3530
+ export function renderApiValidationIssue(issue: ApiValidationIssue): string {
3531
+ const head = `${issue.path} ${issue.message}`;
3532
+ return issue.undeclared?.length ? `${head}: ${issue.undeclared.join(', ')}` : head;
3533
+ }
3534
+
3535
+ /** The failed branch of every validator here, so the two views cannot be built inconsistently. */
3536
+ function invalidResult(validate: ValidateFunction): { valid: false; errors: string[]; issues: ApiValidationIssue[] } {
3537
+ const issues = collectIssues(validate);
3538
+ return { valid: false, errors: issues.map(renderApiValidationIssue), issues };
3365
3539
  }
3366
3540
 
3367
- export type ValidateApiPayloadResult<T> = { valid: true; data: T } | { valid: false; errors: string[] };
3541
+ export type ValidateApiPayloadResult<T> =
3542
+ | { valid: true; data: T }
3543
+ /**
3544
+ * `errors` is the rendered form, complete and ready to log; `issues` is the same failures with
3545
+ * the property names still separable. Shorten from `issues`, never from `errors`.
3546
+ */
3547
+ | { valid: false; errors: string[]; issues: ApiValidationIssue[] };
3368
3548
 
3369
3549
  /**
3370
3550
  * Validates an untyped request body against the component the endpoint publishes.
@@ -3386,7 +3566,7 @@ export function validateApiRequest<N extends ApiComponentName>(
3386
3566
  if (validate(value)) {
3387
3567
  return { valid: true, data: value as ApiComponentType<N> };
3388
3568
  }
3389
- return { valid: false, errors: formatErrors(validate) };
3569
+ return invalidResult(validate);
3390
3570
  }
3391
3571
 
3392
3572
  /**
@@ -3404,7 +3584,7 @@ export function validateApiResponse<N extends ApiComponentName>(
3404
3584
  if (validate(value)) {
3405
3585
  return { valid: true, data: value as ApiComponentType<N> };
3406
3586
  }
3407
- return { valid: false, errors: formatErrors(validate) };
3587
+ return invalidResult(validate);
3408
3588
  }
3409
3589
 
3410
3590
  /**
@@ -3432,7 +3612,9 @@ export function pruneApiResponse<N extends ApiComponentName>(name: N, value: Api
3432
3612
  return pruneToSchema(value, { $ref: apiComponentRef(name) }, ApiSchemaComponents) as ApiComponentType<N>;
3433
3613
  }
3434
3614
 
3435
- export type PruneAndValidateResult<T> = { valid: true; data: T } | { valid: false; data: unknown; errors: string[] };
3615
+ export type PruneAndValidateResult<T> =
3616
+ | { valid: true; data: T }
3617
+ | { valid: false; data: unknown; errors: string[]; issues: ApiValidationIssue[] };
3436
3618
 
3437
3619
  /**
3438
3620
  * Prunes an untyped payload and validates the result against the published component.
@@ -3453,7 +3635,7 @@ export function pruneAndValidateApiResponse<N extends ApiComponentName>(
3453
3635
  if (validate(pruned)) {
3454
3636
  return { valid: true, data: pruned as ApiComponentType<N> };
3455
3637
  }
3456
- return { valid: false, data: pruned, errors: formatErrors(validate) };
3638
+ return { ...invalidResult(validate), data: pruned };
3457
3639
  }
3458
3640
 
3459
3641
  /**
@@ -131,6 +131,27 @@ describe.skipIf(!publishedDocument)('gate — generated artifact = canonical com
131
131
  });
132
132
  });
133
133
 
134
+ describe('content object response classification', () => {
135
+ it('accepts the current sensitivity and compartments fields', () => {
136
+ const result = validateApiResponse('ContentObjectApiResponse', {
137
+ id: 'content-object-id',
138
+ name: 'Classified document',
139
+ updated_by: 'user-id',
140
+ created_by: 'user-id',
141
+ created_at: '2026-08-06T09:00:00.000Z',
142
+ updated_at: '2026-08-06T09:00:00.000Z',
143
+ location: '/',
144
+ status: 'completed',
145
+ properties: {},
146
+ revision: { root: 'content-object-id', head: true },
147
+ sensitivity: 3,
148
+ compartments: ['legal'],
149
+ });
150
+
151
+ expect(result).toMatchObject({ valid: true });
152
+ });
153
+ });
154
+
134
155
  /**
135
156
  * Bodies that probe where the two validators could disagree.
136
157
  *
@@ -4,6 +4,7 @@ import type {
4
4
  AgentTokenRequest,
5
5
  ApiKeyTokenRequest,
6
6
  EnvironmentTokenRequest,
7
+ IssueTokenForbiddenResponse,
7
8
  IssueTokenRequest,
8
9
  IssueTokenResponse,
9
10
  ProjectTokenRequest,
@@ -16,6 +17,7 @@ import type {
16
17
  AgentTokenRequestSchema,
17
18
  ApiKeyTokenRequestSchema,
18
19
  EnvironmentTokenRequestSchema,
20
+ IssueTokenForbiddenResponseSchema,
19
21
  IssueTokenRequestSchema,
20
22
  IssueTokenResponseSchema,
21
23
  ProjectTokenRequestSchema,
@@ -38,6 +40,7 @@ describe('STS canonical contracts', () => {
38
40
  assertType<Equals<ServiceAccountTokenRequest, z.infer<typeof ServiceAccountTokenRequestSchema>>>(true);
39
41
  assertType<Equals<IssueTokenRequest, z.infer<typeof IssueTokenRequestSchema>>>(true);
40
42
  assertType<Equals<IssueTokenResponse, z.infer<typeof IssueTokenResponseSchema>>>(true);
43
+ assertType<Equals<IssueTokenForbiddenResponse, z.infer<typeof IssueTokenForbiddenResponseSchema>>>(true);
41
44
  expect(true).toBe(true);
42
45
  });
43
46
 
@@ -60,9 +63,38 @@ describe('STS canonical contracts', () => {
60
63
  expect(validateApiRequest('IssueTokenRequest', { type: 'project', account_id: 'acc_1' }).valid).toBe(false);
61
64
  });
62
65
 
63
- it('checks both documented response shapes', () => {
66
+ it('checks every documented response shape and forbidden error-code literal', () => {
64
67
  expect(validateApiResponse('IssueTokenResponse', { token: 'signed', token_type: 'Bearer' }).valid).toBe(true);
65
68
  expect(validateApiResponse('IssueTokenResponse', { token: 'signed', token_type: 'bearer' }).valid).toBe(false);
69
+ for (const errorCode of [
70
+ 'requested_scope_unavailable',
71
+ 'no_accessible_account',
72
+ 'restricted_environment',
73
+ 'token_issuance_forbidden',
74
+ ]) {
75
+ expect(
76
+ validateApiResponse('IssueTokenForbiddenResponse', {
77
+ error: 'Forbidden',
78
+ message: 'The requested scope is unavailable',
79
+ errorCode,
80
+ }).valid,
81
+ ).toBe(true);
82
+ }
83
+ expect(
84
+ validateApiResponse('IssueTokenForbiddenResponse', {
85
+ error: 'Forbidden',
86
+ message: 'No',
87
+ errorCode: 'resource_not_found',
88
+ }).valid,
89
+ ).toBe(false);
90
+ expect(
91
+ validateApiResponse('IssueTokenForbiddenResponse', {
92
+ error: 'Forbidden',
93
+ message: 'No',
94
+ errorCode: 'requested_scope_unavailable',
95
+ project_id: 'secret',
96
+ }).valid,
97
+ ).toBe(false);
66
98
  expect(
67
99
  validateApiResponse('IssueTokenUnavailableResponse', {
68
100
  error: 'Service Unavailable',
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { ISSUE_TOKEN_FORBIDDEN_ERROR_CODES } from '../sts-errors.js';
2
3
  import { SystemRolesSchema } from './apikey.js';
3
4
 
4
5
  const ALGORITHM_DESCRIPTION = 'Signing algorithm - defaults to ES256. Use RS256 for Azure AD compatibility.';
@@ -105,6 +106,18 @@ export const IssueTokenResponseSchema = z
105
106
  })
106
107
  .meta({ id: 'IssueTokenResponse' });
107
108
 
109
+ export const IssueTokenForbiddenResponseSchema = z
110
+ .strictObject({
111
+ error: z.string(),
112
+ message: z.string(),
113
+ errorCode: z.enum(ISSUE_TOKEN_FORBIDDEN_ERROR_CODES),
114
+ })
115
+ .meta({
116
+ id: 'IssueTokenForbiddenResponse',
117
+ description:
118
+ 'A safe, machine-readable denial response. Requested resources are intentionally not identified or confirmed.',
119
+ });
120
+
108
121
  export const IssueTokenUnavailableResponseSchema = z
109
122
  .strictObject({ error: z.string(), message: z.string() })
110
123
  .meta({ id: 'IssueTokenUnavailableResponse' });