import { z } from "zod/v4"; /** * Execution model (plan §4, §6.2): app default + per-capability overrides. * * One app can mix models — UI/actions/normal tools Coline-hosted while a * Salesforce sync stays on developer infrastructure. Override keys name * declared capabilities: a tool name (`crm.sync_salesforce`), a file type * key (`crm.contact`, covering its render handlers), or `"home"` for the * home surface. Key membership is validated at the manifest level. */ export const EXECUTION_MODELS = ["coline", "external"] as const; export const executionModelSchema = z.enum(EXECUTION_MODELS); export type ExecutionModel = z.infer; export const appHostingV2Schema = z .strictObject({ default: executionModelSchema.default("coline"), overrides: z.record(z.string().trim().min(1).max(200), executionModelSchema).default({}), external: z .strictObject({ baseUrl: z.url().max(2000), }) .optional(), }) .superRefine((hosting, ctx) => { const usesExternal = hosting.default === "external" || Object.values(hosting.overrides).includes("external"); if (usesExternal && !hosting.external) { ctx.addIssue({ code: "custom", path: ["external"], message: "hosting.external.baseUrl is required when any capability executes externally.", }); } }) .prefault({}); export type AppHostingV2 = z.infer; export type AppHostingV2Input = z.input; /** * Outbound network policy for Coline-hosted logic and tier-2 sandboxes * (plan §6.2, §12.3). Default: no outbound network at all. */ const allowedHostSchema = z .string() .trim() .toLowerCase() .regex( /^(\*\.)?([a-z0-9-]+\.)+[a-z]{2,}$/, "Allowed hosts are domains, optionally with a leading `*.` wildcard.", ) .max(253); export const appNetworkPolicySchema = z .strictObject({ allowedHosts: z.array(allowedHostSchema).max(50).default([]), }) .default({ allowedHosts: [] }); export type AppNetworkPolicy = z.infer;