@ekanos/sdk 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/api-report.md CHANGED
@@ -9,9 +9,9 @@ This is the complete surface of every entrypoint. The authoring guide
9
9
  for these symbols is README.md. Entrypoints marked WORKSPACE-ONLY are
10
10
  not reachable from a published install — see the note in each section.
11
11
 
12
- Total exported names: **235** across 8 entrypoints.
12
+ Total exported names: **255** across 8 entrypoints.
13
13
 
14
- ## `@ekanos/sdk` (35 exports)
14
+ ## `@ekanos/sdk` (37 exports)
15
15
 
16
16
  | Export | Kind |
17
17
  | --- | --- |
@@ -26,11 +26,13 @@ Total exported names: **235** across 8 entrypoints.
26
26
  | `IntegrationCapability` | interface |
27
27
  | `IntegrationComponentProps` | type alias |
28
28
  | `IntegrationConfig` | interface |
29
+ | `IntegrationConnection` | interface |
29
30
  | `IntegrationContext` | interface |
30
31
  | `IntegrationFetch` | type alias |
31
32
  | `IntegrationLogger` | interface |
32
33
  | `IntegrationMetadata` | interface |
33
34
  | `IntegrationPermission` | interface |
35
+ | `IntegrationRouting` | interface |
34
36
  | `IntegrationSecrets` | interface |
35
37
  | `IntegrationStorage` | interface |
36
38
  | `MarketplaceTileProps` | type alias |
@@ -183,46 +185,57 @@ Total exported names: **235** across 8 entrypoints.
183
185
  | `requireContext` | function |
184
186
  | `resolveStorageKeyDeclaration` | function |
185
187
 
186
- ## `@ekanos/sdk/testing` (22 exports)
188
+ ## `@ekanos/sdk/testing` (30 exports)
187
189
 
188
190
  | Export | Kind |
189
191
  | --- | --- |
190
192
  | `ActivateInvocationOutcome` | interface |
193
+ | `ChannelHookOutcome` | interface |
191
194
  | `DefinitionContextOptions` | type alias |
192
195
  | `InvokeActivateOptions` | type alias |
196
+ | `InvokeChannelHookOptions` | interface |
193
197
  | `InvokeScheduleOptions` | interface |
198
+ | `InvokeUnroutedOptions` | interface |
194
199
  | `InvokeWebhookOptions` | interface |
195
200
  | `MockContextOptions` | interface |
196
201
  | `MockFetchHandler` | interface |
197
202
  | `MockIntegrationContext` | interface |
198
203
  | `RecordedFetchCall` | interface |
199
204
  | `RecordedLog` | interface |
205
+ | `RouteKeysOutcome` | interface |
200
206
  | `ScheduleInvocationOutcome` | interface |
201
207
  | `SignatureVerification` | type alias |
208
+ | `UnroutedInvocationOutcome` | interface |
202
209
  | `VerifyDeclaredWebhookSignatureOptions` | interface |
203
210
  | `VerifyWebhookSignatureArgs` | interface |
204
211
  | `WebhookInvocationOutcome` | interface |
205
212
  | `createMockContext` | function |
206
213
  | `createMockFetchProvider` | function |
207
214
  | `invokeActivate` | function |
215
+ | `invokeHandoff` | function |
216
+ | `invokeRouteKeys` | function |
208
217
  | `invokeSchedule` | function |
218
+ | `invokeUnrouted` | function |
209
219
  | `invokeWebhook` | function |
210
220
  | `verifyDeclaredWebhookSignature` | function |
211
221
  | `verifyWebhookSignature` | function |
212
222
 
213
- ## `@ekanos/sdk/integration` (64 exports)
223
+ ## `@ekanos/sdk/integration` (74 exports)
214
224
 
215
225
  | Export | Kind |
216
226
  | --- | --- |
217
227
  | `ADMIN_HIERARCHY_LEVEL` | variable |
218
228
  | `AgentChannelDeliveryResult` | type alias |
229
+ | `AgentChannelHandoff` | interface |
219
230
  | `AgentChannelReply` | interface |
220
231
  | `AgentChannelRun` | interface |
221
232
  | `AgentChannelRunOutcome` | interface |
222
233
  | `AgentConversation` | interface |
223
234
  | `AgentEventDeclaration` | interface |
235
+ | `AgentRouteValue` | interface |
224
236
  | `AgentTriggerFilter` | type alias |
225
237
  | `AgentTriggerFilterField` | type alias |
238
+ | `AgentUnroutedEvent` | interface |
226
239
  | `AttentionHandler` | type alias |
227
240
  | `AttentionSignal` | interface |
228
241
  | `DEFAULT_OAUTH_CONNECTION_ID` | variable |
@@ -238,7 +251,10 @@ Total exported names: **235** across 8 entrypoints.
238
251
  | `IntegrationProposals` | type alias |
239
252
  | `MAX_AGENT_EVENT_DEDUPE_KEY_LENGTH` | variable |
240
253
  | `MAX_RAISED_AGENT_EVENTS` | variable |
254
+ | `MAX_ROUTE_VALUE_LENGTH` | variable |
255
+ | `MAX_ROUTING_KEYS` | variable |
241
256
  | `OAuthProviderDeclaration` | interface |
257
+ | `OAuthSetupDeclaration` | interface |
242
258
  | `OAuthTokens` | interface |
243
259
  | `OWNER_HIERARCHY_LEVEL` | variable |
244
260
  | `OnActivateHandler` | type alias |
@@ -257,8 +273,12 @@ Total exported names: **235** across 8 entrypoints.
257
273
  | `RaisedAgentEvent` | interface |
258
274
  | `ResolveOAuthProviderResult` | type alias |
259
275
  | `ResolveOAuthUrlResult` | type alias |
276
+ | `RouteKeyOption` | interface |
277
+ | `RoutingDeclaration` | interface |
278
+ | `RoutingKeyDeclaration` | interface |
260
279
  | `ScheduleInvocation` | interface |
261
280
  | `ScheduleResult` | interface |
281
+ | `SourceConnectionDeclaration` | interface |
262
282
  | `TeamAppDeclaration` | interface |
263
283
  | `ToolClassificationProposal` | type alias |
264
284
  | `WebhookEvent` | interface |
@@ -8,4 +8,4 @@
8
8
  *
9
9
  * The authoring guide for everything below is this package's README.
10
10
  */
11
- export type { IntegrationActor, IntegrationContext, IntegrationStorage, ScopedStore, IntegrationSecrets, IntegrationFetch, IntegrationLogger, StorageEntry, StorageWriteOptions, StorageSchemas, StorageSchemaMap, StorageScopeSchemas, StorageKeyDeclaration, StorageKeyDeclarationInput, StorageKeySchema, } from '@ekanos/integration-schema';
11
+ export type { IntegrationActor, IntegrationConnection, IntegrationContext, IntegrationRouting, IntegrationStorage, ScopedStore, IntegrationSecrets, IntegrationFetch, IntegrationLogger, StorageEntry, StorageWriteOptions, StorageSchemas, StorageSchemaMap, StorageScopeSchemas, StorageKeyDeclaration, StorageKeyDeclarationInput, StorageKeySchema, } from '@ekanos/integration-schema';
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/context/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The capability-context types now live in `@ekanos/integration-schema` (the\n * dependency-pure package both `@ekanos/sdk` and `@kit/integrations-core`\n * depend on) so the canonical integration-definition schema can reference\n * `IntegrationContext` without a package cycle. This file re-exports them so\n * every existing `@ekanos/sdk` import path (`./types`, `../context/types`,\n * the root barrel) keeps resolving unchanged.\n *\n * The authoring guide for everything below is this package's README.\n */\nexport type {\n IntegrationActor,\n IntegrationContext,\n IntegrationStorage,\n ScopedStore,\n IntegrationSecrets,\n IntegrationFetch,\n IntegrationLogger,\n StorageEntry,\n StorageWriteOptions,\n StorageSchemas,\n StorageSchemaMap,\n StorageScopeSchemas,\n StorageKeyDeclaration,\n StorageKeyDeclarationInput,\n StorageKeySchema,\n} from '@ekanos/integration-schema';\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/context/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The capability-context types now live in `@ekanos/integration-schema` (the\n * dependency-pure package both `@ekanos/sdk` and `@kit/integrations-core`\n * depend on) so the canonical integration-definition schema can reference\n * `IntegrationContext` without a package cycle. This file re-exports them so\n * every existing `@ekanos/sdk` import path (`./types`, `../context/types`,\n * the root barrel) keeps resolving unchanged.\n *\n * The authoring guide for everything below is this package's README.\n */\nexport type {\n IntegrationActor,\n IntegrationConnection,\n IntegrationContext,\n IntegrationRouting,\n IntegrationStorage,\n ScopedStore,\n IntegrationSecrets,\n IntegrationFetch,\n IntegrationLogger,\n StorageEntry,\n StorageWriteOptions,\n StorageSchemas,\n StorageSchemaMap,\n StorageScopeSchemas,\n StorageKeyDeclaration,\n StorageKeyDeclarationInput,\n StorageKeySchema,\n} from '@ekanos/integration-schema';\n"]}
package/dist/index.d.ts CHANGED
@@ -17,4 +17,4 @@
17
17
  * Keep the whole surface readable in one pass.
18
18
  */
19
19
  export type { IntegrationMetadata, IntegrationCapability, IntegrationPermission, IntegrationConfig, IntegrationComponentProps, ActivationFormProps, ActivationResult, MarketplaceTileProps, WidgetConfig, WorkspaceTargetDefinition, WidgetAskContext, WidgetSnapshot, WidgetSuggestedPrompt, AttentionSignal, WidgetVerdict, WidgetVerdictTone, HealthCheckResult, HealthStatus, AccountProductData, AccountProductDataType, } from './types/index.js';
20
- export type { IntegrationContext, IntegrationActor, IntegrationStorage, ScopedStore, IntegrationSecrets, IntegrationFetch, IntegrationLogger, StorageEntry, StorageWriteOptions, StorageSchemas, StorageSchemaMap, StorageScopeSchemas, StorageKeyDeclaration, StorageKeyDeclarationInput, StorageKeySchema, } from './context/types.js';
20
+ export type { IntegrationContext, IntegrationActor, IntegrationConnection, IntegrationRouting, IntegrationStorage, ScopedStore, IntegrationSecrets, IntegrationFetch, IntegrationLogger, StorageEntry, StorageWriteOptions, StorageSchemas, StorageSchemaMap, StorageScopeSchemas, StorageKeyDeclaration, StorageKeyDeclarationInput, StorageKeySchema, } from './context/types.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG","sourcesContent":["/**\n * @ekanos/sdk — the integration contract.\n *\n * Types only. This entrypoint is importable from any module, client or\n * server, and must stay that way: every export below is a `type` re-export,\n * so nothing here can pull runtime code into a client bundle. Runtime lives\n * in `@ekanos/sdk/components`, `@ekanos/sdk/hooks`, and `@ekanos/sdk/mcp`.\n *\n * The SDK OWNS these definitions (`./types` — nothing imported from `@kit/*`);\n * `@kit/integrations-core` keeps copies of some for now, and the parity guard\n * (a host-side parity test asserts it —\n * core-side since the inversion, because the dependency arrow is core → SDK)\n * fails core's typecheck on any drift.\n *\n * Surface discipline: additions require a second consumer and a line in\n * api-report.md, the checked-in snapshot of every entrypoint's exports.\n * Keep the whole surface readable in one pass.\n */\n\nexport type {\n // The integration definition\n IntegrationMetadata,\n IntegrationCapability,\n IntegrationPermission,\n IntegrationConfig,\n\n // Component contracts\n IntegrationComponentProps,\n ActivationFormProps,\n ActivationResult,\n MarketplaceTileProps,\n WidgetConfig,\n WorkspaceTargetDefinition,\n\n // AI ask-footer contract\n WidgetAskContext,\n WidgetSnapshot,\n WidgetSuggestedPrompt,\n\n // Widget verdicts (the design system's widget rules)\n AttentionSignal,\n WidgetVerdict,\n WidgetVerdictTone,\n\n // Health contract (required to implement checkHealth)\n HealthCheckResult,\n HealthStatus,\n\n // Account-scoped data seam\n AccountProductData,\n AccountProductDataType,\n} from './types';\n\nexport type {\n // The capability context — the one object partner code receives;\n // handed to MCP tool run() and server handlers; the mock lives in\n // @ekanos/sdk/testing, the shared runtime values in @ekanos/sdk/context.\n IntegrationContext,\n IntegrationActor,\n IntegrationStorage,\n ScopedStore,\n IntegrationSecrets,\n IntegrationFetch,\n IntegrationLogger,\n StorageEntry,\n StorageWriteOptions,\n\n // Storage schema declaration (ruling 1) — consumed by defineIntegration()\n // and createMockContext()\n StorageSchemas,\n StorageSchemaMap,\n StorageScopeSchemas,\n\n // Per-key declaration form carrying exposure flags. `clientReadable`\n // (default false) is what opts a key in to the generic browser-readable\n // storage route — see StorageKeyDeclaration for the full rationale.\n StorageKeyDeclaration,\n StorageKeyDeclarationInput,\n StorageKeySchema,\n} from './context/types';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG","sourcesContent":["/**\n * @ekanos/sdk — the integration contract.\n *\n * Types only. This entrypoint is importable from any module, client or\n * server, and must stay that way: every export below is a `type` re-export,\n * so nothing here can pull runtime code into a client bundle. Runtime lives\n * in `@ekanos/sdk/components`, `@ekanos/sdk/hooks`, and `@ekanos/sdk/mcp`.\n *\n * The SDK OWNS these definitions (`./types` — nothing imported from `@kit/*`);\n * `@kit/integrations-core` keeps copies of some for now, and the parity guard\n * (a host-side parity test asserts it —\n * core-side since the inversion, because the dependency arrow is core → SDK)\n * fails core's typecheck on any drift.\n *\n * Surface discipline: additions require a second consumer and a line in\n * api-report.md, the checked-in snapshot of every entrypoint's exports.\n * Keep the whole surface readable in one pass.\n */\n\nexport type {\n // The integration definition\n IntegrationMetadata,\n IntegrationCapability,\n IntegrationPermission,\n IntegrationConfig,\n\n // Component contracts\n IntegrationComponentProps,\n ActivationFormProps,\n ActivationResult,\n MarketplaceTileProps,\n WidgetConfig,\n WorkspaceTargetDefinition,\n\n // AI ask-footer contract\n WidgetAskContext,\n WidgetSnapshot,\n WidgetSuggestedPrompt,\n\n // Widget verdicts (the design system's widget rules)\n AttentionSignal,\n WidgetVerdict,\n WidgetVerdictTone,\n\n // Health contract (required to implement checkHealth)\n HealthCheckResult,\n HealthStatus,\n\n // Account-scoped data seam\n AccountProductData,\n AccountProductDataType,\n} from './types';\n\nexport type {\n // The capability context — the one object partner code receives;\n // handed to MCP tool run() and server handlers; the mock lives in\n // @ekanos/sdk/testing, the shared runtime values in @ekanos/sdk/context.\n IntegrationContext,\n IntegrationActor,\n // Whose connection a call runs on, and the routed slice of a Source\n // connection it may touch (plan 241).\n IntegrationConnection,\n IntegrationRouting,\n IntegrationStorage,\n ScopedStore,\n IntegrationSecrets,\n IntegrationFetch,\n IntegrationLogger,\n StorageEntry,\n StorageWriteOptions,\n\n // Storage schema declaration (ruling 1) — consumed by defineIntegration()\n // and createMockContext()\n StorageSchemas,\n StorageSchemaMap,\n StorageScopeSchemas,\n\n // Per-key declaration form carrying exposure flags. `clientReadable`\n // (default false) is what opts a key in to the generic browser-readable\n // storage route — see StorageKeyDeclaration for the full rationale.\n StorageKeyDeclaration,\n StorageKeyDeclarationInput,\n StorageKeySchema,\n} from './context/types';\n"]}
@@ -20,7 +20,7 @@ export { parseCronExpression } from './cron.js';
20
20
  export { PublicActionError } from '../errors/public-action-error.js';
21
21
  export { ADMIN_HIERARCHY_LEVEL, OWNER_HIERARCHY_LEVEL, actorIsAtLeast, } from './actor-role.js';
22
22
  export { DEFAULT_UNTRUSTED_TEXT_MAX_CHARS, untrustedText, untrustedTextNote, } from './untrusted-text.js';
23
- export { DEFAULT_OAUTH_CONNECTION_ID, DEFAULT_SIGNATURE_TOLERANCE_SECONDS, IntegrationDefinitionSchema, MAX_AGENT_EVENT_DEDUPE_KEY_LENGTH, MAX_RAISED_AGENT_EVENTS, PARTNER_STORAGE_LIMITS, RESERVED_OAUTH_AUTHORIZATION_PARAMS, validateIntegrationDefinitions, } from '@ekanos/integration-schema';
23
+ export { DEFAULT_OAUTH_CONNECTION_ID, DEFAULT_SIGNATURE_TOLERANCE_SECONDS, IntegrationDefinitionSchema, MAX_AGENT_EVENT_DEDUPE_KEY_LENGTH, MAX_RAISED_AGENT_EVENTS, MAX_ROUTE_VALUE_LENGTH, MAX_ROUTING_KEYS, PARTNER_STORAGE_LIMITS, RESERVED_OAUTH_AUTHORIZATION_PARAMS, validateIntegrationDefinitions, } from '@ekanos/integration-schema';
24
24
  export { normalizeOAuthUrlValue, oauthProviderPlaceholders, resolveOAuthProvider, resolveOAuthUrl, } from '@ekanos/integration-schema';
25
25
  export type { ResolveOAuthProviderResult, ResolveOAuthUrlResult, } from '@ekanos/integration-schema';
26
- export type { IntegrationDefinition, IntegrationComponentDeclarations, IntegrationProposals, IntegrationCapabilityDeclaration, IntegrationPermissionDeclaration, PartnerWidgetDeclaration, PartnerToolModule, PartnerToolParameters, PartnerActionDeclaration, ToolClassificationProposal, DefinitionCollisionInput, FirstPartyInventory, PartnerWebhookDeclaration, WebhookSignatureDeclaration, WebhookSignatureScheme, WebhookEvent, WebhookResult, RaisedAgentEvent, PartnerAgentChannelDeclaration, AgentChannelRun, AgentEventDeclaration, AgentConversation, AgentTriggerFilter, AgentTriggerFilterField, AgentChannelReply, AgentChannelDeliveryResult, AgentChannelRunOutcome, PartnerScheduleDeclaration, ScheduleInvocation, ScheduleResult, PartnerOAuthDeclaration, PartnerOAuthConnectionDeclaration, TeamAppDeclaration, OAuthProviderDeclaration, OAuthSetupDeclaration, OAuthTokens, OnActivateHandler, AttentionHandler, AttentionSignal, WidgetVerdict, WidgetVerdictTone, } from '@ekanos/integration-schema';
26
+ export type { IntegrationDefinition, IntegrationComponentDeclarations, IntegrationProposals, IntegrationCapabilityDeclaration, IntegrationPermissionDeclaration, PartnerWidgetDeclaration, PartnerToolModule, PartnerToolParameters, PartnerActionDeclaration, ToolClassificationProposal, DefinitionCollisionInput, FirstPartyInventory, PartnerWebhookDeclaration, WebhookSignatureDeclaration, WebhookSignatureScheme, WebhookEvent, WebhookResult, RaisedAgentEvent, PartnerAgentChannelDeclaration, AgentChannelRun, AgentEventDeclaration, AgentConversation, AgentTriggerFilter, AgentTriggerFilterField, AgentChannelReply, AgentChannelDeliveryResult, AgentChannelRunOutcome, AgentChannelHandoff, AgentRouteValue, AgentUnroutedEvent, RoutingDeclaration, RoutingKeyDeclaration, RouteKeyOption, SourceConnectionDeclaration, PartnerScheduleDeclaration, ScheduleInvocation, ScheduleResult, PartnerOAuthDeclaration, PartnerOAuthConnectionDeclaration, TeamAppDeclaration, OAuthProviderDeclaration, OAuthSetupDeclaration, OAuthTokens, OnActivateHandler, AttentionHandler, AttentionSignal, WidgetVerdict, WidgetVerdictTone, } from '@ekanos/integration-schema';
@@ -29,7 +29,7 @@ export { PublicActionError } from '../errors/public-action-error.js';
29
29
  export { ADMIN_HIERARCHY_LEVEL, OWNER_HIERARCHY_LEVEL, actorIsAtLeast, } from './actor-role.js';
30
30
  // Vendor/user-authored text in tool output: one name (`untrusted*`), one clip.
31
31
  export { DEFAULT_UNTRUSTED_TEXT_MAX_CHARS, untrustedText, untrustedTextNote, } from './untrusted-text.js';
32
- export { DEFAULT_OAUTH_CONNECTION_ID, DEFAULT_SIGNATURE_TOLERANCE_SECONDS, IntegrationDefinitionSchema, MAX_AGENT_EVENT_DEDUPE_KEY_LENGTH, MAX_RAISED_AGENT_EVENTS, PARTNER_STORAGE_LIMITS, RESERVED_OAUTH_AUTHORIZATION_PARAMS, validateIntegrationDefinitions, } from '@ekanos/integration-schema';
32
+ export { DEFAULT_OAUTH_CONNECTION_ID, DEFAULT_SIGNATURE_TOLERANCE_SECONDS, IntegrationDefinitionSchema, MAX_AGENT_EVENT_DEDUPE_KEY_LENGTH, MAX_RAISED_AGENT_EVENTS, MAX_ROUTE_VALUE_LENGTH, MAX_ROUTING_KEYS, PARTNER_STORAGE_LIMITS, RESERVED_OAUTH_AUTHORIZATION_PARAMS, validateIntegrationDefinitions, } from '@ekanos/integration-schema';
33
33
  // OAuth endpoint templates (`https://{subdomain}.zendesk.com/…`): the
34
34
  // resolver the host transport and the harness fill them with, exported so a
35
35
  // partner refreshing its own tokens builds the token URL by the same rules.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/integration/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,6EAA6E;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,mEAAmE;AACnE,0EAA0E;AAC1E,0EAA0E;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAE7C,oEAAoE;AACpE,+CAA+C;AAC/C,8CAA8C;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAElE,8EAA8E;AAC9E,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AAEtB,+EAA+E;AAC/E,OAAO,EACL,gCAAgC,EAChC,aAAa,EACb,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,2BAA2B,EAC3B,mCAAmC,EACnC,2BAA2B,EAC3B,iCAAiC,EACjC,uBAAuB,EACvB,sBAAsB,EACtB,mCAAmC,EACnC,8BAA8B,GAC/B,MAAM,4BAA4B,CAAC;AAEpC,sEAAsE;AACtE,4EAA4E;AAC5E,4EAA4E;AAC5E,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,oBAAoB,EACpB,eAAe,GAChB,MAAM,4BAA4B,CAAC","sourcesContent":["/**\n * @ekanos/sdk/integration — the partner authoring contract.\n *\n * `defineIntegration()` is THE way a partner declares an integration\n * Partners never extend a base class.\n * The host adapts the returned definition internally via\n * `registerPartnerIntegration` in `@kit/integrations-core`, which re-parses\n * against the SAME canonical schema (`@ekanos/integration-schema`) at the\n * trust boundary — one contract, no structural twin (F3/F9).\n *\n * Dependency-pure and isomorphic: zod plus the schema package, no `@kit/*`,\n * no `server-only`.\n *\n * Surface discipline: additions require an entry in\n * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.\n */\n\nexport { defineIntegration } from './define-integration';\n\n// Agent events (plan 222): types an event's functions by its payload schema.\nexport { defineAgentEvent } from './define-agent-event';\n\n// Event-surface validation values: the 5-field cron parser backing\n// `defineIntegration()`'s schedule validation, exported so validators and\n// the harness can check/describe an expression the same way the SDK does.\nexport { parseCronExpression } from './cron';\n\n// The one escape hatch from the actions host route's opaque-failure\n// discipline — see the class's own doc comment\n// (the widget-actions opaque-failure ruling).\nexport { PublicActionError } from '../errors/public-action-error';\n\n// Gate a write action on the caller's role (the host fills `ctx.actor.role`).\nexport {\n ADMIN_HIERARCHY_LEVEL,\n OWNER_HIERARCHY_LEVEL,\n actorIsAtLeast,\n} from './actor-role';\n\n// Vendor/user-authored text in tool output: one name (`untrusted*`), one clip.\nexport {\n DEFAULT_UNTRUSTED_TEXT_MAX_CHARS,\n untrustedText,\n untrustedTextNote,\n} from './untrusted-text';\n\nexport {\n DEFAULT_OAUTH_CONNECTION_ID,\n DEFAULT_SIGNATURE_TOLERANCE_SECONDS,\n IntegrationDefinitionSchema,\n MAX_AGENT_EVENT_DEDUPE_KEY_LENGTH,\n MAX_RAISED_AGENT_EVENTS,\n PARTNER_STORAGE_LIMITS,\n RESERVED_OAUTH_AUTHORIZATION_PARAMS,\n validateIntegrationDefinitions,\n} from '@ekanos/integration-schema';\n\n// OAuth endpoint templates (`https://{subdomain}.zendesk.com/…`): the\n// resolver the host transport and the harness fill them with, exported so a\n// partner refreshing its own tokens builds the token URL by the same rules.\nexport {\n normalizeOAuthUrlValue,\n oauthProviderPlaceholders,\n resolveOAuthProvider,\n resolveOAuthUrl,\n} from '@ekanos/integration-schema';\nexport type {\n ResolveOAuthProviderResult,\n ResolveOAuthUrlResult,\n} from '@ekanos/integration-schema';\n\nexport type {\n IntegrationDefinition,\n IntegrationComponentDeclarations,\n IntegrationProposals,\n IntegrationCapabilityDeclaration,\n IntegrationPermissionDeclaration,\n PartnerWidgetDeclaration,\n PartnerToolModule,\n PartnerToolParameters,\n // Widget-invoked server handlers (`actions`): the declaration type a\n // partner needs to export a handler shared between an action and a tool.\n PartnerActionDeclaration,\n ToolClassificationProposal,\n DefinitionCollisionInput,\n FirstPartyInventory,\n // Event surfaces (webhooks, schedules, OAuth): declared in the definition,\n // executed locally by the harness/testing helpers today; the host's real\n // transports (public ingress, scheduler, hosted OAuth callback) bind to\n // these exact declarations later with no partner code change.\n PartnerWebhookDeclaration,\n WebhookSignatureDeclaration,\n WebhookSignatureScheme,\n WebhookEvent,\n WebhookResult,\n // Agent events and channel (plan 222): events a webhook raises to start\n // agents, and the channel a run answers in.\n RaisedAgentEvent,\n PartnerAgentChannelDeclaration,\n AgentChannelRun,\n AgentEventDeclaration,\n AgentConversation,\n AgentTriggerFilter,\n AgentTriggerFilterField,\n AgentChannelReply,\n AgentChannelDeliveryResult,\n AgentChannelRunOutcome,\n PartnerScheduleDeclaration,\n ScheduleInvocation,\n ScheduleResult,\n PartnerOAuthDeclaration,\n PartnerOAuthConnectionDeclaration,\n TeamAppDeclaration,\n OAuthProviderDeclaration,\n OAuthSetupDeclaration,\n OAuthTokens,\n // Activation lifecycle hook: cache seeding / eager validation at connect\n // time. Runs after first persist and after every activationData update;\n // v1 errors are non-fatal (logged as a warning, activation stays active).\n OnActivateHandler,\n // Each widget's verdict, computed on the server for home's Needs you.\n AttentionHandler,\n AttentionSignal,\n WidgetVerdict,\n WidgetVerdictTone,\n} from '@ekanos/integration-schema';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/integration/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,6EAA6E;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,mEAAmE;AACnE,0EAA0E;AAC1E,0EAA0E;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAE7C,oEAAoE;AACpE,+CAA+C;AAC/C,8CAA8C;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAElE,8EAA8E;AAC9E,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AAEtB,+EAA+E;AAC/E,OAAO,EACL,gCAAgC,EAChC,aAAa,EACb,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,2BAA2B,EAC3B,mCAAmC,EACnC,2BAA2B,EAC3B,iCAAiC,EACjC,uBAAuB,EACvB,sBAAsB,EACtB,gBAAgB,EAChB,sBAAsB,EACtB,mCAAmC,EACnC,8BAA8B,GAC/B,MAAM,4BAA4B,CAAC;AAEpC,sEAAsE;AACtE,4EAA4E;AAC5E,4EAA4E;AAC5E,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,oBAAoB,EACpB,eAAe,GAChB,MAAM,4BAA4B,CAAC","sourcesContent":["/**\n * @ekanos/sdk/integration — the partner authoring contract.\n *\n * `defineIntegration()` is THE way a partner declares an integration\n * Partners never extend a base class.\n * The host adapts the returned definition internally via\n * `registerPartnerIntegration` in `@kit/integrations-core`, which re-parses\n * against the SAME canonical schema (`@ekanos/integration-schema`) at the\n * trust boundary — one contract, no structural twin (F3/F9).\n *\n * Dependency-pure and isomorphic: zod plus the schema package, no `@kit/*`,\n * no `server-only`.\n *\n * Surface discipline: additions require an entry in\n * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.\n */\n\nexport { defineIntegration } from './define-integration';\n\n// Agent events (plan 222): types an event's functions by its payload schema.\nexport { defineAgentEvent } from './define-agent-event';\n\n// Event-surface validation values: the 5-field cron parser backing\n// `defineIntegration()`'s schedule validation, exported so validators and\n// the harness can check/describe an expression the same way the SDK does.\nexport { parseCronExpression } from './cron';\n\n// The one escape hatch from the actions host route's opaque-failure\n// discipline — see the class's own doc comment\n// (the widget-actions opaque-failure ruling).\nexport { PublicActionError } from '../errors/public-action-error';\n\n// Gate a write action on the caller's role (the host fills `ctx.actor.role`).\nexport {\n ADMIN_HIERARCHY_LEVEL,\n OWNER_HIERARCHY_LEVEL,\n actorIsAtLeast,\n} from './actor-role';\n\n// Vendor/user-authored text in tool output: one name (`untrusted*`), one clip.\nexport {\n DEFAULT_UNTRUSTED_TEXT_MAX_CHARS,\n untrustedText,\n untrustedTextNote,\n} from './untrusted-text';\n\nexport {\n DEFAULT_OAUTH_CONNECTION_ID,\n DEFAULT_SIGNATURE_TOLERANCE_SECONDS,\n IntegrationDefinitionSchema,\n MAX_AGENT_EVENT_DEDUPE_KEY_LENGTH,\n MAX_RAISED_AGENT_EVENTS,\n MAX_ROUTE_VALUE_LENGTH,\n MAX_ROUTING_KEYS,\n PARTNER_STORAGE_LIMITS,\n RESERVED_OAUTH_AUTHORIZATION_PARAMS,\n validateIntegrationDefinitions,\n} from '@ekanos/integration-schema';\n\n// OAuth endpoint templates (`https://{subdomain}.zendesk.com/…`): the\n// resolver the host transport and the harness fill them with, exported so a\n// partner refreshing its own tokens builds the token URL by the same rules.\nexport {\n normalizeOAuthUrlValue,\n oauthProviderPlaceholders,\n resolveOAuthProvider,\n resolveOAuthUrl,\n} from '@ekanos/integration-schema';\nexport type {\n ResolveOAuthProviderResult,\n ResolveOAuthUrlResult,\n} from '@ekanos/integration-schema';\n\nexport type {\n IntegrationDefinition,\n IntegrationComponentDeclarations,\n IntegrationProposals,\n IntegrationCapabilityDeclaration,\n IntegrationPermissionDeclaration,\n PartnerWidgetDeclaration,\n PartnerToolModule,\n PartnerToolParameters,\n // Widget-invoked server handlers (`actions`): the declaration type a\n // partner needs to export a handler shared between an action and a tool.\n PartnerActionDeclaration,\n ToolClassificationProposal,\n DefinitionCollisionInput,\n FirstPartyInventory,\n // Event surfaces (webhooks, schedules, OAuth): declared in the definition,\n // executed locally by the harness/testing helpers today; the host's real\n // transports (public ingress, scheduler, hosted OAuth callback) bind to\n // these exact declarations later with no partner code change.\n PartnerWebhookDeclaration,\n WebhookSignatureDeclaration,\n WebhookSignatureScheme,\n WebhookEvent,\n WebhookResult,\n // Agent events and channel (plan 222): events a webhook raises to start\n // agents, and the channel a run answers in.\n RaisedAgentEvent,\n PartnerAgentChannelDeclaration,\n AgentChannelRun,\n AgentEventDeclaration,\n AgentConversation,\n AgentTriggerFilter,\n AgentTriggerFilterField,\n AgentChannelReply,\n AgentChannelDeliveryResult,\n AgentChannelRunOutcome,\n AgentChannelHandoff,\n // Routing and Source connections (plan 241): one connection for a whole\n // Source, its events routed to teams by a key such as the organization.\n AgentRouteValue,\n AgentUnroutedEvent,\n RoutingDeclaration,\n RoutingKeyDeclaration,\n RouteKeyOption,\n SourceConnectionDeclaration,\n PartnerScheduleDeclaration,\n ScheduleInvocation,\n ScheduleResult,\n PartnerOAuthDeclaration,\n PartnerOAuthConnectionDeclaration,\n TeamAppDeclaration,\n OAuthProviderDeclaration,\n OAuthSetupDeclaration,\n OAuthTokens,\n // Activation lifecycle hook: cache seeding / eager validation at connect\n // time. Runs after first persist and after every activationData update;\n // v1 errors are non-fatal (logged as a warning, activation stays active).\n OnActivateHandler,\n // Each widget's verdict, computed on the server for home's Needs you.\n AttentionHandler,\n AttentionSignal,\n WidgetVerdict,\n WidgetVerdictTone,\n} from '@ekanos/integration-schema';\n"]}
@@ -16,6 +16,8 @@ export type { MockFetchHandler } from './fetch-matching.js';
16
16
  export { createMockFetchProvider } from './mock-fetch-provider.js';
17
17
  export { invokeWebhook, invokeSchedule, invokeActivate } from './invoke.js';
18
18
  export type { DefinitionContextOptions, InvokeWebhookOptions, InvokeScheduleOptions, InvokeActivateOptions, WebhookInvocationOutcome, ScheduleInvocationOutcome, ActivateInvocationOutcome, } from './invoke.js';
19
+ export { invokeRouteKeys, invokeHandoff, invokeUnrouted } from './invoke.js';
20
+ export type { RouteKeysOutcome, InvokeChannelHookOptions, InvokeUnroutedOptions, ChannelHookOutcome, UnroutedInvocationOutcome, } from './invoke.js';
19
21
  export { verifyDeclaredWebhookSignature } from './verify-signature.js';
20
22
  export type { VerifyDeclaredWebhookSignatureOptions } from './verify-signature.js';
21
23
  export { verifyWebhookSignature } from '@ekanos/integration-schema';
@@ -22,6 +22,11 @@ export { createMockFetchProvider } from './mock-fetch-provider.js';
22
22
  // panel alike. `invokeActivate` does the same for the `onActivate` lifecycle
23
23
  // hook, used by partner unit tests and the harness's activation surface.
24
24
  export { invokeWebhook, invokeSchedule, invokeActivate } from './invoke.js';
25
+ // Routing and channel hooks (plans 241, 242): an event's route values, the
26
+ // `handoff` hook a rejected draft calls, and the `unrouted` hook a Source
27
+ // connection's unmapped event calls. Run a tool or webhook on a Source
28
+ // connection with `createMockContext({ connection, routing })`.
29
+ export { invokeRouteKeys, invokeHandoff, invokeUnrouted } from './invoke.js';
25
30
  // The host's webhook-signature verifier, run against one of the definition's
26
31
  // declarations: sign a payload the vendor's way in a test and prove the
27
32
  // declaration accepts it BEFORE a real delivery is rejected on the host.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAYnD,2EAA2E;AAC3E,gEAAgE;AAChE,+DAA+D;AAC/D,mDAAmD;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,4EAA4E;AAC5E,6EAA6E;AAC7E,4EAA4E;AAC5E,6EAA6E;AAC7E,yEAAyE;AACzE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAWzE,6EAA6E;AAC7E,wEAAwE;AACxE,yEAAyE;AACzE,OAAO,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAC;AAEpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC","sourcesContent":["/**\n * @ekanos/sdk/testing — the partner test harness surface.\n *\n * `createMockContext()` is the day-1 in-memory `IntegrationContext`\n * (proposal §4): partners code and test against `ctx` here before anything\n * lands in a sandbox. Self-contained — no Supabase, no Vault, no network —\n * and enforcement (storage schemas, secret tiers, egress) is the same\n * shared implementation from `@ekanos/sdk/context` that production uses.\n *\n * Surface discipline: additions require an entry in\n * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.\n */\n\nexport { createMockContext } from './mock-context';\nexport type {\n MockContextOptions,\n MockIntegrationContext,\n RecordedFetchCall,\n RecordedLog,\n} from './mock-context';\n\n// Shared by `createMockContext`'s `ctx.fetch` stub and `createMockFetchProvider`\n// below — one fixture shape, one matching implementation, for both surfaces.\nexport type { MockFetchHandler } from './fetch-matching';\n\n// The browser-side counterpart to `createMockContext`'s `fetchHandlers`: a\n// mounted `IntegrationFetchProvider` for widget tests that call\n// `useIntegrationFetch()`, resolving fixtures against the same\n// `MockFetchHandler` shape and matching semantics.\nexport { createMockFetchProvider } from './mock-fetch-provider';\n\n// Event-surface invocation: run declared webhook/schedule handlers locally,\n// with the same payload-validation and context-derivation semantics the real\n// transports will have. Used by partner unit tests and the harness Triggers\n// panel alike. `invokeActivate` does the same for the `onActivate` lifecycle\n// hook, used by partner unit tests and the harness's activation surface.\nexport { invokeWebhook, invokeSchedule, invokeActivate } from './invoke';\nexport type {\n DefinitionContextOptions,\n InvokeWebhookOptions,\n InvokeScheduleOptions,\n InvokeActivateOptions,\n WebhookInvocationOutcome,\n ScheduleInvocationOutcome,\n ActivateInvocationOutcome,\n} from './invoke';\n\n// The host's webhook-signature verifier, run against one of the definition's\n// declarations: sign a payload the vendor's way in a test and prove the\n// declaration accepts it BEFORE a real delivery is rejected on the host.\nexport { verifyDeclaredWebhookSignature } from './verify-signature';\nexport type { VerifyDeclaredWebhookSignatureOptions } from './verify-signature';\nexport { verifyWebhookSignature } from '@ekanos/integration-schema';\nexport type {\n SignatureVerification,\n VerifyWebhookSignatureArgs,\n} from '@ekanos/integration-schema';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAYnD,2EAA2E;AAC3E,gEAAgE;AAChE,+DAA+D;AAC/D,mDAAmD;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,4EAA4E;AAC5E,6EAA6E;AAC7E,4EAA4E;AAC5E,6EAA6E;AAC7E,yEAAyE;AACzE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAWzE,2EAA2E;AAC3E,0EAA0E;AAC1E,uEAAuE;AACvE,gEAAgE;AAChE,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAS1E,6EAA6E;AAC7E,wEAAwE;AACxE,yEAAyE;AACzE,OAAO,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAC;AAEpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC","sourcesContent":["/**\n * @ekanos/sdk/testing — the partner test harness surface.\n *\n * `createMockContext()` is the day-1 in-memory `IntegrationContext`\n * (proposal §4): partners code and test against `ctx` here before anything\n * lands in a sandbox. Self-contained — no Supabase, no Vault, no network —\n * and enforcement (storage schemas, secret tiers, egress) is the same\n * shared implementation from `@ekanos/sdk/context` that production uses.\n *\n * Surface discipline: additions require an entry in\n * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.\n */\n\nexport { createMockContext } from './mock-context';\nexport type {\n MockContextOptions,\n MockIntegrationContext,\n RecordedFetchCall,\n RecordedLog,\n} from './mock-context';\n\n// Shared by `createMockContext`'s `ctx.fetch` stub and `createMockFetchProvider`\n// below — one fixture shape, one matching implementation, for both surfaces.\nexport type { MockFetchHandler } from './fetch-matching';\n\n// The browser-side counterpart to `createMockContext`'s `fetchHandlers`: a\n// mounted `IntegrationFetchProvider` for widget tests that call\n// `useIntegrationFetch()`, resolving fixtures against the same\n// `MockFetchHandler` shape and matching semantics.\nexport { createMockFetchProvider } from './mock-fetch-provider';\n\n// Event-surface invocation: run declared webhook/schedule handlers locally,\n// with the same payload-validation and context-derivation semantics the real\n// transports will have. Used by partner unit tests and the harness Triggers\n// panel alike. `invokeActivate` does the same for the `onActivate` lifecycle\n// hook, used by partner unit tests and the harness's activation surface.\nexport { invokeWebhook, invokeSchedule, invokeActivate } from './invoke';\nexport type {\n DefinitionContextOptions,\n InvokeWebhookOptions,\n InvokeScheduleOptions,\n InvokeActivateOptions,\n WebhookInvocationOutcome,\n ScheduleInvocationOutcome,\n ActivateInvocationOutcome,\n} from './invoke';\n\n// Routing and channel hooks (plans 241, 242): an event's route values, the\n// `handoff` hook a rejected draft calls, and the `unrouted` hook a Source\n// connection's unmapped event calls. Run a tool or webhook on a Source\n// connection with `createMockContext({ connection, routing })`.\nexport { invokeRouteKeys, invokeHandoff, invokeUnrouted } from './invoke';\nexport type {\n RouteKeysOutcome,\n InvokeChannelHookOptions,\n InvokeUnroutedOptions,\n ChannelHookOutcome,\n UnroutedInvocationOutcome,\n} from './invoke';\n\n// The host's webhook-signature verifier, run against one of the definition's\n// declarations: sign a payload the vendor's way in a test and prove the\n// declaration accepts it BEFORE a real delivery is rejected on the host.\nexport { verifyDeclaredWebhookSignature } from './verify-signature';\nexport type { VerifyDeclaredWebhookSignatureOptions } from './verify-signature';\nexport { verifyWebhookSignature } from '@ekanos/integration-schema';\nexport type {\n SignatureVerification,\n VerifyWebhookSignatureArgs,\n} from '@ekanos/integration-schema';\n"]}
@@ -22,7 +22,7 @@
22
22
  * bare invoker; a test asserting the non-fatal behavior should catch its
23
23
  * own rejection.
24
24
  */
25
- import type { IntegrationDefinition, ScheduleInvocation, ScheduleResult, StorageSchemas, WebhookEvent, WebhookResult } from '@ekanos/integration-schema';
25
+ import { type AgentChannelHandoff, type AgentChannelRun, type AgentRouteValue, type AgentUnroutedEvent, type IntegrationDefinition, type JsonValue, type ScheduleInvocation, type ScheduleResult, type StorageSchemas, type WebhookEvent, type WebhookResult } from '@ekanos/integration-schema';
26
26
  import { type MockContextOptions, type MockIntegrationContext } from './mock-context.js';
27
27
  /**
28
28
  * Everything `createMockContext` takes except the fields the definition
@@ -98,4 +98,65 @@ export declare function invokeSchedule<Schemas extends StorageSchemas = StorageS
98
98
  * (see the module doc comment).
99
99
  */
100
100
  export declare function invokeActivate<Schemas extends StorageSchemas = StorageSchemas>(definition: IntegrationDefinition<Schemas>, options?: InvokeActivateOptions<Schemas>): Promise<ActivateInvocationOutcome<Schemas>>;
101
+ export interface RouteKeysOutcome {
102
+ /** The payload after the event's `payloadSchema`. */
103
+ payload: unknown;
104
+ /**
105
+ * The event's value for each routing key it names in `routeKeys`. `null`
106
+ * means the event carries none for that key: routed by it, it is unrouted.
107
+ */
108
+ routes: Record<string, AgentRouteValue | null>;
109
+ }
110
+ export interface InvokeChannelHookOptions<Schemas extends StorageSchemas = StorageSchemas> extends BaseInvokeOptions<Schemas> {
111
+ /** The run the hook acts for. Defaults to fresh ids. */
112
+ run?: AgentChannelRun;
113
+ }
114
+ export interface InvokeUnroutedOptions<Schemas extends StorageSchemas = StorageSchemas> extends BaseInvokeOptions<Schemas> {
115
+ /**
116
+ * The routing key the Source routes by. Defaults to the first of
117
+ * `routing.keys`.
118
+ */
119
+ routeKey?: string;
120
+ }
121
+ export interface ChannelHookOutcome<Schemas extends StorageSchemas = StorageSchemas> {
122
+ /** The context the hook ran against — assert on its recordings. */
123
+ ctx: MockIntegrationContext<Schemas>;
124
+ }
125
+ export interface UnroutedInvocationOutcome<Schemas extends StorageSchemas = StorageSchemas> extends ChannelHookOutcome<Schemas> {
126
+ /** Where the note was posted: the event's `conversation()` target. */
127
+ target: Record<string, JsonValue>;
128
+ /** What the hook received. */
129
+ event: AgentUnroutedEvent;
130
+ }
131
+ /**
132
+ * Computes an agent event's route values the way the host does before
133
+ * routing a Source connection's event to a team: the payload is validated
134
+ * against the event's `payloadSchema`, then each `routeKeys` function runs
135
+ * on the result. Throws if the event is undeclared, the payload is invalid,
136
+ * or a function returns something other than `{ value, label? }` or `null`.
137
+ */
138
+ export declare function invokeRouteKeys<Schemas extends StorageSchemas = StorageSchemas>(definition: IntegrationDefinition<Schemas>, eventId: string, payload: unknown): Promise<RouteKeysOutcome>;
139
+ /**
140
+ * Runs the channel's `handoff` hook (plan 242), the way the host does when a
141
+ * reviewer rejects a run's draft reply. Throws if the definition declares no
142
+ * channel or no `handoff` hook. A throwing hook PROPAGATES here — on the
143
+ * host, hooks are best-effort and a throw is logged and ignored.
144
+ *
145
+ * Pass `contextOptions: { connection: { owner: 'source' }, routing }` to run
146
+ * it the way a provided agent on a Source connection does.
147
+ */
148
+ export declare function invokeHandoff<Schemas extends StorageSchemas = StorageSchemas>(definition: IntegrationDefinition<Schemas>, target: Record<string, JsonValue>, handoff?: AgentChannelHandoff, options?: InvokeChannelHookOptions<Schemas>): Promise<ChannelHookOutcome<Schemas>>;
149
+ /**
150
+ * Runs the channel's `unrouted` hook (plan 241) for one event, the way the
151
+ * host does when a Source connection's event maps to no team: the payload is
152
+ * validated, the target is the event's `conversation()` target, and the
153
+ * route is the event's value for the Source's routing key. The context is
154
+ * the Source connection's — `connection: { owner: 'source' }` and no
155
+ * `routing` — unless you pass your own.
156
+ *
157
+ * Throws if the event, the routing key or the hook is undeclared, the
158
+ * payload is invalid, or the event has no conversation target (the host has
159
+ * nowhere to post, so it never calls the hook).
160
+ */
161
+ export declare function invokeUnrouted<Schemas extends StorageSchemas = StorageSchemas>(definition: IntegrationDefinition<Schemas>, eventId: string, payload: unknown, options?: InvokeUnroutedOptions<Schemas>): Promise<UnroutedInvocationOutcome<Schemas>>;
101
162
  export {};
@@ -1,3 +1,28 @@
1
+ /**
2
+ * Local execution of the declared event surfaces — `invokeWebhook()` and
3
+ * `invokeSchedule()` deliver to a definition's handlers exactly the way a
4
+ * transport would, against a `createMockContext()` built FROM the definition
5
+ * (its slug, storage schemas, and egress list), so a handler unit test
6
+ * exercises the same schemas and the same egress allowlist production will.
7
+ * `invokeActivate()` does the same for the `onActivate` lifecycle hook.
8
+ *
9
+ * Transport semantics reproduced here, so they cannot drift from the docs:
10
+ * - the payload is parsed against `payloadSchema` BEFORE the handler runs;
11
+ * an invalid payload throws and the handler never sees it;
12
+ * - signature verification is the TRANSPORT's job — the local transport
13
+ * logs it as skipped (one warn line on `ctx.logs`), it never verifies and
14
+ * the handler never does either;
15
+ * - a schedule invocation carries `{scheduledFor, invokedAt, trigger}` with
16
+ * `trigger: 'manual'` by default (a human pressed the button);
17
+ * - `invokeActivate()` simply awaits `onActivate(ctx)` and returns the
18
+ * context it ran against — a throw PROPAGATES here. The non-fatal
19
+ * (log-and-continue) handling described on `OnActivateHandler` is HOST
20
+ * POLICY, applied by the transport that calls the hook in production
21
+ * (and by the harness's activation surface locally); this helper is a
22
+ * bare invoker; a test asserting the non-fatal behavior should catch its
23
+ * own rejection.
24
+ */
25
+ import { MAX_ROUTE_VALUE_LENGTH, } from '@ekanos/integration-schema';
1
26
  import { createMockContext, } from './mock-context.js';
2
27
  function contextFor(definition, options) {
3
28
  var _a, _b;
@@ -14,6 +39,21 @@ function contextFor(definition, options) {
14
39
  function listIds(ids) {
15
40
  return ids.length > 0 ? ids.map((id) => `"${id}"`).join(', ') : '(none)';
16
41
  }
42
+ /** Standard Schema validation, the call the host makes; throws with every issue listed. */
43
+ async function validatePayload(schema, payload, rejection) {
44
+ const parsed = await schema['~standard'].validate(payload);
45
+ if (!parsed.issues)
46
+ return parsed.value;
47
+ const issues = parsed.issues
48
+ .map((issue) => {
49
+ var _a;
50
+ const segments = ((_a = issue.path) !== null && _a !== void 0 ? _a : []).map((segment) => typeof segment === 'object' ? String(segment.key) : String(segment));
51
+ const path = segments.length > 0 ? segments.join('.') : '(root)';
52
+ return ` - ${path}: ${issue.message}`;
53
+ })
54
+ .join('\n');
55
+ throw new Error(`${rejection}:\n${issues}`);
56
+ }
17
57
  /**
18
58
  * Delivers one payload to one declared webhook, the way a transport would.
19
59
  * Throws if the id is undeclared or the payload fails `payloadSchema`;
@@ -30,19 +70,8 @@ export async function invokeWebhook(definition, webhookId, payload, options = {}
30
70
  }
31
71
  // The host's webhook route validates through Standard Schema too, so a
32
72
  // partner test exercises the exact call production makes.
33
- const parsed = await webhook.payloadSchema['~standard'].validate(payload);
34
- if (parsed.issues) {
35
- const issues = parsed.issues
36
- .map((issue) => {
37
- var _a;
38
- const segments = ((_a = issue.path) !== null && _a !== void 0 ? _a : []).map((segment) => typeof segment === 'object' ? String(segment.key) : String(segment));
39
- const path = segments.length > 0 ? segments.join('.') : '(root)';
40
- return ` - ${path}: ${issue.message}`;
41
- })
42
- .join('\n');
43
- throw new Error(`Payload rejected by webhook "${webhookId}"'s payloadSchema — the ` +
44
- `transport refuses such a delivery before the handler runs:\n${issues}`);
45
- }
73
+ const parsed = await validatePayload(webhook.payloadSchema, payload, `Payload rejected by webhook "${webhookId}"'s payloadSchema — the ` +
74
+ `transport refuses such a delivery before the handler runs`);
46
75
  const ctx = contextFor(definition, options);
47
76
  if (webhook.signature !== 'none') {
48
77
  ctx.logger.warn({
@@ -57,7 +86,7 @@ export async function invokeWebhook(definition, webhookId, payload, options = {}
57
86
  id: (_c = options.eventId) !== null && _c !== void 0 ? _c : `evt_${crypto.randomUUID()}`,
58
87
  receivedAt: (_d = options.receivedAt) !== null && _d !== void 0 ? _d : new Date().toISOString(),
59
88
  headers: (_e = options.headers) !== null && _e !== void 0 ? _e : {},
60
- payload: parsed.value,
89
+ payload: parsed,
61
90
  };
62
91
  const result = await webhook.handler(ctx, event);
63
92
  return { result, event, ctx };
@@ -103,4 +132,125 @@ export async function invokeActivate(definition, options = {}) {
103
132
  await definition.onActivate(ctx);
104
133
  return { ctx };
105
134
  }
135
+ function findAgentEvent(definition, eventId) {
136
+ var _a, _b;
137
+ const event = ((_a = definition.agentEvents) !== null && _a !== void 0 ? _a : []).find((candidate) => candidate.id === eventId);
138
+ if (!event) {
139
+ throw new Error(`Integration "${definition.slug}" declares no agent event "${eventId}". ` +
140
+ `Declared agent event ids: ${listIds(((_b = definition.agentEvents) !== null && _b !== void 0 ? _b : []).map((e) => e.id))}.`);
141
+ }
142
+ return event;
143
+ }
144
+ function checkedRouteValue(eventId, key, value) {
145
+ if (value === null)
146
+ return null;
147
+ const route = value;
148
+ if (typeof route === 'object' &&
149
+ typeof route.value === 'string' &&
150
+ route.value.length > 0 &&
151
+ route.value.length <= MAX_ROUTE_VALUE_LENGTH &&
152
+ (route.label === undefined || typeof route.label === 'string')) {
153
+ return route.label === undefined
154
+ ? { value: route.value }
155
+ : { value: route.value, label: route.label };
156
+ }
157
+ throw new Error(`${eventId} routeKeys.${key} returned ${JSON.stringify(value)}. Return ` +
158
+ `{ value, label? } with a non-empty value of at most ` +
159
+ `${MAX_ROUTE_VALUE_LENGTH} characters, or null — the host treats ` +
160
+ `anything else as no value and leaves the event unrouted.`);
161
+ }
162
+ /**
163
+ * Computes an agent event's route values the way the host does before
164
+ * routing a Source connection's event to a team: the payload is validated
165
+ * against the event's `payloadSchema`, then each `routeKeys` function runs
166
+ * on the result. Throws if the event is undeclared, the payload is invalid,
167
+ * or a function returns something other than `{ value, label? }` or `null`.
168
+ */
169
+ export async function invokeRouteKeys(definition, eventId, payload) {
170
+ var _a;
171
+ const event = findAgentEvent(definition, eventId);
172
+ const parsed = await validatePayload(event.payloadSchema, payload, `Payload rejected by agent event "${eventId}"'s payloadSchema — the ` +
173
+ `host drops such an event before routing it`);
174
+ const routes = {};
175
+ for (const [key, routeKey] of Object.entries((_a = event.routeKeys) !== null && _a !== void 0 ? _a : {})) {
176
+ routes[key] = checkedRouteValue(eventId, key, routeKey(parsed));
177
+ }
178
+ return { payload: parsed, routes };
179
+ }
180
+ function channelRun(run) {
181
+ return (run !== null && run !== void 0 ? run : {
182
+ runId: `run_${crypto.randomUUID()}`,
183
+ agentRunId: crypto.randomUUID(),
184
+ });
185
+ }
186
+ /**
187
+ * Runs the channel's `handoff` hook (plan 242), the way the host does when a
188
+ * reviewer rejects a run's draft reply. Throws if the definition declares no
189
+ * channel or no `handoff` hook. A throwing hook PROPAGATES here — on the
190
+ * host, hooks are best-effort and a throw is logged and ignored.
191
+ *
192
+ * Pass `contextOptions: { connection: { owner: 'source' }, routing }` to run
193
+ * it the way a provided agent on a Source connection does.
194
+ */
195
+ export async function invokeHandoff(definition, target, handoff = {}, options = {}) {
196
+ var _a, _b;
197
+ const hook = (_b = (_a = definition.agentChannel) === null || _a === void 0 ? void 0 : _a.hooks) === null || _b === void 0 ? void 0 : _b.handoff;
198
+ if (!hook) {
199
+ throw new Error(`Integration "${definition.slug}" declares no agentChannel.hooks.handoff ` +
200
+ `— add one before invoking it.`);
201
+ }
202
+ const ctx = contextFor(definition, options);
203
+ await hook(ctx, target, handoff, channelRun(options.run));
204
+ return { ctx };
205
+ }
206
+ /**
207
+ * Runs the channel's `unrouted` hook (plan 241) for one event, the way the
208
+ * host does when a Source connection's event maps to no team: the payload is
209
+ * validated, the target is the event's `conversation()` target, and the
210
+ * route is the event's value for the Source's routing key. The context is
211
+ * the Source connection's — `connection: { owner: 'source' }` and no
212
+ * `routing` — unless you pass your own.
213
+ *
214
+ * Throws if the event, the routing key or the hook is undeclared, the
215
+ * payload is invalid, or the event has no conversation target (the host has
216
+ * nowhere to post, so it never calls the hook).
217
+ */
218
+ export async function invokeUnrouted(definition, eventId, payload, options = {}) {
219
+ var _a, _b, _c, _d, _e, _f;
220
+ var _g, _h, _j, _k, _l;
221
+ const hook = (_b = (_a = definition.agentChannel) === null || _a === void 0 ? void 0 : _a.hooks) === null || _b === void 0 ? void 0 : _b.unrouted;
222
+ if (!hook) {
223
+ throw new Error(`Integration "${definition.slug}" declares no agentChannel.hooks.unrouted ` +
224
+ `— add one before invoking it.`);
225
+ }
226
+ const keys = ((_g = (_c = definition.routing) === null || _c === void 0 ? void 0 : _c.keys) !== null && _g !== void 0 ? _g : []).map((key) => key.id);
227
+ const key = (_h = options.routeKey) !== null && _h !== void 0 ? _h : keys[0];
228
+ if (key === undefined || !keys.includes(key)) {
229
+ throw new Error(`Integration "${definition.slug}" declares no routing key ` +
230
+ `"${key !== null && key !== void 0 ? key : ''}". Declared routing keys: ${listIds(keys)}.`);
231
+ }
232
+ const { payload: parsed, routes } = await invokeRouteKeys(definition, eventId, payload);
233
+ const conversation = (_j = (_e = (_d = findAgentEvent(definition, eventId)).conversation) === null || _e === void 0 ? void 0 : _e.call(_d, parsed)) !== null && _j !== void 0 ? _j : null;
234
+ if (!conversation) {
235
+ throw new Error(`${eventId} has no conversation target for this payload, so the host ` +
236
+ `has nowhere to post an unrouted note and never calls the hook.`);
237
+ }
238
+ if ((_f = options.contextOptions) === null || _f === void 0 ? void 0 : _f.routing) {
239
+ throw new TypeError('An unrouted event has no team, so its context has no routing — drop ' +
240
+ 'contextOptions.routing.');
241
+ }
242
+ const ctx = contextFor(definition, options.context
243
+ ? options
244
+ : {
245
+ contextOptions: Object.assign({ connection: { owner: 'source' } }, options.contextOptions),
246
+ });
247
+ const route = (_k = routes[key]) !== null && _k !== void 0 ? _k : null;
248
+ const event = {
249
+ eventId,
250
+ payload: parsed,
251
+ route: Object.assign({ key, value: (_l = route === null || route === void 0 ? void 0 : route.value) !== null && _l !== void 0 ? _l : null }, ((route === null || route === void 0 ? void 0 : route.label) === undefined ? {} : { label: route.label })),
252
+ };
253
+ await hook(ctx, conversation.target, event);
254
+ return { ctx, target: conversation.target, event };
255
+ }
106
256
  //# sourceMappingURL=invoke.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"invoke.js","sourceRoot":"","sources":["../../src/testing/invoke.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Local execution of the declared event surfaces — `invokeWebhook()` and\n * `invokeSchedule()` deliver to a definition's handlers exactly the way a\n * transport would, against a `createMockContext()` built FROM the definition\n * (its slug, storage schemas, and egress list), so a handler unit test\n * exercises the same schemas and the same egress allowlist production will.\n * `invokeActivate()` does the same for the `onActivate` lifecycle hook.\n *\n * Transport semantics reproduced here, so they cannot drift from the docs:\n * - the payload is parsed against `payloadSchema` BEFORE the handler runs;\n * an invalid payload throws and the handler never sees it;\n * - signature verification is the TRANSPORT's job — the local transport\n * logs it as skipped (one warn line on `ctx.logs`), it never verifies and\n * the handler never does either;\n * - a schedule invocation carries `{scheduledFor, invokedAt, trigger}` with\n * `trigger: 'manual'` by default (a human pressed the button);\n * - `invokeActivate()` simply awaits `onActivate(ctx)` and returns the\n * context it ran against — a throw PROPAGATES here. The non-fatal\n * (log-and-continue) handling described on `OnActivateHandler` is HOST\n * POLICY, applied by the transport that calls the hook in production\n * (and by the harness's activation surface locally); this helper is a\n * bare invoker; a test asserting the non-fatal behavior should catch its\n * own rejection.\n */\nimport type {\n IntegrationDefinition,\n ScheduleInvocation,\n ScheduleResult,\n StorageSchemas,\n WebhookEvent,\n WebhookResult,\n} from '@ekanos/integration-schema';\n\nimport {\n type MockContextOptions,\n type MockIntegrationContext,\n createMockContext,\n} from './mock-context';\n\n/**\n * Everything `createMockContext` takes except the fields the definition\n * itself is the authority on — the helpers derive `integration`,\n * `storageSchemas`, and `egress` from the definition so a test cannot\n * accidentally run a handler against schemas or an allowlist the definition\n * does not declare.\n */\nexport type DefinitionContextOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> = Omit<\n MockContextOptions<Schemas>,\n 'integration' | 'storageSchemas' | 'egress'\n>;\n\ninterface BaseInvokeOptions<Schemas extends StorageSchemas> {\n /**\n * Reuse an existing mock context so state (storage, secrets, logs)\n * accumulates across invocations — the harness does this. When set,\n * `context` wins and `contextOptions` must be omitted.\n */\n context?: MockIntegrationContext<Schemas>;\n /** Seeds and stubs for the context the helper creates. */\n contextOptions?: DefinitionContextOptions<Schemas>;\n}\n\nexport interface InvokeWebhookOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> extends BaseInvokeOptions<Schemas> {\n /** Delivery headers the event carries. Defaults to `{}`. */\n headers?: Record<string, string>;\n /** Transport-assigned event id. Defaults to a random UUID. */\n eventId?: string;\n /** ISO receipt time. Defaults to now. */\n receivedAt?: string;\n}\n\nexport interface InvokeScheduleOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> extends BaseInvokeOptions<Schemas> {\n /** Defaults to `'manual'` — a human pressed \"Run now\". */\n trigger?: ScheduleInvocation['trigger'];\n /** The tick this invocation stands for. Defaults to now. */\n scheduledFor?: string;\n}\n\nexport type InvokeActivateOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> = BaseInvokeOptions<Schemas>;\n\nexport interface ActivateInvocationOutcome<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n /** The context the handler ran against — assert on its recordings. */\n ctx: MockIntegrationContext<Schemas>;\n}\n\nexport interface WebhookInvocationOutcome<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n result: WebhookResult;\n /** The event the handler received (payload already schema-parsed). */\n event: WebhookEvent;\n /** The context the handler ran against — assert on its recordings. */\n ctx: MockIntegrationContext<Schemas>;\n}\n\nexport interface ScheduleInvocationOutcome<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n result: ScheduleResult;\n invocation: ScheduleInvocation;\n ctx: MockIntegrationContext<Schemas>;\n}\n\nfunction contextFor<Schemas extends StorageSchemas>(\n definition: IntegrationDefinition<Schemas>,\n options: BaseInvokeOptions<Schemas>,\n): MockIntegrationContext<Schemas> {\n if (options.context) {\n if (options.contextOptions) {\n throw new TypeError(\n 'Pass either `context` (reuse an existing mock context) or ' +\n '`contextOptions` (seed a fresh one), not both — seeds cannot be ' +\n 'applied to a context that already exists.',\n );\n }\n return options.context;\n }\n\n return createMockContext<Schemas>({\n ...(options.contextOptions ?? {}),\n integration: { slug: definition.slug },\n ...(definition.storage ? { storageSchemas: definition.storage } : {}),\n egress: definition.egress ?? [],\n });\n}\n\nfunction listIds(ids: readonly string[]): string {\n return ids.length > 0 ? ids.map((id) => `\"${id}\"`).join(', ') : '(none)';\n}\n\n/**\n * Delivers one payload to one declared webhook, the way a transport would.\n * Throws if the id is undeclared or the payload fails `payloadSchema`;\n * returns the handler's result plus the event and the context it ran\n * against. Never verifies signatures — that is the transport's job, and the\n * local transport records the skip as a `warn` log line.\n */\nexport async function invokeWebhook<\n Schemas extends StorageSchemas = StorageSchemas,\n>(\n definition: IntegrationDefinition<Schemas>,\n webhookId: string,\n payload: unknown,\n options: InvokeWebhookOptions<Schemas> = {},\n): Promise<WebhookInvocationOutcome<Schemas>> {\n const webhook = (definition.webhooks ?? []).find((w) => w.id === webhookId);\n\n if (!webhook) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no webhook \"${webhookId}\". ` +\n `Declared webhook ids: ${listIds((definition.webhooks ?? []).map((w) => w.id))}.`,\n );\n }\n\n // The host's webhook route validates through Standard Schema too, so a\n // partner test exercises the exact call production makes.\n const parsed = await webhook.payloadSchema['~standard'].validate(payload);\n\n if (parsed.issues) {\n const issues = parsed.issues\n .map((issue) => {\n const segments = (issue.path ?? []).map((segment) =>\n typeof segment === 'object' ? String(segment.key) : String(segment),\n );\n const path = segments.length > 0 ? segments.join('.') : '(root)';\n return ` - ${path}: ${issue.message}`;\n })\n .join('\\n');\n throw new Error(\n `Payload rejected by webhook \"${webhookId}\"'s payloadSchema — the ` +\n `transport refuses such a delivery before the handler runs:\\n${issues}`,\n );\n }\n\n const ctx = contextFor(definition, options);\n\n if (webhook.signature !== 'none') {\n ctx.logger.warn(\n {\n webhookId,\n header: webhook.signature.header,\n secretName: webhook.signature.secretName,\n },\n 'Signature verification SKIPPED (local transport). The host ingress ' +\n 'verifies this header against the named secret before the handler ' +\n 'runs — handlers never verify signatures themselves.',\n );\n }\n\n const event: WebhookEvent = {\n id: options.eventId ?? `evt_${crypto.randomUUID()}`,\n receivedAt: options.receivedAt ?? new Date().toISOString(),\n headers: options.headers ?? {},\n payload: parsed.value,\n };\n\n const result = await webhook.handler(ctx, event);\n\n return { result, event, ctx };\n}\n\n/**\n * Fires one declared schedule, the way the scheduler would. Throws if the id\n * is undeclared; returns the handler's result plus the invocation and the\n * context it ran against.\n */\nexport async function invokeSchedule<\n Schemas extends StorageSchemas = StorageSchemas,\n>(\n definition: IntegrationDefinition<Schemas>,\n scheduleId: string,\n options: InvokeScheduleOptions<Schemas> = {},\n): Promise<ScheduleInvocationOutcome<Schemas>> {\n const schedule = (definition.schedules ?? []).find(\n (s) => s.id === scheduleId,\n );\n\n if (!schedule) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no schedule \"${scheduleId}\". ` +\n `Declared schedule ids: ${listIds((definition.schedules ?? []).map((s) => s.id))}.`,\n );\n }\n\n const ctx = contextFor(definition, options);\n const now = new Date().toISOString();\n\n const invocation: ScheduleInvocation = {\n scheduledFor: options.scheduledFor ?? now,\n invokedAt: now,\n trigger: options.trigger ?? 'manual',\n };\n\n const result = await schedule.handler(ctx, invocation);\n\n return { result, invocation, ctx };\n}\n\n/**\n * Runs the definition's declared `onActivate` hook, the way the host would\n * after an activation persists or activationData updates. Throws if the\n * definition declares no `onActivate` — there is nothing to invoke, and a\n * silent no-op would let a test believe it exercised a hook that does not\n * exist. A throwing handler PROPAGATES from this helper: the non-fatal\n * (log-a-warning, keep the activation) handling is host policy applied by\n * whatever calls this in production/the harness, not by this bare invoker\n * (see the module doc comment).\n */\nexport async function invokeActivate<\n Schemas extends StorageSchemas = StorageSchemas,\n>(\n definition: IntegrationDefinition<Schemas>,\n options: InvokeActivateOptions<Schemas> = {},\n): Promise<ActivateInvocationOutcome<Schemas>> {\n if (!definition.onActivate) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no onActivate hook — add ` +\n `one to defineIntegration() before invoking it.`,\n );\n }\n\n const ctx = contextFor(definition, options);\n\n await definition.onActivate(ctx);\n\n return { ctx };\n}\n"]}
1
+ {"version":3,"file":"invoke.js","sourceRoot":"","sources":["../../src/testing/invoke.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Local execution of the declared event surfaces — `invokeWebhook()` and\n * `invokeSchedule()` deliver to a definition's handlers exactly the way a\n * transport would, against a `createMockContext()` built FROM the definition\n * (its slug, storage schemas, and egress list), so a handler unit test\n * exercises the same schemas and the same egress allowlist production will.\n * `invokeActivate()` does the same for the `onActivate` lifecycle hook.\n *\n * Transport semantics reproduced here, so they cannot drift from the docs:\n * - the payload is parsed against `payloadSchema` BEFORE the handler runs;\n * an invalid payload throws and the handler never sees it;\n * - signature verification is the TRANSPORT's job — the local transport\n * logs it as skipped (one warn line on `ctx.logs`), it never verifies and\n * the handler never does either;\n * - a schedule invocation carries `{scheduledFor, invokedAt, trigger}` with\n * `trigger: 'manual'` by default (a human pressed the button);\n * - `invokeActivate()` simply awaits `onActivate(ctx)` and returns the\n * context it ran against — a throw PROPAGATES here. The non-fatal\n * (log-and-continue) handling described on `OnActivateHandler` is HOST\n * POLICY, applied by the transport that calls the hook in production\n * (and by the harness's activation surface locally); this helper is a\n * bare invoker; a test asserting the non-fatal behavior should catch its\n * own rejection.\n */\nimport {\n type AgentChannelHandoff,\n type AgentChannelRun,\n type AgentEventDeclaration,\n type AgentRouteValue,\n type AgentUnroutedEvent,\n type IntegrationDefinition,\n type JsonValue,\n MAX_ROUTE_VALUE_LENGTH,\n type ScheduleInvocation,\n type ScheduleResult,\n type StandardSchemaV1,\n type StorageSchemas,\n type WebhookEvent,\n type WebhookResult,\n} from '@ekanos/integration-schema';\n\nimport {\n type MockContextOptions,\n type MockIntegrationContext,\n createMockContext,\n} from './mock-context';\n\n/**\n * Everything `createMockContext` takes except the fields the definition\n * itself is the authority on — the helpers derive `integration`,\n * `storageSchemas`, and `egress` from the definition so a test cannot\n * accidentally run a handler against schemas or an allowlist the definition\n * does not declare.\n */\nexport type DefinitionContextOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> = Omit<\n MockContextOptions<Schemas>,\n 'integration' | 'storageSchemas' | 'egress'\n>;\n\ninterface BaseInvokeOptions<Schemas extends StorageSchemas> {\n /**\n * Reuse an existing mock context so state (storage, secrets, logs)\n * accumulates across invocations — the harness does this. When set,\n * `context` wins and `contextOptions` must be omitted.\n */\n context?: MockIntegrationContext<Schemas>;\n /** Seeds and stubs for the context the helper creates. */\n contextOptions?: DefinitionContextOptions<Schemas>;\n}\n\nexport interface InvokeWebhookOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> extends BaseInvokeOptions<Schemas> {\n /** Delivery headers the event carries. Defaults to `{}`. */\n headers?: Record<string, string>;\n /** Transport-assigned event id. Defaults to a random UUID. */\n eventId?: string;\n /** ISO receipt time. Defaults to now. */\n receivedAt?: string;\n}\n\nexport interface InvokeScheduleOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> extends BaseInvokeOptions<Schemas> {\n /** Defaults to `'manual'` — a human pressed \"Run now\". */\n trigger?: ScheduleInvocation['trigger'];\n /** The tick this invocation stands for. Defaults to now. */\n scheduledFor?: string;\n}\n\nexport type InvokeActivateOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> = BaseInvokeOptions<Schemas>;\n\nexport interface ActivateInvocationOutcome<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n /** The context the handler ran against — assert on its recordings. */\n ctx: MockIntegrationContext<Schemas>;\n}\n\nexport interface WebhookInvocationOutcome<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n result: WebhookResult;\n /** The event the handler received (payload already schema-parsed). */\n event: WebhookEvent;\n /** The context the handler ran against — assert on its recordings. */\n ctx: MockIntegrationContext<Schemas>;\n}\n\nexport interface ScheduleInvocationOutcome<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n result: ScheduleResult;\n invocation: ScheduleInvocation;\n ctx: MockIntegrationContext<Schemas>;\n}\n\nfunction contextFor<Schemas extends StorageSchemas>(\n definition: IntegrationDefinition<Schemas>,\n options: BaseInvokeOptions<Schemas>,\n): MockIntegrationContext<Schemas> {\n if (options.context) {\n if (options.contextOptions) {\n throw new TypeError(\n 'Pass either `context` (reuse an existing mock context) or ' +\n '`contextOptions` (seed a fresh one), not both — seeds cannot be ' +\n 'applied to a context that already exists.',\n );\n }\n return options.context;\n }\n\n return createMockContext<Schemas>({\n ...(options.contextOptions ?? {}),\n integration: { slug: definition.slug },\n ...(definition.storage ? { storageSchemas: definition.storage } : {}),\n egress: definition.egress ?? [],\n });\n}\n\nfunction listIds(ids: readonly string[]): string {\n return ids.length > 0 ? ids.map((id) => `\"${id}\"`).join(', ') : '(none)';\n}\n\n/** Standard Schema validation, the call the host makes; throws with every issue listed. */\nasync function validatePayload(\n schema: StandardSchemaV1,\n payload: unknown,\n rejection: string,\n): Promise<unknown> {\n const parsed = await schema['~standard'].validate(payload);\n if (!parsed.issues) return parsed.value;\n\n const issues = parsed.issues\n .map((issue) => {\n const segments = (issue.path ?? []).map((segment) =>\n typeof segment === 'object' ? String(segment.key) : String(segment),\n );\n const path = segments.length > 0 ? segments.join('.') : '(root)';\n return ` - ${path}: ${issue.message}`;\n })\n .join('\\n');\n throw new Error(`${rejection}:\\n${issues}`);\n}\n\n/**\n * Delivers one payload to one declared webhook, the way a transport would.\n * Throws if the id is undeclared or the payload fails `payloadSchema`;\n * returns the handler's result plus the event and the context it ran\n * against. Never verifies signatures — that is the transport's job, and the\n * local transport records the skip as a `warn` log line.\n */\nexport async function invokeWebhook<\n Schemas extends StorageSchemas = StorageSchemas,\n>(\n definition: IntegrationDefinition<Schemas>,\n webhookId: string,\n payload: unknown,\n options: InvokeWebhookOptions<Schemas> = {},\n): Promise<WebhookInvocationOutcome<Schemas>> {\n const webhook = (definition.webhooks ?? []).find((w) => w.id === webhookId);\n\n if (!webhook) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no webhook \"${webhookId}\". ` +\n `Declared webhook ids: ${listIds((definition.webhooks ?? []).map((w) => w.id))}.`,\n );\n }\n\n // The host's webhook route validates through Standard Schema too, so a\n // partner test exercises the exact call production makes.\n const parsed = await validatePayload(\n webhook.payloadSchema,\n payload,\n `Payload rejected by webhook \"${webhookId}\"'s payloadSchema — the ` +\n `transport refuses such a delivery before the handler runs`,\n );\n\n const ctx = contextFor(definition, options);\n\n if (webhook.signature !== 'none') {\n ctx.logger.warn(\n {\n webhookId,\n header: webhook.signature.header,\n secretName: webhook.signature.secretName,\n },\n 'Signature verification SKIPPED (local transport). The host ingress ' +\n 'verifies this header against the named secret before the handler ' +\n 'runs — handlers never verify signatures themselves.',\n );\n }\n\n const event: WebhookEvent = {\n id: options.eventId ?? `evt_${crypto.randomUUID()}`,\n receivedAt: options.receivedAt ?? new Date().toISOString(),\n headers: options.headers ?? {},\n payload: parsed,\n };\n\n const result = await webhook.handler(ctx, event);\n\n return { result, event, ctx };\n}\n\n/**\n * Fires one declared schedule, the way the scheduler would. Throws if the id\n * is undeclared; returns the handler's result plus the invocation and the\n * context it ran against.\n */\nexport async function invokeSchedule<\n Schemas extends StorageSchemas = StorageSchemas,\n>(\n definition: IntegrationDefinition<Schemas>,\n scheduleId: string,\n options: InvokeScheduleOptions<Schemas> = {},\n): Promise<ScheduleInvocationOutcome<Schemas>> {\n const schedule = (definition.schedules ?? []).find(\n (s) => s.id === scheduleId,\n );\n\n if (!schedule) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no schedule \"${scheduleId}\". ` +\n `Declared schedule ids: ${listIds((definition.schedules ?? []).map((s) => s.id))}.`,\n );\n }\n\n const ctx = contextFor(definition, options);\n const now = new Date().toISOString();\n\n const invocation: ScheduleInvocation = {\n scheduledFor: options.scheduledFor ?? now,\n invokedAt: now,\n trigger: options.trigger ?? 'manual',\n };\n\n const result = await schedule.handler(ctx, invocation);\n\n return { result, invocation, ctx };\n}\n\n/**\n * Runs the definition's declared `onActivate` hook, the way the host would\n * after an activation persists or activationData updates. Throws if the\n * definition declares no `onActivate` — there is nothing to invoke, and a\n * silent no-op would let a test believe it exercised a hook that does not\n * exist. A throwing handler PROPAGATES from this helper: the non-fatal\n * (log-a-warning, keep the activation) handling is host policy applied by\n * whatever calls this in production/the harness, not by this bare invoker\n * (see the module doc comment).\n */\nexport async function invokeActivate<\n Schemas extends StorageSchemas = StorageSchemas,\n>(\n definition: IntegrationDefinition<Schemas>,\n options: InvokeActivateOptions<Schemas> = {},\n): Promise<ActivateInvocationOutcome<Schemas>> {\n if (!definition.onActivate) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no onActivate hook — add ` +\n `one to defineIntegration() before invoking it.`,\n );\n }\n\n const ctx = contextFor(definition, options);\n\n await definition.onActivate(ctx);\n\n return { ctx };\n}\n\n// ---- Routing and channel hooks (plans 241, 242) ----------------------------\n\nexport interface RouteKeysOutcome {\n /** The payload after the event's `payloadSchema`. */\n payload: unknown;\n /**\n * The event's value for each routing key it names in `routeKeys`. `null`\n * means the event carries none for that key: routed by it, it is unrouted.\n */\n routes: Record<string, AgentRouteValue | null>;\n}\n\nexport interface InvokeChannelHookOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> extends BaseInvokeOptions<Schemas> {\n /** The run the hook acts for. Defaults to fresh ids. */\n run?: AgentChannelRun;\n}\n\nexport interface InvokeUnroutedOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> extends BaseInvokeOptions<Schemas> {\n /**\n * The routing key the Source routes by. Defaults to the first of\n * `routing.keys`.\n */\n routeKey?: string;\n}\n\nexport interface ChannelHookOutcome<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n /** The context the hook ran against — assert on its recordings. */\n ctx: MockIntegrationContext<Schemas>;\n}\n\nexport interface UnroutedInvocationOutcome<\n Schemas extends StorageSchemas = StorageSchemas,\n> extends ChannelHookOutcome<Schemas> {\n /** Where the note was posted: the event's `conversation()` target. */\n target: Record<string, JsonValue>;\n /** What the hook received. */\n event: AgentUnroutedEvent;\n}\n\nfunction findAgentEvent<Schemas extends StorageSchemas>(\n definition: IntegrationDefinition<Schemas>,\n eventId: string,\n): AgentEventDeclaration {\n const event = (definition.agentEvents ?? []).find(\n (candidate) => candidate.id === eventId,\n );\n if (!event) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no agent event \"${eventId}\". ` +\n `Declared agent event ids: ${listIds((definition.agentEvents ?? []).map((e) => e.id))}.`,\n );\n }\n return event;\n}\n\nfunction checkedRouteValue(\n eventId: string,\n key: string,\n value: unknown,\n): AgentRouteValue | null {\n if (value === null) return null;\n const route = value as { value?: unknown; label?: unknown } | undefined;\n if (\n typeof route === 'object' &&\n typeof route.value === 'string' &&\n route.value.length > 0 &&\n route.value.length <= MAX_ROUTE_VALUE_LENGTH &&\n (route.label === undefined || typeof route.label === 'string')\n ) {\n return route.label === undefined\n ? { value: route.value }\n : { value: route.value, label: route.label };\n }\n throw new Error(\n `${eventId} routeKeys.${key} returned ${JSON.stringify(value)}. Return ` +\n `{ value, label? } with a non-empty value of at most ` +\n `${MAX_ROUTE_VALUE_LENGTH} characters, or null — the host treats ` +\n `anything else as no value and leaves the event unrouted.`,\n );\n}\n\n/**\n * Computes an agent event's route values the way the host does before\n * routing a Source connection's event to a team: the payload is validated\n * against the event's `payloadSchema`, then each `routeKeys` function runs\n * on the result. Throws if the event is undeclared, the payload is invalid,\n * or a function returns something other than `{ value, label? }` or `null`.\n */\nexport async function invokeRouteKeys<\n Schemas extends StorageSchemas = StorageSchemas,\n>(\n definition: IntegrationDefinition<Schemas>,\n eventId: string,\n payload: unknown,\n): Promise<RouteKeysOutcome> {\n const event = findAgentEvent(definition, eventId);\n const parsed = await validatePayload(\n event.payloadSchema,\n payload,\n `Payload rejected by agent event \"${eventId}\"'s payloadSchema — the ` +\n `host drops such an event before routing it`,\n );\n\n const routes: Record<string, AgentRouteValue | null> = {};\n for (const [key, routeKey] of Object.entries(event.routeKeys ?? {})) {\n routes[key] = checkedRouteValue(eventId, key, routeKey(parsed));\n }\n return { payload: parsed, routes };\n}\n\nfunction channelRun(run: AgentChannelRun | undefined): AgentChannelRun {\n return (\n run ?? {\n runId: `run_${crypto.randomUUID()}`,\n agentRunId: crypto.randomUUID(),\n }\n );\n}\n\n/**\n * Runs the channel's `handoff` hook (plan 242), the way the host does when a\n * reviewer rejects a run's draft reply. Throws if the definition declares no\n * channel or no `handoff` hook. A throwing hook PROPAGATES here — on the\n * host, hooks are best-effort and a throw is logged and ignored.\n *\n * Pass `contextOptions: { connection: { owner: 'source' }, routing }` to run\n * it the way a provided agent on a Source connection does.\n */\nexport async function invokeHandoff<\n Schemas extends StorageSchemas = StorageSchemas,\n>(\n definition: IntegrationDefinition<Schemas>,\n target: Record<string, JsonValue>,\n handoff: AgentChannelHandoff = {},\n options: InvokeChannelHookOptions<Schemas> = {},\n): Promise<ChannelHookOutcome<Schemas>> {\n const hook = definition.agentChannel?.hooks?.handoff;\n if (!hook) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no agentChannel.hooks.handoff ` +\n `— add one before invoking it.`,\n );\n }\n\n const ctx = contextFor(definition, options);\n await hook(ctx, target, handoff, channelRun(options.run));\n return { ctx };\n}\n\n/**\n * Runs the channel's `unrouted` hook (plan 241) for one event, the way the\n * host does when a Source connection's event maps to no team: the payload is\n * validated, the target is the event's `conversation()` target, and the\n * route is the event's value for the Source's routing key. The context is\n * the Source connection's — `connection: { owner: 'source' }` and no\n * `routing` — unless you pass your own.\n *\n * Throws if the event, the routing key or the hook is undeclared, the\n * payload is invalid, or the event has no conversation target (the host has\n * nowhere to post, so it never calls the hook).\n */\nexport async function invokeUnrouted<\n Schemas extends StorageSchemas = StorageSchemas,\n>(\n definition: IntegrationDefinition<Schemas>,\n eventId: string,\n payload: unknown,\n options: InvokeUnroutedOptions<Schemas> = {},\n): Promise<UnroutedInvocationOutcome<Schemas>> {\n const hook = definition.agentChannel?.hooks?.unrouted;\n if (!hook) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no agentChannel.hooks.unrouted ` +\n `— add one before invoking it.`,\n );\n }\n\n const keys = (definition.routing?.keys ?? []).map((key) => key.id);\n const key = options.routeKey ?? keys[0];\n if (key === undefined || !keys.includes(key)) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no routing key ` +\n `\"${key ?? ''}\". Declared routing keys: ${listIds(keys)}.`,\n );\n }\n\n const { payload: parsed, routes } = await invokeRouteKeys(\n definition,\n eventId,\n payload,\n );\n const conversation =\n findAgentEvent(definition, eventId).conversation?.(parsed) ?? null;\n if (!conversation) {\n throw new Error(\n `${eventId} has no conversation target for this payload, so the host ` +\n `has nowhere to post an unrouted note and never calls the hook.`,\n );\n }\n\n if (options.contextOptions?.routing) {\n throw new TypeError(\n 'An unrouted event has no team, so its context has no routing — drop ' +\n 'contextOptions.routing.',\n );\n }\n const ctx = contextFor(\n definition,\n options.context\n ? options\n : {\n contextOptions: {\n connection: { owner: 'source' },\n ...options.contextOptions,\n },\n },\n );\n\n const route = routes[key] ?? null;\n const event: AgentUnroutedEvent = {\n eventId,\n payload: parsed,\n route: {\n key,\n value: route?.value ?? null,\n ...(route?.label === undefined ? {} : { label: route.label }),\n },\n };\n await hook(ctx, conversation.target, event);\n return { ctx, target: conversation.target, event };\n}\n"]}
@@ -8,7 +8,7 @@
8
8
  * egress surprise later.
9
9
  */
10
10
  import type { z } from 'zod';
11
- import type { IntegrationActor, IntegrationContext, StorageKeySchema, StorageSchemaMap, StorageSchemas } from '../context/types.js';
11
+ import type { IntegrationActor, IntegrationConnection, IntegrationContext, IntegrationRouting, StorageKeySchema, StorageSchemaMap, StorageSchemas } from '../context/types.js';
12
12
  import { type MockFetchHandler } from './fetch-matching.js';
13
13
  type ScopeSeed<M> = M extends StorageSchemaMap ? {
14
14
  [K in keyof M & string]?: z.input<StorageKeySchema<M[K]>>;
@@ -21,6 +21,18 @@ export interface MockContextOptions<Schemas extends StorageSchemas = StorageSche
21
21
  actor?: IntegrationActor;
22
22
  sourceId?: string | null;
23
23
  timezone?: string | null;
24
+ /**
25
+ * Whose connection the call runs on (plan 241). Defaults to
26
+ * `{ owner: 'team' }`; pass `{ owner: 'source' }` to run a tool, webhook
27
+ * or channel hook the way the host runs it on a Source connection.
28
+ */
29
+ connection?: IntegrationConnection;
30
+ /**
31
+ * The routed values the call may touch on a Source connection — what the
32
+ * host derives from the team's mappings. Only with
33
+ * `connection: { owner: 'source' }`, as on the host.
34
+ */
35
+ routing?: IntegrationRouting;
24
36
  integration: {
25
37
  slug: string;
26
38
  productId?: string;
@@ -62,6 +74,8 @@ export interface RecordedLog {
62
74
  message: string;
63
75
  }
64
76
  export interface MockIntegrationContext<Schemas extends StorageSchemas = StorageSchemas> extends IntegrationContext<Schemas> {
77
+ /** Always set on the mock: `{ owner: 'team' }` unless the options say otherwise. */
78
+ readonly connection: IntegrationConnection;
65
79
  /** Every ctx.fetch call, allowed or denied, in order. */
66
80
  readonly fetchCalls: readonly RecordedFetchCall[];
67
81
  /** Every log line, per level. */
@@ -87,6 +87,39 @@ function createInMemoryStore(args) {
87
87
  function utf8Bytes(value) {
88
88
  return new TextEncoder().encode(value).length;
89
89
  }
90
+ /**
91
+ * The host sets `ctx.routing` only on a Source connection, so a mock that
92
+ * pairs it with a team connection would prove scoping the host never runs.
93
+ */
94
+ function resolveConnection(connection, routing) {
95
+ var _a;
96
+ const owner = (_a = connection === null || connection === void 0 ? void 0 : connection.owner) !== null && _a !== void 0 ? _a : 'team';
97
+ if (owner !== 'team' && owner !== 'source') {
98
+ throw new TypeError(`connection.owner must be 'team' or 'source', not ${JSON.stringify(owner)}.`);
99
+ }
100
+ const resolved = Object.freeze({ owner });
101
+ if (!routing)
102
+ return { connection: resolved };
103
+ if (owner !== 'source') {
104
+ throw new TypeError('routing is only set on a Source connection: pass ' +
105
+ "connection: { owner: 'source' } with it. A team's own connection " +
106
+ 'reaches only that team, so the host never scopes it.');
107
+ }
108
+ if (typeof routing.key !== 'string' ||
109
+ routing.key.length === 0 ||
110
+ !Array.isArray(routing.values) ||
111
+ routing.values.some((value) => typeof value !== 'string')) {
112
+ throw new TypeError('routing must be { key: string, values: string[] }: the routing key ' +
113
+ 'id and the values mapped to the team.');
114
+ }
115
+ return {
116
+ connection: resolved,
117
+ routing: Object.freeze({
118
+ key: routing.key,
119
+ values: Object.freeze([...routing.values]),
120
+ }),
121
+ };
122
+ }
90
123
  function dumpScope(rows) {
91
124
  const out = {};
92
125
  for (const [key, row] of rows) {
@@ -105,6 +138,7 @@ export function createMockContext(options) {
105
138
  { kind: 'user', userId, role: { name: 'owner', hierarchyLevel: 1 } }
106
139
  : { kind: 'machine', tokenId: 'mock-token-id', createdBy: null });
107
140
  const schemas = (_f = options.storageSchemas) !== null && _f !== void 0 ? _f : {};
141
+ const { connection, routing } = resolveConnection(options.connection, options.routing);
108
142
  const accountRows = new Map();
109
143
  const userRows = new Map();
110
144
  const seedScope = (scope, rows, seed) => {
@@ -217,18 +251,10 @@ export function createMockContext(options) {
217
251
  warn: record('warn'),
218
252
  error: record('error'),
219
253
  };
220
- return {
221
- accountId: (_l = options.accountId) !== null && _l !== void 0 ? _l : 'mock-account-id',
222
- accountSlug: (_m = options.accountSlug) !== null && _m !== void 0 ? _m : null,
223
- userId: actor.kind === 'machine' ? null : userId,
224
- actor,
225
- sourceId: (_o = options.sourceId) !== null && _o !== void 0 ? _o : null,
226
- integration: {
254
+ return Object.assign(Object.assign({ accountId: (_l = options.accountId) !== null && _l !== void 0 ? _l : 'mock-account-id', accountSlug: (_m = options.accountSlug) !== null && _m !== void 0 ? _m : null, userId: actor.kind === 'machine' ? null : userId, actor, sourceId: (_o = options.sourceId) !== null && _o !== void 0 ? _o : null, integration: {
227
255
  slug: options.integration.slug,
228
256
  productId: (_p = options.integration.productId) !== null && _p !== void 0 ? _p : 'mock-product-id',
229
- },
230
- timezone: (_q = options.timezone) !== null && _q !== void 0 ? _q : null,
231
- storage,
257
+ }, timezone: (_q = options.timezone) !== null && _q !== void 0 ? _q : null, connection }, (routing ? { routing } : {})), { storage,
232
258
  secrets,
233
259
  fetch,
234
260
  logger,
@@ -236,7 +262,6 @@ export function createMockContext(options) {
236
262
  logs,
237
263
  dumpStorage() {
238
264
  return { account: dumpScope(accountRows), user: dumpScope(userRows) };
239
- },
240
- };
265
+ } });
241
266
  }
242
267
  //# sourceMappingURL=mock-context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mock-context.js","sourceRoot":"","sources":["../../src/testing/mock-context.ts"],"names":[],"mappings":"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","sourcesContent":["import { PARTNER_STORAGE_LIMITS } from '@ekanos/integration-schema';\n/**\n * `createMockContext()` — the day-1 in-memory `IntegrationContext` for the\n * partner harness (proposal §4, amended by rulings 1–4). Self-contained:\n * nothing here touches Supabase, Vault, or the network. Enforcement is the\n * SAME shared implementation the production wrapper uses\n * (`@ekanos/sdk/context`): schema validation via `parseStorageValue`, egress\n * via `isEgressAllowed` — a test that passes the mock cannot be a schema or\n * egress surprise later.\n */\nimport type { z } from 'zod';\n\nimport { isEgressAllowed } from '../context/egress';\nimport {\n EgressDeniedError,\n SecretAccessError,\n StorageValidationError,\n} from '../context/errors';\nimport {\n assertStorageKeyDeclared,\n assertStorageKeyWritable,\n parseStorageValue,\n} from '../context/storage-validation';\nimport type {\n IntegrationActor,\n IntegrationContext,\n IntegrationFetch,\n IntegrationLogger,\n IntegrationSecrets,\n IntegrationStorage,\n ScopedStore,\n StorageEntry,\n StorageKeySchema,\n StorageSchemaMap,\n StorageSchemas,\n StorageWriteOptions,\n} from '../context/types';\nimport { type MockFetchHandler, matchFetchHandler } from './fetch-matching';\n\ntype StorageScope = 'account' | 'user';\n\n// Seeds are typed off the DECLARED schema, resolved out of whichever\n// declaration form the key used — a bare zod schema or a\n// `{ schema, clientReadable }` descriptor.\ntype ScopeSeed<M> = M extends StorageSchemaMap\n ? { [K in keyof M & string]?: z.input<StorageKeySchema<M[K]>> }\n : Record<never, never>;\n\n// Re-exported from here for backward compatibility — `MockFetchHandler`'s\n// canonical home is now `./fetch-matching`, shared with `createMockFetchProvider`.\nexport type { MockFetchHandler } from './fetch-matching';\n\nexport interface MockContextOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n accountId?: string;\n accountSlug?: string | null;\n userId?: string | null;\n actor?: IntegrationActor;\n sourceId?: string | null;\n timezone?: string | null;\n integration: { slug: string; productId?: string };\n /**\n * The storage declaration `defineIntegration({ storage })` will carry —\n * the mock enforces the identical schemas via the same shared validator\n * (ruling 1). Omit it and every storage access throws.\n */\n storageSchemas?: Schemas;\n /** Seed rows: key → data, per scope. Validated on construction via the write path. */\n storage?: {\n account?: ScopeSeed<Schemas['account']>;\n user?: ScopeSeed<Schemas['user']>;\n };\n /**\n * Seed secrets, by tier (ruling 2): `account` values are the rows this\n * integration stored — writable via `secrets.set()`; `admin` values stand\n * in for admin-issued source/global-tier credentials — readable, and a\n * `set()` against a name resolvable only there throws `SecretAccessError`.\n */\n secrets?: {\n account?: Record<string, string>;\n admin?: Record<string, string>;\n };\n /** Same allowlist the manifest would declare — enforced by the stub. */\n egress?: readonly string[];\n /** Programmable responses; unmatched allowed calls resolve 200 `{}`. */\n fetchHandlers?: MockFetchHandler[];\n}\n\nexport interface RecordedFetchCall {\n url: string;\n init?: RequestInit;\n denied: boolean;\n}\n\nexport interface RecordedLog {\n level: 'debug' | 'info' | 'warn' | 'error';\n context: Record<string, unknown>;\n message: string;\n}\n\nexport interface MockIntegrationContext<\n Schemas extends StorageSchemas = StorageSchemas,\n> extends IntegrationContext<Schemas> {\n /** Every ctx.fetch call, allowed or denied, in order. */\n readonly fetchCalls: readonly RecordedFetchCall[];\n /** Every log line, per level. */\n readonly logs: readonly RecordedLog[];\n /** Current readable storage state (expired entries excluded), key → data. */\n dumpStorage(): {\n account: Record<string, unknown>;\n user: Record<string, unknown>;\n };\n}\n\ninterface StoredRow {\n data: unknown;\n externalId: string | null;\n expiresAt: string | null;\n updatedAt: string;\n}\n\nfunction isExpired(row: StoredRow): boolean {\n return row.expiresAt !== null && Date.parse(row.expiresAt) <= Date.now();\n}\n\nfunction parseExpiresAt(\n options: StorageWriteOptions | undefined,\n): string | null {\n const expiresAt = options?.expiresAt;\n if (expiresAt === undefined) return null;\n if (Number.isNaN(Date.parse(expiresAt))) {\n throw new TypeError(\n `expiresAt \"${expiresAt}\" is not a parseable timestamp. Pass an ` +\n `ISO-8601 string, e.g. new Date(Date.now() + 60_000).toISOString().`,\n );\n }\n return expiresAt;\n}\n\nfunction createInMemoryStore(args: {\n scope: StorageScope;\n schemas: StorageSchemaMap | undefined;\n rows: Map<string, StoredRow>;\n guard: () => void;\n}): ScopedStore {\n const { scope, schemas, rows, guard } = args;\n\n return {\n async get(key: string): Promise<StorageEntry<unknown> | null> {\n guard();\n assertStorageKeyDeclared({ scope, key, schemas });\n const row = rows.get(key);\n if (!row) return null;\n if (isExpired(row)) {\n rows.delete(key);\n return null;\n }\n const data = parseStorageValue({\n scope,\n key,\n operation: 'read',\n schemas,\n value: row.data,\n });\n return {\n data,\n externalId: row.externalId,\n expiresAt: row.expiresAt,\n updatedAt: row.updatedAt,\n };\n },\n\n async set(\n key: string,\n data: unknown,\n options?: StorageWriteOptions,\n ): Promise<void> {\n guard();\n assertStorageKeyWritable({ scope, key });\n const parsed = parseStorageValue({\n scope,\n key,\n operation: 'write',\n schemas,\n value: data,\n });\n // The host's per-value cap (finding E), enforced here with the host's\n // own wording so `ekanos test` fails where production would.\n const serializedBytes = utf8Bytes(JSON.stringify(parsed));\n if (serializedBytes > PARTNER_STORAGE_LIMITS.maxSerializedValueBytes) {\n throw new StorageValidationError({\n scope,\n key,\n hint:\n `The value written to ${scope} storage key \"${key}\" serializes ` +\n `to ${serializedBytes} bytes, above the ` +\n `${PARTNER_STORAGE_LIMITS.maxSerializedValueBytes}-byte per-value ` +\n `limit. Store less per key: trim cached payloads, or split state ` +\n `across purpose-named keys.`,\n });\n }\n rows.set(key, {\n data: parsed,\n externalId: options?.externalId ?? null,\n expiresAt: parseExpiresAt(options),\n updatedAt: new Date().toISOString(),\n });\n },\n\n async delete(key: string): Promise<void> {\n guard();\n assertStorageKeyWritable({ scope, key });\n assertStorageKeyDeclared({ scope, key, schemas });\n rows.delete(key);\n },\n };\n}\n\n/** Byte length of a string as UTF-8, without Buffer (this module runs in the browser-side harness too). */\nfunction utf8Bytes(value: string): number {\n return new TextEncoder().encode(value).length;\n}\n\nfunction dumpScope(rows: Map<string, StoredRow>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, row] of rows) {\n if (!isExpired(row)) out[key] = row.data;\n }\n return out;\n}\n\nexport function createMockContext<\n Schemas extends StorageSchemas = StorageSchemas,\n>(options: MockContextOptions<Schemas>): MockIntegrationContext<Schemas> {\n const userId = options.userId !== undefined ? options.userId : 'mock-user-id';\n const actor: IntegrationActor =\n options.actor ??\n (userId !== null\n ? // The host always resolves a role; the mock's default user is the\n // account owner. Pass `actor` with a lower role to test a gate.\n { kind: 'user', userId, role: { name: 'owner', hierarchyLevel: 1 } }\n : { kind: 'machine', tokenId: 'mock-token-id', createdBy: null });\n\n const schemas: StorageSchemas = options.storageSchemas ?? {};\n\n const accountRows = new Map<string, StoredRow>();\n const userRows = new Map<string, StoredRow>();\n\n const seedScope = (\n scope: StorageScope,\n rows: Map<string, StoredRow>,\n seed: Record<string, unknown> | undefined,\n ) => {\n for (const [key, value] of Object.entries(seed ?? {})) {\n const parsed = parseStorageValue({\n scope,\n key,\n operation: 'write',\n schemas: schemas[scope],\n value,\n });\n rows.set(key, {\n data: parsed,\n externalId: null,\n expiresAt: null,\n updatedAt: new Date().toISOString(),\n });\n }\n };\n seedScope('account', accountRows, options.storage?.account);\n seedScope('user', userRows, options.storage?.user);\n\n const guardUserScope = () => {\n if (actor.kind === 'machine') {\n throw new Error(\n 'ctx.storage.user is unavailable for machine actors: there is no ' +\n 'user to scope the row to. Store machine-written state in ' +\n 'ctx.storage.account instead.',\n );\n }\n };\n\n // The stores validate every key and value at runtime via the shared\n // validator; the cast narrows the runtime-checked store to the\n // schema-typed interface the declaration promises.\n const storage = {\n account: createInMemoryStore({\n scope: 'account',\n schemas: schemas.account,\n rows: accountRows,\n guard: () => void 0,\n }),\n user: createInMemoryStore({\n scope: 'user',\n schemas: schemas.user,\n rows: userRows,\n guard: guardUserScope,\n }),\n } as unknown as IntegrationStorage<Schemas>;\n\n const accountSecrets = new Map(\n Object.entries(options.secrets?.account ?? {}),\n );\n const adminSecrets = new Map(Object.entries(options.secrets?.admin ?? {}));\n\n const secrets: IntegrationSecrets = {\n async get(name) {\n return accountSecrets.get(name) ?? adminSecrets.get(name) ?? null;\n },\n async names() {\n return [\n ...new Set([...accountSecrets.keys(), ...adminSecrets.keys()]),\n ].sort();\n },\n async set(name, value) {\n if (!accountSecrets.has(name) && adminSecrets.has(name)) {\n throw new SecretAccessError(name);\n }\n // The host's caps (finding E): one Vault record per value, one row +\n // record per distinct name. Same wording as the host.\n const valueBytes = utf8Bytes(value);\n if (valueBytes > PARTNER_STORAGE_LIMITS.maxSerializedValueBytes) {\n throw new TypeError(\n `secrets.set(\"${name}\", …): the value is ${valueBytes} bytes, ` +\n `above the ${PARTNER_STORAGE_LIMITS.maxSerializedValueBytes}-byte ` +\n `per-secret limit. Secrets hold credentials, not payloads — store ` +\n `bulk data under a ctx.storage key instead.`,\n );\n }\n if (\n !accountSecrets.has(name) &&\n accountSecrets.size >= PARTNER_STORAGE_LIMITS.maxSecretNames\n ) {\n throw new Error(\n `secrets.set(\"${name}\", …): this integration already stores ` +\n `${PARTNER_STORAGE_LIMITS.maxSecretNames} distinct secret names, ` +\n `the per-integration limit. Reuse or rotate an existing name.`,\n );\n }\n accountSecrets.set(name, value);\n },\n };\n\n const fetchCalls: RecordedFetchCall[] = [];\n const egress = options.egress ?? [];\n const fetchHandlers = options.fetchHandlers ?? [];\n\n // Not an async function: a denied call must throw synchronously, before\n // any I/O (proposal §2.3) — same behavior the production wrapper has.\n const fetch: IntegrationFetch = (input, init) => {\n const url = new URL(input).toString();\n const allowed = isEgressAllowed(url, egress);\n fetchCalls.push(\n init === undefined\n ? { url, denied: !allowed }\n : { url, init, denied: !allowed },\n );\n if (!allowed) {\n throw new EgressDeniedError(new URL(url).origin);\n }\n const handler = matchFetchHandler(url, fetchHandlers);\n if (handler) {\n return Promise.resolve(handler.respond(new Request(url, init)));\n }\n return Promise.resolve(\n new Response('{}', {\n status: 200,\n headers: { 'content-type': 'application/json' },\n }),\n );\n };\n\n const logs: RecordedLog[] = [];\n const record =\n (level: RecordedLog['level']) =>\n (context: Record<string, unknown>, message: string) => {\n logs.push({ level, context, message });\n };\n const logger: IntegrationLogger = {\n debug: record('debug'),\n info: record('info'),\n warn: record('warn'),\n error: record('error'),\n };\n\n return {\n accountId: options.accountId ?? 'mock-account-id',\n accountSlug: options.accountSlug ?? null,\n userId: actor.kind === 'machine' ? null : userId,\n actor,\n sourceId: options.sourceId ?? null,\n integration: {\n slug: options.integration.slug,\n productId: options.integration.productId ?? 'mock-product-id',\n },\n timezone: options.timezone ?? null,\n storage,\n secrets,\n fetch,\n logger,\n fetchCalls,\n logs,\n dumpStorage() {\n return { account: dumpScope(accountRows), user: dumpScope(userRows) };\n },\n };\n}\n"]}
1
+ {"version":3,"file":"mock-context.js","sourceRoot":"","sources":["../../src/testing/mock-context.ts"],"names":[],"mappings":"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","sourcesContent":["import { PARTNER_STORAGE_LIMITS } from '@ekanos/integration-schema';\n/**\n * `createMockContext()` — the day-1 in-memory `IntegrationContext` for the\n * partner harness (proposal §4, amended by rulings 1–4). Self-contained:\n * nothing here touches Supabase, Vault, or the network. Enforcement is the\n * SAME shared implementation the production wrapper uses\n * (`@ekanos/sdk/context`): schema validation via `parseStorageValue`, egress\n * via `isEgressAllowed` — a test that passes the mock cannot be a schema or\n * egress surprise later.\n */\nimport type { z } from 'zod';\n\nimport { isEgressAllowed } from '../context/egress';\nimport {\n EgressDeniedError,\n SecretAccessError,\n StorageValidationError,\n} from '../context/errors';\nimport {\n assertStorageKeyDeclared,\n assertStorageKeyWritable,\n parseStorageValue,\n} from '../context/storage-validation';\nimport type {\n IntegrationActor,\n IntegrationConnection,\n IntegrationContext,\n IntegrationFetch,\n IntegrationLogger,\n IntegrationRouting,\n IntegrationSecrets,\n IntegrationStorage,\n ScopedStore,\n StorageEntry,\n StorageKeySchema,\n StorageSchemaMap,\n StorageSchemas,\n StorageWriteOptions,\n} from '../context/types';\nimport { type MockFetchHandler, matchFetchHandler } from './fetch-matching';\n\ntype StorageScope = 'account' | 'user';\n\n// Seeds are typed off the DECLARED schema, resolved out of whichever\n// declaration form the key used — a bare zod schema or a\n// `{ schema, clientReadable }` descriptor.\ntype ScopeSeed<M> = M extends StorageSchemaMap\n ? { [K in keyof M & string]?: z.input<StorageKeySchema<M[K]>> }\n : Record<never, never>;\n\n// Re-exported from here for backward compatibility — `MockFetchHandler`'s\n// canonical home is now `./fetch-matching`, shared with `createMockFetchProvider`.\nexport type { MockFetchHandler } from './fetch-matching';\n\nexport interface MockContextOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n accountId?: string;\n accountSlug?: string | null;\n userId?: string | null;\n actor?: IntegrationActor;\n sourceId?: string | null;\n timezone?: string | null;\n /**\n * Whose connection the call runs on (plan 241). Defaults to\n * `{ owner: 'team' }`; pass `{ owner: 'source' }` to run a tool, webhook\n * or channel hook the way the host runs it on a Source connection.\n */\n connection?: IntegrationConnection;\n /**\n * The routed values the call may touch on a Source connection — what the\n * host derives from the team's mappings. Only with\n * `connection: { owner: 'source' }`, as on the host.\n */\n routing?: IntegrationRouting;\n integration: { slug: string; productId?: string };\n /**\n * The storage declaration `defineIntegration({ storage })` will carry —\n * the mock enforces the identical schemas via the same shared validator\n * (ruling 1). Omit it and every storage access throws.\n */\n storageSchemas?: Schemas;\n /** Seed rows: key → data, per scope. Validated on construction via the write path. */\n storage?: {\n account?: ScopeSeed<Schemas['account']>;\n user?: ScopeSeed<Schemas['user']>;\n };\n /**\n * Seed secrets, by tier (ruling 2): `account` values are the rows this\n * integration stored — writable via `secrets.set()`; `admin` values stand\n * in for admin-issued source/global-tier credentials — readable, and a\n * `set()` against a name resolvable only there throws `SecretAccessError`.\n */\n secrets?: {\n account?: Record<string, string>;\n admin?: Record<string, string>;\n };\n /** Same allowlist the manifest would declare — enforced by the stub. */\n egress?: readonly string[];\n /** Programmable responses; unmatched allowed calls resolve 200 `{}`. */\n fetchHandlers?: MockFetchHandler[];\n}\n\nexport interface RecordedFetchCall {\n url: string;\n init?: RequestInit;\n denied: boolean;\n}\n\nexport interface RecordedLog {\n level: 'debug' | 'info' | 'warn' | 'error';\n context: Record<string, unknown>;\n message: string;\n}\n\nexport interface MockIntegrationContext<\n Schemas extends StorageSchemas = StorageSchemas,\n> extends IntegrationContext<Schemas> {\n /** Always set on the mock: `{ owner: 'team' }` unless the options say otherwise. */\n readonly connection: IntegrationConnection;\n /** Every ctx.fetch call, allowed or denied, in order. */\n readonly fetchCalls: readonly RecordedFetchCall[];\n /** Every log line, per level. */\n readonly logs: readonly RecordedLog[];\n /** Current readable storage state (expired entries excluded), key → data. */\n dumpStorage(): {\n account: Record<string, unknown>;\n user: Record<string, unknown>;\n };\n}\n\ninterface StoredRow {\n data: unknown;\n externalId: string | null;\n expiresAt: string | null;\n updatedAt: string;\n}\n\nfunction isExpired(row: StoredRow): boolean {\n return row.expiresAt !== null && Date.parse(row.expiresAt) <= Date.now();\n}\n\nfunction parseExpiresAt(\n options: StorageWriteOptions | undefined,\n): string | null {\n const expiresAt = options?.expiresAt;\n if (expiresAt === undefined) return null;\n if (Number.isNaN(Date.parse(expiresAt))) {\n throw new TypeError(\n `expiresAt \"${expiresAt}\" is not a parseable timestamp. Pass an ` +\n `ISO-8601 string, e.g. new Date(Date.now() + 60_000).toISOString().`,\n );\n }\n return expiresAt;\n}\n\nfunction createInMemoryStore(args: {\n scope: StorageScope;\n schemas: StorageSchemaMap | undefined;\n rows: Map<string, StoredRow>;\n guard: () => void;\n}): ScopedStore {\n const { scope, schemas, rows, guard } = args;\n\n return {\n async get(key: string): Promise<StorageEntry<unknown> | null> {\n guard();\n assertStorageKeyDeclared({ scope, key, schemas });\n const row = rows.get(key);\n if (!row) return null;\n if (isExpired(row)) {\n rows.delete(key);\n return null;\n }\n const data = parseStorageValue({\n scope,\n key,\n operation: 'read',\n schemas,\n value: row.data,\n });\n return {\n data,\n externalId: row.externalId,\n expiresAt: row.expiresAt,\n updatedAt: row.updatedAt,\n };\n },\n\n async set(\n key: string,\n data: unknown,\n options?: StorageWriteOptions,\n ): Promise<void> {\n guard();\n assertStorageKeyWritable({ scope, key });\n const parsed = parseStorageValue({\n scope,\n key,\n operation: 'write',\n schemas,\n value: data,\n });\n // The host's per-value cap (finding E), enforced here with the host's\n // own wording so `ekanos test` fails where production would.\n const serializedBytes = utf8Bytes(JSON.stringify(parsed));\n if (serializedBytes > PARTNER_STORAGE_LIMITS.maxSerializedValueBytes) {\n throw new StorageValidationError({\n scope,\n key,\n hint:\n `The value written to ${scope} storage key \"${key}\" serializes ` +\n `to ${serializedBytes} bytes, above the ` +\n `${PARTNER_STORAGE_LIMITS.maxSerializedValueBytes}-byte per-value ` +\n `limit. Store less per key: trim cached payloads, or split state ` +\n `across purpose-named keys.`,\n });\n }\n rows.set(key, {\n data: parsed,\n externalId: options?.externalId ?? null,\n expiresAt: parseExpiresAt(options),\n updatedAt: new Date().toISOString(),\n });\n },\n\n async delete(key: string): Promise<void> {\n guard();\n assertStorageKeyWritable({ scope, key });\n assertStorageKeyDeclared({ scope, key, schemas });\n rows.delete(key);\n },\n };\n}\n\n/** Byte length of a string as UTF-8, without Buffer (this module runs in the browser-side harness too). */\nfunction utf8Bytes(value: string): number {\n return new TextEncoder().encode(value).length;\n}\n\n/**\n * The host sets `ctx.routing` only on a Source connection, so a mock that\n * pairs it with a team connection would prove scoping the host never runs.\n */\nfunction resolveConnection(\n connection: IntegrationConnection | undefined,\n routing: IntegrationRouting | undefined,\n): { connection: IntegrationConnection; routing?: IntegrationRouting } {\n const owner = connection?.owner ?? 'team';\n if (owner !== 'team' && owner !== 'source') {\n throw new TypeError(\n `connection.owner must be 'team' or 'source', not ${JSON.stringify(owner)}.`,\n );\n }\n const resolved = Object.freeze({ owner });\n if (!routing) return { connection: resolved };\n if (owner !== 'source') {\n throw new TypeError(\n 'routing is only set on a Source connection: pass ' +\n \"connection: { owner: 'source' } with it. A team's own connection \" +\n 'reaches only that team, so the host never scopes it.',\n );\n }\n if (\n typeof routing.key !== 'string' ||\n routing.key.length === 0 ||\n !Array.isArray(routing.values) ||\n routing.values.some((value) => typeof value !== 'string')\n ) {\n throw new TypeError(\n 'routing must be { key: string, values: string[] }: the routing key ' +\n 'id and the values mapped to the team.',\n );\n }\n return {\n connection: resolved,\n routing: Object.freeze({\n key: routing.key,\n values: Object.freeze([...routing.values]),\n }),\n };\n}\n\nfunction dumpScope(rows: Map<string, StoredRow>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, row] of rows) {\n if (!isExpired(row)) out[key] = row.data;\n }\n return out;\n}\n\nexport function createMockContext<\n Schemas extends StorageSchemas = StorageSchemas,\n>(options: MockContextOptions<Schemas>): MockIntegrationContext<Schemas> {\n const userId = options.userId !== undefined ? options.userId : 'mock-user-id';\n const actor: IntegrationActor =\n options.actor ??\n (userId !== null\n ? // The host always resolves a role; the mock's default user is the\n // account owner. Pass `actor` with a lower role to test a gate.\n { kind: 'user', userId, role: { name: 'owner', hierarchyLevel: 1 } }\n : { kind: 'machine', tokenId: 'mock-token-id', createdBy: null });\n\n const schemas: StorageSchemas = options.storageSchemas ?? {};\n const { connection, routing } = resolveConnection(\n options.connection,\n options.routing,\n );\n\n const accountRows = new Map<string, StoredRow>();\n const userRows = new Map<string, StoredRow>();\n\n const seedScope = (\n scope: StorageScope,\n rows: Map<string, StoredRow>,\n seed: Record<string, unknown> | undefined,\n ) => {\n for (const [key, value] of Object.entries(seed ?? {})) {\n const parsed = parseStorageValue({\n scope,\n key,\n operation: 'write',\n schemas: schemas[scope],\n value,\n });\n rows.set(key, {\n data: parsed,\n externalId: null,\n expiresAt: null,\n updatedAt: new Date().toISOString(),\n });\n }\n };\n seedScope('account', accountRows, options.storage?.account);\n seedScope('user', userRows, options.storage?.user);\n\n const guardUserScope = () => {\n if (actor.kind === 'machine') {\n throw new Error(\n 'ctx.storage.user is unavailable for machine actors: there is no ' +\n 'user to scope the row to. Store machine-written state in ' +\n 'ctx.storage.account instead.',\n );\n }\n };\n\n // The stores validate every key and value at runtime via the shared\n // validator; the cast narrows the runtime-checked store to the\n // schema-typed interface the declaration promises.\n const storage = {\n account: createInMemoryStore({\n scope: 'account',\n schemas: schemas.account,\n rows: accountRows,\n guard: () => void 0,\n }),\n user: createInMemoryStore({\n scope: 'user',\n schemas: schemas.user,\n rows: userRows,\n guard: guardUserScope,\n }),\n } as unknown as IntegrationStorage<Schemas>;\n\n const accountSecrets = new Map(\n Object.entries(options.secrets?.account ?? {}),\n );\n const adminSecrets = new Map(Object.entries(options.secrets?.admin ?? {}));\n\n const secrets: IntegrationSecrets = {\n async get(name) {\n return accountSecrets.get(name) ?? adminSecrets.get(name) ?? null;\n },\n async names() {\n return [\n ...new Set([...accountSecrets.keys(), ...adminSecrets.keys()]),\n ].sort();\n },\n async set(name, value) {\n if (!accountSecrets.has(name) && adminSecrets.has(name)) {\n throw new SecretAccessError(name);\n }\n // The host's caps (finding E): one Vault record per value, one row +\n // record per distinct name. Same wording as the host.\n const valueBytes = utf8Bytes(value);\n if (valueBytes > PARTNER_STORAGE_LIMITS.maxSerializedValueBytes) {\n throw new TypeError(\n `secrets.set(\"${name}\", …): the value is ${valueBytes} bytes, ` +\n `above the ${PARTNER_STORAGE_LIMITS.maxSerializedValueBytes}-byte ` +\n `per-secret limit. Secrets hold credentials, not payloads — store ` +\n `bulk data under a ctx.storage key instead.`,\n );\n }\n if (\n !accountSecrets.has(name) &&\n accountSecrets.size >= PARTNER_STORAGE_LIMITS.maxSecretNames\n ) {\n throw new Error(\n `secrets.set(\"${name}\", …): this integration already stores ` +\n `${PARTNER_STORAGE_LIMITS.maxSecretNames} distinct secret names, ` +\n `the per-integration limit. Reuse or rotate an existing name.`,\n );\n }\n accountSecrets.set(name, value);\n },\n };\n\n const fetchCalls: RecordedFetchCall[] = [];\n const egress = options.egress ?? [];\n const fetchHandlers = options.fetchHandlers ?? [];\n\n // Not an async function: a denied call must throw synchronously, before\n // any I/O (proposal §2.3) — same behavior the production wrapper has.\n const fetch: IntegrationFetch = (input, init) => {\n const url = new URL(input).toString();\n const allowed = isEgressAllowed(url, egress);\n fetchCalls.push(\n init === undefined\n ? { url, denied: !allowed }\n : { url, init, denied: !allowed },\n );\n if (!allowed) {\n throw new EgressDeniedError(new URL(url).origin);\n }\n const handler = matchFetchHandler(url, fetchHandlers);\n if (handler) {\n return Promise.resolve(handler.respond(new Request(url, init)));\n }\n return Promise.resolve(\n new Response('{}', {\n status: 200,\n headers: { 'content-type': 'application/json' },\n }),\n );\n };\n\n const logs: RecordedLog[] = [];\n const record =\n (level: RecordedLog['level']) =>\n (context: Record<string, unknown>, message: string) => {\n logs.push({ level, context, message });\n };\n const logger: IntegrationLogger = {\n debug: record('debug'),\n info: record('info'),\n warn: record('warn'),\n error: record('error'),\n };\n\n return {\n accountId: options.accountId ?? 'mock-account-id',\n accountSlug: options.accountSlug ?? null,\n userId: actor.kind === 'machine' ? null : userId,\n actor,\n sourceId: options.sourceId ?? null,\n integration: {\n slug: options.integration.slug,\n productId: options.integration.productId ?? 'mock-product-id',\n },\n timezone: options.timezone ?? null,\n connection,\n ...(routing ? { routing } : {}),\n storage,\n secrets,\n fetch,\n logger,\n fetchCalls,\n logs,\n dumpStorage() {\n return { account: dumpScope(accountRows), user: dumpScope(userRows) };\n },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekanos/sdk",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "private": false,
5
5
  "description": "The official SDK for building Ekanos integrations.",
6
6
  "homepage": "https://sdk.ekanos.dev/sdk/overview",
@@ -77,8 +77,8 @@
77
77
  "access": "public"
78
78
  },
79
79
  "dependencies": {
80
- "@ekanos/integration-schema": "0.13.0",
81
- "@ekanos/ui": "0.13.0",
80
+ "@ekanos/integration-schema": "0.15.0",
81
+ "@ekanos/ui": "0.15.0",
82
82
  "@supabase/supabase-js": "2.87.1",
83
83
  "server-only": "^0.0.1"
84
84
  },