Draft plugin architecture and connector docs (#1474)

* docs: outline plugin architecture and connector design

* feat(den-api): add plugin architecture admin and webhook foundation

* refactor(den-api): rename plugin routes and split github connector env

* fix(den-api): scope plugin connector records by organization

---------

Co-authored-by: src-opn <src-opn@users.noreply.github.com>
This commit is contained in:
Source Open
2026-04-17 16:10:23 -07:00
committed by GitHub
parent f19b3fe680
commit 2c4bd553cb
33 changed files with 9613 additions and 0 deletions

View File

@@ -16,6 +16,7 @@ import { registerAuthRoutes } from "./routes/auth/index.js"
import { registerMeRoutes } from "./routes/me/index.js"
import { registerOrgRoutes } from "./routes/org/index.js"
import { registerVersionRoutes } from "./routes/version/index.js"
import { registerWebhookRoutes } from "./routes/webhooks/index.js"
import { registerWorkerRoutes } from "./routes/workers/index.js"
import type { AuthContextVariables } from "./session.js"
import { sessionMiddleware } from "./session.js"
@@ -108,6 +109,7 @@ registerAuthRoutes(app)
registerMeRoutes(app)
registerOrgRoutes(app)
registerVersionRoutes(app)
registerWebhookRoutes(app)
registerWorkerRoutes(app)
app.get(

View File

@@ -13,6 +13,11 @@ const EnvSchema = z.object({
DEN_BETTER_AUTH_TRUSTED_ORIGINS: z.string().optional(),
GITHUB_CLIENT_ID: z.string().optional(),
GITHUB_CLIENT_SECRET: z.string().optional(),
GITHUB_CONNECTOR_APP_ID: z.string().optional(),
GITHUB_CONNECTOR_APP_CLIENT_ID: z.string().optional(),
GITHUB_CONNECTOR_APP_CLIENT_SECRET: z.string().optional(),
GITHUB_CONNECTOR_APP_PRIVATE_KEY: z.string().optional(),
GITHUB_CONNECTOR_APP_WEBHOOK_SECRET: z.string().optional(),
GOOGLE_CLIENT_ID: z.string().optional(),
GOOGLE_CLIENT_SECRET: z.string().optional(),
LOOPS_API_KEY: z.string().optional(),
@@ -158,6 +163,13 @@ export const env = {
clientId: optionalString(parsed.GITHUB_CLIENT_ID),
clientSecret: optionalString(parsed.GITHUB_CLIENT_SECRET),
},
githubConnectorApp: {
appId: optionalString(parsed.GITHUB_CONNECTOR_APP_ID),
clientId: optionalString(parsed.GITHUB_CONNECTOR_APP_CLIENT_ID),
clientSecret: optionalString(parsed.GITHUB_CONNECTOR_APP_CLIENT_SECRET),
privateKey: optionalString(parsed.GITHUB_CONNECTOR_APP_PRIVATE_KEY),
webhookSecret: optionalString(parsed.GITHUB_CONNECTOR_APP_WEBHOOK_SECRET),
},
google: {
clientId: optionalString(parsed.GOOGLE_CLIENT_ID),
clientSecret: optionalString(parsed.GOOGLE_CLIENT_SECRET),

View File

@@ -5,6 +5,7 @@ import { registerOrgCoreRoutes } from "./core.js"
import { registerOrgInvitationRoutes } from "./invitations.js"
import { registerOrgLlmProviderRoutes } from "./llm-providers.js"
import { registerOrgMemberRoutes } from "./members.js"
import { registerPluginArchRoutes } from "./plugin-system/routes.js"
import { registerOrgRoleRoutes } from "./roles.js"
import { registerOrgSkillRoutes } from "./skills.js"
import { registerOrgTeamRoutes } from "./teams.js"
@@ -16,6 +17,7 @@ export function registerOrgRoutes<T extends { Variables: OrgRouteVariables }>(ap
registerOrgInvitationRoutes(app)
registerOrgLlmProviderRoutes(app)
registerOrgMemberRoutes(app)
registerPluginArchRoutes(app)
registerOrgRoleRoutes(app)
registerOrgSkillRoutes(app)
registerOrgTeamRoutes(app)

View File

@@ -0,0 +1,230 @@
import { and, eq, inArray, isNull } from "@openwork-ee/den-db/drizzle"
import {
ConfigObjectAccessGrantTable,
ConfigObjectTable,
ConnectorInstanceAccessGrantTable,
ConnectorInstanceTable,
PluginAccessGrantTable,
PluginConfigObjectTable,
PluginTable,
} from "@openwork-ee/den-db/schema"
import type { MemberTeamSummary, OrganizationContext } from "../../../orgs.js"
import { db } from "../../../db.js"
import { memberHasRole } from "../shared.js"
export type PluginArchResourceKind = "config_object" | "connector_instance" | "plugin"
export type PluginArchRole = "viewer" | "editor" | "manager"
export type PluginArchCapability = "config_object.create" | "connector_account.create" | "connector_instance.create" | "plugin.create"
export type PluginArchActorContext = {
memberTeams: MemberTeamSummary[]
organizationContext: OrganizationContext
}
type MemberId = OrganizationContext["currentMember"]["id"]
type TeamId = MemberTeamSummary["id"]
type ConfigObjectId = typeof ConfigObjectTable.$inferSelect.id
type PluginId = typeof PluginTable.$inferSelect.id
type ConnectorInstanceId = typeof ConnectorInstanceTable.$inferSelect.id
type ConfigObjectGrantRow = Pick<typeof ConfigObjectAccessGrantTable.$inferSelect, "orgMembershipId" | "orgWide" | "removedAt" | "role" | "teamId">
type PluginGrantRow = Pick<typeof PluginAccessGrantTable.$inferSelect, "orgMembershipId" | "orgWide" | "removedAt" | "role" | "teamId">
type ConnectorInstanceGrantRow = Pick<typeof ConnectorInstanceAccessGrantTable.$inferSelect, "orgMembershipId" | "orgWide" | "removedAt" | "role" | "teamId">
type GrantRow = ConfigObjectGrantRow | PluginGrantRow | ConnectorInstanceGrantRow
type PluginResourceLookupInput = {
context: PluginArchActorContext
resourceId: PluginId
resourceKind: "plugin"
}
type ConnectorInstanceResourceLookupInput = {
context: PluginArchActorContext
resourceId: ConnectorInstanceId
resourceKind: "connector_instance"
}
type ConfigObjectResourceLookupInput = {
context: PluginArchActorContext
resourceId: ConfigObjectId
resourceKind: "config_object"
}
type ResourceLookupInput =
| PluginResourceLookupInput
| ConnectorInstanceResourceLookupInput
| ConfigObjectResourceLookupInput
type RequireResourceRoleInput = ResourceLookupInput & { role: PluginArchRole }
export class PluginArchAuthorizationError extends Error {
constructor(
readonly status: 403,
readonly error: "forbidden",
message: string,
) {
super(message)
this.name = "PluginArchAuthorizationError"
}
}
const rolePriority: Record<PluginArchRole, number> = {
viewer: 1,
editor: 2,
manager: 3,
}
function maxRole(current: PluginArchRole | null, candidate: PluginArchRole | null) {
if (!candidate) return current
if (!current) return candidate
return rolePriority[candidate] > rolePriority[current] ? candidate : current
}
export function isPluginArchOrgAdmin(context: PluginArchActorContext) {
return context.organizationContext.currentMember.isOwner || memberHasRole(context.organizationContext.currentMember.role, "admin")
}
export function hasPluginArchCapability(context: PluginArchActorContext, _capability: PluginArchCapability) {
return isPluginArchOrgAdmin(context)
}
function roleSatisfies(role: PluginArchRole | null, required: PluginArchRole) {
if (!role) return false
return rolePriority[role] >= rolePriority[required]
}
export function resolvePluginArchGrantRole(input: {
grants: GrantRow[]
memberId: MemberId
teamIds: TeamId[]
}) {
const teamIds = new Set(input.teamIds)
let resolved: PluginArchRole | null = null
for (const grant of input.grants) {
if (grant.removedAt) continue
const applies = grant.orgWide || grant.orgMembershipId === input.memberId || (grant.teamId ? teamIds.has(grant.teamId) : false)
if (!applies) continue
resolved = maxRole(resolved, grant.role)
}
return resolved
}
async function resolveGrantRole(input: {
grants: GrantRow[]
context: PluginArchActorContext
}) {
return resolvePluginArchGrantRole({
grants: input.grants,
memberId: input.context.organizationContext.currentMember.id,
teamIds: input.context.memberTeams.map((team) => team.id),
})
}
async function resolvePluginRoleForIds(context: PluginArchActorContext, pluginIds: PluginId[]) {
if (pluginIds.length === 0) {
return null
}
if (isPluginArchOrgAdmin(context)) {
return "manager" satisfies PluginArchRole
}
const grants = await db
.select({
orgMembershipId: PluginAccessGrantTable.orgMembershipId,
orgWide: PluginAccessGrantTable.orgWide,
removedAt: PluginAccessGrantTable.removedAt,
role: PluginAccessGrantTable.role,
teamId: PluginAccessGrantTable.teamId,
})
.from(PluginAccessGrantTable)
.where(inArray(PluginAccessGrantTable.pluginId, pluginIds))
return resolveGrantRole({ context, grants })
}
export async function resolvePluginArchResourceRole(input: ResourceLookupInput) {
if (isPluginArchOrgAdmin(input.context)) {
return "manager" satisfies PluginArchRole
}
if (input.resourceKind === "plugin") {
const grants = await db
.select({
orgMembershipId: PluginAccessGrantTable.orgMembershipId,
orgWide: PluginAccessGrantTable.orgWide,
removedAt: PluginAccessGrantTable.removedAt,
role: PluginAccessGrantTable.role,
teamId: PluginAccessGrantTable.teamId,
})
.from(PluginAccessGrantTable)
.where(eq(PluginAccessGrantTable.pluginId, input.resourceId))
return resolveGrantRole({ context: input.context, grants })
}
if (input.resourceKind === "connector_instance") {
const grants = await db
.select({
orgMembershipId: ConnectorInstanceAccessGrantTable.orgMembershipId,
orgWide: ConnectorInstanceAccessGrantTable.orgWide,
removedAt: ConnectorInstanceAccessGrantTable.removedAt,
role: ConnectorInstanceAccessGrantTable.role,
teamId: ConnectorInstanceAccessGrantTable.teamId,
})
.from(ConnectorInstanceAccessGrantTable)
.where(eq(ConnectorInstanceAccessGrantTable.connectorInstanceId, input.resourceId))
return resolveGrantRole({ context: input.context, grants })
}
const directGrants = await db
.select({
orgMembershipId: ConfigObjectAccessGrantTable.orgMembershipId,
orgWide: ConfigObjectAccessGrantTable.orgWide,
removedAt: ConfigObjectAccessGrantTable.removedAt,
role: ConfigObjectAccessGrantTable.role,
teamId: ConfigObjectAccessGrantTable.teamId,
})
.from(ConfigObjectAccessGrantTable)
.where(eq(ConfigObjectAccessGrantTable.configObjectId, input.resourceId))
let resolved = await resolveGrantRole({ context: input.context, grants: directGrants })
if (resolved) {
return resolved
}
const memberships = await db
.select({ pluginId: PluginConfigObjectTable.pluginId })
.from(PluginConfigObjectTable)
.where(and(eq(PluginConfigObjectTable.configObjectId, input.resourceId), isNull(PluginConfigObjectTable.removedAt)))
const pluginRole = await resolvePluginRoleForIds(input.context, memberships.map((membership) => membership.pluginId))
resolved = maxRole(resolved, pluginRole ? "viewer" : null)
return resolved
}
export async function requirePluginArchCapability(context: PluginArchActorContext, capability: PluginArchCapability) {
if (hasPluginArchCapability(context, capability)) {
return
}
throw new PluginArchAuthorizationError(403, "forbidden", `Missing organization capability: ${capability}`)
}
export async function requirePluginArchResourceRole(input: {
context: PluginArchActorContext
resourceId: ConfigObjectId | ConnectorInstanceId | PluginId
resourceKind: PluginArchResourceKind
role: PluginArchRole
}) {
const resolved = await resolvePluginArchResourceRole(input as RequireResourceRoleInput)
if (roleSatisfies(resolved, input.role)) {
return resolved
}
throw new PluginArchAuthorizationError(
403,
"forbidden",
`Missing ${input.role} access for ${input.resourceKind.replace(/_/g, " ")}.`,
)
}

View File

@@ -0,0 +1,781 @@
import type { z } from "zod"
import {
accessGrantListResponseSchema,
accessGrantMutationResponseSchema,
configObjectAccessGrantParamsSchema,
configObjectCreateSchema,
configObjectCreateVersionSchema,
configObjectDetailResponseSchema,
configObjectListQuerySchema,
configObjectListResponseSchema,
configObjectMutationResponseSchema,
configObjectParamsSchema,
configObjectPluginAttachSchema,
configObjectVersionDetailResponseSchema,
configObjectVersionListQuerySchema,
configObjectVersionListResponseSchema,
configObjectVersionParamsSchema,
connectorAccountCreateSchema,
connectorAccountDetailResponseSchema,
connectorAccountDisconnectSchema,
connectorAccountListQuerySchema,
connectorAccountListResponseSchema,
connectorAccountMutationResponseSchema,
connectorAccountParamsSchema,
connectorAccountRepositoryParamsSchema,
connectorInstanceAccessGrantParamsSchema,
connectorInstanceCreateSchema,
connectorInstanceDetailResponseSchema,
connectorInstanceListQuerySchema,
connectorInstanceListResponseSchema,
connectorInstanceMutationResponseSchema,
connectorInstanceParamsSchema,
connectorInstanceUpdateSchema,
connectorMappingCreateSchema,
connectorMappingListQuerySchema,
connectorMappingListResponseSchema,
connectorMappingMutationResponseSchema,
connectorMappingParamsSchema,
connectorMappingUpdateSchema,
connectorSyncAsyncResponseSchema,
connectorSyncEventDetailResponseSchema,
connectorSyncEventListQuerySchema,
connectorSyncEventListResponseSchema,
connectorSyncEventParamsSchema,
connectorTargetCreateSchema,
connectorTargetDetailResponseSchema,
connectorTargetListQuerySchema,
connectorTargetListResponseSchema,
connectorTargetMutationResponseSchema,
connectorTargetParamsSchema,
connectorTargetUpdateSchema,
githubConnectorAccountCreateSchema,
githubConnectorSetupSchema,
githubRepositoryListQuerySchema,
githubRepositoryListResponseSchema,
githubSetupResponseSchema,
githubValidateTargetResponseSchema,
githubValidateTargetSchema,
githubWebhookAcceptedResponseSchema,
githubWebhookHeadersSchema,
githubWebhookIgnoredResponseSchema,
githubWebhookRawBodySchema,
githubWebhookUnauthorizedResponseSchema,
pluginAccessGrantParamsSchema,
pluginConfigObjectParamsSchema,
pluginCreateSchema,
pluginDetailResponseSchema,
pluginListQuerySchema,
pluginListResponseSchema,
pluginMembershipMutationResponseSchema,
pluginMembershipListResponseSchema,
pluginMembershipWriteSchema,
pluginMutationResponseSchema,
pluginParamsSchema,
pluginUpdateSchema,
resourceAccessGrantWriteSchema,
} from "./schemas.js"
import { orgIdParamSchema } from "../shared.js"
type EndpointMethod = "DELETE" | "GET" | "PATCH" | "POST"
type EndpointAudience = "admin" | "public_webhook"
type EndpointTag = "Config Objects" | "Plugins" | "Connectors" | "GitHub" | "Webhooks"
type EndpointContract = {
audience: EndpointAudience
description: string
method: EndpointMethod
path: string
request?: {
body?: z.ZodTypeAny
headers?: z.ZodTypeAny
params?: z.ZodTypeAny
query?: z.ZodTypeAny
}
response: {
description: string
schema?: z.ZodTypeAny
status: 200 | 201 | 202 | 204 | 401
}
tag: EndpointTag
}
type DeferredEndpointContract = {
description: string
method: EndpointMethod
path: string
reason: string
tag: EndpointTag
}
const orgBasePath = "/v1/orgs/:orgId"
export const pluginArchRoutePaths = {
configObjects: `${orgBasePath}/config-objects`,
configObject: `${orgBasePath}/config-objects/:configObjectId`,
configObjectArchive: `${orgBasePath}/config-objects/:configObjectId/archive`,
configObjectDelete: `${orgBasePath}/config-objects/:configObjectId/delete`,
configObjectRestore: `${orgBasePath}/config-objects/:configObjectId/restore`,
configObjectPlugins: `${orgBasePath}/config-objects/:configObjectId/plugins`,
configObjectPlugin: `${orgBasePath}/config-objects/:configObjectId/plugins/:pluginId`,
configObjectAccess: `${orgBasePath}/config-objects/:configObjectId/access`,
configObjectAccessGrant: `${orgBasePath}/config-objects/:configObjectId/access/:grantId`,
configObjectVersions: `${orgBasePath}/config-objects/:configObjectId/versions`,
configObjectVersion: `${orgBasePath}/config-objects/:configObjectId/versions/:versionId`,
configObjectLatestVersion: `${orgBasePath}/config-objects/:configObjectId/versions/latest`,
configObjectCompareVersions: `${orgBasePath}/config-objects/:configObjectId/versions/compare`,
skills: `${orgBasePath}/skills`,
agents: `${orgBasePath}/agents`,
commands: `${orgBasePath}/commands`,
tools: `${orgBasePath}/tools`,
mcps: `${orgBasePath}/mcps`,
plugins: `${orgBasePath}/plugins`,
plugin: `${orgBasePath}/plugins/:pluginId`,
pluginArchive: `${orgBasePath}/plugins/:pluginId/archive`,
pluginRestore: `${orgBasePath}/plugins/:pluginId/restore`,
pluginConfigObjects: `${orgBasePath}/plugins/:pluginId/config-objects`,
pluginConfigObject: `${orgBasePath}/plugins/:pluginId/config-objects/:configObjectId`,
pluginResolved: `${orgBasePath}/plugins/:pluginId/resolved`,
pluginReleases: `${orgBasePath}/plugins/:pluginId/releases`,
pluginAccess: `${orgBasePath}/plugins/:pluginId/access`,
pluginAccessGrant: `${orgBasePath}/plugins/:pluginId/access/:grantId`,
connectorAccounts: `${orgBasePath}/connector-accounts`,
connectorAccount: `${orgBasePath}/connector-accounts/:connectorAccountId`,
connectorAccountDisconnect: `${orgBasePath}/connector-accounts/:connectorAccountId/disconnect`,
connectorInstances: `${orgBasePath}/connector-instances`,
connectorInstance: `${orgBasePath}/connector-instances/:connectorInstanceId`,
connectorInstanceArchive: `${orgBasePath}/connector-instances/:connectorInstanceId/archive`,
connectorInstanceDisable: `${orgBasePath}/connector-instances/:connectorInstanceId/disable`,
connectorInstanceEnable: `${orgBasePath}/connector-instances/:connectorInstanceId/enable`,
connectorInstanceAccess: `${orgBasePath}/connector-instances/:connectorInstanceId/access`,
connectorInstanceAccessGrant: `${orgBasePath}/connector-instances/:connectorInstanceId/access/:grantId`,
connectorTargets: `${orgBasePath}/connector-instances/:connectorInstanceId/targets`,
connectorTarget: `${orgBasePath}/connector-targets/:connectorTargetId`,
connectorTargetResync: `${orgBasePath}/connector-targets/:connectorTargetId/resync`,
connectorTargetMappings: `${orgBasePath}/connector-targets/:connectorTargetId/mappings`,
connectorMapping: `${orgBasePath}/connector-mappings/:connectorMappingId`,
connectorMappingPreview: `${orgBasePath}/connector-mappings/:connectorMappingId/preview`,
connectorSyncEvents: `${orgBasePath}/connector-sync-events`,
connectorSyncEvent: `${orgBasePath}/connector-sync-events/:connectorSyncEventId`,
connectorSyncEventRetry: `${orgBasePath}/connector-sync-events/:connectorSyncEventId/retry`,
githubSetup: `${orgBasePath}/connectors/github/setup`,
githubAccounts: `${orgBasePath}/connectors/github/accounts`,
githubAccountRepositories: `${orgBasePath}/connectors/github/accounts/:connectorAccountId/repositories`,
githubValidateTarget: `${orgBasePath}/connectors/github/validate-target`,
githubWebhookIngress: "/api/webhooks/connectors/github",
} as const
export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
listConfigObjects: {
audience: "admin",
description: "List current config object projections with search and connector filters.",
method: "GET",
path: pluginArchRoutePaths.configObjects,
request: { params: orgIdParamSchema, query: configObjectListQuerySchema },
response: { description: "Current config object rows.", schema: configObjectListResponseSchema, status: 200 },
tag: "Config Objects",
},
getConfigObject: {
audience: "admin",
description: "Get one config object with its latest version projection.",
method: "GET",
path: pluginArchRoutePaths.configObject,
request: { params: configObjectParamsSchema },
response: { description: "Current config object detail.", schema: configObjectDetailResponseSchema, status: 200 },
tag: "Config Objects",
},
createConfigObject: {
audience: "admin",
description: "Create a cloud or imported config object and optionally attach it to plugins.",
method: "POST",
path: pluginArchRoutePaths.configObjects,
request: { body: configObjectCreateSchema, params: orgIdParamSchema },
response: { description: "Config object created successfully.", schema: configObjectMutationResponseSchema, status: 201 },
tag: "Config Objects",
},
createConfigObjectVersion: {
audience: "admin",
description: "Create a new immutable version for an existing config object.",
method: "POST",
path: pluginArchRoutePaths.configObjectVersions,
request: { body: configObjectCreateVersionSchema, params: configObjectParamsSchema },
response: { description: "Latest config object detail after version creation.", schema: configObjectMutationResponseSchema, status: 201 },
tag: "Config Objects",
},
archiveConfigObject: {
audience: "admin",
description: "Archive a config object without removing history.",
method: "POST",
path: pluginArchRoutePaths.configObjectArchive,
request: { params: configObjectParamsSchema },
response: { description: "Archived config object detail.", schema: configObjectMutationResponseSchema, status: 200 },
tag: "Config Objects",
},
deleteConfigObject: {
audience: "admin",
description: "Soft-delete a config object while preserving history.",
method: "POST",
path: pluginArchRoutePaths.configObjectDelete,
request: { params: configObjectParamsSchema },
response: { description: "Deleted config object detail.", schema: configObjectMutationResponseSchema, status: 200 },
tag: "Config Objects",
},
restoreConfigObject: {
audience: "admin",
description: "Restore a deleted or archived config object.",
method: "POST",
path: pluginArchRoutePaths.configObjectRestore,
request: { params: configObjectParamsSchema },
response: { description: "Restored config object detail.", schema: configObjectMutationResponseSchema, status: 200 },
tag: "Config Objects",
},
listConfigObjectPlugins: {
audience: "admin",
description: "List the plugins that currently include a config object.",
method: "GET",
path: pluginArchRoutePaths.configObjectPlugins,
request: { params: configObjectParamsSchema },
response: { description: "Plugin memberships for the config object.", schema: pluginMembershipListResponseSchema, status: 200 },
tag: "Config Objects",
},
attachConfigObjectToPlugin: {
audience: "admin",
description: "Attach a config object to a plugin using plugin-scoped write access.",
method: "POST",
path: pluginArchRoutePaths.configObjectPlugins,
request: { body: configObjectPluginAttachSchema, params: configObjectParamsSchema },
response: { description: "Plugin membership created successfully.", schema: pluginMembershipMutationResponseSchema, status: 201 },
tag: "Config Objects",
},
removeConfigObjectFromPlugin: {
audience: "admin",
description: "Remove one active plugin membership from a config object.",
method: "DELETE",
path: pluginArchRoutePaths.configObjectPlugin,
request: { params: configObjectParamsSchema.extend({ pluginId: pluginParamsSchema.shape.pluginId }) },
response: { description: "Plugin membership removed successfully.", status: 204 },
tag: "Config Objects",
},
listConfigObjectAccess: {
audience: "admin",
description: "List direct, team, and org-wide grants for a config object.",
method: "GET",
path: pluginArchRoutePaths.configObjectAccess,
request: { params: configObjectParamsSchema },
response: { description: "Config object access grants.", schema: accessGrantListResponseSchema, status: 200 },
tag: "Config Objects",
},
grantConfigObjectAccess: {
audience: "admin",
description: "Create one direct, team, or org-wide access grant for a config object.",
method: "POST",
path: pluginArchRoutePaths.configObjectAccess,
request: { body: resourceAccessGrantWriteSchema, params: configObjectParamsSchema },
response: { description: "Config object access grant created successfully.", schema: accessGrantMutationResponseSchema, status: 201 },
tag: "Config Objects",
},
revokeConfigObjectAccess: {
audience: "admin",
description: "Soft-revoke one config object access grant.",
method: "DELETE",
path: pluginArchRoutePaths.configObjectAccessGrant,
request: { params: configObjectAccessGrantParamsSchema },
response: { description: "Config object access grant revoked successfully.", status: 204 },
tag: "Config Objects",
},
listConfigObjectVersions: {
audience: "admin",
description: "List immutable versions for a config object.",
method: "GET",
path: pluginArchRoutePaths.configObjectVersions,
request: { params: configObjectParamsSchema, query: configObjectVersionListQuerySchema },
response: { description: "Config object versions.", schema: configObjectVersionListResponseSchema, status: 200 },
tag: "Config Objects",
},
getConfigObjectVersion: {
audience: "admin",
description: "Get one immutable config object version.",
method: "GET",
path: pluginArchRoutePaths.configObjectVersion,
request: { params: configObjectVersionParamsSchema },
response: { description: "Config object version detail.", schema: configObjectVersionDetailResponseSchema, status: 200 },
tag: "Config Objects",
},
getLatestConfigObjectVersion: {
audience: "admin",
description: "Resolve the latest config object version using created_at and id ordering.",
method: "GET",
path: pluginArchRoutePaths.configObjectLatestVersion,
request: { params: configObjectParamsSchema },
response: { description: "Latest config object version detail.", schema: configObjectVersionDetailResponseSchema, status: 200 },
tag: "Config Objects",
},
listPlugins: {
audience: "admin",
description: "List accessible plugins for the organization.",
method: "GET",
path: pluginArchRoutePaths.plugins,
request: { params: orgIdParamSchema, query: pluginListQuerySchema },
response: { description: "Plugin list.", schema: pluginListResponseSchema, status: 200 },
tag: "Plugins",
},
getPlugin: {
audience: "admin",
description: "Get one plugin and its current metadata.",
method: "GET",
path: pluginArchRoutePaths.plugin,
request: { params: pluginParamsSchema },
response: { description: "Plugin detail.", schema: pluginDetailResponseSchema, status: 200 },
tag: "Plugins",
},
createPlugin: {
audience: "admin",
description: "Create a private-by-default plugin.",
method: "POST",
path: pluginArchRoutePaths.plugins,
request: { body: pluginCreateSchema, params: orgIdParamSchema },
response: { description: "Plugin created successfully.", schema: pluginMutationResponseSchema, status: 201 },
tag: "Plugins",
},
updatePlugin: {
audience: "admin",
description: "Patch plugin metadata.",
method: "PATCH",
path: pluginArchRoutePaths.plugin,
request: { body: pluginUpdateSchema, params: pluginParamsSchema },
response: { description: "Plugin updated successfully.", schema: pluginMutationResponseSchema, status: 200 },
tag: "Plugins",
},
archivePlugin: {
audience: "admin",
description: "Archive a plugin without deleting membership history.",
method: "POST",
path: pluginArchRoutePaths.pluginArchive,
request: { params: pluginParamsSchema },
response: { description: "Archived plugin detail.", schema: pluginMutationResponseSchema, status: 200 },
tag: "Plugins",
},
restorePlugin: {
audience: "admin",
description: "Restore an archived or deleted plugin.",
method: "POST",
path: pluginArchRoutePaths.pluginRestore,
request: { params: pluginParamsSchema },
response: { description: "Restored plugin detail.", schema: pluginMutationResponseSchema, status: 200 },
tag: "Plugins",
},
listPluginConfigObjects: {
audience: "admin",
description: "List plugin memberships and the current config object projections they reference.",
method: "GET",
path: pluginArchRoutePaths.pluginConfigObjects,
request: { params: pluginParamsSchema },
response: { description: "Plugin memberships.", schema: pluginMembershipListResponseSchema, status: 200 },
tag: "Plugins",
},
addPluginConfigObject: {
audience: "admin",
description: "Add a config object to a plugin using plugin-scoped write access.",
method: "POST",
path: pluginArchRoutePaths.pluginConfigObjects,
request: { body: pluginMembershipWriteSchema, params: pluginParamsSchema },
response: { description: "Plugin membership created successfully.", schema: pluginMembershipMutationResponseSchema, status: 201 },
tag: "Plugins",
},
removePluginConfigObject: {
audience: "admin",
description: "Remove one config object membership from a plugin.",
method: "DELETE",
path: pluginArchRoutePaths.pluginConfigObject,
request: { params: pluginConfigObjectParamsSchema },
response: { description: "Plugin membership removed successfully.", status: 204 },
tag: "Plugins",
},
getResolvedPlugin: {
audience: "admin",
description: "Preview the resolved latest-version members of a plugin without invoking delivery.",
method: "GET",
path: pluginArchRoutePaths.pluginResolved,
request: { params: pluginParamsSchema },
response: { description: "Resolved plugin membership view.", schema: pluginMembershipListResponseSchema, status: 200 },
tag: "Plugins",
},
listPluginAccess: {
audience: "admin",
description: "List direct, team, and org-wide grants for a plugin.",
method: "GET",
path: pluginArchRoutePaths.pluginAccess,
request: { params: pluginParamsSchema },
response: { description: "Plugin access grants.", schema: accessGrantListResponseSchema, status: 200 },
tag: "Plugins",
},
grantPluginAccess: {
audience: "admin",
description: "Create one direct, team, or org-wide access grant for a plugin.",
method: "POST",
path: pluginArchRoutePaths.pluginAccess,
request: { body: resourceAccessGrantWriteSchema, params: pluginParamsSchema },
response: { description: "Plugin access grant created successfully.", schema: accessGrantMutationResponseSchema, status: 201 },
tag: "Plugins",
},
revokePluginAccess: {
audience: "admin",
description: "Soft-revoke one plugin access grant.",
method: "DELETE",
path: pluginArchRoutePaths.pluginAccessGrant,
request: { params: pluginAccessGrantParamsSchema },
response: { description: "Plugin access grant revoked successfully.", status: 204 },
tag: "Plugins",
},
listConnectorAccounts: {
audience: "admin",
description: "List connector accounts such as GitHub App installations available to the org.",
method: "GET",
path: pluginArchRoutePaths.connectorAccounts,
request: { params: orgIdParamSchema, query: connectorAccountListQuerySchema },
response: { description: "Connector account list.", schema: connectorAccountListResponseSchema, status: 200 },
tag: "Connectors",
},
createConnectorAccount: {
audience: "admin",
description: "Create a reusable connector account record.",
method: "POST",
path: pluginArchRoutePaths.connectorAccounts,
request: { body: connectorAccountCreateSchema, params: orgIdParamSchema },
response: { description: "Connector account created successfully.", schema: connectorAccountMutationResponseSchema, status: 201 },
tag: "Connectors",
},
getConnectorAccount: {
audience: "admin",
description: "Get one connector account record.",
method: "GET",
path: pluginArchRoutePaths.connectorAccount,
request: { params: connectorAccountParamsSchema },
response: { description: "Connector account detail.", schema: connectorAccountDetailResponseSchema, status: 200 },
tag: "Connectors",
},
disconnectConnectorAccount: {
audience: "admin",
description: "Disconnect one connector account without deleting historical sync state.",
method: "POST",
path: pluginArchRoutePaths.connectorAccountDisconnect,
request: { body: connectorAccountDisconnectSchema, params: connectorAccountParamsSchema },
response: { description: "Connector account disconnected successfully.", schema: connectorAccountMutationResponseSchema, status: 200 },
tag: "Connectors",
},
listConnectorInstances: {
audience: "admin",
description: "List configured connector instances for the org.",
method: "GET",
path: pluginArchRoutePaths.connectorInstances,
request: { params: orgIdParamSchema, query: connectorInstanceListQuerySchema },
response: { description: "Connector instance list.", schema: connectorInstanceListResponseSchema, status: 200 },
tag: "Connectors",
},
createConnectorInstance: {
audience: "admin",
description: "Create a connector instance backed by one connector account.",
method: "POST",
path: pluginArchRoutePaths.connectorInstances,
request: { body: connectorInstanceCreateSchema, params: orgIdParamSchema },
response: { description: "Connector instance created successfully.", schema: connectorInstanceMutationResponseSchema, status: 201 },
tag: "Connectors",
},
getConnectorInstance: {
audience: "admin",
description: "Get one connector instance.",
method: "GET",
path: pluginArchRoutePaths.connectorInstance,
request: { params: connectorInstanceParamsSchema },
response: { description: "Connector instance detail.", schema: connectorInstanceDetailResponseSchema, status: 200 },
tag: "Connectors",
},
updateConnectorInstance: {
audience: "admin",
description: "Patch connector instance metadata or config.",
method: "PATCH",
path: pluginArchRoutePaths.connectorInstance,
request: { body: connectorInstanceUpdateSchema, params: connectorInstanceParamsSchema },
response: { description: "Connector instance updated successfully.", schema: connectorInstanceMutationResponseSchema, status: 200 },
tag: "Connectors",
},
archiveConnectorInstance: {
audience: "admin",
description: "Archive a connector instance.",
method: "POST",
path: pluginArchRoutePaths.connectorInstanceArchive,
request: { params: connectorInstanceParamsSchema },
response: { description: "Connector instance archived successfully.", schema: connectorInstanceMutationResponseSchema, status: 200 },
tag: "Connectors",
},
disableConnectorInstance: {
audience: "admin",
description: "Disable sync execution for a connector instance.",
method: "POST",
path: pluginArchRoutePaths.connectorInstanceDisable,
request: { params: connectorInstanceParamsSchema },
response: { description: "Connector instance disabled successfully.", schema: connectorInstanceMutationResponseSchema, status: 200 },
tag: "Connectors",
},
enableConnectorInstance: {
audience: "admin",
description: "Re-enable sync execution for a connector instance.",
method: "POST",
path: pluginArchRoutePaths.connectorInstanceEnable,
request: { params: connectorInstanceParamsSchema },
response: { description: "Connector instance enabled successfully.", schema: connectorInstanceMutationResponseSchema, status: 200 },
tag: "Connectors",
},
listConnectorInstanceAccess: {
audience: "admin",
description: "List direct, team, and org-wide grants for a connector instance.",
method: "GET",
path: pluginArchRoutePaths.connectorInstanceAccess,
request: { params: connectorInstanceParamsSchema },
response: { description: "Connector instance access grants.", schema: accessGrantListResponseSchema, status: 200 },
tag: "Connectors",
},
grantConnectorInstanceAccess: {
audience: "admin",
description: "Create one direct, team, or org-wide access grant for a connector instance.",
method: "POST",
path: pluginArchRoutePaths.connectorInstanceAccess,
request: { body: resourceAccessGrantWriteSchema, params: connectorInstanceParamsSchema },
response: { description: "Connector instance access grant created successfully.", schema: accessGrantMutationResponseSchema, status: 201 },
tag: "Connectors",
},
revokeConnectorInstanceAccess: {
audience: "admin",
description: "Soft-revoke one connector instance access grant.",
method: "DELETE",
path: pluginArchRoutePaths.connectorInstanceAccessGrant,
request: { params: connectorInstanceAccessGrantParamsSchema },
response: { description: "Connector instance access grant revoked successfully.", status: 204 },
tag: "Connectors",
},
listConnectorTargets: {
audience: "admin",
description: "List external targets configured under a connector instance.",
method: "GET",
path: pluginArchRoutePaths.connectorTargets,
request: { params: connectorInstanceParamsSchema, query: connectorTargetListQuerySchema },
response: { description: "Connector target list.", schema: connectorTargetListResponseSchema, status: 200 },
tag: "Connectors",
},
createConnectorTarget: {
audience: "admin",
description: "Create one connector target such as a GitHub repository branch.",
method: "POST",
path: pluginArchRoutePaths.connectorTargets,
request: { body: connectorTargetCreateSchema, params: connectorInstanceParamsSchema },
response: { description: "Connector target created successfully.", schema: connectorTargetMutationResponseSchema, status: 201 },
tag: "Connectors",
},
getConnectorTarget: {
audience: "admin",
description: "Get one connector target.",
method: "GET",
path: pluginArchRoutePaths.connectorTarget,
request: { params: connectorTargetParamsSchema },
response: { description: "Connector target detail.", schema: connectorTargetDetailResponseSchema, status: 200 },
tag: "Connectors",
},
updateConnectorTarget: {
audience: "admin",
description: "Patch one connector target.",
method: "PATCH",
path: pluginArchRoutePaths.connectorTarget,
request: { body: connectorTargetUpdateSchema, params: connectorTargetParamsSchema },
response: { description: "Connector target updated successfully.", schema: connectorTargetMutationResponseSchema, status: 200 },
tag: "Connectors",
},
resyncConnectorTarget: {
audience: "admin",
description: "Queue a manual reconciliation run for one connector target.",
method: "POST",
path: pluginArchRoutePaths.connectorTargetResync,
request: { params: connectorTargetParamsSchema },
response: { description: "Connector target resync queued successfully.", schema: connectorSyncAsyncResponseSchema, status: 202 },
tag: "Connectors",
},
listConnectorMappings: {
audience: "admin",
description: "List mappings configured under a connector target.",
method: "GET",
path: pluginArchRoutePaths.connectorTargetMappings,
request: { params: connectorTargetParamsSchema, query: connectorMappingListQuerySchema },
response: { description: "Connector mapping list.", schema: connectorMappingListResponseSchema, status: 200 },
tag: "Connectors",
},
createConnectorMapping: {
audience: "admin",
description: "Create a path or API mapping for a connector target.",
method: "POST",
path: pluginArchRoutePaths.connectorTargetMappings,
request: { body: connectorMappingCreateSchema, params: connectorTargetParamsSchema },
response: { description: "Connector mapping created successfully.", schema: connectorMappingMutationResponseSchema, status: 201 },
tag: "Connectors",
},
updateConnectorMapping: {
audience: "admin",
description: "Patch one connector mapping.",
method: "PATCH",
path: pluginArchRoutePaths.connectorMapping,
request: { body: connectorMappingUpdateSchema, params: connectorMappingParamsSchema },
response: { description: "Connector mapping updated successfully.", schema: connectorMappingMutationResponseSchema, status: 200 },
tag: "Connectors",
},
deleteConnectorMapping: {
audience: "admin",
description: "Delete one connector mapping.",
method: "DELETE",
path: pluginArchRoutePaths.connectorMapping,
request: { params: connectorMappingParamsSchema },
response: { description: "Connector mapping deleted successfully.", status: 204 },
tag: "Connectors",
},
listConnectorSyncEvents: {
audience: "admin",
description: "List connector sync events for inspection and debugging.",
method: "GET",
path: pluginArchRoutePaths.connectorSyncEvents,
request: { params: orgIdParamSchema, query: connectorSyncEventListQuerySchema },
response: { description: "Connector sync event list.", schema: connectorSyncEventListResponseSchema, status: 200 },
tag: "Connectors",
},
getConnectorSyncEvent: {
audience: "admin",
description: "Get one connector sync event.",
method: "GET",
path: pluginArchRoutePaths.connectorSyncEvent,
request: { params: connectorSyncEventParamsSchema },
response: { description: "Connector sync event detail.", schema: connectorSyncEventDetailResponseSchema, status: 200 },
tag: "Connectors",
},
retryConnectorSyncEvent: {
audience: "admin",
description: "Queue a retry for a failed or partial connector sync event.",
method: "POST",
path: pluginArchRoutePaths.connectorSyncEventRetry,
request: { params: connectorSyncEventParamsSchema },
response: { description: "Connector sync retry queued successfully.", schema: connectorSyncAsyncResponseSchema, status: 202 },
tag: "Connectors",
},
githubSetup: {
audience: "admin",
description: "Create the GitHub connector account, instance, target, and initial mappings in one setup flow.",
method: "POST",
path: pluginArchRoutePaths.githubSetup,
request: { body: githubConnectorSetupSchema, params: orgIdParamSchema },
response: { description: "GitHub connector setup created successfully.", schema: githubSetupResponseSchema, status: 201 },
tag: "GitHub",
},
githubCreateAccount: {
audience: "admin",
description: "Persist a GitHub App installation as a reusable connector account.",
method: "POST",
path: pluginArchRoutePaths.githubAccounts,
request: { body: githubConnectorAccountCreateSchema, params: orgIdParamSchema },
response: { description: "GitHub connector account created successfully.", schema: connectorAccountMutationResponseSchema, status: 201 },
tag: "GitHub",
},
githubListRepositories: {
audience: "admin",
description: "List repositories visible to one GitHub connector account.",
method: "GET",
path: pluginArchRoutePaths.githubAccountRepositories,
request: { params: connectorAccountRepositoryParamsSchema, query: githubRepositoryListQuerySchema },
response: { description: "GitHub repositories visible to the installation.", schema: githubRepositoryListResponseSchema, status: 200 },
tag: "GitHub",
},
githubValidateTarget: {
audience: "admin",
description: "Validate one GitHub repository-branch target before persisting it.",
method: "POST",
path: pluginArchRoutePaths.githubValidateTarget,
request: { body: githubValidateTargetSchema, params: orgIdParamSchema },
response: { description: "GitHub target validation result.", schema: githubValidateTargetResponseSchema, status: 200 },
tag: "GitHub",
},
githubWebhookIngress: {
audience: "public_webhook",
description: "Accept a GitHub App webhook delivery, verify the raw-body signature, and enqueue any relevant sync work.",
method: "POST",
path: pluginArchRoutePaths.githubWebhookIngress,
request: { body: githubWebhookRawBodySchema, headers: githubWebhookHeadersSchema },
response: { description: "Valid webhook accepted or ignored.", schema: githubWebhookAcceptedResponseSchema.or(githubWebhookIgnoredResponseSchema), status: 202 },
tag: "Webhooks",
},
}
export const deferredPluginArchEndpointContracts: DeferredEndpointContract[] = [
{
description: "Compare two config object versions.",
method: "GET",
path: pluginArchRoutePaths.configObjectCompareVersions,
reason: "Diff semantics can wait until immutable version storage exists.",
tag: "Config Objects",
},
{
description: "Type-specific convenience endpoints for skills.",
method: "GET",
path: pluginArchRoutePaths.skills,
reason: "Shared config-object routes land first; per-type wrappers follow once the core surface is working.",
tag: "Config Objects",
},
{
description: "Type-specific convenience endpoints for agents.",
method: "GET",
path: pluginArchRoutePaths.agents,
reason: "Shared config-object routes land first; per-type wrappers follow once the core surface is working.",
tag: "Config Objects",
},
{
description: "Type-specific convenience endpoints for commands.",
method: "GET",
path: pluginArchRoutePaths.commands,
reason: "Shared config-object routes land first; per-type wrappers follow once the core surface is working.",
tag: "Config Objects",
},
{
description: "Type-specific convenience endpoints for tools.",
method: "GET",
path: pluginArchRoutePaths.tools,
reason: "Shared config-object routes land first; per-type wrappers follow once the core surface is working.",
tag: "Config Objects",
},
{
description: "Type-specific convenience endpoints for MCPs.",
method: "GET",
path: pluginArchRoutePaths.mcps,
reason: "Shared config-object routes land first; per-type wrappers follow once the core surface is working.",
tag: "Config Objects",
},
{
description: "Create and list plugin releases.",
method: "POST",
path: pluginArchRoutePaths.pluginReleases,
reason: "Delivery and release snapshots stay deferred until the admin and webhook slice is live.",
tag: "Plugins",
},
{
description: "Preview one connector mapping against remote source data.",
method: "POST",
path: pluginArchRoutePaths.connectorMappingPreview,
reason: "Mapping preview depends on the later reconciliation engine and should not block the first admin slice.",
tag: "Connectors",
},
]
export const pluginArchContractSummary = {
implementationHome: {
adminApi: "ee/apps/den-api/src/routes/org",
persistence: "ee/packages/den-db/src/schema",
webhookIngress: "ee/apps/den-api/src/routes",
},
outOfScope: [
"plugin delivery/install endpoints",
"plugin release snapshot implementation",
"type-specific convenience wrappers",
],
} as const

View File

@@ -0,0 +1,5 @@
export * from "./contracts.js"
export * from "./access.js"
export * from "./routes.js"
export * from "./schemas.js"
export * from "./store.js"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,653 @@
import {
accessRoleValues,
configObjectCreatedViaValues,
configObjectSourceModeValues,
configObjectStatusValues,
configObjectTypeValues,
connectorAccountStatusValues,
connectorInstanceStatusValues,
connectorMappingKindValues,
connectorSyncEventTypeValues,
connectorSyncStatusValues,
connectorTargetKindValues,
connectorTypeValues,
membershipSourceValues,
pluginStatusValues,
} from "@openwork-ee/den-db/schema"
import { z } from "zod"
import { denTypeIdSchema } from "../../../openapi.js"
import { idParamSchema, orgIdParamSchema } from "../shared.js"
const cursorSchema = z.string().trim().min(1).max(255)
const jsonObjectSchema = z.object({}).passthrough()
const rawSourceTextSchema = z.string().trim().min(1)
const nullableStringSchema = z.string().trim().min(1).nullable()
const nullableTimestampSchema = z.string().datetime({ offset: true }).nullable()
const queryBooleanSchema = z.enum(["true", "false"]).transform((value) => value === "true")
export const githubWebhookEventValues = ["push", "installation", "installation_repositories", "repository"] as const
export const configObjectIdSchema = denTypeIdSchema("configObject")
export const configObjectVersionIdSchema = denTypeIdSchema("configObjectVersion")
export const configObjectAccessGrantIdSchema = denTypeIdSchema("configObjectAccessGrant")
export const pluginIdSchema = denTypeIdSchema("plugin")
export const pluginConfigObjectIdSchema = denTypeIdSchema("pluginConfigObject")
export const pluginAccessGrantIdSchema = denTypeIdSchema("pluginAccessGrant")
export const connectorAccountIdSchema = denTypeIdSchema("connectorAccount")
export const connectorInstanceIdSchema = denTypeIdSchema("connectorInstance")
export const connectorInstanceAccessGrantIdSchema = denTypeIdSchema("connectorInstanceAccessGrant")
export const connectorTargetIdSchema = denTypeIdSchema("connectorTarget")
export const connectorMappingIdSchema = denTypeIdSchema("connectorMapping")
export const connectorSyncEventIdSchema = denTypeIdSchema("connectorSyncEvent")
export const connectorSourceBindingIdSchema = denTypeIdSchema("connectorSourceBinding")
export const connectorSourceTombstoneIdSchema = denTypeIdSchema("connectorSourceTombstone")
export const memberIdSchema = denTypeIdSchema("member")
export const teamIdSchema = denTypeIdSchema("team")
export const configObjectTypeSchema = z.enum(configObjectTypeValues)
export const configObjectSourceModeSchema = z.enum(configObjectSourceModeValues)
export const configObjectCreatedViaSchema = z.enum(configObjectCreatedViaValues)
export const configObjectStatusSchema = z.enum(configObjectStatusValues)
export const pluginStatusSchema = z.enum(pluginStatusValues)
export const membershipSourceSchema = z.enum(membershipSourceValues)
export const accessRoleSchema = z.enum(accessRoleValues)
export const connectorTypeSchema = z.enum(connectorTypeValues)
export const connectorAccountStatusSchema = z.enum(connectorAccountStatusValues)
export const connectorInstanceStatusSchema = z.enum(connectorInstanceStatusValues)
export const connectorTargetKindSchema = z.enum(connectorTargetKindValues)
export const connectorMappingKindSchema = z.enum(connectorMappingKindValues)
export const connectorSyncStatusSchema = z.enum(connectorSyncStatusValues)
export const connectorSyncEventTypeSchema = z.enum(connectorSyncEventTypeValues)
export const githubWebhookEventSchema = z.enum(githubWebhookEventValues)
export const pluginArchPaginationQuerySchema = z.object({
cursor: cursorSchema.optional(),
limit: z.coerce.number().int().min(1).max(100).optional(),
})
export const configObjectListQuerySchema = pluginArchPaginationQuerySchema.extend({
type: configObjectTypeSchema.optional(),
status: configObjectStatusSchema.optional(),
sourceMode: configObjectSourceModeSchema.optional(),
pluginId: pluginIdSchema.optional(),
connectorInstanceId: connectorInstanceIdSchema.optional(),
includeDeleted: queryBooleanSchema.optional(),
q: z.string().trim().min(1).max(255).optional(),
})
export const configObjectVersionListQuerySchema = pluginArchPaginationQuerySchema.extend({
includeDeleted: queryBooleanSchema.optional(),
})
export const pluginListQuerySchema = pluginArchPaginationQuerySchema.extend({
status: pluginStatusSchema.optional(),
q: z.string().trim().min(1).max(255).optional(),
})
export const connectorAccountListQuerySchema = pluginArchPaginationQuerySchema.extend({
connectorType: connectorTypeSchema.optional(),
status: connectorAccountStatusSchema.optional(),
q: z.string().trim().min(1).max(255).optional(),
})
export const connectorInstanceListQuerySchema = pluginArchPaginationQuerySchema.extend({
connectorAccountId: connectorAccountIdSchema.optional(),
connectorType: connectorTypeSchema.optional(),
pluginId: pluginIdSchema.optional(),
status: connectorInstanceStatusSchema.optional(),
q: z.string().trim().min(1).max(255).optional(),
})
export const connectorTargetListQuerySchema = pluginArchPaginationQuerySchema.extend({
targetKind: connectorTargetKindSchema.optional(),
q: z.string().trim().min(1).max(255).optional(),
})
export const connectorMappingListQuerySchema = pluginArchPaginationQuerySchema.extend({
mappingKind: connectorMappingKindSchema.optional(),
objectType: configObjectTypeSchema.optional(),
pluginId: pluginIdSchema.optional(),
q: z.string().trim().min(1).max(255).optional(),
})
export const connectorSyncEventListQuerySchema = pluginArchPaginationQuerySchema.extend({
connectorInstanceId: connectorInstanceIdSchema.optional(),
connectorTargetId: connectorTargetIdSchema.optional(),
eventType: connectorSyncEventTypeSchema.optional(),
status: connectorSyncStatusSchema.optional(),
q: z.string().trim().min(1).max(255).optional(),
})
export const githubRepositoryListQuerySchema = pluginArchPaginationQuerySchema.extend({
q: z.string().trim().min(1).max(255).optional(),
})
export const configObjectParamsSchema = orgIdParamSchema.extend(idParamSchema("configObjectId", "configObject").shape)
export const configObjectVersionParamsSchema = configObjectParamsSchema.extend(idParamSchema("versionId", "configObjectVersion").shape)
export const configObjectAccessGrantParamsSchema = configObjectParamsSchema.extend(idParamSchema("grantId", "configObjectAccessGrant").shape)
export const pluginParamsSchema = orgIdParamSchema.extend(idParamSchema("pluginId", "plugin").shape)
export const pluginConfigObjectParamsSchema = pluginParamsSchema.extend(idParamSchema("configObjectId", "configObject").shape)
export const pluginAccessGrantParamsSchema = pluginParamsSchema.extend(idParamSchema("grantId", "pluginAccessGrant").shape)
export const connectorAccountParamsSchema = orgIdParamSchema.extend(idParamSchema("connectorAccountId", "connectorAccount").shape)
export const connectorInstanceParamsSchema = orgIdParamSchema.extend(idParamSchema("connectorInstanceId", "connectorInstance").shape)
export const connectorInstanceAccessGrantParamsSchema = connectorInstanceParamsSchema.extend(idParamSchema("grantId", "connectorInstanceAccessGrant").shape)
export const connectorTargetParamsSchema = orgIdParamSchema.extend(idParamSchema("connectorTargetId", "connectorTarget").shape)
export const connectorMappingParamsSchema = orgIdParamSchema.extend(idParamSchema("connectorMappingId", "connectorMapping").shape)
export const connectorSyncEventParamsSchema = orgIdParamSchema.extend(idParamSchema("connectorSyncEventId", "connectorSyncEvent").shape)
export const connectorAccountRepositoryParamsSchema = connectorAccountParamsSchema
export const configObjectInputSchema = z.object({
rawSourceText: rawSourceTextSchema.optional(),
normalizedPayloadJson: jsonObjectSchema.optional(),
parserMode: z.string().trim().min(1).max(100).optional(),
schemaVersion: z.string().trim().min(1).max(100).optional(),
metadata: jsonObjectSchema.optional(),
}).superRefine((value, ctx) => {
if (!value.rawSourceText && !value.normalizedPayloadJson) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Provide either rawSourceText or normalizedPayloadJson.",
path: ["rawSourceText"],
})
}
})
export const configObjectCreateSchema = z.object({
type: configObjectTypeSchema,
sourceMode: configObjectSourceModeSchema,
pluginIds: z.array(pluginIdSchema).max(100).optional(),
input: configObjectInputSchema,
})
export const configObjectCreateVersionSchema = z.object({
input: configObjectInputSchema,
reason: z.string().trim().min(1).max(255).optional(),
})
export const configObjectPluginAttachSchema = z.object({
pluginId: pluginIdSchema,
membershipSource: membershipSourceSchema.optional(),
})
export const resourceAccessGrantWriteSchema = z.object({
orgMembershipId: memberIdSchema.optional(),
teamId: teamIdSchema.optional(),
orgWide: z.boolean().optional().default(false),
role: accessRoleSchema,
}).superRefine((value, ctx) => {
const count = Number(Boolean(value.orgMembershipId)) + Number(Boolean(value.teamId)) + Number(Boolean(value.orgWide))
if (count !== 1) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Provide exactly one of orgMembershipId, teamId, or orgWide=true.",
path: ["orgMembershipId"],
})
}
})
export const pluginCreateSchema = z.object({
name: z.string().trim().min(1).max(255),
description: nullableStringSchema.optional(),
})
export const pluginUpdateSchema = z.object({
name: z.string().trim().min(1).max(255).optional(),
description: nullableStringSchema.optional(),
}).superRefine((value, ctx) => {
if (value.name === undefined && value.description === undefined) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Provide at least one field to update.",
path: ["name"],
})
}
})
export const pluginMembershipWriteSchema = z.object({
configObjectId: configObjectIdSchema,
membershipSource: membershipSourceSchema.optional(),
})
export const connectorAccountCreateSchema = z.object({
connectorType: connectorTypeSchema,
remoteId: z.string().trim().min(1).max(255),
externalAccountRef: z.string().trim().min(1).max(255).nullable().optional(),
displayName: z.string().trim().min(1).max(255),
metadata: jsonObjectSchema.optional(),
})
export const connectorAccountDisconnectSchema = z.object({
reason: z.string().trim().min(1).max(255).optional(),
}).optional()
export const connectorInstanceCreateSchema = z.object({
connectorAccountId: connectorAccountIdSchema,
connectorType: connectorTypeSchema,
remoteId: z.string().trim().min(1).max(255).nullable().optional(),
name: z.string().trim().min(1).max(255),
config: jsonObjectSchema.optional(),
})
export const connectorInstanceUpdateSchema = z.object({
remoteId: z.string().trim().min(1).max(255).nullable().optional(),
name: z.string().trim().min(1).max(255).optional(),
status: connectorInstanceStatusSchema.optional(),
config: jsonObjectSchema.optional(),
}).superRefine((value, ctx) => {
if (value.remoteId === undefined && value.name === undefined && value.status === undefined && value.config === undefined) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Provide at least one field to update.",
path: ["name"],
})
}
})
export const connectorTargetCreateSchema = z.object({
connectorType: connectorTypeSchema,
remoteId: z.string().trim().min(1).max(255),
targetKind: connectorTargetKindSchema,
externalTargetRef: z.string().trim().min(1).max(255).nullable().optional(),
config: jsonObjectSchema,
})
export const connectorTargetUpdateSchema = z.object({
remoteId: z.string().trim().min(1).max(255).optional(),
externalTargetRef: z.string().trim().min(1).max(255).nullable().optional(),
config: jsonObjectSchema.optional(),
}).superRefine((value, ctx) => {
if (value.remoteId === undefined && value.externalTargetRef === undefined && value.config === undefined) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Provide at least one field to update.",
path: ["remoteId"],
})
}
})
export const connectorMappingCreateSchema = z.object({
mappingKind: connectorMappingKindSchema,
selector: z.string().trim().min(1).max(255),
objectType: configObjectTypeSchema,
pluginId: pluginIdSchema.nullable().optional(),
autoAddToPlugin: z.boolean().default(false),
config: jsonObjectSchema.optional(),
})
export const connectorMappingUpdateSchema = z.object({
selector: z.string().trim().min(1).max(255).optional(),
objectType: configObjectTypeSchema.optional(),
pluginId: pluginIdSchema.nullable().optional(),
autoAddToPlugin: z.boolean().optional(),
config: jsonObjectSchema.optional(),
}).superRefine((value, ctx) => {
if (
value.selector === undefined
&& value.objectType === undefined
&& value.pluginId === undefined
&& value.autoAddToPlugin === undefined
&& value.config === undefined
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Provide at least one field to update.",
path: ["selector"],
})
}
})
export const githubConnectorSetupSchema = z.object({
installationId: z.number().int().positive(),
connectorAccountId: connectorAccountIdSchema.optional(),
connectorInstanceName: z.string().trim().min(1).max(255),
repositoryId: z.number().int().positive(),
repositoryFullName: z.string().trim().min(1).max(255),
branch: z.string().trim().min(1).max(255),
ref: z.string().trim().min(1).max(255),
mappings: z.array(connectorMappingCreateSchema).max(100).default([]),
})
export const githubConnectorAccountCreateSchema = z.object({
installationId: z.number().int().positive(),
accountLogin: z.string().trim().min(1).max(255),
accountType: z.enum(["Organization", "User"]),
displayName: z.string().trim().min(1).max(255),
})
export const githubValidateTargetSchema = z.object({
installationId: z.number().int().positive(),
repositoryId: z.number().int().positive(),
repositoryFullName: z.string().trim().min(1).max(255),
branch: z.string().trim().min(1).max(255),
ref: z.string().trim().min(1).max(255),
})
export const accessGrantSchema = z.object({
id: z.union([configObjectAccessGrantIdSchema, pluginAccessGrantIdSchema, connectorInstanceAccessGrantIdSchema]),
orgMembershipId: memberIdSchema.nullable(),
teamId: teamIdSchema.nullable(),
orgWide: z.boolean(),
role: accessRoleSchema,
createdByOrgMembershipId: memberIdSchema,
createdAt: z.string().datetime({ offset: true }),
removedAt: nullableTimestampSchema,
}).meta({ ref: "PluginArchAccessGrant" })
export const configObjectVersionSchema = z.object({
id: configObjectVersionIdSchema,
configObjectId: configObjectIdSchema,
schemaVersion: z.string().trim().min(1).max(100).nullable(),
normalizedPayloadJson: jsonObjectSchema.nullable(),
rawSourceText: z.string().nullable(),
createdVia: configObjectCreatedViaSchema,
createdByOrgMembershipId: memberIdSchema.nullable(),
connectorSyncEventId: connectorSyncEventIdSchema.nullable(),
sourceRevisionRef: z.string().trim().min(1).max(255).nullable(),
isDeletedVersion: z.boolean(),
createdAt: z.string().datetime({ offset: true }),
}).meta({ ref: "PluginArchConfigObjectVersion" })
export const configObjectSchema = z.object({
id: configObjectIdSchema,
organizationId: denTypeIdSchema("organization"),
objectType: configObjectTypeSchema,
sourceMode: configObjectSourceModeSchema,
title: z.string().trim().min(1).max(255),
description: nullableStringSchema,
searchText: z.string().trim().min(1).max(65535).nullable(),
currentFileName: z.string().trim().min(1).max(255).nullable(),
currentFileExtension: z.string().trim().min(1).max(32).nullable(),
currentRelativePath: z.string().trim().min(1).max(255).nullable(),
status: configObjectStatusSchema,
createdByOrgMembershipId: memberIdSchema,
connectorInstanceId: connectorInstanceIdSchema.nullable(),
createdAt: z.string().datetime({ offset: true }),
updatedAt: z.string().datetime({ offset: true }),
deletedAt: nullableTimestampSchema,
latestVersion: configObjectVersionSchema.nullable(),
}).meta({ ref: "PluginArchConfigObject" })
export const pluginMembershipSchema = z.object({
id: pluginConfigObjectIdSchema,
pluginId: pluginIdSchema,
configObjectId: configObjectIdSchema,
membershipSource: membershipSourceSchema,
connectorMappingId: connectorMappingIdSchema.nullable(),
createdByOrgMembershipId: memberIdSchema.nullable(),
createdAt: z.string().datetime({ offset: true }),
removedAt: nullableTimestampSchema,
configObject: configObjectSchema.optional(),
}).meta({ ref: "PluginArchPluginMembership" })
export const pluginSchema = z.object({
id: pluginIdSchema,
organizationId: denTypeIdSchema("organization"),
name: z.string().trim().min(1).max(255),
description: nullableStringSchema,
status: pluginStatusSchema,
createdByOrgMembershipId: memberIdSchema,
createdAt: z.string().datetime({ offset: true }),
updatedAt: z.string().datetime({ offset: true }),
deletedAt: nullableTimestampSchema,
memberCount: z.number().int().nonnegative().optional(),
}).meta({ ref: "PluginArchPlugin" })
export const connectorAccountSchema = z.object({
id: connectorAccountIdSchema,
organizationId: denTypeIdSchema("organization"),
connectorType: connectorTypeSchema,
remoteId: z.string().trim().min(1).max(255),
externalAccountRef: z.string().trim().min(1).max(255).nullable(),
displayName: z.string().trim().min(1).max(255),
status: connectorAccountStatusSchema,
createdByOrgMembershipId: memberIdSchema,
createdAt: z.string().datetime({ offset: true }),
updatedAt: z.string().datetime({ offset: true }),
metadata: jsonObjectSchema.optional(),
}).meta({ ref: "PluginArchConnectorAccount" })
export const connectorInstanceSchema = z.object({
id: connectorInstanceIdSchema,
organizationId: denTypeIdSchema("organization"),
connectorAccountId: connectorAccountIdSchema,
connectorType: connectorTypeSchema,
remoteId: z.string().trim().min(1).max(255).nullable(),
name: z.string().trim().min(1).max(255),
status: connectorInstanceStatusSchema,
instanceConfigJson: jsonObjectSchema.nullable(),
lastSyncedAt: nullableTimestampSchema,
lastSyncStatus: connectorSyncStatusSchema.nullable(),
lastSyncCursor: z.string().trim().min(1).max(255).nullable(),
createdByOrgMembershipId: memberIdSchema,
createdAt: z.string().datetime({ offset: true }),
updatedAt: z.string().datetime({ offset: true }),
}).meta({ ref: "PluginArchConnectorInstance" })
export const connectorTargetSchema = z.object({
id: connectorTargetIdSchema,
connectorInstanceId: connectorInstanceIdSchema,
connectorType: connectorTypeSchema,
remoteId: z.string().trim().min(1).max(255),
targetKind: connectorTargetKindSchema,
externalTargetRef: z.string().trim().min(1).max(255).nullable(),
targetConfigJson: jsonObjectSchema,
createdAt: z.string().datetime({ offset: true }),
updatedAt: z.string().datetime({ offset: true }),
}).meta({ ref: "PluginArchConnectorTarget" })
export const connectorMappingSchema = z.object({
id: connectorMappingIdSchema,
connectorInstanceId: connectorInstanceIdSchema,
connectorTargetId: connectorTargetIdSchema,
connectorType: connectorTypeSchema,
remoteId: z.string().trim().min(1).max(255).nullable(),
mappingKind: connectorMappingKindSchema,
selector: z.string().trim().min(1).max(255),
objectType: configObjectTypeSchema,
pluginId: pluginIdSchema.nullable(),
autoAddToPlugin: z.boolean(),
mappingConfigJson: jsonObjectSchema.nullable(),
createdAt: z.string().datetime({ offset: true }),
updatedAt: z.string().datetime({ offset: true }),
}).meta({ ref: "PluginArchConnectorMapping" })
export const connectorSyncSummarySchema = z.object({
createdCount: z.number().int().nonnegative().optional(),
updatedCount: z.number().int().nonnegative().optional(),
deletedCount: z.number().int().nonnegative().optional(),
skippedCount: z.number().int().nonnegative().optional(),
failedCount: z.number().int().nonnegative().optional(),
failures: z.array(jsonObjectSchema).optional(),
}).passthrough().meta({ ref: "PluginArchConnectorSyncSummary" })
export const connectorSyncEventSchema = z.object({
id: connectorSyncEventIdSchema,
connectorInstanceId: connectorInstanceIdSchema,
connectorTargetId: connectorTargetIdSchema.nullable(),
connectorType: connectorTypeSchema,
remoteId: z.string().trim().min(1).max(255).nullable(),
eventType: connectorSyncEventTypeSchema,
externalEventRef: z.string().trim().min(1).max(255).nullable(),
sourceRevisionRef: z.string().trim().min(1).max(255).nullable(),
status: connectorSyncStatusSchema,
summaryJson: connectorSyncSummarySchema.nullable(),
startedAt: z.string().datetime({ offset: true }),
completedAt: nullableTimestampSchema,
}).meta({ ref: "PluginArchConnectorSyncEvent" })
export const connectorSourceBindingSchema = z.object({
id: connectorSourceBindingIdSchema,
configObjectId: configObjectIdSchema,
connectorInstanceId: connectorInstanceIdSchema,
connectorTargetId: connectorTargetIdSchema,
connectorMappingId: connectorMappingIdSchema,
connectorType: connectorTypeSchema,
remoteId: z.string().trim().min(1).max(255).nullable(),
externalLocator: z.string().trim().min(1).max(255),
externalStableRef: z.string().trim().min(1).max(255).nullable(),
lastSeenSourceRevisionRef: z.string().trim().min(1).max(255).nullable(),
status: configObjectStatusSchema,
createdAt: z.string().datetime({ offset: true }),
updatedAt: z.string().datetime({ offset: true }),
deletedAt: nullableTimestampSchema,
}).meta({ ref: "PluginArchConnectorSourceBinding" })
export const connectorSourceTombstoneSchema = z.object({
id: connectorSourceTombstoneIdSchema,
connectorInstanceId: connectorInstanceIdSchema,
connectorTargetId: connectorTargetIdSchema,
connectorMappingId: connectorMappingIdSchema,
connectorType: connectorTypeSchema,
remoteId: z.string().trim().min(1).max(255).nullable(),
externalLocator: z.string().trim().min(1).max(255),
formerConfigObjectId: configObjectIdSchema,
deletedInSyncEventId: connectorSyncEventIdSchema,
deletedSourceRevisionRef: z.string().trim().min(1).max(255).nullable(),
createdAt: z.string().datetime({ offset: true }),
}).meta({ ref: "PluginArchConnectorSourceTombstone" })
export const githubWebhookHeadersSchema = z.object({
xHubSignature256: z.string().trim().min(1),
xGithubEvent: githubWebhookEventSchema,
xGithubDelivery: z.string().trim().min(1),
}).meta({ ref: "PluginArchGithubWebhookHeaders" })
export const githubWebhookPayloadSchema = z.object({
after: z.string().trim().min(1).optional(),
installation: z.object({
id: z.number().int().positive(),
}).passthrough().optional(),
ref: z.string().trim().min(1).optional(),
repository: z.object({
full_name: z.string().trim().min(1),
id: z.number().int().positive(),
}).passthrough().optional(),
}).passthrough().meta({ ref: "PluginArchGithubWebhookPayload" })
export const githubWebhookEnvelopeSchema = z.object({
deliveryId: z.string().trim().min(1),
event: githubWebhookEventSchema,
installationId: z.number().int().positive().optional(),
repositoryId: z.number().int().positive().optional(),
repositoryFullName: z.string().trim().min(1).optional(),
ref: z.string().trim().min(1).optional(),
headSha: z.string().trim().min(1).optional(),
payload: githubWebhookPayloadSchema,
}).meta({ ref: "PluginArchGithubWebhookEnvelope" })
export const githubConnectorSyncJobSchema = z.object({
connectorType: z.literal("github"),
connectorInstanceId: connectorInstanceIdSchema,
connectorTargetId: connectorTargetIdSchema,
connectorSyncEventId: connectorSyncEventIdSchema,
deliveryId: z.string().trim().min(1),
installationId: z.number().int().positive(),
repositoryId: z.number().int().positive(),
repositoryFullName: z.string().trim().min(1),
ref: z.string().trim().min(1),
headSha: z.string().trim().min(1),
}).meta({ ref: "PluginArchGithubConnectorSyncJob" })
export const githubWebhookRawBodySchema = z.string().min(1).meta({ ref: "PluginArchGithubWebhookRawBody" })
export const githubWebhookAcceptedResponseSchema = z.object({
ok: z.literal(true),
accepted: z.literal(true),
event: githubWebhookEventSchema,
deliveryId: z.string().trim().min(1),
queued: z.boolean(),
}).meta({ ref: "PluginArchGithubWebhookAcceptedResponse" })
export const githubWebhookIgnoredResponseSchema = z.object({
ok: z.literal(true),
accepted: z.literal(false),
reason: z.string().trim().min(1),
}).meta({ ref: "PluginArchGithubWebhookIgnoredResponse" })
export const githubWebhookUnauthorizedResponseSchema = z.object({
ok: z.literal(false),
error: z.literal("invalid signature"),
}).meta({ ref: "PluginArchGithubWebhookUnauthorizedResponse" })
export function pluginArchListResponseSchema<TSchema extends z.ZodTypeAny>(ref: string, itemSchema: TSchema) {
return z.object({
items: z.array(itemSchema),
nextCursor: cursorSchema.nullable(),
}).meta({ ref })
}
export function pluginArchDetailResponseSchema<TSchema extends z.ZodTypeAny>(ref: string, itemSchema: TSchema) {
return z.object({
item: itemSchema,
}).meta({ ref })
}
export function pluginArchMutationResponseSchema<TSchema extends z.ZodTypeAny>(ref: string, itemSchema: TSchema) {
return z.object({
ok: z.literal(true),
item: itemSchema,
}).meta({ ref })
}
export function pluginArchAsyncResponseSchema<TSchema extends z.ZodTypeAny>(ref: string, jobSchema: TSchema) {
return z.object({
ok: z.literal(true),
queued: z.literal(true),
job: jobSchema,
}).meta({ ref })
}
export const configObjectListResponseSchema = pluginArchListResponseSchema("PluginArchConfigObjectListResponse", configObjectSchema)
export const configObjectDetailResponseSchema = pluginArchDetailResponseSchema("PluginArchConfigObjectDetailResponse", configObjectSchema)
export const configObjectMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchConfigObjectMutationResponse", configObjectSchema)
export const configObjectVersionListResponseSchema = pluginArchListResponseSchema("PluginArchConfigObjectVersionListResponse", configObjectVersionSchema)
export const configObjectVersionDetailResponseSchema = pluginArchDetailResponseSchema("PluginArchConfigObjectVersionDetailResponse", configObjectVersionSchema)
export const pluginListResponseSchema = pluginArchListResponseSchema("PluginArchPluginListResponse", pluginSchema)
export const pluginDetailResponseSchema = pluginArchDetailResponseSchema("PluginArchPluginDetailResponse", pluginSchema)
export const pluginMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchPluginMutationResponse", pluginSchema)
export const pluginMembershipListResponseSchema = pluginArchListResponseSchema("PluginArchPluginMembershipListResponse", pluginMembershipSchema)
export const pluginMembershipDetailResponseSchema = pluginArchDetailResponseSchema("PluginArchPluginMembershipDetailResponse", pluginMembershipSchema)
export const pluginMembershipMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchPluginMembershipMutationResponse", pluginMembershipSchema)
export const accessGrantListResponseSchema = pluginArchListResponseSchema("PluginArchAccessGrantListResponse", accessGrantSchema)
export const accessGrantMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchAccessGrantMutationResponse", accessGrantSchema)
export const connectorAccountListResponseSchema = pluginArchListResponseSchema("PluginArchConnectorAccountListResponse", connectorAccountSchema)
export const connectorAccountDetailResponseSchema = pluginArchDetailResponseSchema("PluginArchConnectorAccountDetailResponse", connectorAccountSchema)
export const connectorAccountMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchConnectorAccountMutationResponse", connectorAccountSchema)
export const connectorInstanceListResponseSchema = pluginArchListResponseSchema("PluginArchConnectorInstanceListResponse", connectorInstanceSchema)
export const connectorInstanceDetailResponseSchema = pluginArchDetailResponseSchema("PluginArchConnectorInstanceDetailResponse", connectorInstanceSchema)
export const connectorInstanceMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchConnectorInstanceMutationResponse", connectorInstanceSchema)
export const connectorTargetListResponseSchema = pluginArchListResponseSchema("PluginArchConnectorTargetListResponse", connectorTargetSchema)
export const connectorTargetDetailResponseSchema = pluginArchDetailResponseSchema("PluginArchConnectorTargetDetailResponse", connectorTargetSchema)
export const connectorTargetMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchConnectorTargetMutationResponse", connectorTargetSchema)
export const connectorMappingListResponseSchema = pluginArchListResponseSchema("PluginArchConnectorMappingListResponse", connectorMappingSchema)
export const connectorMappingMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchConnectorMappingMutationResponse", connectorMappingSchema)
export const connectorSyncEventListResponseSchema = pluginArchListResponseSchema("PluginArchConnectorSyncEventListResponse", connectorSyncEventSchema)
export const connectorSyncEventDetailResponseSchema = pluginArchDetailResponseSchema("PluginArchConnectorSyncEventDetailResponse", connectorSyncEventSchema)
export const connectorSyncAsyncResponseSchema = pluginArchAsyncResponseSchema(
"PluginArchConnectorSyncAsyncResponse",
z.object({ id: connectorSyncEventIdSchema }),
)
export const githubRepositorySchema = z.object({
id: z.number().int().positive(),
fullName: z.string().trim().min(1),
defaultBranch: z.string().trim().min(1).nullable(),
private: z.boolean(),
}).meta({ ref: "PluginArchGithubRepository" })
export const githubRepositoryListResponseSchema = pluginArchListResponseSchema("PluginArchGithubRepositoryListResponse", githubRepositorySchema)
export const githubSetupResponseSchema = pluginArchMutationResponseSchema(
"PluginArchGithubSetupResponse",
z.object({
connectorAccount: connectorAccountSchema,
connectorInstance: connectorInstanceSchema,
connectorTarget: connectorTargetSchema,
}),
)
export const githubValidateTargetResponseSchema = pluginArchMutationResponseSchema(
"PluginArchGithubValidateTargetResponse",
z.object({
branchExists: z.boolean(),
defaultBranch: z.string().trim().min(1).nullable(),
repositoryAccessible: z.boolean(),
}),
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,100 @@
import { createHmac, timingSafeEqual } from "node:crypto"
import type { Env, Hono } from "hono"
import { describeRoute } from "hono-openapi"
import { env } from "../../env.js"
import { emptyResponse, jsonResponse } from "../../openapi.js"
import { enqueueGithubWebhookSync } from "../org/plugin-system/store.js"
import {
githubWebhookAcceptedResponseSchema,
githubWebhookIgnoredResponseSchema,
githubWebhookUnauthorizedResponseSchema,
} from "../org/plugin-system/schemas.js"
import { pluginArchRoutePaths } from "../org/plugin-system/contracts.js"
export function signGithubBody(rawBody: string, secret: string) {
return `sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`
}
export function safeCompareGithubSignature(received: string, expected: string) {
const encoder = new TextEncoder()
const receivedBuffer = encoder.encode(received)
const expectedBuffer = encoder.encode(expected)
if (receivedBuffer.length !== expectedBuffer.length) {
return false
}
return timingSafeEqual(receivedBuffer, expectedBuffer)
}
export function registerGithubWebhookRoutes<T extends Env>(app: Hono<T>) {
app.post(
pluginArchRoutePaths.githubWebhookIngress,
describeRoute({
tags: ["Webhooks"],
summary: "GitHub webhook ingress",
description: "Verifies a GitHub App webhook signature against the raw request body, then records any relevant sync work.",
responses: {
200: jsonResponse("Ignored but valid GitHub webhook delivery.", githubWebhookIgnoredResponseSchema),
202: jsonResponse("Accepted GitHub webhook delivery.", githubWebhookAcceptedResponseSchema),
401: jsonResponse("Invalid GitHub webhook signature.", githubWebhookUnauthorizedResponseSchema),
503: emptyResponse("GitHub webhook secret is not configured."),
},
}),
async (c) => {
const secret = env.githubConnectorApp.webhookSecret
if (!secret) {
return c.body(null, 503)
}
const rawBody = await c.req.raw.text()
const signature = c.req.raw.headers.get("x-hub-signature-256")?.trim() ?? ""
if (!signature) {
return c.json({ ok: false, error: "invalid signature" }, 401)
}
const expected = signGithubBody(rawBody, secret)
if (!safeCompareGithubSignature(signature, expected)) {
return c.json({ ok: false, error: "invalid signature" }, 401)
}
const event = c.req.raw.headers.get("x-github-event")?.trim() ?? ""
const deliveryId = c.req.raw.headers.get("x-github-delivery")?.trim() ?? ""
if (!event || !deliveryId) {
return c.json({ ok: true, accepted: false, reason: "event ignored" }, 200)
}
const normalizedEvent = event === "push" || event === "installation" || event === "installation_repositories" || event === "repository"
? event
: null
if (!normalizedEvent) {
return c.json({ ok: true, accepted: false, reason: "event ignored" }, 200)
}
const payload = JSON.parse(rawBody) as Record<string, unknown>
const installationId = payload.installation && typeof payload.installation === "object" && typeof (payload.installation as Record<string, unknown>).id === "number"
? (payload.installation as Record<string, unknown>).id as number
: undefined
const repository = payload.repository && typeof payload.repository === "object" ? payload.repository as Record<string, unknown> : null
const repositoryFullName = typeof repository?.full_name === "string" ? repository.full_name : undefined
const repositoryId = typeof repository?.id === "number" ? repository.id : undefined
const ref = typeof payload.ref === "string" ? payload.ref : undefined
const headSha = typeof payload.after === "string" ? payload.after : undefined
const accepted = await enqueueGithubWebhookSync({
deliveryId,
event: normalizedEvent,
headSha,
installationId,
payload,
ref,
repositoryFullName,
repositoryId,
})
if (!accepted.accepted) {
return c.json({ ok: true, accepted: false, reason: accepted.reason }, 200)
}
return c.json({ ok: true, accepted: true, deliveryId, event: normalizedEvent, queued: accepted.queued }, 202)
},
)
}

View File

@@ -0,0 +1,6 @@
import type { Env, Hono } from "hono"
import { registerGithubWebhookRoutes } from "./github.js"
export function registerWebhookRoutes<T extends Env>(app: Hono<T>) {
registerGithubWebhookRoutes(app)
}

View File

@@ -0,0 +1,91 @@
import { afterEach, beforeAll, expect, test } from "bun:test"
import { Hono } from "hono"
function seedRequiredEnv() {
process.env.DATABASE_URL = process.env.DATABASE_URL ?? "mysql://root:password@127.0.0.1:3306/openwork_test"
process.env.DEN_DB_ENCRYPTION_KEY = process.env.DEN_DB_ENCRYPTION_KEY ?? "x".repeat(32)
process.env.BETTER_AUTH_SECRET = process.env.BETTER_AUTH_SECRET ?? "y".repeat(32)
process.env.BETTER_AUTH_URL = process.env.BETTER_AUTH_URL ?? "http://127.0.0.1:8790"
}
let envModule: typeof import("../src/env.js")
let githubModule: typeof import("../src/routes/webhooks/github.js")
beforeAll(async () => {
seedRequiredEnv()
envModule = await import("../src/env.js")
githubModule = await import("../src/routes/webhooks/github.js")
})
afterEach(() => {
envModule.env.githubConnectorApp.webhookSecret = "super-secret"
})
function createWebhookApp() {
const app = new Hono()
githubModule.registerGithubWebhookRoutes(app)
return app
}
test("webhook route rejects invalid signatures before JSON parsing", async () => {
envModule.env.githubConnectorApp.webhookSecret = "super-secret"
const app = createWebhookApp()
const response = await app.request("http://den.local/api/webhooks/connectors/github", {
body: "{",
headers: {
"x-github-delivery": "delivery-1",
"x-github-event": "push",
"x-hub-signature-256": "sha256=wrong",
},
method: "POST",
})
expect(response.status).toBe(401)
await expect(response.json()).resolves.toEqual({ ok: false, error: "invalid signature" })
})
test("webhook route returns 503 when the GitHub webhook secret is unset", async () => {
envModule.env.githubConnectorApp.webhookSecret = undefined
const app = createWebhookApp()
const response = await app.request("http://den.local/api/webhooks/connectors/github", {
body: "{}",
headers: {
"x-github-delivery": "delivery-2",
"x-github-event": "push",
"x-hub-signature-256": "sha256=unused",
},
method: "POST",
})
expect(response.status).toBe(503)
})
test("webhook route accepts a valid signature and ignores unbound deliveries cleanly", async () => {
envModule.env.githubConnectorApp.webhookSecret = "super-secret"
const app = createWebhookApp()
const payload = JSON.stringify({
after: "abc123",
ref: "refs/heads/main",
repository: {
full_name: "different-ai/openwork",
id: 42,
},
})
const response = await app.request("http://den.local/api/webhooks/connectors/github", {
body: payload,
headers: {
"x-github-delivery": "delivery-3",
"x-github-event": "push",
"x-hub-signature-256": githubModule.signGithubBody(payload, "super-secret"),
},
method: "POST",
})
expect(response.status).toBe(200)
await expect(response.json()).resolves.toEqual({
ok: true,
accepted: false,
reason: "missing installation id",
})
})

View File

@@ -0,0 +1,90 @@
import { beforeAll, expect, test } from "bun:test"
function seedRequiredEnv() {
process.env.DATABASE_URL = process.env.DATABASE_URL ?? "mysql://root:password@127.0.0.1:3306/openwork_test"
process.env.DEN_DB_ENCRYPTION_KEY = process.env.DEN_DB_ENCRYPTION_KEY ?? "x".repeat(32)
process.env.BETTER_AUTH_SECRET = process.env.BETTER_AUTH_SECRET ?? "y".repeat(32)
process.env.BETTER_AUTH_URL = process.env.BETTER_AUTH_URL ?? "http://127.0.0.1:8790"
}
let accessModule: typeof import("../src/routes/org/plugin-system/access.js")
beforeAll(async () => {
seedRequiredEnv()
accessModule = await import("../src/routes/org/plugin-system/access.js")
})
function createActorContext(input?: { isOwner?: boolean; role?: string; teamIds?: string[] }) {
return {
memberTeams: (input?.teamIds ?? []).map((teamId) => ({
createdAt: new Date("2026-04-17T00:00:00.000Z"),
id: teamId,
name: teamId,
organizationId: "org_test",
updatedAt: new Date("2026-04-17T00:00:00.000Z"),
})),
organizationContext: {
currentMember: {
createdAt: new Date("2026-04-17T00:00:00.000Z"),
id: "member_current",
isOwner: input?.isOwner ?? false,
role: input?.role ?? "member",
userId: "user_current",
},
},
} as any
}
test("org owners and admins get plugin-system capability access", () => {
expect(accessModule.isPluginArchOrgAdmin(createActorContext({ isOwner: true }))).toBe(true)
expect(accessModule.isPluginArchOrgAdmin(createActorContext({ role: "member,admin" }))).toBe(true)
expect(accessModule.isPluginArchOrgAdmin(createActorContext({ role: "member" }))).toBe(false)
expect(accessModule.hasPluginArchCapability(createActorContext({ isOwner: true }), "plugin.create")).toBe(true)
expect(accessModule.hasPluginArchCapability(createActorContext({ role: "admin" }), "connector_instance.create")).toBe(true)
expect(accessModule.hasPluginArchCapability(createActorContext({ role: "member" }), "config_object.create")).toBe(false)
})
test("grant resolution supports direct, team, org-wide, and highest-role precedence", () => {
const grants = [
{
orgMembershipId: null,
orgWide: true,
removedAt: null,
role: "viewer",
teamId: null,
},
{
orgMembershipId: null,
orgWide: false,
removedAt: null,
role: "editor",
teamId: "team_alpha",
},
{
orgMembershipId: "member_current",
orgWide: false,
removedAt: null,
role: "manager",
teamId: null,
},
] as const
expect(accessModule.resolvePluginArchGrantRole({ grants: [...grants], memberId: "member_current", teamIds: ["team_alpha"] })).toBe("manager")
expect(accessModule.resolvePluginArchGrantRole({ grants: [...grants], memberId: "other_member", teamIds: ["team_alpha"] })).toBe("editor")
expect(accessModule.resolvePluginArchGrantRole({ grants: [...grants], memberId: "other_member", teamIds: [] })).toBe("viewer")
})
test("removed grants are ignored during resolution", () => {
expect(accessModule.resolvePluginArchGrantRole({
grants: [{
orgMembershipId: "member_current",
orgWide: false,
removedAt: new Date("2026-04-17T00:00:00.000Z"),
role: "manager",
teamId: null,
}],
memberId: "member_current",
teamIds: [],
})).toBeNull()
})

View File

@@ -0,0 +1,284 @@
CREATE TABLE IF NOT EXISTS `config_object` (
`id` varchar(64) NOT NULL,
`organization_id` varchar(64) NOT NULL,
`object_type` enum('skill','agent','command','tool','mcp','hook','context','custom') NOT NULL,
`source_mode` enum('cloud','import','connector') NOT NULL,
`title` varchar(255) NOT NULL,
`description` text,
`search_text` text,
`current_file_name` varchar(255),
`current_file_extension` varchar(64),
`current_relative_path` varchar(2048),
`status` enum('active','inactive','deleted','archived','ingestion_error') NOT NULL DEFAULT 'active',
`created_by_org_membership_id` varchar(64) NOT NULL,
`connector_instance_id` varchar(64),
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
`deleted_at` timestamp(3) NULL,
CONSTRAINT `config_object_id` PRIMARY KEY(`id`),
KEY `config_object_organization_id` (`organization_id`),
KEY `config_object_type` (`object_type`),
KEY `config_object_source_mode` (`source_mode`),
KEY `config_object_status` (`status`),
KEY `config_object_created_by_org_membership_id` (`created_by_org_membership_id`),
KEY `config_object_connector_instance_id` (`connector_instance_id`),
KEY `config_object_current_relative_path` (`current_relative_path`)
);
CREATE TABLE IF NOT EXISTS `config_object_version` (
`id` varchar(64) NOT NULL,
`config_object_id` varchar(64) NOT NULL,
`normalized_payload_json` text,
`raw_source_text` text,
`schema_version` varchar(100),
`created_via` enum('cloud','import','connector','system') NOT NULL,
`created_by_org_membership_id` varchar(64),
`connector_sync_event_id` varchar(64),
`source_revision_ref` varchar(255),
`is_deleted_version` boolean NOT NULL DEFAULT false,
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
CONSTRAINT `config_object_version_id` PRIMARY KEY(`id`),
KEY `config_object_version_config_object_id` (`config_object_id`),
KEY `config_object_version_created_by_org_membership_id` (`created_by_org_membership_id`),
KEY `config_object_version_connector_sync_event_id` (`connector_sync_event_id`),
KEY `config_object_version_source_revision_ref` (`source_revision_ref`),
KEY `config_object_version_lookup_latest` (`config_object_id`, `created_at`, `id`)
);
CREATE TABLE IF NOT EXISTS `plugin` (
`id` varchar(64) NOT NULL,
`organization_id` varchar(64) NOT NULL,
`name` varchar(255) NOT NULL,
`description` text,
`status` enum('active','inactive','deleted','archived') NOT NULL DEFAULT 'active',
`created_by_org_membership_id` varchar(64) NOT NULL,
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
`deleted_at` timestamp(3) NULL,
CONSTRAINT `plugin_id` PRIMARY KEY(`id`),
KEY `plugin_organization_id` (`organization_id`),
KEY `plugin_created_by_org_membership_id` (`created_by_org_membership_id`),
KEY `plugin_status` (`status`),
KEY `plugin_name` (`name`)
);
CREATE TABLE IF NOT EXISTS `plugin_config_object` (
`id` varchar(64) NOT NULL,
`plugin_id` varchar(64) NOT NULL,
`config_object_id` varchar(64) NOT NULL,
`membership_source` enum('manual','connector','api','system') NOT NULL DEFAULT 'manual',
`connector_mapping_id` varchar(64),
`created_by_org_membership_id` varchar(64),
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`removed_at` timestamp(3) NULL,
CONSTRAINT `plugin_config_object_id` PRIMARY KEY(`id`),
CONSTRAINT `plugin_config_object_plugin_config_object` UNIQUE(`plugin_id`, `config_object_id`),
KEY `plugin_config_object_plugin_id` (`plugin_id`),
KEY `plugin_config_object_config_object_id` (`config_object_id`),
KEY `plugin_config_object_connector_mapping_id` (`connector_mapping_id`)
);
CREATE TABLE IF NOT EXISTS `config_object_access_grant` (
`id` varchar(64) NOT NULL,
`config_object_id` varchar(64) NOT NULL,
`org_membership_id` varchar(64),
`team_id` varchar(64),
`org_wide` boolean NOT NULL DEFAULT false,
`role` enum('viewer','editor','manager') NOT NULL,
`created_by_org_membership_id` varchar(64) NOT NULL,
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`removed_at` timestamp(3) NULL,
CONSTRAINT `config_object_access_grant_id` PRIMARY KEY(`id`),
CONSTRAINT `config_object_access_grant_object_org_membership` UNIQUE(`config_object_id`, `org_membership_id`),
CONSTRAINT `config_object_access_grant_object_team` UNIQUE(`config_object_id`, `team_id`),
KEY `config_object_access_grant_config_object_id` (`config_object_id`),
KEY `config_object_access_grant_org_membership_id` (`org_membership_id`),
KEY `config_object_access_grant_team_id` (`team_id`),
KEY `config_object_access_grant_org_wide` (`org_wide`)
);
CREATE TABLE IF NOT EXISTS `plugin_access_grant` (
`id` varchar(64) NOT NULL,
`plugin_id` varchar(64) NOT NULL,
`org_membership_id` varchar(64),
`team_id` varchar(64),
`org_wide` boolean NOT NULL DEFAULT false,
`role` enum('viewer','editor','manager') NOT NULL,
`created_by_org_membership_id` varchar(64) NOT NULL,
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`removed_at` timestamp(3) NULL,
CONSTRAINT `plugin_access_grant_id` PRIMARY KEY(`id`),
CONSTRAINT `plugin_access_grant_plugin_org_membership` UNIQUE(`plugin_id`, `org_membership_id`),
CONSTRAINT `plugin_access_grant_plugin_team` UNIQUE(`plugin_id`, `team_id`),
KEY `plugin_access_grant_plugin_id` (`plugin_id`),
KEY `plugin_access_grant_org_membership_id` (`org_membership_id`),
KEY `plugin_access_grant_team_id` (`team_id`),
KEY `plugin_access_grant_org_wide` (`org_wide`)
);
CREATE TABLE IF NOT EXISTS `connector_account` (
`id` varchar(64) NOT NULL,
`organization_id` varchar(64) NOT NULL,
`connector_type` enum('github') NOT NULL,
`remote_id` varchar(255) NOT NULL,
`external_account_ref` varchar(255),
`display_name` varchar(255) NOT NULL,
`status` enum('active','inactive','disconnected','error') NOT NULL DEFAULT 'active',
`created_by_org_membership_id` varchar(64) NOT NULL,
`metadata_json` json,
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
CONSTRAINT `connector_account_id` PRIMARY KEY(`id`),
CONSTRAINT `connector_account_org_type_remote_id` UNIQUE(`organization_id`, `connector_type`, `remote_id`),
KEY `connector_account_organization_id` (`organization_id`),
KEY `connector_account_created_by_org_membership_id` (`created_by_org_membership_id`),
KEY `connector_account_connector_type` (`connector_type`),
KEY `connector_account_status` (`status`)
);
CREATE TABLE IF NOT EXISTS `connector_instance` (
`id` varchar(64) NOT NULL,
`organization_id` varchar(64) NOT NULL,
`connector_account_id` varchar(64) NOT NULL,
`connector_type` enum('github') NOT NULL,
`remote_id` varchar(255),
`name` varchar(255) NOT NULL,
`status` enum('active','disabled','archived','error') NOT NULL DEFAULT 'active',
`instance_config_json` json,
`last_synced_at` timestamp(3) NULL,
`last_sync_status` enum('pending','queued','running','completed','failed','partial','ignored'),
`last_sync_cursor` text,
`created_by_org_membership_id` varchar(64) NOT NULL,
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
CONSTRAINT `connector_instance_id` PRIMARY KEY(`id`),
CONSTRAINT `connector_instance_org_name` UNIQUE(`organization_id`, `name`),
KEY `connector_instance_organization_id` (`organization_id`),
KEY `connector_instance_connector_account_id` (`connector_account_id`),
KEY `connector_instance_created_by_org_membership_id` (`created_by_org_membership_id`),
KEY `connector_instance_connector_type` (`connector_type`),
KEY `connector_instance_status` (`status`)
);
CREATE TABLE IF NOT EXISTS `connector_instance_access_grant` (
`id` varchar(64) NOT NULL,
`connector_instance_id` varchar(64) NOT NULL,
`org_membership_id` varchar(64),
`team_id` varchar(64),
`org_wide` boolean NOT NULL DEFAULT false,
`role` enum('viewer','editor','manager') NOT NULL,
`created_by_org_membership_id` varchar(64) NOT NULL,
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`removed_at` timestamp(3) NULL,
CONSTRAINT `connector_instance_access_grant_id` PRIMARY KEY(`id`),
CONSTRAINT `connector_instance_access_grant_instance_org_membership` UNIQUE(`connector_instance_id`, `org_membership_id`),
CONSTRAINT `connector_instance_access_grant_instance_team` UNIQUE(`connector_instance_id`, `team_id`),
KEY `connector_instance_access_grant_instance_id` (`connector_instance_id`),
KEY `connector_instance_access_grant_org_membership_id` (`org_membership_id`),
KEY `connector_instance_access_grant_team_id` (`team_id`),
KEY `connector_instance_access_grant_org_wide` (`org_wide`)
);
CREATE TABLE IF NOT EXISTS `connector_target` (
`id` varchar(64) NOT NULL,
`connector_instance_id` varchar(64) NOT NULL,
`connector_type` enum('github') NOT NULL,
`remote_id` varchar(255) NOT NULL,
`target_kind` enum('repository_branch') NOT NULL,
`external_target_ref` varchar(255),
`target_config_json` json NOT NULL,
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
CONSTRAINT `connector_target_id` PRIMARY KEY(`id`),
CONSTRAINT `connector_target_instance_remote_id` UNIQUE(`connector_instance_id`, `remote_id`),
KEY `connector_target_connector_instance_id` (`connector_instance_id`),
KEY `connector_target_connector_type` (`connector_type`),
KEY `connector_target_target_kind` (`target_kind`)
);
CREATE TABLE IF NOT EXISTS `connector_mapping` (
`id` varchar(64) NOT NULL,
`connector_instance_id` varchar(64) NOT NULL,
`connector_target_id` varchar(64) NOT NULL,
`connector_type` enum('github') NOT NULL,
`remote_id` varchar(255),
`mapping_kind` enum('path','api','custom') NOT NULL,
`selector` varchar(1024) NOT NULL,
`object_type` enum('skill','agent','command','tool','mcp','hook','context','custom') NOT NULL,
`plugin_id` varchar(64),
`auto_add_to_plugin` boolean NOT NULL DEFAULT false,
`mapping_config_json` json,
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
CONSTRAINT `connector_mapping_id` PRIMARY KEY(`id`),
CONSTRAINT `connector_mapping_target_selector_object_type` UNIQUE(`connector_target_id`, `selector`, `object_type`),
KEY `connector_mapping_connector_instance_id` (`connector_instance_id`),
KEY `connector_mapping_connector_target_id` (`connector_target_id`),
KEY `connector_mapping_object_type` (`object_type`),
KEY `connector_mapping_plugin_id` (`plugin_id`)
);
CREATE TABLE IF NOT EXISTS `connector_sync_event` (
`id` varchar(64) NOT NULL,
`connector_instance_id` varchar(64) NOT NULL,
`connector_target_id` varchar(64),
`connector_type` enum('github') NOT NULL,
`remote_id` varchar(255),
`event_type` enum('push','installation','installation_repositories','repository','manual_resync') NOT NULL,
`external_event_ref` varchar(255),
`source_revision_ref` varchar(255),
`status` enum('pending','queued','running','completed','failed','partial','ignored') NOT NULL DEFAULT 'pending',
`summary_json` json,
`started_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`completed_at` timestamp(3) NULL,
CONSTRAINT `connector_sync_event_id` PRIMARY KEY(`id`),
KEY `connector_sync_event_connector_instance_id` (`connector_instance_id`),
KEY `connector_sync_event_connector_target_id` (`connector_target_id`),
KEY `connector_sync_event_event_type` (`event_type`),
KEY `connector_sync_event_status` (`status`),
KEY `connector_sync_event_source_revision_ref` (`source_revision_ref`),
KEY `connector_sync_event_external_event_ref` (`external_event_ref`)
);
CREATE TABLE IF NOT EXISTS `connector_source_binding` (
`id` varchar(64) NOT NULL,
`config_object_id` varchar(64) NOT NULL,
`connector_instance_id` varchar(64) NOT NULL,
`connector_target_id` varchar(64) NOT NULL,
`connector_mapping_id` varchar(64) NOT NULL,
`connector_type` enum('github') NOT NULL,
`remote_id` varchar(255),
`external_locator` varchar(2048) NOT NULL,
`external_stable_ref` varchar(255),
`last_seen_source_revision_ref` varchar(255),
`status` enum('active','inactive','deleted','archived','ingestion_error') NOT NULL DEFAULT 'active',
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
`deleted_at` timestamp(3) NULL,
CONSTRAINT `connector_source_binding_id` PRIMARY KEY(`id`),
CONSTRAINT `connector_source_binding_config_object` UNIQUE(`config_object_id`),
KEY `connector_source_binding_connector_instance_id` (`connector_instance_id`),
KEY `connector_source_binding_connector_target_id` (`connector_target_id`),
KEY `connector_source_binding_connector_mapping_id` (`connector_mapping_id`),
KEY `connector_source_binding_external_locator` (`external_locator`)
);
CREATE TABLE IF NOT EXISTS `connector_source_tombstone` (
`id` varchar(64) NOT NULL,
`connector_instance_id` varchar(64) NOT NULL,
`connector_target_id` varchar(64) NOT NULL,
`connector_mapping_id` varchar(64) NOT NULL,
`connector_type` enum('github') NOT NULL,
`remote_id` varchar(255),
`external_locator` varchar(2048) NOT NULL,
`former_config_object_id` varchar(64) NOT NULL,
`deleted_in_sync_event_id` varchar(64) NOT NULL,
`deleted_source_revision_ref` varchar(255),
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
CONSTRAINT `connector_source_tombstone_id` PRIMARY KEY(`id`),
KEY `connector_source_tombstone_connector_instance_id` (`connector_instance_id`),
KEY `connector_source_tombstone_connector_target_id` (`connector_target_id`),
KEY `connector_source_tombstone_connector_mapping_id` (`connector_mapping_id`),
KEY `connector_source_tombstone_external_locator` (`external_locator`),
KEY `connector_source_tombstone_former_config_object_id` (`former_config_object_id`)
);

View File

@@ -64,6 +64,13 @@
"when": 1775350000000,
"tag": "0009_api_keys",
"breakpoints": true
},
{
"idx": 10,
"version": "5",
"when": 1776427000000,
"tag": "0010_plugin_arch",
"breakpoints": true
}
]
}

View File

@@ -1,6 +1,7 @@
export * from "./auth"
export * from "./org"
export * from "./sharables/llm-providers"
export * from "./sharables/plugin-arch"
export * from "./sharables/skills"
export * from "./teams"
export * from "./workers"

View File

@@ -0,0 +1,656 @@
import { relations, sql } from "drizzle-orm"
import {
boolean,
index,
json,
mysqlEnum,
mysqlTable,
text,
timestamp,
uniqueIndex,
varchar,
} from "drizzle-orm/mysql-core"
import { denTypeIdColumn, encryptedColumn, encryptedTextColumn } from "../../columns"
import { MemberTable, OrganizationTable } from "../org"
import { TeamTable } from "../teams"
export const configObjectTypeValues = ["skill", "agent", "command", "tool", "mcp", "hook", "context", "custom"] as const
export const configObjectSourceModeValues = ["cloud", "import", "connector"] as const
export const configObjectStatusValues = ["active", "inactive", "deleted", "archived", "ingestion_error"] as const
export const configObjectCreatedViaValues = ["cloud", "import", "connector", "system"] as const
export const pluginStatusValues = ["active", "inactive", "deleted", "archived"] as const
export const membershipSourceValues = ["manual", "connector", "api", "system"] as const
export const accessRoleValues = ["viewer", "editor", "manager"] as const
export const connectorTypeValues = ["github"] as const
export const connectorAccountStatusValues = ["active", "inactive", "disconnected", "error"] as const
export const connectorInstanceStatusValues = ["active", "disabled", "archived", "error"] as const
export const connectorTargetKindValues = ["repository_branch"] as const
export const connectorMappingKindValues = ["path", "api", "custom"] as const
export const connectorSyncEventTypeValues = ["push", "installation", "installation_repositories", "repository", "manual_resync"] as const
export const connectorSyncStatusValues = ["pending", "queued", "running", "completed", "failed", "partial", "ignored"] as const
function encryptedJsonColumn<TData extends Record<string, unknown> | Array<unknown> | null>(columnName: string) {
return encryptedColumn<TData>(columnName, {
deserialize: (value) => JSON.parse(value) as TData,
serialize: (value) => JSON.stringify(value),
})
}
export const ConfigObjectTable = mysqlTable(
"config_object",
{
id: denTypeIdColumn("configObject", "id").notNull().primaryKey(),
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
objectType: mysqlEnum("object_type", configObjectTypeValues).notNull(),
sourceMode: mysqlEnum("source_mode", configObjectSourceModeValues).notNull(),
title: varchar("title", { length: 255 }).notNull(),
description: text("description"),
searchText: text("search_text"),
currentFileName: varchar("current_file_name", { length: 255 }),
currentFileExtension: varchar("current_file_extension", { length: 64 }),
currentRelativePath: varchar("current_relative_path", { length: 255 }),
status: mysqlEnum("status", configObjectStatusValues).notNull().default("active"),
createdByOrgMembershipId: denTypeIdColumn("member", "created_by_org_membership_id").notNull(),
connectorInstanceId: denTypeIdColumn("connectorInstance", "connector_instance_id"),
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { fsp: 3 }).notNull().default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`),
deletedAt: timestamp("deleted_at", { fsp: 3 }),
},
(table) => [
index("config_object_organization_id").on(table.organizationId),
index("config_object_type").on(table.objectType),
index("config_object_source_mode").on(table.sourceMode),
index("config_object_status").on(table.status),
index("config_object_created_by_org_membership_id").on(table.createdByOrgMembershipId),
index("config_object_connector_instance_id").on(table.connectorInstanceId),
index("config_object_current_relative_path").on(table.currentRelativePath),
],
)
export const ConfigObjectVersionTable = mysqlTable(
"config_object_version",
{
id: denTypeIdColumn("configObjectVersion", "id").notNull().primaryKey(),
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
configObjectId: denTypeIdColumn("configObject", "config_object_id").notNull(),
normalizedPayloadJson: encryptedJsonColumn<Record<string, unknown> | null>("normalized_payload_json"),
rawSourceText: encryptedTextColumn("raw_source_text"),
schemaVersion: varchar("schema_version", { length: 100 }),
createdVia: mysqlEnum("created_via", configObjectCreatedViaValues).notNull(),
createdByOrgMembershipId: denTypeIdColumn("member", "created_by_org_membership_id"),
connectorSyncEventId: denTypeIdColumn("connectorSyncEvent", "connector_sync_event_id"),
sourceRevisionRef: varchar("source_revision_ref", { length: 255 }),
isDeletedVersion: boolean("is_deleted_version").notNull().default(false),
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
},
(table) => [
index("config_object_version_organization_id").on(table.organizationId),
index("config_object_version_config_object_id").on(table.configObjectId),
index("config_object_version_created_by_org_membership_id").on(table.createdByOrgMembershipId),
index("config_object_version_connector_sync_event_id").on(table.connectorSyncEventId),
index("config_object_version_source_revision_ref").on(table.sourceRevisionRef),
index("config_object_version_lookup_latest").on(table.configObjectId, table.createdAt, table.id),
],
)
export const PluginTable = mysqlTable(
"plugin",
{
id: denTypeIdColumn("plugin", "id").notNull().primaryKey(),
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
name: varchar("name", { length: 255 }).notNull(),
description: text("description"),
status: mysqlEnum("status", pluginStatusValues).notNull().default("active"),
createdByOrgMembershipId: denTypeIdColumn("member", "created_by_org_membership_id").notNull(),
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { fsp: 3 }).notNull().default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`),
deletedAt: timestamp("deleted_at", { fsp: 3 }),
},
(table) => [
index("plugin_organization_id").on(table.organizationId),
index("plugin_created_by_org_membership_id").on(table.createdByOrgMembershipId),
index("plugin_status").on(table.status),
index("plugin_name").on(table.name),
],
)
export const PluginConfigObjectTable = mysqlTable(
"plugin_config_object",
{
id: denTypeIdColumn("pluginConfigObject", "id").notNull().primaryKey(),
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
pluginId: denTypeIdColumn("plugin", "plugin_id").notNull(),
configObjectId: denTypeIdColumn("configObject", "config_object_id").notNull(),
membershipSource: mysqlEnum("membership_source", membershipSourceValues).notNull().default("manual"),
connectorMappingId: denTypeIdColumn("connectorMapping", "connector_mapping_id"),
createdByOrgMembershipId: denTypeIdColumn("member", "created_by_org_membership_id"),
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
removedAt: timestamp("removed_at", { fsp: 3 }),
},
(table) => [
index("plugin_config_object_organization_id").on(table.organizationId),
index("plugin_config_object_plugin_id").on(table.pluginId),
index("plugin_config_object_config_object_id").on(table.configObjectId),
index("plugin_config_object_connector_mapping_id").on(table.connectorMappingId),
uniqueIndex("plugin_config_object_plugin_config_object").on(table.pluginId, table.configObjectId),
],
)
export const ConfigObjectAccessGrantTable = mysqlTable(
"config_object_access_grant",
{
id: denTypeIdColumn("configObjectAccessGrant", "id").notNull().primaryKey(),
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
configObjectId: denTypeIdColumn("configObject", "config_object_id").notNull(),
orgMembershipId: denTypeIdColumn("member", "org_membership_id"),
teamId: denTypeIdColumn("team", "team_id"),
orgWide: boolean("org_wide").notNull().default(false),
role: mysqlEnum("role", accessRoleValues).notNull(),
createdByOrgMembershipId: denTypeIdColumn("member", "created_by_org_membership_id").notNull(),
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
removedAt: timestamp("removed_at", { fsp: 3 }),
},
(table) => [
index("config_object_access_grant_organization_id").on(table.organizationId),
index("config_object_access_grant_config_object_id").on(table.configObjectId),
index("config_object_access_grant_org_membership_id").on(table.orgMembershipId),
index("config_object_access_grant_team_id").on(table.teamId),
index("config_object_access_grant_org_wide").on(table.orgWide),
uniqueIndex("config_object_access_grant_object_org_membership").on(table.configObjectId, table.orgMembershipId),
uniqueIndex("config_object_access_grant_object_team").on(table.configObjectId, table.teamId),
],
)
export const PluginAccessGrantTable = mysqlTable(
"plugin_access_grant",
{
id: denTypeIdColumn("pluginAccessGrant", "id").notNull().primaryKey(),
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
pluginId: denTypeIdColumn("plugin", "plugin_id").notNull(),
orgMembershipId: denTypeIdColumn("member", "org_membership_id"),
teamId: denTypeIdColumn("team", "team_id"),
orgWide: boolean("org_wide").notNull().default(false),
role: mysqlEnum("role", accessRoleValues).notNull(),
createdByOrgMembershipId: denTypeIdColumn("member", "created_by_org_membership_id").notNull(),
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
removedAt: timestamp("removed_at", { fsp: 3 }),
},
(table) => [
index("plugin_access_grant_organization_id").on(table.organizationId),
index("plugin_access_grant_plugin_id").on(table.pluginId),
index("plugin_access_grant_org_membership_id").on(table.orgMembershipId),
index("plugin_access_grant_team_id").on(table.teamId),
index("plugin_access_grant_org_wide").on(table.orgWide),
uniqueIndex("plugin_access_grant_plugin_org_membership").on(table.pluginId, table.orgMembershipId),
uniqueIndex("plugin_access_grant_plugin_team").on(table.pluginId, table.teamId),
],
)
export const ConnectorAccountTable = mysqlTable(
"connector_account",
{
id: denTypeIdColumn("connectorAccount", "id").notNull().primaryKey(),
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
connectorType: mysqlEnum("connector_type", connectorTypeValues).notNull(),
remoteId: varchar("remote_id", { length: 255 }).notNull(),
externalAccountRef: varchar("external_account_ref", { length: 255 }),
displayName: varchar("display_name", { length: 255 }).notNull(),
status: mysqlEnum("status", connectorAccountStatusValues).notNull().default("active"),
createdByOrgMembershipId: denTypeIdColumn("member", "created_by_org_membership_id").notNull(),
metadataJson: json("metadata_json").$type<Record<string, unknown> | null>(),
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { fsp: 3 }).notNull().default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`),
},
(table) => [
index("connector_account_organization_id").on(table.organizationId),
index("connector_account_created_by_org_membership_id").on(table.createdByOrgMembershipId),
index("connector_account_connector_type").on(table.connectorType),
index("connector_account_status").on(table.status),
uniqueIndex("connector_account_org_type_remote_id").on(table.organizationId, table.connectorType, table.remoteId),
],
)
export const ConnectorInstanceTable = mysqlTable(
"connector_instance",
{
id: denTypeIdColumn("connectorInstance", "id").notNull().primaryKey(),
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
connectorAccountId: denTypeIdColumn("connectorAccount", "connector_account_id").notNull(),
connectorType: mysqlEnum("connector_type", connectorTypeValues).notNull(),
remoteId: varchar("remote_id", { length: 255 }),
name: varchar("name", { length: 255 }).notNull(),
status: mysqlEnum("status", connectorInstanceStatusValues).notNull().default("active"),
instanceConfigJson: json("instance_config_json").$type<Record<string, unknown> | null>(),
lastSyncedAt: timestamp("last_synced_at", { fsp: 3 }),
lastSyncStatus: mysqlEnum("last_sync_status", connectorSyncStatusValues),
lastSyncCursor: text("last_sync_cursor"),
createdByOrgMembershipId: denTypeIdColumn("member", "created_by_org_membership_id").notNull(),
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { fsp: 3 }).notNull().default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`),
},
(table) => [
index("connector_instance_organization_id").on(table.organizationId),
index("connector_instance_connector_account_id").on(table.connectorAccountId),
index("connector_instance_created_by_org_membership_id").on(table.createdByOrgMembershipId),
index("connector_instance_connector_type").on(table.connectorType),
index("connector_instance_status").on(table.status),
uniqueIndex("connector_instance_org_name").on(table.organizationId, table.name),
],
)
export const ConnectorInstanceAccessGrantTable = mysqlTable(
"connector_instance_access_grant",
{
id: denTypeIdColumn("connectorInstanceAccessGrant", "id").notNull().primaryKey(),
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
connectorInstanceId: denTypeIdColumn("connectorInstance", "connector_instance_id").notNull(),
orgMembershipId: denTypeIdColumn("member", "org_membership_id"),
teamId: denTypeIdColumn("team", "team_id"),
orgWide: boolean("org_wide").notNull().default(false),
role: mysqlEnum("role", accessRoleValues).notNull(),
createdByOrgMembershipId: denTypeIdColumn("member", "created_by_org_membership_id").notNull(),
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
removedAt: timestamp("removed_at", { fsp: 3 }),
},
(table) => [
index("connector_instance_access_grant_organization_id").on(table.organizationId),
index("connector_instance_access_grant_instance_id").on(table.connectorInstanceId),
index("connector_instance_access_grant_org_membership_id").on(table.orgMembershipId),
index("connector_instance_access_grant_team_id").on(table.teamId),
index("connector_instance_access_grant_org_wide").on(table.orgWide),
uniqueIndex("connector_instance_access_grant_instance_org_membership").on(table.connectorInstanceId, table.orgMembershipId),
uniqueIndex("connector_instance_access_grant_instance_team").on(table.connectorInstanceId, table.teamId),
],
)
export const ConnectorTargetTable = mysqlTable(
"connector_target",
{
id: denTypeIdColumn("connectorTarget", "id").notNull().primaryKey(),
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
connectorInstanceId: denTypeIdColumn("connectorInstance", "connector_instance_id").notNull(),
connectorType: mysqlEnum("connector_type", connectorTypeValues).notNull(),
remoteId: varchar("remote_id", { length: 255 }).notNull(),
targetKind: mysqlEnum("target_kind", connectorTargetKindValues).notNull(),
externalTargetRef: varchar("external_target_ref", { length: 255 }),
targetConfigJson: json("target_config_json").$type<Record<string, unknown>>().notNull(),
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { fsp: 3 }).notNull().default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`),
},
(table) => [
index("connector_target_organization_id").on(table.organizationId),
index("connector_target_connector_instance_id").on(table.connectorInstanceId),
index("connector_target_connector_type").on(table.connectorType),
index("connector_target_target_kind").on(table.targetKind),
uniqueIndex("connector_target_instance_remote_id").on(table.connectorInstanceId, table.remoteId),
],
)
export const ConnectorMappingTable = mysqlTable(
"connector_mapping",
{
id: denTypeIdColumn("connectorMapping", "id").notNull().primaryKey(),
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
connectorInstanceId: denTypeIdColumn("connectorInstance", "connector_instance_id").notNull(),
connectorTargetId: denTypeIdColumn("connectorTarget", "connector_target_id").notNull(),
connectorType: mysqlEnum("connector_type", connectorTypeValues).notNull(),
remoteId: varchar("remote_id", { length: 255 }),
mappingKind: mysqlEnum("mapping_kind", connectorMappingKindValues).notNull(),
selector: varchar("selector", { length: 255 }).notNull(),
objectType: mysqlEnum("object_type", configObjectTypeValues).notNull(),
pluginId: denTypeIdColumn("plugin", "plugin_id"),
autoAddToPlugin: boolean("auto_add_to_plugin").notNull().default(false),
mappingConfigJson: json("mapping_config_json").$type<Record<string, unknown> | null>(),
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { fsp: 3 }).notNull().default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`),
},
(table) => [
index("connector_mapping_organization_id").on(table.organizationId),
index("connector_mapping_connector_instance_id").on(table.connectorInstanceId),
index("connector_mapping_connector_target_id").on(table.connectorTargetId),
index("connector_mapping_object_type").on(table.objectType),
index("connector_mapping_plugin_id").on(table.pluginId),
uniqueIndex("connector_mapping_target_selector_object_type").on(table.connectorTargetId, table.selector, table.objectType),
],
)
export const ConnectorSyncEventTable = mysqlTable(
"connector_sync_event",
{
id: denTypeIdColumn("connectorSyncEvent", "id").notNull().primaryKey(),
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
connectorInstanceId: denTypeIdColumn("connectorInstance", "connector_instance_id").notNull(),
connectorTargetId: denTypeIdColumn("connectorTarget", "connector_target_id"),
connectorType: mysqlEnum("connector_type", connectorTypeValues).notNull(),
remoteId: varchar("remote_id", { length: 255 }),
eventType: mysqlEnum("event_type", connectorSyncEventTypeValues).notNull(),
externalEventRef: varchar("external_event_ref", { length: 255 }),
sourceRevisionRef: varchar("source_revision_ref", { length: 255 }),
status: mysqlEnum("status", connectorSyncStatusValues).notNull().default("pending"),
summaryJson: json("summary_json").$type<Record<string, unknown> | null>(),
startedAt: timestamp("started_at", { fsp: 3 }).notNull().defaultNow(),
completedAt: timestamp("completed_at", { fsp: 3 }),
},
(table) => [
index("connector_sync_event_organization_id").on(table.organizationId),
index("connector_sync_event_connector_instance_id").on(table.connectorInstanceId),
index("connector_sync_event_connector_target_id").on(table.connectorTargetId),
index("connector_sync_event_event_type").on(table.eventType),
index("connector_sync_event_status").on(table.status),
index("connector_sync_event_source_revision_ref").on(table.sourceRevisionRef),
index("connector_sync_event_external_event_ref").on(table.externalEventRef),
],
)
export const ConnectorSourceBindingTable = mysqlTable(
"connector_source_binding",
{
id: denTypeIdColumn("connectorSourceBinding", "id").notNull().primaryKey(),
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
configObjectId: denTypeIdColumn("configObject", "config_object_id").notNull(),
connectorInstanceId: denTypeIdColumn("connectorInstance", "connector_instance_id").notNull(),
connectorTargetId: denTypeIdColumn("connectorTarget", "connector_target_id").notNull(),
connectorMappingId: denTypeIdColumn("connectorMapping", "connector_mapping_id").notNull(),
connectorType: mysqlEnum("connector_type", connectorTypeValues).notNull(),
remoteId: varchar("remote_id", { length: 255 }),
externalLocator: varchar("external_locator", { length: 255 }).notNull(),
externalStableRef: varchar("external_stable_ref", { length: 255 }),
lastSeenSourceRevisionRef: varchar("last_seen_source_revision_ref", { length: 255 }),
status: mysqlEnum("status", configObjectStatusValues).notNull().default("active"),
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { fsp: 3 }).notNull().default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`),
deletedAt: timestamp("deleted_at", { fsp: 3 }),
},
(table) => [
index("connector_source_binding_organization_id").on(table.organizationId),
index("connector_source_binding_config_object_id").on(table.configObjectId),
index("connector_source_binding_connector_instance_id").on(table.connectorInstanceId),
index("connector_source_binding_connector_target_id").on(table.connectorTargetId),
index("connector_source_binding_connector_mapping_id").on(table.connectorMappingId),
index("connector_source_binding_external_locator").on(table.externalLocator),
uniqueIndex("connector_source_binding_config_object").on(table.configObjectId),
],
)
export const ConnectorSourceTombstoneTable = mysqlTable(
"connector_source_tombstone",
{
id: denTypeIdColumn("connectorSourceTombstone", "id").notNull().primaryKey(),
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
connectorInstanceId: denTypeIdColumn("connectorInstance", "connector_instance_id").notNull(),
connectorTargetId: denTypeIdColumn("connectorTarget", "connector_target_id").notNull(),
connectorMappingId: denTypeIdColumn("connectorMapping", "connector_mapping_id").notNull(),
connectorType: mysqlEnum("connector_type", connectorTypeValues).notNull(),
remoteId: varchar("remote_id", { length: 255 }),
externalLocator: varchar("external_locator", { length: 255 }).notNull(),
formerConfigObjectId: denTypeIdColumn("configObject", "former_config_object_id").notNull(),
deletedInSyncEventId: denTypeIdColumn("connectorSyncEvent", "deleted_in_sync_event_id").notNull(),
deletedSourceRevisionRef: varchar("deleted_source_revision_ref", { length: 255 }),
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
},
(table) => [
index("connector_source_tombstone_organization_id").on(table.organizationId),
index("connector_source_tombstone_connector_instance_id").on(table.connectorInstanceId),
index("connector_source_tombstone_connector_target_id").on(table.connectorTargetId),
index("connector_source_tombstone_connector_mapping_id").on(table.connectorMappingId),
index("connector_source_tombstone_external_locator").on(table.externalLocator),
index("connector_source_tombstone_former_config_object_id").on(table.formerConfigObjectId),
],
)
export const configObjectRelations = relations(ConfigObjectTable, ({ many, one }) => ({
accessGrants: many(ConfigObjectAccessGrantTable),
connectorInstance: one(ConnectorInstanceTable, {
fields: [ConfigObjectTable.connectorInstanceId],
references: [ConnectorInstanceTable.id],
}),
createdByOrgMembership: one(MemberTable, {
fields: [ConfigObjectTable.createdByOrgMembershipId],
references: [MemberTable.id],
}),
memberships: many(PluginConfigObjectTable),
organization: one(OrganizationTable, {
fields: [ConfigObjectTable.organizationId],
references: [OrganizationTable.id],
}),
sourceBindings: many(ConnectorSourceBindingTable),
versions: many(ConfigObjectVersionTable),
}))
export const configObjectVersionRelations = relations(ConfigObjectVersionTable, ({ one }) => ({
configObject: one(ConfigObjectTable, {
fields: [ConfigObjectVersionTable.configObjectId],
references: [ConfigObjectTable.id],
}),
connectorSyncEvent: one(ConnectorSyncEventTable, {
fields: [ConfigObjectVersionTable.connectorSyncEventId],
references: [ConnectorSyncEventTable.id],
}),
createdByOrgMembership: one(MemberTable, {
fields: [ConfigObjectVersionTable.createdByOrgMembershipId],
references: [MemberTable.id],
}),
}))
export const pluginRelations = relations(PluginTable, ({ many, one }) => ({
accessGrants: many(PluginAccessGrantTable),
createdByOrgMembership: one(MemberTable, {
fields: [PluginTable.createdByOrgMembershipId],
references: [MemberTable.id],
}),
memberships: many(PluginConfigObjectTable),
organization: one(OrganizationTable, {
fields: [PluginTable.organizationId],
references: [OrganizationTable.id],
}),
mappings: many(ConnectorMappingTable),
}))
export const pluginConfigObjectRelations = relations(PluginConfigObjectTable, ({ one }) => ({
configObject: one(ConfigObjectTable, {
fields: [PluginConfigObjectTable.configObjectId],
references: [ConfigObjectTable.id],
}),
connectorMapping: one(ConnectorMappingTable, {
fields: [PluginConfigObjectTable.connectorMappingId],
references: [ConnectorMappingTable.id],
}),
createdByOrgMembership: one(MemberTable, {
fields: [PluginConfigObjectTable.createdByOrgMembershipId],
references: [MemberTable.id],
}),
plugin: one(PluginTable, {
fields: [PluginConfigObjectTable.pluginId],
references: [PluginTable.id],
}),
}))
export const configObjectAccessGrantRelations = relations(ConfigObjectAccessGrantTable, ({ one }) => ({
configObject: one(ConfigObjectTable, {
fields: [ConfigObjectAccessGrantTable.configObjectId],
references: [ConfigObjectTable.id],
}),
createdByOrgMembership: one(MemberTable, {
fields: [ConfigObjectAccessGrantTable.createdByOrgMembershipId],
references: [MemberTable.id],
}),
orgMembership: one(MemberTable, {
fields: [ConfigObjectAccessGrantTable.orgMembershipId],
references: [MemberTable.id],
}),
team: one(TeamTable, {
fields: [ConfigObjectAccessGrantTable.teamId],
references: [TeamTable.id],
}),
}))
export const pluginAccessGrantRelations = relations(PluginAccessGrantTable, ({ one }) => ({
createdByOrgMembership: one(MemberTable, {
fields: [PluginAccessGrantTable.createdByOrgMembershipId],
references: [MemberTable.id],
}),
orgMembership: one(MemberTable, {
fields: [PluginAccessGrantTable.orgMembershipId],
references: [MemberTable.id],
}),
plugin: one(PluginTable, {
fields: [PluginAccessGrantTable.pluginId],
references: [PluginTable.id],
}),
team: one(TeamTable, {
fields: [PluginAccessGrantTable.teamId],
references: [TeamTable.id],
}),
}))
export const connectorAccountRelations = relations(ConnectorAccountTable, ({ many, one }) => ({
createdByOrgMembership: one(MemberTable, {
fields: [ConnectorAccountTable.createdByOrgMembershipId],
references: [MemberTable.id],
}),
instances: many(ConnectorInstanceTable),
organization: one(OrganizationTable, {
fields: [ConnectorAccountTable.organizationId],
references: [OrganizationTable.id],
}),
}))
export const connectorInstanceRelations = relations(ConnectorInstanceTable, ({ many, one }) => ({
accessGrants: many(ConnectorInstanceAccessGrantTable),
account: one(ConnectorAccountTable, {
fields: [ConnectorInstanceTable.connectorAccountId],
references: [ConnectorAccountTable.id],
}),
configObjects: many(ConfigObjectTable),
createdByOrgMembership: one(MemberTable, {
fields: [ConnectorInstanceTable.createdByOrgMembershipId],
references: [MemberTable.id],
}),
mappings: many(ConnectorMappingTable),
organization: one(OrganizationTable, {
fields: [ConnectorInstanceTable.organizationId],
references: [OrganizationTable.id],
}),
sourceBindings: many(ConnectorSourceBindingTable),
syncEvents: many(ConnectorSyncEventTable),
targets: many(ConnectorTargetTable),
tombstones: many(ConnectorSourceTombstoneTable),
}))
export const connectorInstanceAccessGrantRelations = relations(ConnectorInstanceAccessGrantTable, ({ one }) => ({
connectorInstance: one(ConnectorInstanceTable, {
fields: [ConnectorInstanceAccessGrantTable.connectorInstanceId],
references: [ConnectorInstanceTable.id],
}),
createdByOrgMembership: one(MemberTable, {
fields: [ConnectorInstanceAccessGrantTable.createdByOrgMembershipId],
references: [MemberTable.id],
}),
orgMembership: one(MemberTable, {
fields: [ConnectorInstanceAccessGrantTable.orgMembershipId],
references: [MemberTable.id],
}),
team: one(TeamTable, {
fields: [ConnectorInstanceAccessGrantTable.teamId],
references: [TeamTable.id],
}),
}))
export const connectorTargetRelations = relations(ConnectorTargetTable, ({ many, one }) => ({
connectorInstance: one(ConnectorInstanceTable, {
fields: [ConnectorTargetTable.connectorInstanceId],
references: [ConnectorInstanceTable.id],
}),
mappings: many(ConnectorMappingTable),
sourceBindings: many(ConnectorSourceBindingTable),
syncEvents: many(ConnectorSyncEventTable),
tombstones: many(ConnectorSourceTombstoneTable),
}))
export const connectorMappingRelations = relations(ConnectorMappingTable, ({ many, one }) => ({
connectorInstance: one(ConnectorInstanceTable, {
fields: [ConnectorMappingTable.connectorInstanceId],
references: [ConnectorInstanceTable.id],
}),
connectorTarget: one(ConnectorTargetTable, {
fields: [ConnectorMappingTable.connectorTargetId],
references: [ConnectorTargetTable.id],
}),
plugin: one(PluginTable, {
fields: [ConnectorMappingTable.pluginId],
references: [PluginTable.id],
}),
pluginMemberships: many(PluginConfigObjectTable),
sourceBindings: many(ConnectorSourceBindingTable),
tombstones: many(ConnectorSourceTombstoneTable),
}))
export const connectorSyncEventRelations = relations(ConnectorSyncEventTable, ({ many, one }) => ({
connectorInstance: one(ConnectorInstanceTable, {
fields: [ConnectorSyncEventTable.connectorInstanceId],
references: [ConnectorInstanceTable.id],
}),
connectorTarget: one(ConnectorTargetTable, {
fields: [ConnectorSyncEventTable.connectorTargetId],
references: [ConnectorTargetTable.id],
}),
tombstones: many(ConnectorSourceTombstoneTable),
versions: many(ConfigObjectVersionTable),
}))
export const connectorSourceBindingRelations = relations(ConnectorSourceBindingTable, ({ one }) => ({
configObject: one(ConfigObjectTable, {
fields: [ConnectorSourceBindingTable.configObjectId],
references: [ConfigObjectTable.id],
}),
connectorInstance: one(ConnectorInstanceTable, {
fields: [ConnectorSourceBindingTable.connectorInstanceId],
references: [ConnectorInstanceTable.id],
}),
connectorMapping: one(ConnectorMappingTable, {
fields: [ConnectorSourceBindingTable.connectorMappingId],
references: [ConnectorMappingTable.id],
}),
connectorTarget: one(ConnectorTargetTable, {
fields: [ConnectorSourceBindingTable.connectorTargetId],
references: [ConnectorTargetTable.id],
}),
}))
export const connectorSourceTombstoneRelations = relations(ConnectorSourceTombstoneTable, ({ one }) => ({
connectorInstance: one(ConnectorInstanceTable, {
fields: [ConnectorSourceTombstoneTable.connectorInstanceId],
references: [ConnectorInstanceTable.id],
}),
connectorMapping: one(ConnectorMappingTable, {
fields: [ConnectorSourceTombstoneTable.connectorMappingId],
references: [ConnectorMappingTable.id],
}),
connectorTarget: one(ConnectorTargetTable, {
fields: [ConnectorSourceTombstoneTable.connectorTargetId],
references: [ConnectorTargetTable.id],
}),
deletedInSyncEvent: one(ConnectorSyncEventTable, {
fields: [ConnectorSourceTombstoneTable.deletedInSyncEventId],
references: [ConnectorSyncEventTable.id],
}),
formerConfigObject: one(ConfigObjectTable, {
fields: [ConnectorSourceTombstoneTable.formerConfigObjectId],
references: [ConfigObjectTable.id],
}),
}))
export const configObject = ConfigObjectTable
export const configObjectVersion = ConfigObjectVersionTable
export const plugin = PluginTable
export const pluginConfigObject = PluginConfigObjectTable
export const configObjectAccessGrant = ConfigObjectAccessGrantTable
export const pluginAccessGrant = PluginAccessGrantTable
export const connectorAccount = ConnectorAccountTable
export const connectorInstance = ConnectorInstanceTable
export const connectorInstanceAccessGrant = ConnectorInstanceAccessGrantTable
export const connectorTarget = ConnectorTargetTable
export const connectorMapping = ConnectorMappingTable
export const connectorSyncEvent = ConnectorSyncEventTable
export const connectorSourceBinding = ConnectorSourceBindingTable
export const connectorSourceTombstone = ConnectorSourceTombstoneTable

View File

@@ -26,6 +26,20 @@ export const idTypesMapNameToPrefix = {
skillHub: "shb",
skillHubSkill: "shs",
skillHubMember: "shm",
configObject: "cob",
configObjectVersion: "cov",
configObjectAccessGrant: "coa",
plugin: "plg",
pluginConfigObject: "pco",
pluginAccessGrant: "pag",
connectorAccount: "cac",
connectorInstance: "cin",
connectorInstanceAccessGrant: "cia",
connectorTarget: "ctg",
connectorMapping: "cmp",
connectorSyncEvent: "cse",
connectorSourceBinding: "csb",
connectorSourceTombstone: "cst",
llmProvider: "lpr",
llmProviderModel: "lpm",
llmProviderAccess: "lpa",