feat: add opt-in chat provider and data foundation (#13100)

Add dormant provider contracts, qualified patched adapters, tenant-scoped persistence and lifecycle ownership without activating chat routes. Preserve the experimental integration as dependent PR #13038.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip authored and GitHub committed 2026-09-09 13:49:12 -05:00
1 parent 7d84b183fb
commit 6abeb67334
143 files changed
+789391 -162

No files matched your search

@@ -0,0 +1,113 @@
import { describe, expect, it } from "vitest";
import {
configureChatEndpointSchema,
microsoftTeamsCredentialIdSchema,
resolveChatActionSchema,
resolveChatPublicationSchema,
chatPublicationStateSchema,
} from "./chat-channels.js";
describe("Microsoft Teams chat credential validation", () => {
it("normalizes canonical Entra application and tenant UUIDs", () => {
const parsed = configureChatEndpointSchema.parse({
action: "configure",
credentials: {
clientId: " 76D0CB17-5EC4-4B3D-983B-DA8A01DC02C4 ",
tenantId: "F8CDEF31-A31E-4B4A-93E4-5F571E91255A",
clientSecret: "keep-case-sensitive-secret",
},
});
expect(parsed.credentials).toEqual({
clientId: "76d0cb17-5ec4-4b3d-983b-da8a01dc02c4",
tenantId: "f8cdef31-a31e-4b4a-93e4-5f571e91255a",
clientSecret: "keep-case-sensitive-secret",
});
});
it.each([
"common",
"organizations",
"contoso.onmicrosoft.com",
"76d0cb175ec44b3d983bda8a01dc02c4",
"00000000-0000-0000-0000-000000000000",
])("rejects non-canonical Teams credential id %s", (value) => {
expect(microsoftTeamsCredentialIdSchema.safeParse(value).success).toBe(
false,
);
expect(
configureChatEndpointSchema.safeParse({
action: "configure",
credentials: {
clientId: "76d0cb17-5ec4-4b3d-983b-da8a01dc02c4",
tenantId: value,
clientSecret: "secret",
},
}).success,
).toBe(false);
});
it("does not impose Teams UUID rules on unrelated provider credentials", () => {
expect(
configureChatEndpointSchema.parse({
action: "configure",
credentials: {
botToken: "xoxb-test",
signingSecret: "secret",
},
}),
).toEqual({
action: "configure",
credentials: {
botToken: "xoxb-test",
signingSecret: "secret",
},
});
});
});
describe("chat provider-action resolution validation", () => {
it("accepts consent waiting and a closed versioned file-stage precondition", () => {
expect(chatPublicationStateSchema.parse("awaiting_consent")).toBe(
"awaiting_consent",
);
const request = {
action: "cancel",
fileTransfer: { phase: "upload_unknown", version: 3 },
};
expect(resolveChatPublicationSchema.parse(request)).toEqual(request);
expect(resolveChatPublicationSchema.parse({ action: "cancel" })).toEqual({
action: "cancel",
});
});
it.each([
{ phase: "upload_unknown", version: 0 },
{ phase: "upload_unknown", version: 1.5 },
{ phase: "upload_unknown", version: Number.MAX_SAFE_INTEGER + 1 },
{ phase: "new_unreviewed_phase", version: 1 },
{ phase: "upload_unknown" },
{ phase: "upload_unknown", version: 1, uploadUrl: "private" },
])(
"rejects malformed file-stage resolution preconditions %#",
(fileTransfer) => {
expect(
resolveChatPublicationSchema.safeParse({
action: "cancel",
fileTransfer,
}).success,
).toBe(false);
},
);
it.each(["mark_delivered", "retry_anyway", "cancel"] as const)(
"accepts the explicit %s resolution",
(action) => {
expect(resolveChatActionSchema.parse({ action })).toEqual({ action });
},
);
it("rejects automatic or unknown provider-effect replay modes", () => {
expect(resolveChatActionSchema.safeParse({ action: "retry" }).success).toBe(
false,
);
});
});
@@ -0,0 +1,193 @@
import { z } from "zod";
import { multilineTextSchema } from "./text.js";
import {
CHAT_CONCURRENCY_POLICIES,
CHAT_DELIVERY_STATES,
CHAT_ENDPOINT_STATUSES,
CHAT_EVENT_KINDS,
CHAT_FILE_TRANSFER_PHASES,
CHAT_IDENTITY_LINK_STATUSES,
CHAT_PRINCIPAL_KINDS,
CHAT_PROVIDERS,
CHAT_PUBLICATION_STATES,
CHAT_RESOURCE_AVAILABILITIES,
} from "../types/chat-channels.js";
export const chatProviderSchema = z.enum(CHAT_PROVIDERS);
export const chatEndpointStatusSchema = z.enum(CHAT_ENDPOINT_STATUSES);
export const chatConcurrencyPolicySchema = z.enum(CHAT_CONCURRENCY_POLICIES);
export const chatEventKindSchema = z.enum(CHAT_EVENT_KINDS);
export const chatDeliveryStateSchema = z.enum(CHAT_DELIVERY_STATES);
export const chatPublicationStateSchema = z.enum(CHAT_PUBLICATION_STATES);
export const chatPrincipalKindSchema = z.enum(CHAT_PRINCIPAL_KINDS);
export const chatIdentityLinkStatusSchema = z.enum(CHAT_IDENTITY_LINK_STATUSES);
export const chatResourceAvailabilitySchema = z.enum(
CHAT_RESOURCE_AVAILABILITIES,
);
/**
* Microsoft emits Entra application and tenant identifiers in canonical UUID
* form in Bot Framework activities. Tenant aliases such as `common` or an
* `onmicrosoft.com` domain can be accepted by the token endpoint, but cannot
* be compared safely with the activity tenant id used by Paperclip's runtime
* fence. Normalize the UUIDs at the API boundary instead.
*/
export const microsoftTeamsCredentialIdSchema = z
.string()
.trim()
.uuid()
.refine((value) => value !== "00000000-0000-0000-0000-000000000000", {
message: "Microsoft Teams credential IDs cannot be the nil UUID",
})
.transform((value) => value.toLowerCase());
const chatEndpointCredentialsSchema = z
.record(z.string(), z.string().min(1))
.superRefine((credentials, ctx) => {
for (const key of ["clientId", "tenantId"] as const) {
const value = credentials[key];
if (value === undefined) continue;
const parsed = microsoftTeamsCredentialIdSchema.safeParse(value);
if (parsed.success) continue;
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: [key],
message: `${key} must be a canonical Microsoft Entra UUID`,
});
}
})
.transform((credentials) => {
const normalized = { ...credentials };
for (const key of ["clientId", "tenantId"] as const) {
const value = normalized[key];
if (value !== undefined) {
normalized[key] = microsoftTeamsCredentialIdSchema.parse(value);
}
}
return normalized;
});
export const createChatEndpointSchema = z
.object({
provider: chatProviderSchema,
assignedAgentId: z.string().uuid(),
applicationId: z.string().uuid().optional(),
name: z.string().trim().min(1).max(160).optional(),
})
.strict();
export const updateChatEndpointSchema = z
.object({
allowDirectMessages: z.boolean().optional(),
allowGroupChats: z.boolean().optional(),
allowUnlinkedPeople: z.boolean().optional(),
})
.strict()
.refine((value) => Object.keys(value).length > 0, {
message: "At least one chat endpoint field is required",
});
export const configureChatEndpointSchema = z
.object({
action: z.enum([
"configure",
"verify",
"pause",
"resume",
"reconnect",
"remove",
]),
credentials: chatEndpointCredentialsSchema.optional(),
})
.strict()
.superRefine((value, ctx) => {
if (
value.credentials &&
value.action !== "configure" &&
value.action !== "reconnect"
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["credentials"],
message: `Credentials are not accepted for the ${value.action} action`,
});
}
});
export const replaceChatEndpointResourcesSchema = z
.object({
resources: z
.array(
z
.object({
id: z.string().uuid(),
enabled: z.boolean(),
})
.strict(),
)
.max(500),
})
.strict();
export const publishChatCommentSchema = z
.object({
commentId: z.string().uuid(),
})
.strict();
export const publishChatBoardMessageSchema = z
.object({
body: multilineTextSchema.pipe(z.string().trim().min(1).max(100_000)),
idempotencyKey: z.string().trim().min(16).max(200),
attachmentIds: z
.array(z.string().uuid())
.max(20)
.refine((ids) => new Set(ids).size === ids.length, {
message: "Attachment ids must be unique",
})
.optional(),
})
.strict();
export const publishChatPublicationSchema = z.union([
publishChatCommentSchema,
publishChatBoardMessageSchema,
]);
export const resolveChatPublicationSchema = z
.object({
action: z.enum(["mark_delivered", "retry_anyway", "cancel"]),
fileTransfer: z
.object({
phase: z.enum(CHAT_FILE_TRANSFER_PHASES),
version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
})
.strict()
.optional(),
})
.strict();
export const resolveChatActionSchema = z
.object({
action: z.enum(["mark_delivered", "retry_anyway", "cancel"]),
})
.strict();
export const createChatIdentityLinkIntentSchema = z
.object({
expiresInSeconds: z.number().int().min(300).max(86_400).default(1_800),
})
.strict()
.default({ expiresInSeconds: 1_800 });
export const confirmChatIdentityLinkSchema = z
.object({
token: z.string().min(32).max(4096),
})
.strict();
export const replayChatDeliverySchema = z.object({}).strict();
export const chatPublicEndpointIdSchema = z
.string()
.regex(/^[a-zA-Z0-9_-]{32,128}$/);