@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 +19 -12
- package/dist/capability-context.d.ts +1 -1
- package/dist/capability-context.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +10 -2
- package/dist/index.js.map +1 -1
- package/dist/integration-definition.d.ts +72 -12
- package/dist/integration-definition.js +239 -60
- package/dist/integration-definition.js.map +1 -1
- package/dist/product-id.d.ts +1 -1
- package/dist/product-id.js +1 -1
- package/dist/product-id.js.map +1 -1
- package/dist/workspace-target.d.ts +1 -1
- package/dist/workspace-target.js +1 -1
- package/dist/workspace-target.js.map +1 -1
- package/package.json +5 -5
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
|
-
|
|
14
|
-
resolves at once:
|
|
13
|
+
A security review found three problems a shared schema resolves at once:
|
|
15
14
|
|
|
16
|
-
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
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
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
*
|
|
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 *
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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.
|
|
1725
|
-
*
|
|
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
|
-
*
|
|
1762
|
-
*
|
|
1763
|
-
* inventory
|
|
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 {};
|