@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,84 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { type ApiComponentName, ApiSchemaComponents, validateApiResponse } from './registry.js';
3
+
4
+ /**
5
+ * The build-time half of `discriminator: true`.
6
+ *
7
+ * AJV applies its discriminator rules when it COMPILES a schema, not when it validates one, and it
8
+ * signals a union it cannot handle by throwing. Left unchecked that turns into a 500 on the first
9
+ * request to whichever endpoint publishes the offending component — a failure mode that depends on
10
+ * traffic and would reach a deployed environment before anyone saw it. Compiling every registered
11
+ * component here moves that to the build, and names the component when it happens.
12
+ *
13
+ * The rules a union has to satisfy, all of which `synthesizeDiscriminator` in the adapter already
14
+ * enforces before emitting a discriminator: `oneOf` members resolving to object schemas, a tag
15
+ * property present and REQUIRED in every branch, and a `const` or single-valued `enum` for the tag
16
+ * in each. A hand-written `.meta({ discriminator })` gets no such check when it is written, which
17
+ * is the case this test exists for.
18
+ *
19
+ * Generating a validator for each of the ~1000 components is real work: well under a second on a
20
+ * developer machine, ~8s on a loaded CI runner, past vitest's 5s default, hence the explicit budget
21
+ * below. It is generous on purpose — an unsupported discriminator makes AJV THROW during
22
+ * compilation, so a genuine regression is caught by the try/catch and never by the clock.
23
+ */
24
+ const COMPILE_ALL_TIMEOUT_MS = 60_000;
25
+
26
+ describe('published discriminators', () => {
27
+ it(
28
+ 'compiles every registered component under AJV discriminator support',
29
+ () => {
30
+ const failures: string[] = [];
31
+ for (const name of Object.keys(ApiSchemaComponents)) {
32
+ try {
33
+ // Compilation is the subject; the value only has to reach the validator. An
34
+ // invalid result is expected and irrelevant.
35
+ validateApiResponse(name as ApiComponentName, undefined);
36
+ } catch (err: unknown) {
37
+ failures.push(`${name}: ${err instanceof Error ? err.message : String(err)}`);
38
+ }
39
+ }
40
+ expect(failures, `components AJV could not compile:\n${failures.join('\n')}`).toEqual([]);
41
+ },
42
+ COMPILE_ALL_TIMEOUT_MS,
43
+ );
44
+
45
+ it('keeps discriminator mapping in the published components for generated clients', () => {
46
+ // AJV's copy drops `mapping`; the OpenAPI document must not. A Java or Go client reads the
47
+ // mapping to pick a concrete subtype, so losing it here would be a silent codegen
48
+ // regression that no schema assertion elsewhere would catch.
49
+ const union = ApiSchemaComponents.ToolCollectionObject as {
50
+ discriminator?: { propertyName?: string; mapping?: Record<string, string> };
51
+ };
52
+ expect(union.discriminator?.propertyName).toBe('type');
53
+ expect(union.discriminator?.mapping).toMatchObject({
54
+ mcp: '#/components/schemas/MCPToolCollectionObject',
55
+ });
56
+ });
57
+
58
+ it('reports only the tagged branch when a legacy MCP collection is missing its id', () => {
59
+ // The `hubspot-mcp-dev` shape: created before the write path required `id`, still served by
60
+ // the five-level resolution chain in fetch-tools.ts. Without discriminator support AJV also
61
+ // reported `oauth_app` as an undeclared property and `type` as not matching a constant —
62
+ // both from the vertesia_sdk branch this value never claimed to be.
63
+ const legacy = {
64
+ type: 'mcp',
65
+ name: 'HubSpot',
66
+ namespace: 'hubspot',
67
+ description: 'HubSpot MCP',
68
+ auth: 'oauth',
69
+ url: 'https://mcp.hubspot.com',
70
+ oauth_app: 'hubspot',
71
+ };
72
+ const result = validateApiResponse('ToolCollectionObject', legacy);
73
+ expect(result.valid).toBe(false);
74
+ if (result.valid) return;
75
+ expect(result.errors).toEqual(["/ must have required property 'id'"]);
76
+ });
77
+
78
+ it('rejects a value whose tag names no branch', () => {
79
+ const result = validateApiResponse('ToolCollectionObject', { type: 'nonesuch', url: 'https://example.com' });
80
+ expect(result.valid).toBe(false);
81
+ if (result.valid) return;
82
+ expect(result.errors.join('; ')).toContain('tag');
83
+ });
84
+ });
@@ -0,0 +1,67 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { AppDeleteSummary } from '../apps.js';
3
+ import { AppDeleteSummarySchema } from './app-lifecycle.js';
4
+ import { validateApiResponse } from './registry.js';
5
+
6
+ /** Exact type identity — `extends` in both directions is too weak (any/unknown slip through). */
7
+ type Equals<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
8
+ function assertType<T extends true>(_ok: T): void {}
9
+
10
+ /**
11
+ * `DeleteApp` published `CountResult` while returning this shape. Response validation therefore
12
+ * reported a missing `count` and every field below as unexpected — and where the check fails closed
13
+ * (local development) that surfaced as a 500 raised AFTER the app had already been deleted, which is
14
+ * the worst possible moment for it. These assertions pin the component to what the handler builds.
15
+ */
16
+ const REAL_SUMMARY: AppDeleteSummary = {
17
+ confirmed: true,
18
+ app_id: '68b1779130afe5403a1589bc',
19
+ app_name: 'acme-app',
20
+ versions: 2,
21
+ installations: 1,
22
+ storage_prefix: 'apps/acme-app',
23
+ deleted: true,
24
+ warnings: [],
25
+ };
26
+
27
+ describe('AppDeleteSummary — as the DeleteApp response is validated', () => {
28
+ it('accepts what the handler returns after a confirmed delete', () => {
29
+ expect(validateApiResponse('AppDeleteSummary', REAL_SUMMARY).valid).toBe(true);
30
+ });
31
+
32
+ it('accepts the dry-run preview, which is the same shape with deleted: false', () => {
33
+ const preview = { ...REAL_SUMMARY, confirmed: false, deleted: false };
34
+ expect(validateApiResponse('AppDeleteSummary', preview).valid).toBe(true);
35
+ });
36
+
37
+ it('accepts a git-backed app, whose summary carries the repo URL', () => {
38
+ const withRepo = { ...REAL_SUMMARY, git_repo_url: 'https://git.example.com/acme/acme-app.git' };
39
+ expect(validateApiResponse('AppDeleteSummary', withRepo).valid).toBe(true);
40
+ });
41
+
42
+ it('accepts the warnings a partially-degraded cascade collects', () => {
43
+ const warned = {
44
+ ...REAL_SUMMARY,
45
+ warnings: ['git repo delete failed: 502', 'version cleanup failed: timeout'],
46
+ };
47
+ expect(validateApiResponse('AppDeleteSummary', warned).valid).toBe(true);
48
+ });
49
+
50
+ it('is NOT interchangeable with CountResult, the component this endpoint used to publish', () => {
51
+ // The regression in one line: neither validates as the other, so the mismatch was total.
52
+ expect(validateApiResponse('CountResult', REAL_SUMMARY).valid).toBe(false);
53
+ expect(validateApiResponse('AppDeleteSummary', { count: 1 }).valid).toBe(false);
54
+ });
55
+
56
+ it('rejects an undeclared field, so the component cannot silently drift from the handler', () => {
57
+ expect(validateApiResponse('AppDeleteSummary', { ...REAL_SUMMARY, secrets_purged: 3 }).valid).toBe(false);
58
+ });
59
+
60
+ it('keeps the public type derived from the schema rather than hand-written beside it', () => {
61
+ assertType<Equals<AppDeleteSummary, ReturnType<typeof AppDeleteSummarySchema.parse>>>(true);
62
+ // Every field the handler always sets is required; only git_repo_url is optional.
63
+ expect(AppDeleteSummarySchema.safeParse({ ...REAL_SUMMARY, warnings: undefined }).success).toBe(false);
64
+ const { git_repo_url: _omitted, ...withoutRepo } = { ...REAL_SUMMARY, git_repo_url: 'https://x' };
65
+ expect(AppDeleteSummarySchema.safeParse(withoutRepo).success).toBe(true);
66
+ });
67
+ });
@@ -180,6 +180,8 @@ export const AppPackageScopeSchema = z
180
180
  'settings',
181
181
  'widgets',
182
182
  'activities',
183
+ 'hooks',
184
+ 'subscriptions',
183
185
  'all',
184
186
  ])
185
187
  .meta({ id: 'AppPackageScope' });
@@ -324,6 +326,35 @@ export const DeleteAppVersionResponseSchema = z
324
326
  })
325
327
  .meta({ id: 'DeleteAppVersionResponse' });
326
328
 
329
+ export const AppDeleteSummarySchema = z
330
+ .strictObject({
331
+ confirmed: z.boolean().meta({
332
+ description:
333
+ 'Whether `?confirm=true` was sent. Without it the endpoint reports what WOULD be removed and ' +
334
+ 'deletes nothing — `deleted` stays false.',
335
+ }),
336
+ app_id: z.string(),
337
+ app_name: z.string(),
338
+ versions: z.number().meta({ description: 'AppVersion records the cascade covers.' }),
339
+ installations: z.number().meta({ description: 'AppInstallation records the cascade covers.' }),
340
+ storage_prefix: z.string(),
341
+ git_repo_url: z.string().optional(),
342
+ deleted: z.boolean().meta({
343
+ description: 'Whether the app record itself was removed. False for a dry run.',
344
+ }),
345
+ warnings: z.array(z.string()).meta({
346
+ description:
347
+ 'Cascade steps that failed without aborting the deletion. Credential cleanup is NOT among them — ' +
348
+ 'a failed API key purge fails the request outright and keeps the app row.',
349
+ }),
350
+ })
351
+ .meta({
352
+ id: 'AppDeleteSummary',
353
+ description:
354
+ 'Result of `DELETE /apps/:id`. Doubles as the dry-run preview: without `?confirm=true` the same ' +
355
+ 'shape comes back with `deleted: false` describing what the cascade would remove.',
356
+ });
357
+
327
358
  export const AppRepoBranchSchema = z
328
359
  .strictObject({
329
360
  name: z.string(),
@@ -469,6 +500,28 @@ export const OAuthClientCredentialsMapSchema = z
469
500
 
470
501
  export const AppOAuthCollectionParamsSchema = OAuthClientCredentialsMapSchema.meta({ id: 'AppOAuthCollectionParams' });
471
502
 
503
+ export const McpApiKeyCredentialSchema = z
504
+ .strictObject({
505
+ // Matches SetMcpApiKeyRequest, including the `\S` pattern — see the note there on why the
506
+ // trim alone does not survive into the AJV-enforced JSON Schema.
507
+ api_key: z
508
+ .string()
509
+ .trim()
510
+ .min(1)
511
+ .regex(/\S/, 'API key must not be blank')
512
+ .meta({
513
+ description:
514
+ 'The key the installer holds for this collection. Surrounding whitespace is stripped. Stored ' +
515
+ 'encrypted in the installing project and never returned.',
516
+ }),
517
+ })
518
+ .meta({ id: 'McpApiKeyCredential' });
519
+
520
+ export const AppApiKeyCollectionParamsSchema = z
521
+ .object({})
522
+ .catchall(McpApiKeyCredentialSchema)
523
+ .meta({ id: 'AppApiKeyCollectionParams' });
524
+
472
525
  export const AppInspectionIssueSchema = z
473
526
  .strictObject({
474
527
  severity: z.enum(['error', 'warning']),
@@ -775,6 +828,12 @@ export const AppInstallationPayloadSchema = z
775
828
  description:
776
829
  'OAuth credentials for named providers, keyed by the provider key from oauth_providers. Collected from the user at install time for providers with required_at_install. Separate from oauth_params to avoid key collisions between provider keys and collection ids.',
777
830
  }).optional(),
831
+ api_key_params: AppApiKeyCollectionParamsSchema.meta({
832
+ description:
833
+ "API keys for auth: 'api_key' collections, keyed by collection.id. Collected from the user at " +
834
+ 'install time for collections with api_key_config.required_at_install. Each key is stored in the ' +
835
+ "installing project's encrypted secret store, replacing any key already held for that collection.",
836
+ }).optional(),
778
837
  })
779
838
  .meta({ id: 'AppInstallationPayload' });
780
839
 
@@ -3,12 +3,15 @@
3
3
  import type { JSONSchema } from '@llumiverse/common';
4
4
  import { z } from 'zod';
5
5
  import type { CompositeAppMenuNavItem } from '../apps.js';
6
+ import type { PlatformEvent } from '../platform-event.js';
6
7
  import type { InCodeProcessDefinition } from '../store/process.js';
8
+ import { SystemRolesSchema } from './apikey.js';
7
9
  import {
8
10
  AgentToolDefinitionSchema,
9
11
  AppInstallationOAuthBindingSchema,
10
12
  AppInstallationProviderBindingSchema,
11
13
  AppVersionRecordSchema,
14
+ EventRefSchema,
12
15
  } from './app-lifecycle.js';
13
16
  import {
14
17
  AppAccessControlSchema,
@@ -19,12 +22,14 @@ import {
19
22
  MCPOAuthConfigSchema,
20
23
  ToolCollectionObjectSchema,
21
24
  } from './apps.js';
25
+ import { AuditMeterSchema } from './audit-trail.js';
22
26
  import {
23
27
  DashboardDataSourceSchema,
24
28
  DashboardLayoutSchema,
25
29
  DashboardPanelSchema,
26
30
  DashboardQuerySchema,
27
31
  } from './dashboard.js';
32
+ import { EventPrioritySchema, EventSubscriptionFilterSchema } from './events.js';
28
33
  import { StringValueMapSchema } from './files.js';
29
34
  import { RemoteActivityDefinitionSchema } from './integrations.js';
30
35
  import { CatalogInteractionRefSchema } from './interaction.js';
@@ -804,6 +809,99 @@ export const CompositeAppConfigSchema = z
804
809
  'CompositeApp shell configuration. This is the main configuration interface for storing CompositeApp settings. Used as the MongoDB model for persisting CompositeApp configurations.',
805
810
  });
806
811
 
812
+ export const AppPackageEventHookSchema = z
813
+ .strictObject({
814
+ name: z.string().meta({ description: 'Registered event hook name.' }),
815
+ path: z.string().meta({ description: 'Authenticated endpoint that receives the event delivery envelope.' }),
816
+ description: z.string().meta({ description: 'Optional description of the event hook behavior.' }).optional(),
817
+ })
818
+ .meta({
819
+ id: 'AppPackageEventHook',
820
+ description: 'An authenticated event hook exposed by the app runtime.',
821
+ });
822
+
823
+ export const AppPackageHooksSchema = z
824
+ .strictObject({
825
+ install: z.string().meta({ description: 'Authenticated endpoint for the app install hook.' }).optional(),
826
+ uninstall: z.string().meta({ description: 'Authenticated endpoint for the app uninstall hook.' }).optional(),
827
+ events: z
828
+ .array(AppPackageEventHookSchema)
829
+ .meta({ description: 'Named event hooks exposed by the app runtime.' })
830
+ .optional(),
831
+ })
832
+ .meta({
833
+ id: 'AppPackageHooks',
834
+ description:
835
+ 'Lifecycle and event hooks exposed by the app runtime. Lifecycle entries are informational; Studio invokes their conventional sibling endpoints directly.',
836
+ });
837
+
838
+ const AppEventHookPlatformEventSchema = EventRefSchema.extend({
839
+ timestamp: z.string(),
840
+ source: z.string(),
841
+ audit_trail: z.boolean().optional(),
842
+ replay_of: z.string().optional(),
843
+ replay_root_event_id: z.string().optional(),
844
+ replayed_by: z.string().optional(),
845
+ request_id: z.string().nullable().optional(),
846
+ status: z.number().optional(),
847
+ success: z.boolean().optional(),
848
+ principal_id: z.string().nullable().optional(),
849
+ principal_type: z.string().nullable().optional(),
850
+ effective_principal_id: z.string().nullable().optional(),
851
+ roles: z.array(z.string()).optional(),
852
+ account_name: z.string().nullable().optional(),
853
+ project_name: z.string().nullable().optional(),
854
+ provider: z.string().nullable().optional(),
855
+ meters: z.array(AuditMeterSchema).optional(),
856
+ resource_data: z.record(z.string(), z.unknown()).optional(),
857
+ resource_version: z.string().optional(),
858
+ details: z.record(z.string(), z.unknown()).optional(),
859
+ }) satisfies z.ZodType<PlatformEvent>;
860
+
861
+ export const AppEventHookDeliverySchema = z
862
+ .strictObject({
863
+ id: z.string().meta({ description: 'Event-delivery intent id.' }),
864
+ subscription_id: z.string().meta({ description: 'Event subscription id.' }),
865
+ attempt: z.number().finite().meta({ description: 'Current delivery attempt number.' }),
866
+ })
867
+ .meta({
868
+ id: 'AppEventHookDelivery',
869
+ description: 'Delivery metadata accompanying an app event-hook invocation.',
870
+ });
871
+
872
+ export const AppEventHookPayloadSchema = z
873
+ .strictObject({
874
+ event: AppEventHookPlatformEventSchema,
875
+ delivery: AppEventHookDeliverySchema,
876
+ })
877
+ .meta({
878
+ id: 'AppEventHookPayload',
879
+ description: 'Canonical platform event envelope delivered to an authenticated app event hook.',
880
+ });
881
+
882
+ export const AppEventSubscriptionDefinitionSchema = z
883
+ .strictObject({
884
+ id: z
885
+ .string()
886
+ .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)
887
+ .meta({ description: 'Stable app-local subscription id in kebab case.' }),
888
+ name: z.string().meta({ description: 'Human-readable subscription name.' }),
889
+ description: z.string().meta({ description: 'Optional description of the subscription behavior.' }).optional(),
890
+ hook: z.string().meta({ description: 'Name of an event hook registered by the same app package.' }),
891
+ filter: EventSubscriptionFilterSchema,
892
+ run_as_role: SystemRolesSchema.meta({
893
+ description:
894
+ 'Identity used for event delivery. Use automation for the standard event-triggered execution identity.',
895
+ }),
896
+ enabled: z.boolean().meta({ description: 'Whether the installed subscription is enabled.' }).optional(),
897
+ priority: EventPrioritySchema.meta({ description: 'Delivery priority for matching events.' }).optional(),
898
+ })
899
+ .meta({
900
+ id: 'AppEventSubscriptionDefinition',
901
+ description:
902
+ 'An app-owned event subscription. Studio derives its project scope and delivery target from the app installation and referenced event hook.',
903
+ });
904
+
807
905
  export const AppPackageSchema = z
808
906
  .strictObject({
809
907
  ui: AppUIConfigSchema.meta({ description: 'The UI configuration of the app' }).optional(),
@@ -850,6 +948,11 @@ export const AppPackageSchema = z
850
948
  settings_schema: JSONSchemaRefSchema.meta({
851
949
  description: 'A JSON chema for the app installation settings.',
852
950
  }).optional(),
951
+ hooks: AppPackageHooksSchema.optional(),
952
+ subscriptions: z
953
+ .array(AppEventSubscriptionDefinitionSchema)
954
+ .meta({ description: 'Event subscriptions contributed by the app.' })
955
+ .optional(),
853
956
  })
854
957
  .meta({ id: 'AppPackage' });
855
958
 
@@ -112,9 +112,13 @@ export const AppUIConfigSchema = z
112
112
  })
113
113
  .meta({ id: 'AppUIConfig' });
114
114
 
115
- export const ToolCollectionAuthTypeSchema = z
116
- .enum(['oauth', 'other'])
117
- .meta({ id: 'ToolCollectionAuthType', description: 'Authentication type for tool collections' });
115
+ export const ToolCollectionAuthTypeSchema = z.enum(['oauth', 'api_key', 'other']).meta({
116
+ id: 'ToolCollectionAuthType',
117
+ description:
118
+ "Authentication type for tool collections.\n- 'oauth': the runtime resolves a per-user or per-project " +
119
+ "OAuth access token\n- 'api_key': a static key held in the project's secret store is sent as the RFC 6750 " +
120
+ 'bearer token (`Authorization: Bearer <key>`)',
121
+ });
118
122
 
119
123
  export const MCPOAuthConfigSchema = z
120
124
  .strictObject({
@@ -162,6 +166,33 @@ export const MCPOAuthConfigSchema = z
162
166
  'oauth_bindings on AppInstallation.',
163
167
  });
164
168
 
169
+ export const MCPApiKeyConfigSchema = z
170
+ .strictObject({
171
+ required_at_install: z
172
+ .boolean()
173
+ .optional()
174
+ .meta({
175
+ description:
176
+ 'When true, the installer must supply the key in the install dialog. Use this for a manifest ' +
177
+ 'published to projects that each hold their own key. Leave unset when the key was already ' +
178
+ 'stored by whoever registered the server.',
179
+ }),
180
+ instructions: z
181
+ .string()
182
+ .optional()
183
+ .meta({
184
+ description:
185
+ 'Shown in the install dialog above the key field — typically where to generate the key on the ' +
186
+ 'remote service.',
187
+ }),
188
+ })
189
+ .meta({
190
+ id: 'MCPApiKeyConfig',
191
+ description:
192
+ "Install-time provisioning blueprint for an `auth: 'api_key'` MCP collection. Declares whether the " +
193
+ 'installer is prompted for the key. Never holds the key itself — manifests are shareable documents.',
194
+ });
195
+
165
196
  /**
166
197
  * The two collection kinds share a base interface, so their published components both begin with
167
198
  * `url` and `auth`. Spreading one shape is what keeps that true of the emission as well.
@@ -211,6 +242,11 @@ export const MCPToolCollectionObjectSchema = z
211
242
  'required_at_install params. The created app is recorded in AppInstallation.oauth_bindings. ' +
212
243
  'Mutually exclusive with oauth_provider.',
213
244
  }),
245
+ api_key_config: MCPApiKeyConfigSchema.optional().meta({
246
+ description:
247
+ "Install-time provisioning blueprint for auth: 'api_key' collections. Only meaningful alongside " +
248
+ "auth: 'api_key'; ignored otherwise.",
249
+ }),
214
250
  oauth_provider: z
215
251
  .string()
216
252
  .optional()
@@ -371,6 +407,39 @@ export const McpOAuthTokenRequestSchema = z
371
407
  })
372
408
  .meta({ id: 'McpOAuthTokenRequest' });
373
409
 
410
+ export const SetMcpApiKeyRequestSchema = z
411
+ .strictObject({
412
+ // `.regex(/\S/)` rather than `.trim().min(1)` alone: request bodies are validated by AJV
413
+ // against the EMITTED JSON Schema, where a Zod transform like `.trim()` leaves no trace —
414
+ // `minLength: 1` on its own happily accepts " ". The pattern emits and is enforced, so a
415
+ // whitespace-only key is rejected at the boundary instead of becoming an encrypted empty
416
+ // key that still reports `configured: true`. The trim still normalizes for Zod consumers.
417
+ api_key: z
418
+ .string()
419
+ .trim()
420
+ .min(1)
421
+ .regex(/\S/, 'API key must not be blank')
422
+ .meta({
423
+ description:
424
+ 'The static key issued by the remote MCP server. Surrounding whitespace is stripped. Stored ' +
425
+ 'encrypted in the project secret store and sent as the RFC 6750 bearer token on every request ' +
426
+ 'to the collection URL. Never returned by the API.',
427
+ }),
428
+ })
429
+ .meta({ id: 'SetMcpApiKeyRequest' });
430
+
431
+ export const McpApiKeyStatusSchema = z
432
+ .strictObject({
433
+ configured: z.boolean().meta({ description: 'Whether a key is stored for this collection.' }),
434
+ // Always present — null when unset, never absent. `.nullable()` rather than `.nullish()`
435
+ // so the published contract says so and generated clients do not treat it as optional.
436
+ hint: z
437
+ .string()
438
+ .nullable()
439
+ .meta({ description: 'Last few characters of the stored key, for display only. Null when unset.' }),
440
+ })
441
+ .meta({ id: 'McpApiKeyStatus', description: 'Whether an API key is configured for an MCP tool collection' });
442
+
374
443
  export const OAuthAuthStatusSchema = z
375
444
  .strictObject({
376
445
  collection_id: z.string(),
@@ -787,6 +787,19 @@ export const ContentObjectApiResponseSchema = z
787
787
  parts_etag: z.string().optional(),
788
788
  transcript: z.looseObject({}).optional(),
789
789
  security: StringArrayMapSchema.optional(),
790
+ sensitivity: z
791
+ .number()
792
+ .meta({
793
+ description:
794
+ 'BLP sensitivity level — set directly or inherited from collections (max across collections).',
795
+ })
796
+ .optional(),
797
+ compartments: z
798
+ .array(z.string())
799
+ .meta({
800
+ description: 'Compartments — set directly or inherited from collections (union across collections).',
801
+ })
802
+ .optional(),
790
803
  inherited_properties: z.array(InheritedPropertyMetadataSchema).optional(),
791
804
  })
792
805
  .meta({ id: 'ContentObjectApiResponse' });
@@ -184,6 +184,17 @@ export const WebhookEventDeliveryTargetSchema = z
184
184
  })
185
185
  .meta({ id: 'WebhookEventDeliveryTarget' });
186
186
 
187
+ export const AppEventDeliveryTargetSchema = z
188
+ .strictObject({
189
+ type: z.literal('app'),
190
+ app_id: z.string(),
191
+ installation_id: z.string(),
192
+ hook: z.string(),
193
+ url: z.string(),
194
+ timeout_ms: z.number().optional(),
195
+ })
196
+ .meta({ id: 'AppEventDeliveryTarget' });
197
+
187
198
  export const WorkflowEventDeliveryTargetSchema = z
188
199
  .strictObject({
189
200
  type: z.literal('workflow'),
@@ -432,6 +443,17 @@ export const WebhookEventDeliveryTargetInputSchema = z
432
443
  })
433
444
  .meta({ id: 'WebhookEventDeliveryTargetInput' });
434
445
 
446
+ export const AppEventDeliveryTargetInputSchema = z
447
+ .strictObject({
448
+ type: z.literal('app'),
449
+ app_id: z.string(),
450
+ installation_id: z.string(),
451
+ hook: z.string(),
452
+ url: z.string(),
453
+ timeout_ms: z.number().optional(),
454
+ })
455
+ .meta({ id: 'AppEventDeliveryTargetInput' });
456
+
435
457
  export const WorkflowEventDeliveryTargetInputSchema = z
436
458
  .strictObject({
437
459
  type: z.literal('workflow'),
@@ -615,7 +637,7 @@ export const EventDeliveryQueueSubscriptionSummarySchema = z
615
637
  .strictObject({
616
638
  subscription_id: z.string(),
617
639
  subscription_name: z.string(),
618
- target_type: z.enum(['workflow', 'webhook', 'agent', 'process']),
640
+ target_type: z.enum(['workflow', 'webhook', 'app', 'agent', 'process']),
619
641
  total: z.number(),
620
642
  queued: z.number(),
621
643
  deferred: z.number(),
@@ -632,12 +654,45 @@ export const EventDeliveryQueueSubscriptionSummarySchema = z
632
654
  export const EventDeliveryQueueSummaryPayloadSchema = z
633
655
  .strictObject({
634
656
  subscription_id: z.string().optional(),
635
- target_type: z.array(z.enum(['workflow', 'webhook', 'agent', 'process'])).optional(),
657
+ target_type: z.array(z.enum(['workflow', 'webhook', 'app', 'agent', 'process'])).optional(),
636
658
  sort_by: EventDeliveryQueueSortFieldSchema.optional(),
637
659
  sort_order: z.enum(['asc', 'desc']).optional(),
638
660
  })
639
661
  .meta({ id: 'EventDeliveryQueueSummaryPayload' });
640
662
 
663
+ export const CancelEventDeliveryIntentsPayloadSchema = z
664
+ .strictObject({
665
+ subscription_id: z
666
+ .string()
667
+ .min(1)
668
+ .meta({ description: 'Restrict cancellation to one subscription.' })
669
+ .optional(),
670
+ target_type: z
671
+ .array(z.enum(['workflow', 'webhook', 'app', 'agent', 'process']))
672
+ .min(1)
673
+ .meta({ description: 'Restrict cancellation to one or more delivery target types.' })
674
+ .optional(),
675
+ })
676
+ .meta({ id: 'CancelEventDeliveryIntentsPayload' });
677
+
678
+ export const CancelEventDeliveryIntentsResponseSchema = z
679
+ .strictObject({
680
+ environment: z.string().meta({ description: 'Deployment environment whose queue was changed.' }),
681
+ cleared_through: z.string().meta({
682
+ description: 'Server-side watermark; intents created after this time were not changed.',
683
+ format: 'date-time',
684
+ }),
685
+ cancelled: z
686
+ .number()
687
+ .int()
688
+ .nonnegative()
689
+ .meta({ description: 'Pending or retrying intents moved to the cancelled terminal state.' }),
690
+ active_untouched: z.number().int().nonnegative().meta({
691
+ description: 'Matching evaluating, starting, or running intents left for normal reconciliation.',
692
+ }),
693
+ })
694
+ .meta({ id: 'CancelEventDeliveryIntentsResponse' });
695
+
641
696
  export const EventIngestChannelMutationResponseSchema = z
642
697
  .strictObject({
643
698
  channel: EventIngestChannelSchema,
@@ -724,7 +779,7 @@ export const EventDeliveryIntentSummarySchema = z
724
779
  event_id: z.string(),
725
780
  subscription_id: z.string(),
726
781
  subscription_name: z.string(),
727
- target_type: z.enum(['workflow', 'webhook', 'agent', 'process']),
782
+ target_type: z.enum(['workflow', 'webhook', 'app', 'agent', 'process']),
728
783
  workflow_class: nullableStringSchema.optional(),
729
784
  priority: EventPrioritySchema,
730
785
  status: EventDeliveryIntentStatusSchema,
@@ -812,6 +867,7 @@ export const EventDeliveryTargetSchema: z.ZodType = z
812
867
  .discriminatedUnion('type', [
813
868
  WorkflowEventDeliveryTargetSchema,
814
869
  WebhookEventDeliveryTargetSchema,
870
+ AppEventDeliveryTargetSchema,
815
871
  AgentEventDeliveryTargetSchema,
816
872
  z.lazy(() => ProcessEventDeliveryTargetSchema) as unknown as z.ZodObject,
817
873
  ])
@@ -821,6 +877,7 @@ export const EventDeliveryTargetInputSchema: z.ZodType = z
821
877
  .discriminatedUnion('type', [
822
878
  WorkflowEventDeliveryTargetInputSchema,
823
879
  WebhookEventDeliveryTargetInputSchema,
880
+ AppEventDeliveryTargetInputSchema,
824
881
  AgentEventDeliveryTargetSchema,
825
882
  z.lazy(() => ProcessEventDeliveryTargetSchema) as unknown as z.ZodObject,
826
883
  ])
@@ -841,6 +898,9 @@ export const EventSubscriptionSchema: z.ZodType = z
841
898
  protected: z.boolean(),
842
899
  enabled: z.boolean(),
843
900
  priority: EventPrioritySchema.optional(),
901
+ app_installation_id: z.string().optional(),
902
+ app_id: z.string().optional(),
903
+ app_subscription_id: z.string().optional(),
844
904
  created_by: z.string().optional(),
845
905
  updated_by: z.string().optional(),
846
906
  created_at: z.string().optional(),
@@ -0,0 +1,27 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { AsyncConversationExecutionPayload } from '../interaction.js';
3
+ import { AsyncConversationExecutionPayloadSchema } from './interaction.js';
4
+
5
+ describe('AsyncConversationExecutionPayload contract', () => {
6
+ it('retains an immutable app-version execution target', () => {
7
+ const payload: AsyncConversationExecutionPayload = {
8
+ type: 'conversation',
9
+ interaction: 'sys:AppTester',
10
+ app_version: '20260804T022611971Z',
11
+ };
12
+
13
+ expect(AsyncConversationExecutionPayloadSchema.parse(payload)).toMatchObject({
14
+ app_version: '20260804T022611971Z',
15
+ });
16
+ });
17
+
18
+ it('rejects a non-string app-version target', () => {
19
+ expect(() =>
20
+ AsyncConversationExecutionPayloadSchema.parse({
21
+ type: 'conversation',
22
+ interaction: 'sys:AppTester',
23
+ app_version: 42,
24
+ }),
25
+ ).toThrow();
26
+ });
27
+ });
@@ -1996,6 +1996,13 @@ export const AsyncConversationExecutionPayloadSchema = z
1996
1996
  description:
1997
1997
  'The interaction name and suffixed by an optional tag or version separated from the name using a @ character If no version/tag part is specified then the latest version is used. Example: ReviewContract, ReviewContract@draft, ReviewContract@1, ReviewContract@some-tag',
1998
1998
  }),
1999
+ app_version: z
2000
+ .string()
2001
+ .meta({
2002
+ description:
2003
+ 'Immutable app-version target inherited by this conversation execution. The workflow applies it to app-owned resource resolution; callers normally set the x-vertesia-app-version header instead of populating this field directly.',
2004
+ })
2005
+ .optional(),
1999
2006
  data: z
2000
2007
  .unknown()
2001
2008
  .meta({