/** * Agent tag key for proactive physical-instance capacity thresholds. * Each value encodes one pool policy (see {@link encodeCapacityPolicyTag} / * {@link parseCapacityPolicyTags}). * * Versioned wire format (pipe-delimited, one tag value per pool): * `v2|{encodedAgentName}|{engine}|{thresholdType}|{thresholdValue}` * * Example: * `v2|customer-opa|postgres|minAvailableCapacityPercent|20` */ export const DATABASE_LIFECYCLE_TAG_CAPACITY_POLICIES = 'databaseLifecycle:capacityPolicies'; /** * Engines App Databases can provision. AWS only: managed Aurora PostgreSQL or * RDS PostgreSQL with IAM database authentication. This is the allowlist for * every wire surface — request bodies, query parameters, capacity-policy tags, * and the `databaseLifecycle:engines` capability tag — so a worker advertising * anything else contributes no engines. */ export const DATABASE_ENGINES = ['postgres'] as const; export type DatabaseEngine = (typeof DATABASE_ENGINES)[number]; export const PHYSICAL_DATABASE_CAPACITY_THRESHOLD_TYPES = ['minAvailableCapacityPercent'] as const; export type PhysicalDatabaseCapacityThresholdType = (typeof PHYSICAL_DATABASE_CAPACITY_THRESHOLD_TYPES)[number]; export type PhysicalDatabaseCapacityPolicy = { agentName: string; engine: DatabaseEngine; thresholdType: 'minAvailableCapacityPercent'; thresholdValue: number; }; export type PhysicalDatabasePoolKey = { organizationId: string; agentName: string; engine: DatabaseEngine; }; export class CapacityPolicyValidationError extends Error { readonly code = 'capacity_policy_invalid'; constructor( message: string, readonly details: string[] = [] ) { super(message); this.name = 'CapacityPolicyValidationError'; } } /** Stable idempotency and identity key for an organization physical pool. */ export function computePhysicalDatabasePoolKey(pool: PhysicalDatabasePoolKey): string { const agentName = normalizeCapacityPolicyAgentName(pool.agentName); return ['provision_physical_database', encodeURIComponent(pool.organizationId), encodeURIComponent(agentName), pool.engine].join(':'); } export function encodeCapacityPolicyTag(policy: PhysicalDatabaseCapacityPolicy): string { const agentName = normalizeCapacityPolicyAgentName(policy.agentName); return ['v2', encodeURIComponent(agentName), policy.engine, policy.thresholdType, String(policy.thresholdValue)].join('|'); } /** * Parse and validate capacity-policy tags. Fail closed: any malformed value * throws {@link CapacityPolicyValidationError}. Missing tag key ⇒ empty list * (no proactive provisioning for that agent). */ export function parseCapacityPolicyTags(published: string[] | undefined): PhysicalDatabaseCapacityPolicy[] { if (published === undefined) { return []; } if (!Array.isArray(published)) { throw new CapacityPolicyValidationError('databaseLifecycle:capacityPolicies must be an array of policy strings.'); } const policies: PhysicalDatabaseCapacityPolicy[] = []; const errors: string[] = []; const seen = new Set(); for (const raw of published) { if (typeof raw !== 'string' || raw.trim() === '') { errors.push('capacity policy value must be a non-empty string'); continue; } try { const policy = parseCapacityPolicyTag(raw); const identity = JSON.stringify([policy.agentName, policy.engine]); if (seen.has(identity)) { errors.push(`duplicate capacity policy for pool ${identity}`); continue; } seen.add(identity); policies.push(policy); } catch (error) { errors.push(error instanceof Error ? error.message : String(error)); } } if (errors.length > 0) { throw new CapacityPolicyValidationError( `Invalid databaseLifecycle:capacityPolicies (${errors.length} error${errors.length === 1 ? '' : 's'}).`, errors ); } return policies; } /** * Detect threshold disagreements for the same physical pool. * Profile tags are routing metadata and do not participate in identity. * * Registration callers soft-skip peer conflicts (rolling threshold updates); * the registering agent's own invalid tags still fail closed. */ export function validateCapacityPolicyCompatibility( candidatePolicies: readonly PhysicalDatabaseCapacityPolicy[], existingPolicies: readonly PhysicalDatabaseCapacityPolicy[] ): void { const errors: string[] = []; for (const candidate of candidatePolicies) { const candidateAgentName = normalizeCapacityPolicyAgentName(candidate.agentName); for (const existing of existingPolicies) { const existingAgentName = normalizeCapacityPolicyAgentName(existing.agentName); if (candidate.engine !== existing.engine || candidateAgentName !== existingAgentName) { continue; } if (candidate.thresholdValue !== existing.thresholdValue) { errors.push( `different thresholds for pool ${JSON.stringify([candidateAgentName, candidate.engine])}: ${candidate.thresholdValue} and ${existing.thresholdValue}` ); } } } if (errors.length > 0) { throw new CapacityPolicyValidationError( `Conflicting databaseLifecycle:capacityPolicies (${errors.length} conflict${errors.length === 1 ? '' : 's'}): ${errors.join('; ')}`, errors ); } } export function parseCapacityPolicyTag(raw: string): PhysicalDatabaseCapacityPolicy { const parts = raw.split('|'); if (parts.length !== 5) { throw new Error( `expected 5 pipe-delimited fields (version|agentName|engine|thresholdType|value), got ${parts.length} in ${JSON.stringify(raw)}` ); } const [version, agentNameEncoded, engineRaw, thresholdTypeRaw, thresholdValueRaw] = parts; if (version !== 'v2') { throw new Error(`unsupported version ${JSON.stringify(version)}`); } if (!isKnownValue(engineRaw, DATABASE_ENGINES)) { throw new Error(`unknown engine ${JSON.stringify(engineRaw)}`); } if (!isKnownValue(thresholdTypeRaw, PHYSICAL_DATABASE_CAPACITY_THRESHOLD_TYPES)) { throw new Error(`unknown thresholdType ${JSON.stringify(thresholdTypeRaw)}`); } let decodedAgentName: string; try { decodedAgentName = decodeURIComponent(agentNameEncoded); } catch { throw new Error(`agentName is not valid URI encoding: ${JSON.stringify(agentNameEncoded)}`); } const agentName = normalizeCapacityPolicyAgentName(decodedAgentName); if (thresholdValueRaw.trim() === '' || !/^-?\d+$/.test(thresholdValueRaw)) { throw new Error('thresholdValue must be a non-empty integer'); } const thresholdValue = Number(thresholdValueRaw); if (!Number.isSafeInteger(thresholdValue)) { throw new Error(`thresholdValue must be an integer, got ${JSON.stringify(thresholdValueRaw)}`); } if (thresholdValue < 0) { throw new Error(`thresholdValue must be >= 0, got ${thresholdValue}`); } if (thresholdValue > 100) { throw new Error(`minAvailableCapacityPercent must be <= 100, got ${thresholdValue}`); } return { agentName, engine: engineRaw, thresholdType: 'minAvailableCapacityPercent', thresholdValue }; } function isKnownValue(value: string, known: readonly T[]): value is T { return known.some((candidate) => candidate === value); } function normalizeCapacityPolicyAgentName(agentName: string): string { const normalized = agentName.trim(); if (normalized.length === 0) { throw new CapacityPolicyValidationError('agentName must be non-empty after trimming.'); } return normalized; }