@ekanos/integration-schema 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,19 +10,25 @@ a dependency cycle and there is no hand-written structural twin to drift.
10
10
 
11
11
  ## Why it exists
12
12
 
13
- Adversarial review (GPT-5.6, 2026-08-27) found three problems a shared schema
14
- resolves at once:
13
+ A security review found three problems a shared schema resolves at once:
15
14
 
16
- - **F3** — the host must re-parse partner exports at the trust boundary, not
15
+ - The host must re-parse partner exports at the trust boundary rather than
17
16
  trust a `.d.ts`. `registerPartnerIntegration()` calls
18
- `parseIntegrationDefinition()` from here.
19
- - **F9** hand-written structural twins in `@kit/integrations-core` could not
20
- catch security-relevant drift. There are no twins now: one type, imported by
21
- both sides.
22
- - **F10** the SDK validated a weaker workspace contract than the host. The
17
+ `parseIntegrationDefinition()` from here, so a definition is validated twice
18
+ against the same rules: once when you author it, once when the host accepts
19
+ it.
20
+ - Hand-written structural twins on the host side could not catch
21
+ security-relevant drift. There are no twins now: one type, imported by both
22
+ sides.
23
+ - The SDK used to validate a weaker workspace contract than the host. The
23
24
  workspace-target schema lives here; the host injects icon-name validity via
24
25
  `createWorkspaceTargetListSchema({ isValidIcon })`.
25
26
 
27
+ **Authoring an integration? Read the `@ekanos/sdk` README instead.** It is the
28
+ partner guide, and it documents every field this schema validates with a worked
29
+ example. This package is the contract underneath it: you install it only if you
30
+ need the schema or the types without the SDK.
31
+
26
32
  ## What's here
27
33
 
28
34
  - `IntegrationDefinitionSchema` — the strict canonical zod schema.
@@ -40,9 +46,10 @@ Dependency-pure by construction: `zod` only. No `@kit/*`, no React, no
40
46
 
41
47
  ## Publishing
42
48
 
43
- This package is publishable (`version` set, not `private`) and is a **runtime
44
- dependency of `@ekanos/sdk`**, so it must be published **before** the SDK — a
45
- fresh `npm install @ekanos/sdk` resolves `@ekanos/integration-schema` from the
46
- registry. The compiled ESM in `dist/` uses explicit `.js` relative specifiers
49
+ This package is a **runtime dependency of `@ekanos/sdk`**, so it must be
50
+ published **before** the SDK — a fresh `npm install @ekanos/sdk` resolves
51
+ `@ekanos/integration-schema` from the registry. (It carries `private: true`
52
+ between releases as a deliberate publish latch; a release commit lifts it on
53
+ every publishable package at once.) The compiled ESM in `dist/` uses explicit `.js` relative specifiers
47
54
  so a real Node import resolves without a bundler; the SDK's `pack:test`
48
55
  exercises that path end to end.
@@ -7,7 +7,7 @@
7
7
  * zod-type-only, erased at compile time. The value half of the contract —
8
8
  * error classes, the egress matcher, the storage validators — lives in
9
9
  * `@ekanos/sdk/context`; the in-memory mock in `@ekanos/sdk/testing`.
10
- * Spec: docs/devex/capability-context-proposal.md (all six §7 rulings).
10
+ * The authoring guide for everything below is the `@ekanos/sdk` README.
11
11
  */
12
12
  import type { z } from 'zod';
13
13
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"capability-context.js","sourceRoot":"","sources":["../src/capability-context.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The capability context — the one object partner code receives.\n *\n * Canonical home (moved here from `@ekanos/sdk`'s `context/types.ts` so the\n * definition schema below can reference `IntegrationContext` without a\n * package cycle; `@ekanos/sdk` re-exports every type from here). Pure types,\n * zod-type-only, erased at compile time. The value half of the contract —\n * error classes, the egress matcher, the storage validators — lives in\n * `@ekanos/sdk/context`; the in-memory mock in `@ekanos/sdk/testing`.\n * Spec: docs/devex/capability-context-proposal.md (all six §7 rulings).\n */\nimport type { z } from 'zod';\n\n/**\n * Who is executing. A closed union: a machine context without a grant is\n * unrepresentable rather than merely checked (proposal §2).\n */\nexport type IntegrationActor =\n | { readonly kind: 'user'; readonly userId: string }\n | {\n readonly kind: 'machine';\n readonly tokenId: string;\n readonly createdBy: string | null;\n };\n\n/**\n * One storage key's declaration in EXPLICIT form: the zod schema plus the\n * exposure flags that decide which host surfaces may serve the key.\n *\n * Exposure is opt-in and fails closed. A declaration's keys are server-side\n * by construction — partner code reaches them only through `ctx.storage`,\n * which runs behind the capability layer. The generic browser-readable\n * storage route (`GET /api/integrations/[slug]/storage`) would otherwise\n * turn EVERY declared key into a browser-readable one, including keys a\n * partner reasonably treated as server-only: sync cursors, cached upstream\n * responses, internal bookkeeping. So the route serves a key only when its\n * declaration opts in with `clientReadable: true`.\n */\nexport interface StorageKeyDeclaration<S extends z.ZodType = z.ZodType> {\n readonly schema: S;\n /**\n * Opt in to the generic browser-readable storage route. **Defaults to\n * `false`** — an omitted flag, and the bare-schema declaration form, both\n * mean server-only. Set it only for values the browser legitimately needs\n * and that are safe for any active member of the account to read (widget\n * settings, display preferences). Never for cursors, cached upstream\n * payloads, or anything a `secret` would be a better home for.\n */\n readonly clientReadable?: boolean;\n}\n\n/**\n * How a single storage key may be declared: a bare zod schema (the original\n * form — always server-only), or a `StorageKeyDeclaration` descriptor that\n * carries exposure flags alongside the schema. Both forms validate reads and\n * writes identically; only the descriptor can widen exposure.\n */\nexport type StorageKeyDeclarationInput<S extends z.ZodType = z.ZodType> =\n | S\n | StorageKeyDeclaration<S>;\n\n/**\n * Storage schemas for one scope: key → zod schema, or key → declaration\n * descriptor (ruling 1). Keys are strict — a key with no declared schema\n * cannot be read, written, or deleted; the attempt is a type error where the\n * schema map is statically known, and always a runtime\n * `StorageValidationError`.\n */\nexport type StorageSchemaMap = Record<string, StorageKeyDeclarationInput>;\n\n/**\n * Resolves the zod schema out of either declaration form, so `ScopedStore`\n * stays schema-typed whichever form the author chose.\n */\nexport type StorageKeySchema<D> = [D] extends [z.ZodType]\n ? D\n : D extends StorageKeyDeclaration<infer S>\n ? S\n : D extends z.ZodType\n ? D\n : never;\n\n/**\n * The storage declaration `defineIntegration({ storage })` consumes and\n * `createMockContext({ storageSchemas })` enforces (ruling 1). A scope left\n * undeclared has no usable keys.\n */\nexport interface StorageSchemas {\n account?: StorageSchemaMap;\n user?: StorageSchemaMap;\n}\n\n/**\n * Resolves the schema map for one scope of a declaration. A scope the\n * integration did not declare resolves to an empty map (no key typechecks);\n * only the unparameterized default (`StorageSchemas` itself) stays\n * permissive, for signatures that cannot know the integration's schemas.\n */\nexport type StorageScopeSchemas<\n Schemas extends StorageSchemas,\n Scope extends keyof StorageSchemas,\n> = [Schemas[Scope]] extends [undefined]\n ? Record<never, never>\n : NonNullable<Schemas[Scope]>;\n\nexport interface StorageEntry<T> {\n data: T;\n externalId: string | null;\n expiresAt: string | null;\n updatedAt: string;\n}\n\nexport interface StorageWriteOptions {\n externalId?: string;\n /** ISO-8601 timestamp; an entry past it reads as `null`. */\n expiresAt?: string;\n}\n\n/**\n * One scope of `ctx.storage`, bound to `{account, product}` (and `userId`\n * for the user scope) at construction — there is no id parameter to lie in.\n *\n * Schema-typed (ruling 1): `get` returns the declared schema's output type,\n * `set` validates before write, and a read whose stored data no longer\n * matches the schema throws `StorageValidationError` — schema evolution is\n * handled explicitly (versioned keys or `z.union`), never silently.\n * All methods are async (ruling 4).\n */\nexport interface ScopedStore<\n Schemas extends StorageSchemaMap = StorageSchemaMap,\n> {\n get<K extends keyof Schemas & string>(\n key: K,\n ): Promise<StorageEntry<z.output<StorageKeySchema<Schemas[K]>>> | null>;\n set<K extends keyof Schemas & string>(\n key: K,\n data: z.input<StorageKeySchema<Schemas[K]>>,\n options?: StorageWriteOptions,\n ): Promise<void>;\n delete<K extends keyof Schemas & string>(key: K): Promise<void>;\n}\n\nexport interface IntegrationStorage<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n /** account_product_data, bound to {accountId, productId}. */\n readonly account: ScopedStore<StorageScopeSchemas<Schemas, 'account'>>;\n /**\n * user_product_data, bound to {userId, accountId, productId}.\n * Throws for machine actors.\n */\n readonly user: ScopedStore<StorageScopeSchemas<Schemas, 'user'>>;\n}\n\n/**\n * Reads from the credential set the host resolved (account → source →\n * global tiers + hydrated activation secrets) before partner code ran.\n * All methods are async (ruling 4).\n */\nexport interface IntegrationSecrets {\n /** A named field of the resolved credential set + hydrated activation secrets. */\n get(name: string): Promise<string | null>;\n /** All resolved secret names (values not included) — for capability probing. */\n names(): Promise<string[]>;\n /**\n * Write back a rotated value (OAuth refresh). Tier-bound (ruling 2):\n * writes go only to the account-tier store bound to {account, product} at\n * construction. Source- and global-tier credentials (admin-issued) are\n * structurally unreachable from this path; a write against a name that\n * resolves only from those tiers throws `SecretAccessError`.\n */\n set(name: string, value: string): Promise<void>;\n}\n\n/**\n * Host-allowlisted egress (ruling 3): origin-only allowlist with explicit\n * `*.` subdomain wildcards, enforced against the RESOLVED URL's origin —\n * redirect targets included. A denied call throws `EgressDeniedError`\n * synchronously, before any network I/O.\n */\nexport type IntegrationFetch = (\n input: string | URL,\n init?: RequestInit,\n) => Promise<Response>;\n\n/**\n * Structured, pino-style. The host pre-binds `{integration, accountId,\n * actor}` so every partner log line is attributable.\n */\nexport interface IntegrationLogger {\n debug(context: Record<string, unknown>, message: string): void;\n info(context: Record<string, unknown>, message: string): void;\n warn(context: Record<string, unknown>, message: string): void;\n error(context: Record<string, unknown>, message: string): void;\n}\n\n/**\n * One host-constructed object whose every method is already scoped to\n * `{account, integration}` before partner code runs. Handed to MCP tool\n * `run()` and SDK server handlers alike (ruling 5: one type, one mock).\n */\nexport interface IntegrationContext<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n /** Identity facts — read-only, informational. Authorization already happened. */\n readonly accountId: string;\n readonly accountSlug: string | null;\n readonly userId: string | null; // null for machine actors\n readonly actor: IntegrationActor;\n readonly sourceId: string | null;\n readonly integration: { readonly slug: string; readonly productId: string };\n readonly timezone: string | null;\n\n readonly storage: IntegrationStorage<Schemas>;\n readonly secrets: IntegrationSecrets;\n readonly fetch: IntegrationFetch;\n readonly logger: IntegrationLogger;\n}\n"]}
1
+ {"version":3,"file":"capability-context.js","sourceRoot":"","sources":["../src/capability-context.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The capability context — the one object partner code receives.\n *\n * Canonical home (moved here from `@ekanos/sdk`'s `context/types.ts` so the\n * definition schema below can reference `IntegrationContext` without a\n * package cycle; `@ekanos/sdk` re-exports every type from here). Pure types,\n * zod-type-only, erased at compile time. The value half of the contract —\n * error classes, the egress matcher, the storage validators — lives in\n * `@ekanos/sdk/context`; the in-memory mock in `@ekanos/sdk/testing`.\n * The authoring guide for everything below is the `@ekanos/sdk` README.\n */\nimport type { z } from 'zod';\n\n/**\n * Who is executing. A closed union: a machine context without a grant is\n * unrepresentable rather than merely checked (proposal §2).\n */\nexport type IntegrationActor =\n | { readonly kind: 'user'; readonly userId: string }\n | {\n readonly kind: 'machine';\n readonly tokenId: string;\n readonly createdBy: string | null;\n };\n\n/**\n * One storage key's declaration in EXPLICIT form: the zod schema plus the\n * exposure flags that decide which host surfaces may serve the key.\n *\n * Exposure is opt-in and fails closed. A declaration's keys are server-side\n * by construction — partner code reaches them only through `ctx.storage`,\n * which runs behind the capability layer. The generic browser-readable\n * storage route (`GET /api/integrations/[slug]/storage`) would otherwise\n * turn EVERY declared key into a browser-readable one, including keys a\n * partner reasonably treated as server-only: sync cursors, cached upstream\n * responses, internal bookkeeping. So the route serves a key only when its\n * declaration opts in with `clientReadable: true`.\n */\nexport interface StorageKeyDeclaration<S extends z.ZodType = z.ZodType> {\n readonly schema: S;\n /**\n * Opt in to the generic browser-readable storage route. **Defaults to\n * `false`** — an omitted flag, and the bare-schema declaration form, both\n * mean server-only. Set it only for values the browser legitimately needs\n * and that are safe for any active member of the account to read (widget\n * settings, display preferences). Never for cursors, cached upstream\n * payloads, or anything a `secret` would be a better home for.\n */\n readonly clientReadable?: boolean;\n}\n\n/**\n * How a single storage key may be declared: a bare zod schema (the original\n * form — always server-only), or a `StorageKeyDeclaration` descriptor that\n * carries exposure flags alongside the schema. Both forms validate reads and\n * writes identically; only the descriptor can widen exposure.\n */\nexport type StorageKeyDeclarationInput<S extends z.ZodType = z.ZodType> =\n | S\n | StorageKeyDeclaration<S>;\n\n/**\n * Storage schemas for one scope: key → zod schema, or key → declaration\n * descriptor (ruling 1). Keys are strict — a key with no declared schema\n * cannot be read, written, or deleted; the attempt is a type error where the\n * schema map is statically known, and always a runtime\n * `StorageValidationError`.\n */\nexport type StorageSchemaMap = Record<string, StorageKeyDeclarationInput>;\n\n/**\n * Resolves the zod schema out of either declaration form, so `ScopedStore`\n * stays schema-typed whichever form the author chose.\n */\nexport type StorageKeySchema<D> = [D] extends [z.ZodType]\n ? D\n : D extends StorageKeyDeclaration<infer S>\n ? S\n : D extends z.ZodType\n ? D\n : never;\n\n/**\n * The storage declaration `defineIntegration({ storage })` consumes and\n * `createMockContext({ storageSchemas })` enforces (ruling 1). A scope left\n * undeclared has no usable keys.\n */\nexport interface StorageSchemas {\n account?: StorageSchemaMap;\n user?: StorageSchemaMap;\n}\n\n/**\n * Resolves the schema map for one scope of a declaration. A scope the\n * integration did not declare resolves to an empty map (no key typechecks);\n * only the unparameterized default (`StorageSchemas` itself) stays\n * permissive, for signatures that cannot know the integration's schemas.\n */\nexport type StorageScopeSchemas<\n Schemas extends StorageSchemas,\n Scope extends keyof StorageSchemas,\n> = [Schemas[Scope]] extends [undefined]\n ? Record<never, never>\n : NonNullable<Schemas[Scope]>;\n\nexport interface StorageEntry<T> {\n data: T;\n externalId: string | null;\n expiresAt: string | null;\n updatedAt: string;\n}\n\nexport interface StorageWriteOptions {\n externalId?: string;\n /** ISO-8601 timestamp; an entry past it reads as `null`. */\n expiresAt?: string;\n}\n\n/**\n * One scope of `ctx.storage`, bound to `{account, product}` (and `userId`\n * for the user scope) at construction — there is no id parameter to lie in.\n *\n * Schema-typed (ruling 1): `get` returns the declared schema's output type,\n * `set` validates before write, and a read whose stored data no longer\n * matches the schema throws `StorageValidationError` — schema evolution is\n * handled explicitly (versioned keys or `z.union`), never silently.\n * All methods are async (ruling 4).\n */\nexport interface ScopedStore<\n Schemas extends StorageSchemaMap = StorageSchemaMap,\n> {\n get<K extends keyof Schemas & string>(\n key: K,\n ): Promise<StorageEntry<z.output<StorageKeySchema<Schemas[K]>>> | null>;\n set<K extends keyof Schemas & string>(\n key: K,\n data: z.input<StorageKeySchema<Schemas[K]>>,\n options?: StorageWriteOptions,\n ): Promise<void>;\n delete<K extends keyof Schemas & string>(key: K): Promise<void>;\n}\n\nexport interface IntegrationStorage<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n /** account_product_data, bound to {accountId, productId}. */\n readonly account: ScopedStore<StorageScopeSchemas<Schemas, 'account'>>;\n /**\n * user_product_data, bound to {userId, accountId, productId}.\n * Throws for machine actors.\n */\n readonly user: ScopedStore<StorageScopeSchemas<Schemas, 'user'>>;\n}\n\n/**\n * Reads from the credential set the host resolved (account → source →\n * global tiers + hydrated activation secrets) before partner code ran.\n * All methods are async (ruling 4).\n */\nexport interface IntegrationSecrets {\n /** A named field of the resolved credential set + hydrated activation secrets. */\n get(name: string): Promise<string | null>;\n /** All resolved secret names (values not included) — for capability probing. */\n names(): Promise<string[]>;\n /**\n * Write back a rotated value (OAuth refresh). Tier-bound (ruling 2):\n * writes go only to the account-tier store bound to {account, product} at\n * construction. Source- and global-tier credentials (admin-issued) are\n * structurally unreachable from this path; a write against a name that\n * resolves only from those tiers throws `SecretAccessError`.\n */\n set(name: string, value: string): Promise<void>;\n}\n\n/**\n * Host-allowlisted egress (ruling 3): origin-only allowlist with explicit\n * `*.` subdomain wildcards, enforced against the RESOLVED URL's origin —\n * redirect targets included. A denied call throws `EgressDeniedError`\n * synchronously, before any network I/O.\n */\nexport type IntegrationFetch = (\n input: string | URL,\n init?: RequestInit,\n) => Promise<Response>;\n\n/**\n * Structured, pino-style. The host pre-binds `{integration, accountId,\n * actor}` so every partner log line is attributable.\n */\nexport interface IntegrationLogger {\n debug(context: Record<string, unknown>, message: string): void;\n info(context: Record<string, unknown>, message: string): void;\n warn(context: Record<string, unknown>, message: string): void;\n error(context: Record<string, unknown>, message: string): void;\n}\n\n/**\n * One host-constructed object whose every method is already scoped to\n * `{account, integration}` before partner code runs. Handed to MCP tool\n * `run()` and SDK server handlers alike (ruling 5: one type, one mock).\n */\nexport interface IntegrationContext<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n /** Identity facts — read-only, informational. Authorization already happened. */\n readonly accountId: string;\n readonly accountSlug: string | null;\n readonly userId: string | null; // null for machine actors\n readonly actor: IntegrationActor;\n readonly sourceId: string | null;\n readonly integration: { readonly slug: string; readonly productId: string };\n readonly timezone: string | null;\n\n readonly storage: IntegrationStorage<Schemas>;\n readonly secrets: IntegrationSecrets;\n readonly fetch: IntegrationFetch;\n readonly logger: IntegrationLogger;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  * Relative specifiers are extensionless here on purpose. Workspace consumers
14
14
  * resolve this package's raw `src/*.ts` through a bundler, so a `./x.js`
15
15
  * specifier would point at a file that does not exist. The published ESM still
16
- * needs the extension for a real Node import, so `scripts/rewrite-esm-specifiers.mjs`
16
+ * needs the extension for a real Node import, so the build's specifier-rewrite step
17
17
  * adds it to `dist/` after the compiler runs — see that file for the full
18
18
  * rationale. Do not hand-write `.js` extensions back into this source tree.
19
19
  */
@@ -22,5 +22,6 @@ export type { ComponentReference } from './component-reference.js';
22
22
  export { isComponentReference } from './component-reference.js';
23
23
  export { DNS_NAMESPACE, EKANOS_INTEGRATION_NAMESPACE, derivePlanId, deriveProductId, uuidv5, } from './product-id.js';
24
24
  export { WorkspaceTargetSchema, WorkspaceTargetListSchema, WorkspaceTargetSlugSchema, WorkspaceTargetLayoutSchema, createWorkspaceTargetSchema, createWorkspaceTargetListSchema, normalizeWorkspaceTargets, type WorkspaceTargetSchemaOptions, type WorkspaceTargetDefinition, type ResolvedWorkspaceTarget, } from './workspace-target.js';
25
- export { IntegrationDefinitionSchema, parseIntegrationDefinition, validateIntegrationDefinitions, getDiscoveredToolName, assertPlainDeclaration, deepFreezeDefinition, } from './integration-definition.js';
25
+ export { IntegrationDefinitionSchema, parseIntegrationDefinition, validateIntegrationDefinitions, collectDefinitionFindings, collectCollisionFindings, getDiscoveredToolName, assertPlainDeclaration, deepFreezeDefinition, ACCOUNT_STORAGE_DATA_TYPES, USER_STORAGE_DATA_TYPES, RESERVED_STORAGE_DATA_TYPES, } from './integration-definition.js';
26
+ export type { Finding, CollectDefinitionOptions, } from './integration-definition.js';
26
27
  export type { IntegrationDefinition, IntegrationComponentDeclarations, IntegrationProposals, IntegrationCapabilityDeclaration, IntegrationPermissionDeclaration, PartnerWidgetDeclaration, PartnerToolModule, PartnerToolParameters, ToolClassificationProposal, DefinitionCollisionInput, FirstPartyInventory, JsonValue, PartnerWebhookDeclaration, WebhookSignatureDeclaration, WebhookEvent, WebhookResult, PartnerScheduleDeclaration, ScheduleInvocation, ScheduleResult, PartnerOAuthDeclaration, OAuthProviderDeclaration, OAuthTokens, } from './integration-definition.js';
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@
13
13
  * Relative specifiers are extensionless here on purpose. Workspace consumers
14
14
  * resolve this package's raw `src/*.ts` through a bundler, so a `./x.js`
15
15
  * specifier would point at a file that does not exist. The published ESM still
16
- * needs the extension for a real Node import, so `scripts/rewrite-esm-specifiers.mjs`
16
+ * needs the extension for a real Node import, so the build's specifier-rewrite step
17
17
  * adds it to `dist/` after the compiler runs — see that file for the full
18
18
  * rationale. Do not hand-write `.js` extensions back into this source tree.
19
19
  */
@@ -25,5 +25,13 @@ export { DNS_NAMESPACE, EKANOS_INTEGRATION_NAMESPACE, derivePlanId, deriveProduc
25
25
  // Workspace-target contract (F10): one schema, host injects icon validity
26
26
  export { WorkspaceTargetSchema, WorkspaceTargetListSchema, WorkspaceTargetSlugSchema, WorkspaceTargetLayoutSchema, createWorkspaceTargetSchema, createWorkspaceTargetListSchema, normalizeWorkspaceTargets, } from './workspace-target.js';
27
27
  // The integration-definition contract
28
- export { IntegrationDefinitionSchema, parseIntegrationDefinition, validateIntegrationDefinitions, getDiscoveredToolName, assertPlainDeclaration, deepFreezeDefinition, } from './integration-definition.js';
28
+ export { IntegrationDefinitionSchema, parseIntegrationDefinition, validateIntegrationDefinitions,
29
+ // Non-throwing sibling collectors (0.2.0 surface add): recover structured
30
+ // findings instead of a thrown, pre-formatted string. `@ekanos/cli validate`
31
+ // consumes these; the throwing functions above are built on the same rules.
32
+ collectDefinitionFindings, collectCollisionFindings, getDiscoveredToolName, assertPlainDeclaration, deepFreezeDefinition,
33
+ // The storage data_type allowlists. Exported because the HOST imports them
34
+ // for its runtime `ctx.storage` check — one list, validated at authoring
35
+ // time here and at access time there, so the two cannot drift.
36
+ ACCOUNT_STORAGE_DATA_TYPES, USER_STORAGE_DATA_TYPES, RESERVED_STORAGE_DATA_TYPES, } from './integration-definition.js';
29
37
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAsBH,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,6EAA6E;AAC7E,6EAA6E;AAC7E,kCAAkC;AAClC,OAAO,EACL,aAAa,EACb,4BAA4B,EAC5B,YAAY,EACZ,eAAe,EACf,MAAM,GACP,MAAM,cAAc,CAAC;AAEtB,0EAA0E;AAC1E,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,yBAAyB,EACzB,2BAA2B,EAC3B,2BAA2B,EAC3B,+BAA+B,EAC/B,yBAAyB,GAI1B,MAAM,oBAAoB,CAAC;AAE5B,sCAAsC;AACtC,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,EAC1B,8BAA8B,EAC9B,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC","sourcesContent":["/**\n * @ekanos/integration-schema — the canonical, dependency-pure contract for\n * Ekanos integration definitions.\n *\n * ONE zod schema, ONE set of types, imported by BOTH `@ekanos/sdk`\n * (authoring: `defineIntegration()`) and `@kit/integrations-core` (host trust\n * boundary: `registerPartnerIntegration()` re-parses against this schema).\n * This package depends on nothing but zod, so neither consumer forms a cycle\n * and there is no hand-written structural twin to drift (F3/F9/F10).\n *\n * Depends on: zod. Nothing else — no `@kit/*`, no React, no `server-only`.\n *\n * Relative specifiers are extensionless here on purpose. Workspace consumers\n * resolve this package's raw `src/*.ts` through a bundler, so a `./x.js`\n * specifier would point at a file that does not exist. The published ESM still\n * needs the extension for a real Node import, so `scripts/rewrite-esm-specifiers.mjs`\n * adds it to `dist/` after the compiler runs — see that file for the full\n * rationale. Do not hand-write `.js` extensions back into this source tree.\n */\n\n// Capability-context types (canonical home; @ekanos/sdk re-exports these)\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 './capability-context';\n\nexport type { ComponentReference } from './component-reference';\nexport { isComponentReference } from './component-reference';\n\n// Deterministic slug → uuid derivation. One slug, one product id, everywhere\n// and forever — the SDK, the seed generator and any future gate all agree by\n// computing rather than choosing.\nexport {\n DNS_NAMESPACE,\n EKANOS_INTEGRATION_NAMESPACE,\n derivePlanId,\n deriveProductId,\n uuidv5,\n} from './product-id';\n\n// Workspace-target contract (F10): one schema, host injects icon validity\nexport {\n WorkspaceTargetSchema,\n WorkspaceTargetListSchema,\n WorkspaceTargetSlugSchema,\n WorkspaceTargetLayoutSchema,\n createWorkspaceTargetSchema,\n createWorkspaceTargetListSchema,\n normalizeWorkspaceTargets,\n type WorkspaceTargetSchemaOptions,\n type WorkspaceTargetDefinition,\n type ResolvedWorkspaceTarget,\n} from './workspace-target';\n\n// The integration-definition contract\nexport {\n IntegrationDefinitionSchema,\n parseIntegrationDefinition,\n validateIntegrationDefinitions,\n getDiscoveredToolName,\n assertPlainDeclaration,\n deepFreezeDefinition,\n} from './integration-definition';\n\nexport type {\n IntegrationDefinition,\n IntegrationComponentDeclarations,\n IntegrationProposals,\n IntegrationCapabilityDeclaration,\n IntegrationPermissionDeclaration,\n PartnerWidgetDeclaration,\n PartnerToolModule,\n PartnerToolParameters,\n ToolClassificationProposal,\n DefinitionCollisionInput,\n FirstPartyInventory,\n JsonValue,\n // Event surfaces (webhooks, schedules, OAuth) — interface-first: the local\n // harness executes them today; the host transports bind to them later.\n PartnerWebhookDeclaration,\n WebhookSignatureDeclaration,\n WebhookEvent,\n WebhookResult,\n PartnerScheduleDeclaration,\n ScheduleInvocation,\n ScheduleResult,\n PartnerOAuthDeclaration,\n OAuthProviderDeclaration,\n OAuthTokens,\n} from './integration-definition';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAsBH,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,6EAA6E;AAC7E,6EAA6E;AAC7E,kCAAkC;AAClC,OAAO,EACL,aAAa,EACb,4BAA4B,EAC5B,YAAY,EACZ,eAAe,EACf,MAAM,GACP,MAAM,cAAc,CAAC;AAEtB,0EAA0E;AAC1E,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,yBAAyB,EACzB,2BAA2B,EAC3B,2BAA2B,EAC3B,+BAA+B,EAC/B,yBAAyB,GAI1B,MAAM,oBAAoB,CAAC;AAE5B,sCAAsC;AACtC,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,EAC1B,8BAA8B;AAC9B,0EAA0E;AAC1E,6EAA6E;AAC7E,4EAA4E;AAC5E,yBAAyB,EACzB,wBAAwB,EACxB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB;AACpB,2EAA2E;AAC3E,yEAAyE;AACzE,+DAA+D;AAC/D,0BAA0B,EAC1B,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,0BAA0B,CAAC","sourcesContent":["/**\n * @ekanos/integration-schema — the canonical, dependency-pure contract for\n * Ekanos integration definitions.\n *\n * ONE zod schema, ONE set of types, imported by BOTH `@ekanos/sdk`\n * (authoring: `defineIntegration()`) and `@kit/integrations-core` (host trust\n * boundary: `registerPartnerIntegration()` re-parses against this schema).\n * This package depends on nothing but zod, so neither consumer forms a cycle\n * and there is no hand-written structural twin to drift (F3/F9/F10).\n *\n * Depends on: zod. Nothing else — no `@kit/*`, no React, no `server-only`.\n *\n * Relative specifiers are extensionless here on purpose. Workspace consumers\n * resolve this package's raw `src/*.ts` through a bundler, so a `./x.js`\n * specifier would point at a file that does not exist. The published ESM still\n * needs the extension for a real Node import, so the build's specifier-rewrite step\n * adds it to `dist/` after the compiler runs — see that file for the full\n * rationale. Do not hand-write `.js` extensions back into this source tree.\n */\n\n// Capability-context types (canonical home; @ekanos/sdk re-exports these)\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 './capability-context';\n\nexport type { ComponentReference } from './component-reference';\nexport { isComponentReference } from './component-reference';\n\n// Deterministic slug → uuid derivation. One slug, one product id, everywhere\n// and forever — the SDK, the seed generator and any future gate all agree by\n// computing rather than choosing.\nexport {\n DNS_NAMESPACE,\n EKANOS_INTEGRATION_NAMESPACE,\n derivePlanId,\n deriveProductId,\n uuidv5,\n} from './product-id';\n\n// Workspace-target contract (F10): one schema, host injects icon validity\nexport {\n WorkspaceTargetSchema,\n WorkspaceTargetListSchema,\n WorkspaceTargetSlugSchema,\n WorkspaceTargetLayoutSchema,\n createWorkspaceTargetSchema,\n createWorkspaceTargetListSchema,\n normalizeWorkspaceTargets,\n type WorkspaceTargetSchemaOptions,\n type WorkspaceTargetDefinition,\n type ResolvedWorkspaceTarget,\n} from './workspace-target';\n\n// The integration-definition contract\nexport {\n IntegrationDefinitionSchema,\n parseIntegrationDefinition,\n validateIntegrationDefinitions,\n // Non-throwing sibling collectors (0.2.0 surface add): recover structured\n // findings instead of a thrown, pre-formatted string. `@ekanos/cli validate`\n // consumes these; the throwing functions above are built on the same rules.\n collectDefinitionFindings,\n collectCollisionFindings,\n getDiscoveredToolName,\n assertPlainDeclaration,\n deepFreezeDefinition,\n // The storage data_type allowlists. Exported because the HOST imports them\n // for its runtime `ctx.storage` check — one list, validated at authoring\n // time here and at access time there, so the two cannot drift.\n ACCOUNT_STORAGE_DATA_TYPES,\n USER_STORAGE_DATA_TYPES,\n RESERVED_STORAGE_DATA_TYPES,\n} from './integration-definition';\n\nexport type {\n Finding,\n CollectDefinitionOptions,\n} from './integration-definition';\n\nexport type {\n IntegrationDefinition,\n IntegrationComponentDeclarations,\n IntegrationProposals,\n IntegrationCapabilityDeclaration,\n IntegrationPermissionDeclaration,\n PartnerWidgetDeclaration,\n PartnerToolModule,\n PartnerToolParameters,\n ToolClassificationProposal,\n DefinitionCollisionInput,\n FirstPartyInventory,\n JsonValue,\n // Event surfaces (webhooks, schedules, OAuth) — interface-first: the local\n // harness executes them today; the host transports bind to them later.\n PartnerWebhookDeclaration,\n WebhookSignatureDeclaration,\n WebhookEvent,\n WebhookResult,\n PartnerScheduleDeclaration,\n ScheduleInvocation,\n ScheduleResult,\n PartnerOAuthDeclaration,\n OAuthProviderDeclaration,\n OAuthTokens,\n} from './integration-definition';\n"]}
@@ -17,6 +17,37 @@ type JsonPrimitive = string | number | boolean | null;
17
17
  export type JsonValue = JsonPrimitive | JsonValue[] | {
18
18
  [key: string]: JsonValue;
19
19
  };
20
+ /**
21
+ * The `data_type` values each storage scope permits.
22
+ *
23
+ * THIS IS THE SOURCE OF TRUTH, and it lives here rather than in the host
24
+ * because this package is the one a partner's `defineIntegration()` validates
25
+ * against — a declaration naming an impossible data_type should fail at
26
+ * authoring time, not on the first `ctx.storage` call in production. The host
27
+ * (`apps/web/lib/server/integration-context.ts`) imports these same arrays for
28
+ * its runtime check, so the two cannot drift.
29
+ *
30
+ * They mirror the DB CHECK constraints on `account_product_data` and
31
+ * `user_product_data` (`apps/web/supabase/schemas/26-integrations.sql`), with
32
+ * one deliberate subtraction: see `RESERVED_STORAGE_DATA_TYPES`.
33
+ */
34
+ export declare const ACCOUNT_STORAGE_DATA_TYPES: readonly ["activation", "settings", "metrics_summary", "sync_state", "cache"];
35
+ export declare const USER_STORAGE_DATA_TYPES: readonly ["config", "preferences", "cache"];
36
+ /**
37
+ * In the DB CHECK but NEVER addressable through `ctx.storage`.
38
+ *
39
+ * `secret` rows are host-managed per-name credential rows whose values live in
40
+ * Vault; partner code reaches them only through `ctx.secrets`, which never
41
+ * exposes a value or a vault id. Letting a partner DECLARE `secret` storage
42
+ * would hand them a key that collides with host-managed secret rows, so it is
43
+ * rejected here with its own message rather than the generic "not an allowed
44
+ * data_type" — an author who wrote `secret` meant something specific and needs
45
+ * to be pointed at `ctx.secrets`.
46
+ *
47
+ * Kept as its own list rather than simply omitted from the arrays above so the
48
+ * reason survives: `secret` is a real column value, not a typo.
49
+ */
50
+ export declare const RESERVED_STORAGE_DATA_TYPES: readonly ["secret"];
20
51
  declare const capabilitySchema: z.ZodObject<{
21
52
  label: z.ZodString;
22
53
  description: z.ZodString;
@@ -854,8 +885,8 @@ export declare const IntegrationDefinitionSchema: z.ZodEffects<z.ZodObject<{
854
885
  outputExample?: Record<string, JsonValue> | undefined;
855
886
  }>, "many">>;
856
887
  storage: z.ZodOptional<z.ZodObject<{
857
- account: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEffects<z.ZodType<StorageKeyDeclarationInput, z.ZodTypeDef, StorageKeyDeclarationInput>, StorageKeyDeclarationInput, StorageKeyDeclarationInput>>>;
858
- user: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEffects<z.ZodType<StorageKeyDeclarationInput, z.ZodTypeDef, StorageKeyDeclarationInput>, StorageKeyDeclarationInput, StorageKeyDeclarationInput>>>;
888
+ account: z.ZodOptional<z.ZodRecord<z.ZodEffects<z.ZodString, string, string>, z.ZodEffects<z.ZodType<StorageKeyDeclarationInput, z.ZodTypeDef, StorageKeyDeclarationInput>, StorageKeyDeclarationInput, StorageKeyDeclarationInput>>>;
889
+ user: z.ZodOptional<z.ZodRecord<z.ZodEffects<z.ZodString, string, string>, z.ZodEffects<z.ZodType<StorageKeyDeclarationInput, z.ZodTypeDef, StorageKeyDeclarationInput>, StorageKeyDeclarationInput, StorageKeyDeclarationInput>>>;
859
890
  }, "strict", z.ZodTypeAny, {
860
891
  user?: Record<string, StorageKeyDeclarationInput> | undefined;
861
892
  account?: Record<string, StorageKeyDeclarationInput> | undefined;
@@ -1681,6 +1712,38 @@ export interface IntegrationDefinition<Schemas extends StorageSchemas = StorageS
1681
1712
  oauth?: PartnerOAuthDeclaration<Schemas>;
1682
1713
  proposes?: IntegrationProposals;
1683
1714
  }
1715
+ /**
1716
+ * One structured validation result. This is the committed shape the CLI and
1717
+ * any other tooling consumes — the collectors below return arrays of these,
1718
+ * and the throwing entry points (`parseIntegrationDefinition`,
1719
+ * `validateIntegrationDefinitions`) are built on the exact same rules so there
1720
+ * is one rule set, not two.
1721
+ *
1722
+ * `line` is deliberately optional and usually ABSENT: zod reports a `path`
1723
+ * (`components.widgets[2].id`), not a byte offset into a source file, and
1724
+ * fabricating a line number would be a lie. The zod path lives in `message`;
1725
+ * `file` names the module the definition was loaded from when the caller knows
1726
+ * it. `hint` is always a non-empty, imperative remediation instruction.
1727
+ */
1728
+ export interface Finding {
1729
+ check: string;
1730
+ severity: 'error' | 'warn' | 'info';
1731
+ file?: string;
1732
+ line?: number;
1733
+ message: string;
1734
+ hint: string;
1735
+ }
1736
+ export interface CollectDefinitionOptions {
1737
+ /** The module the definition was loaded from, stamped onto every finding. */
1738
+ file?: string;
1739
+ }
1740
+ /**
1741
+ * Non-throwing sibling of `parseIntegrationDefinition`: validates a single
1742
+ * integration definition against the canonical schema and returns structured
1743
+ * findings instead of throwing a pre-formatted string. An empty array means
1744
+ * the definition is valid. Used by `@ekanos/cli validate`.
1745
+ */
1746
+ export declare function collectDefinitionFindings(input: unknown, options?: CollectDefinitionOptions): Finding[];
1684
1747
  /**
1685
1748
  * F4: reject accessor/proxy/class-instance/cyclic declaration containers
1686
1749
  * before parsing. An object with getters (or a proxy) can return validated
@@ -1721,8 +1784,8 @@ export declare function parseIntegrationDefinition(input: unknown): IntegrationD
1721
1784
  * to the same EFFECTIVE name (`{slug:"foo",tool:"bar_baz"}` and
1722
1785
  * `{slug:"foo-bar",tool:"baz"}` both become `foo_bar_baz`), so collision
1723
1786
  * checking MUST compare effective names, and runtime discovery MUST throw on a
1724
- * duplicate assignment. Both call this one helper
1725
- * (`packages/agents/src/tools/tool-discovery.ts`).
1787
+ * duplicate assignment. Host-side tool discovery calls this same helper, so
1788
+ * there is one definition of the effective name.
1726
1789
  */
1727
1790
  export declare function getDiscoveredToolName(slug: string, rawName: string): string;
1728
1791
  /**
@@ -1758,14 +1821,11 @@ export interface FirstPartyInventory {
1758
1821
  toolNames?: readonly string[];
1759
1822
  }
1760
1823
  /**
1761
- * Detects cross-definition collisions (duplicate slugs, widget ids, tool
1762
- * names across the partner set) AND collisions against the first-party
1763
- * inventory, throwing one error listing EVERY collision.
1764
- *
1765
- * This is the build-time gate the host registry deliberately lacks:
1766
- * `integrationRegistry.register()` keys by slug via `Map.set` and silently
1767
- * OVERWRITES, and duplicate widget/tool ids resolve last- or
1768
- * first-registration-wins by import order.
1824
+ * Non-throwing sibling of `validateIntegrationDefinitions`: returns structured
1825
+ * collision findings across the partner set (and against the first-party
1826
+ * inventory) instead of throwing. An empty array means no collisions. Used by
1827
+ * `@ekanos/cli validate`.
1769
1828
  */
1829
+ export declare function collectCollisionFindings(definitions: readonly DefinitionCollisionInput[], firstParty?: FirstPartyInventory): Finding[];
1770
1830
  export declare function validateIntegrationDefinitions(definitions: readonly DefinitionCollisionInput[], firstParty?: FirstPartyInventory): void;
1771
1831
  export {};