export interface FleetSettings { provisioningKeepDays: number; actionKeepDays: number; failureKeepDays: number; maintenanceKeepDays: number; recoveryKeepDays: number; hostResourceKeepDays: number; instanceResourceKeepDays: number; metricsKeepDays: number; telemetryKeepDays: number; telemetryMaxPerServer: number; telemetryMaxTotal: number; matchKeepDays: number; betaKeepDays: number; fileContentKeepDays: number; payloadKeepDays: number; versionKeepDays: number; versionKeepCount: number; gameFileGraceDays: number; gameFileKeepCount: number; uploadKeepDays: number; auditKeepDays: number; historyKeepCount: number; deployKeepDays: number; observationKeepDays: number; restartConcurrency: number; createConcurrency: number; destroyConcurrency: number; restartConcurrencyPerHost: number; createConcurrencyPerHost: number; destroyConcurrencyPerHost: number; lifecycleConcurrency: number; rebuildConcurrency: number; preparationConcurrency: number; } export const defaultFleetSettings: FleetSettings = { provisioningKeepDays: 0, actionKeepDays: 0, failureKeepDays: 0, maintenanceKeepDays: 30, recoveryKeepDays: 0, hostResourceKeepDays: 7, instanceResourceKeepDays: 1, metricsKeepDays: 31, telemetryKeepDays: 7, telemetryMaxPerServer: 200, telemetryMaxTotal: 10000, matchKeepDays: 0, betaKeepDays: 0, fileContentKeepDays: 30, payloadKeepDays: 30, versionKeepDays: 0, versionKeepCount: 0, gameFileGraceDays: 0, gameFileKeepCount: 1, uploadKeepDays: 0, auditKeepDays: 0, historyKeepCount: 0, deployKeepDays: 7, observationKeepDays: 1, restartConcurrency: 1, createConcurrency: 1, destroyConcurrency: 1, restartConcurrencyPerHost: 0, createConcurrencyPerHost: 0, destroyConcurrencyPerHost: 0, lifecycleConcurrency: 0, rebuildConcurrency: 0, preparationConcurrency: 0, }; export const fleetSettingLabels: Record = { provisioningKeepDays: 'Provisioning history retention', actionKeepDays: 'Action operation history retention', failureKeepDays: 'Resolved failure history retention', maintenanceKeepDays: 'Maintenance history retention', recoveryKeepDays: 'Resolved recovery incident retention', hostResourceKeepDays: 'Host resource history retention', instanceResourceKeepDays: 'Instance resource sample retention', metricsKeepDays: 'Fleet metrics history retention', telemetryKeepDays: 'Telemetry diagnostic history retention', telemetryMaxPerServer: 'Maximum telemetry records per server', telemetryMaxTotal: 'Maximum telemetry records overall', matchKeepDays: 'Match history retention', betaKeepDays: 'Completed beta history retention', fileContentKeepDays: 'File diffs and rollback snapshot retention', payloadKeepDays: 'Launch and command file retention', versionKeepDays: 'Unused artifact version retention', versionKeepCount: 'Minimum artifact versions to keep', gameFileGraceDays: 'Unused game-file grace period', gameFileKeepCount: 'Minimum game-file versions to keep', uploadKeepDays: 'Abandoned upload retention', auditKeepDays: 'Authentication audit history retention', historyKeepCount: 'Minimum history records to keep', deployKeepDays: 'Campaign history retention', observationKeepDays: 'Routine operation history retention', restartConcurrency: 'Concurrent restarts', createConcurrency: 'Concurrent server startups', destroyConcurrency: 'Concurrent server destructions', restartConcurrencyPerHost: 'Concurrent restarts per host', createConcurrencyPerHost: 'Concurrent startups per host', destroyConcurrencyPerHost: 'Concurrent destructions per host', lifecycleConcurrency: 'Combined lifecycle limit', rebuildConcurrency: 'Concurrent rebuilds', preparationConcurrency: 'Hosts preparing game files simultaneously', }; export function validateFleetSettings(input: unknown): FleetSettings { if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('Provide fleet settings.'); const values = input as Record; for (const key of Object.keys(values)) { if (!Object.hasOwn(defaultFleetSettings, key)) throw new Error(`Unknown setting: ${key}`); } for (const key of Object.keys(defaultFleetSettings) as (keyof FleetSettings)[]) { const value = values[key]; const minimum = settingMinimum(key); if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < minimum) { throw new Error(`${fleetSettingLabels[key]} must be ${minimum === 0 ? 'zero or a positive whole number' : 'a positive whole number'}.`); } if (key.endsWith('Days') && value > 36500) { throw new Error(`${fleetSettingLabels[key]} must be at most 36500 days.`); } } return Object.fromEntries(Object.keys(defaultFleetSettings).map(key => [key, values[key]])) as unknown as FleetSettings; } export const additionalConcurrencyKeys: (keyof FleetSettings)[] = ["restartConcurrencyPerHost", "createConcurrencyPerHost", "destroyConcurrencyPerHost", "lifecycleConcurrency", "rebuildConcurrency", "preparationConcurrency"]; export const concurrencyKeys: (keyof FleetSettings)[] = ['restartConcurrency', 'createConcurrency', 'destroyConcurrency', ...additionalConcurrencyKeys]; export const retentionKeys: (keyof FleetSettings)[] = ['deployKeepDays', 'observationKeepDays', 'provisioningKeepDays', 'actionKeepDays', 'failureKeepDays', 'maintenanceKeepDays', 'recoveryKeepDays', 'hostResourceKeepDays', 'instanceResourceKeepDays', 'metricsKeepDays', 'telemetryKeepDays', 'telemetryMaxPerServer', 'telemetryMaxTotal', 'matchKeepDays', 'betaKeepDays', 'fileContentKeepDays', 'payloadKeepDays', 'versionKeepDays', 'versionKeepCount', 'gameFileGraceDays', 'gameFileKeepCount', 'uploadKeepDays', 'auditKeepDays', 'historyKeepCount']; export function settingMinimum(key: keyof FleetSettings): number { return ['restartConcurrency','createConcurrency','destroyConcurrency','gameFileKeepCount'].includes(key) ? 1 : 0; } export const settingHelp: Partial> = { provisioningKeepDays: 'Completed lifecycle history. Recovery, source workflows and cancellation dependencies remain protected.', actionKeepDays: 'Completed agent actions. Referenced commands and retry evidence remain protected. Expired actions keep compact receipts to prevent duplicate execution.', failureKeepDays: 'Explicitly resolved failed operations and campaigns, measured from resolution. Unresolved failures remain protected.', maintenanceKeepDays: 'Finished non-update maintenance. Update attempts remain durable to prevent duplicate automatic retries.', recoveryKeepDays: 'Resolved watchdog incidents with no unfinished recovery. Dismissed incidents remain as suppression evidence.', hostResourceKeepDays: 'Host CPU and memory history.', instanceResourceKeepDays: 'Minute resource samples. Capacity learning continues to use its independent 24-hour window.', metricsKeepDays: 'Historical fleet occupancy and utilization samples.', telemetryKeepDays: 'Raw received telemetry diagnostics. Independent of saved matches. Count limits also apply.', telemetryMaxPerServer: 'Count cap per server, in addition to the age limit. 0 disables this count cap.', telemetryMaxTotal: 'Fleet-wide count cap for telemetry diagnostics. 0 disables this count cap.', matchKeepDays: 'Completed and interrupted matches and player details. Expiry changes leaderboards and player totals; no lifetime aggregates are preserved.', betaKeepDays: 'Closed betas, revisions and test evidence, after linked campaigns and recovery work no longer need them.', fileContentKeepDays: 'Expired campaign file bodies are removed while summaries remain. Rollback becomes unavailable for those campaigns. Active beta snapshots remain protected.', payloadKeepDays: 'Expire file bodies in completed launch recipes, commands and receipts while keeping their history. Unfinished work and protected records keep their files. 0 keeps files indefinitely.', versionKeepDays: 'Unused script, plugin, configuration and baseline versions. Current and referenced versions are always protected.', versionKeepCount: 'Keep at least this many recent versions per artifact. With no age expiry, a positive count removes older unused versions.', gameFileGraceDays: 'Delay removal after a version becomes superseded. Latest files, referenced versions and offline host copies remain protected. 0 adds no delay.', gameFileKeepCount: 'Keep this many recent published versions per game, in addition to every referenced or protected version.', uploadKeepDays: 'Remove incomplete browser uploads after this many days without activity. Active transfers and publication are protected.', auditKeepDays: 'Recorded authentication and account-administration events.', historyKeepCount: 'Keep at least this many recent records per history category. Does not apply to metrics, resource samples, telemetry count caps or game-file storage.', restartConcurrency: 'Fleet recovery slots stay occupied until container and game health are verified.', createConcurrency: 'Fleet startup slots cover creation and rebuilds through health and selected-map verification.', destroyConcurrency: 'Fleet removals hold capacity until teardown and cleanup finish.', restartConcurrencyPerHost: 'Additional recovery limit on each host, including starts and rebuilds.', createConcurrencyPerHost: 'Additional startup limit on each host, including rebuilds.', destroyConcurrencyPerHost: 'Additional removal limit on each host, including migration source removal.', lifecycleConcurrency: 'Combined fleet limit for creates, starts, stops, restarts, rebuilds and destructions. Each workflow counts once.', rebuildConcurrency: 'Additional fleet limit for rebuilds, including host drain migrations.', preparationConcurrency: 'Maximum hosts downloading or verifying game files at once. Interrupted transfers keep their slot until reconciled.', deployKeepDays: 'Eligible campaign records and file diffs. Pending, unresolved failed, held and recovery-linked campaigns are protected.', observationKeepDays: 'Successful monitoring and file-read records. Failures and actions are retained. 0 keeps records indefinitely.' };