const EXECUTION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,199}$/; const MAX_EXECUTION_ID_LENGTH = 200; interface BuildExecutionIdOptions { now?: Date; random?: number; } export const validateExecutionId = ( value: unknown, label = "executionId", ): string => { if (typeof value !== "string" || !EXECUTION_ID_PATTERN.test(value)) { throw new Error( `${label} must match the canonical execution ID rule: ` + "1-200 letters, numbers, underscores, or hyphens, beginning with a letter or number", ); } return value; }; export const buildExecutionId = ( targetId: string, options: BuildExecutionIdOptions = {}, ): string => { const timestamp = (options.now || new Date()).toISOString().replace(/\D/g, ""); const random = options.random ?? Math.random(); const nonce = random.toString(36).slice(2).padEnd(8, "0").slice(0, 8); const prefix = `exec-${timestamp}-${nonce}`; const targetBudget = MAX_EXECUTION_ID_LENGTH - prefix.length - 1; const targetSlug = targetId .replace(/[^A-Za-z0-9_-]/g, "-") .slice(0, targetBudget); return validateExecutionId(`${prefix}-${targetSlug}`); };