import { createHash } from 'node:crypto'; import fs from 'node:fs'; import type { DatabaseSync } from 'node:sqlite'; import { visitLegacyEnvironmentAdoption, type LegacyEnvironmentCandidate, type LegacyEnvironmentInventory, type LegacyEnvironmentRowInspection, } from '@qinglong/local-admin/reconciliation-secret-and-config-inspection'; import { LegacyAdoptionPublicationDigest, legacyAdoptionTaskProvenanceDigest, legacyAdoptionTriggerProvenanceDigest, } from '@qinglong/local-sqlite/adoption-provenance'; import { LocalDeploymentConfigurationError } from '../../../foundation/error'; import { cutoverDigest } from '../../../cutover/targetEvidence'; const HEADER_KIND = 'qinglong3-local-reconciliation-secret-config-plan-header'; const ROW_KIND = 'qinglong3-local-reconciliation-secret-config-plan-row'; const CANDIDATE_KIND = 'qinglong3-local-reconciliation-secret-config-plan-candidate'; const FOOTER_KIND = 'qinglong3-local-reconciliation-secret-config-plan-footer'; const RECEIPT_SCHEMA = 'qinglong3-local-reconciliation-secret-config-plan-receipt'; const DIGEST_PATTERN = /^[0-9a-f]{64}$/; const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; const MAX_LINE_BYTES = 64 * 1024; const HASH_BUFFER_BYTES = 64 * 1024; const MAX_EDGE_AUTOMATION_ADOPTION_RECORDS = 128; const MAX_STANDALONE_AUTOMATION_ADOPTION_RECORDS = 512; const MAX_AUTOMATION_ADOPTION_TASKS = 100_000; const MAX_AUTOMATION_ADOPTION_TRIGGERS = 500_000; export const MAX_EDGE_LOCAL_RECONCILIATION_SECRET_CONFIG_PLAN_BYTES = 8 * 1024 * 1024; export const MAX_STANDALONE_LOCAL_RECONCILIATION_SECRET_CONFIG_PLAN_BYTES = 32 * 1024 * 1024; export interface LocalReconciliationSecretConfigPlanHeader { readonly schemaVersion: 1; readonly kind: typeof HEADER_KIND; readonly secretConfigId: string; readonly applicationId: string; readonly applicationPlanDigest: string; readonly reviewDigest: string; readonly reviewAuthorizationDigest: string; readonly reviewDecisionSetDigest: string; readonly reviewDecisionFileDigest: string; readonly bundleDigest: string; readonly bundleFingerprintDigest: string; readonly profile: 'edge' | 'standalone'; readonly projectId: string; readonly tableDisposition: 'absent' | 'manual_external'; readonly unadaptedLegacyConfigCount: number; readonly preparedHeadDigest: string; readonly preparedAtMs: number; readonly headerDigest: string; } export interface LocalReconciliationSecretConfigPlanRow { readonly schemaVersion: 1; readonly kind: typeof ROW_KIND; readonly rowOrdinal: number; readonly sourceDigest: string; readonly disposition: LegacyEnvironmentRowInspection['disposition']; readonly reasons: LegacyEnvironmentRowInspection['reasons']; readonly rowPlanDigest: string; } export type LocalReconciliationSecretConfigCandidateRequirement = | 'review_apply_binding' | 'review_preserve_disabled' | 'review_skip_conflict'; export type LocalReconciliationSecretConfigTarget = | Readonly<{ state: 'absent' }> | Readonly<{ state: 'occupied'; version: number; contentDigest: string; }>; export interface LocalReconciliationSecretConfigPlanCandidate { readonly schemaVersion: 1; readonly kind: typeof CANDIDATE_KIND; readonly candidateOrdinal: number; readonly candidateType: LegacyEnvironmentCandidate['kind']; readonly candidateDigest: string; readonly sourceRowCount: number; readonly sourceSetDigest: string; readonly proposedSecretName: string; readonly target: LocalReconciliationSecretConfigTarget; readonly requirement: LocalReconciliationSecretConfigCandidateRequirement; readonly candidatePlanDigest: string; } export interface LocalReconciliationSecretConfigPlanSummary { readonly tableState: LegacyEnvironmentInventory['tableState']; readonly rowCount: number; readonly activeRowCount: number; readonly disabledRowCount: number; readonly manualRowCount: number; readonly activeGroupCount: number; readonly bindingReadyCount: number; readonly preservationReadyCount: number; readonly manualGroupCount: number; readonly eligibleBindingCount: number; readonly eligiblePreservationCount: number; readonly targetConflictCount: number; readonly automationAdoptionRecordCount: number; readonly adoptedLegacyTaskCount: number; readonly adoptedLegacyTriggerCount: number; readonly adoptionProvenanceTaskCount: number; readonly adoptionProvenanceTriggerCount: number; readonly automationAdoptionProvenanceState: | 'complete' | 'missing' | 'drifted'; readonly unadaptedLegacyConfigCount: number; readonly outcome: 'ready' | 'manual_required' | 'no_effect'; } export interface LocalReconciliationSecretConfigPlanFooter extends LocalReconciliationSecretConfigPlanSummary { readonly schemaVersion: 1; readonly kind: typeof FOOTER_KIND; readonly secretConfigId: string; readonly legacyInventoryDigest: string; readonly rowSetDigest: string; readonly candidateSetDigest: string; readonly automationAdoptionSetDigest: string; readonly secretConfigPlanDigest: string; } export interface LocalReconciliationSecretConfigPlanReceipt extends LocalReconciliationSecretConfigPlanSummary { readonly schema: typeof RECEIPT_SCHEMA; readonly schemaVersion: 1; readonly state: 'reconciliation_secret_config_planned'; readonly secretConfigId: string; readonly applicationId: string; readonly applicationPlanDigest: string; readonly preparedHeadDigest: string; readonly legacyInventoryDigest: string; readonly rowSetDigest: string; readonly candidateSetDigest: string; readonly automationAdoptionSetDigest: string; readonly secretConfigPlanDigest: string; readonly planFileBytes: number; readonly planFileDigest: string; readonly preparedAtMs: number; readonly receiptDigest: string; } export interface WriteLocalReconciliationSecretConfigPlanOptions { readonly descriptor: number; readonly maxBytes: number; readonly header: Omit< LocalReconciliationSecretConfigPlanHeader, 'headerDigest' >; readonly legacy: DatabaseSync; readonly target: DatabaseSync; } function fail(message: string, cause?: unknown): never { throw new LocalDeploymentConfigurationError( `reconciliation secret config row plan ${message}`, { cause }, ); } function exact( value: unknown, keys: readonly string[], label: string, ): Record { if (!value || typeof value !== 'object' || Array.isArray(value)) { fail(`${label} must be an object`); } const record = value as Record; const actual = Object.keys(record).sort(); const expected = [...keys].sort(); if ( actual.length !== expected.length || actual.some((key, index) => key !== expected[index]) ) { fail(`${label} shape is invalid`); } return record; } function line(value: unknown): Buffer { const bytes = Buffer.from(`${JSON.stringify(value)}\n`, 'utf8'); if (bytes.byteLength < 3 || bytes.byteLength > MAX_LINE_BYTES + 1) { bytes.fill(0); fail('record exceeds its line bound'); } return bytes; } function writeAll(descriptor: number, bytes: Buffer): void { let offset = 0; while (offset < bytes.byteLength) { const written = fs.writeSync( descriptor, bytes, offset, bytes.byteLength - offset, ); if (written < 1) fail('write stalled'); offset += written; } } function bytesDigest(value: unknown, length: number, label: string): string { if (!(value instanceof Uint8Array) || value.byteLength !== length) { fail(`target ${label} is invalid`); } return createHash('sha256').update(value).digest('hex'); } function adoptionText( row: Readonly>, key: string, pattern?: RegExp, ): string { const value = row[key]; if ( typeof value !== 'string' || value.length < 1 || value.length > 128 || (pattern !== undefined && !pattern.test(value)) ) { fail(`target Automation adoption ${key} drifted`); } return value; } function adoptionCount( row: Readonly>, key: string, maximum: number, ): number { const value = row[key]; if ( !Number.isSafeInteger(value) || (value as number) < 0 || (value as number) > maximum ) { fail(`target Automation adoption ${key} drifted`); } return value as number; } function targetAutomationAdoptionProjection( target: DatabaseSync, projectId: string, profile: 'edge' | 'standalone', ): Readonly<{ recordCount: number; adoptedTaskCount: number; adoptedTriggerCount: number; provenanceTaskCount: number; provenanceTriggerCount: number; provenanceState: 'complete' | 'missing' | 'drifted'; setDigest: string; }> { const maximumRecords = profile === 'edge' ? MAX_EDGE_AUTOMATION_ADOPTION_RECORDS : MAX_STANDALONE_AUTOMATION_ADOPTION_RECORDS; const hash = createHash('sha256').update( 'qinglong3.local-reconciliation-secret-config-automation-adoption-set.v1\0', ); const records = new Map< string, { readonly expectedTaskCount: number; readonly expectedTriggerCount: number; readonly expectedPublicationDigest: string; readonly publication: LegacyAdoptionPublicationDigest; taskCount: number; triggerCount: number; } >(); let recordCount = 0; let adoptedTaskCount = 0; let adoptedTriggerCount = 0; let provenanceTaskCount = 0; let provenanceTriggerCount = 0; let drifted = false; try { const rows = target .prepare( `SELECT "mutation_id" AS "mutationId", "decision_id" AS "decisionId", "plan_digest" AS "planDigest", "inventory_digest" AS "inventoryDigest", "decision_digest" AS "decisionDigest", "receipt_digest" AS "receiptDigest", "authorization_file_digest" AS "authorizationFileDigest", "publication_digest" AS "publicationDigest", "row_count" AS "rowCount", "adopted_task_count" AS "adoptedTaskCount", "adopted_trigger_count" AS "adoptedTriggerCount", "skipped_count" AS "skippedCount", "audit_event_id" AS "auditEventId", "created_at_ms" AS "createdAtMs" FROM "QingLong3LegacyAdoptions" WHERE "project_id" = ? ORDER BY "created_at_ms" ASC, "mutation_id" ASC`, ) .iterate(projectId) as Iterable>>; for (const row of rows) { recordCount += 1; if (recordCount > maximumRecords) { fail('target Automation adoption projection exceeds profile budget'); } const rowCount = adoptionCount(row, 'rowCount', 100_000); const selectedAdoptedTaskCount = adoptionCount( row, 'adoptedTaskCount', rowCount, ); const skippedCount = adoptionCount(row, 'skippedCount', rowCount); if (selectedAdoptedTaskCount + skippedCount !== rowCount) { fail('target Automation adoption row accounting drifted'); } adoptedTaskCount += selectedAdoptedTaskCount; if (!Number.isSafeInteger(adoptedTaskCount)) { fail('target Automation adoption task count overflowed'); } const selectedAdoptedTriggerCount = adoptionCount( row, 'adoptedTriggerCount', 500_000, ); adoptedTriggerCount += selectedAdoptedTriggerCount; if (!Number.isSafeInteger(adoptedTriggerCount)) { fail('target Automation adoption trigger count overflowed'); } const payload = Object.freeze({ mutationId: adoptionText(row, 'mutationId', UUID_V4_PATTERN), decisionId: adoptionText( row, 'decisionId', /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/, ), planDigest: adoptionText(row, 'planDigest', DIGEST_PATTERN), inventoryDigest: adoptionText( row, 'inventoryDigest', DIGEST_PATTERN, ), decisionDigest: adoptionText(row, 'decisionDigest', DIGEST_PATTERN), receiptDigest: adoptionText(row, 'receiptDigest', DIGEST_PATTERN), authorizationFileDigest: adoptionText( row, 'authorizationFileDigest', DIGEST_PATTERN, ), publicationDigest: adoptionText( row, 'publicationDigest', DIGEST_PATTERN, ), rowCount, adoptedTaskCount: selectedAdoptedTaskCount, adoptedTriggerCount: selectedAdoptedTriggerCount, skippedCount, auditEventId: adoptionText(row, 'auditEventId', UUID_V4_PATTERN), createdAtMs: adoptionCount( row, 'createdAtMs', Number.MAX_SAFE_INTEGER, ), }); if (payload.auditEventId !== payload.mutationId) { fail('target Automation adoption audit binding drifted'); } if (records.has(payload.mutationId)) { fail('target Automation adoption identity is duplicated'); } records.set(payload.mutationId, { expectedTaskCount: selectedAdoptedTaskCount, expectedTriggerCount: selectedAdoptedTriggerCount, expectedPublicationDigest: payload.publicationDigest, publication: new LegacyAdoptionPublicationDigest(payload.mutationId), taskCount: 0, triggerCount: 0, }); hash.update('\0').update(JSON.stringify(payload)); } const tasks = target .prepare( `SELECT provenance."adoption_mutation_id" AS "adoptionMutationId", provenance."row_ordinal" AS "rowOrdinal", provenance."project_id" AS "projectId", provenance."source_digest" AS "sourceDigest", provenance."task_id" AS "taskId", provenance."task_revision" AS "taskRevision", provenance."task_mutation_id" AS "taskMutationId", provenance."task_content_digest" AS "taskContentDigest", provenance."trigger_count" AS "triggerCount", provenance."item_digest" AS "itemDigest", head."current_revision" AS "currentRevision", revision."mutation_id" AS "storedMutationId", revision."content_digest" AS "storedContentDigest", ownership."package_name" AS "packageName" FROM "QingLong3LegacyAdoptionTasks" AS provenance JOIN "QingLong3LegacyAdoptions" AS adoption ON adoption."mutation_id" = provenance."adoption_mutation_id" AND adoption."project_id" = provenance."project_id" LEFT JOIN "QingLong3TaskDefinitions" AS head ON head."project_id" = provenance."project_id" AND head."task_id" = provenance."task_id" LEFT JOIN "QingLong3TaskDefinitionRevisions" AS revision ON revision."project_id" = provenance."project_id" AND revision."task_id" = provenance."task_id" AND revision."revision" = provenance."task_revision" LEFT JOIN "QingLong3PluginPackageTaskOwnerships" AS ownership ON ownership."project_id" = provenance."project_id" AND ownership."task_id" = provenance."task_id" WHERE adoption."project_id" = ? ORDER BY adoption."created_at_ms" ASC, adoption."mutation_id" ASC, provenance."row_ordinal" ASC`, ) .iterate(projectId) as Iterable>>; for (const row of tasks) { provenanceTaskCount += 1; if (provenanceTaskCount > MAX_AUTOMATION_ADOPTION_TASKS) { fail('target Automation adoption Task provenance exceeds budget'); } const adoptionMutationId = adoptionText( row, 'adoptionMutationId', UUID_V4_PATTERN, ); const selected = records.get(adoptionMutationId); const rowOrdinal = adoptionCount( row, 'rowOrdinal', MAX_AUTOMATION_ADOPTION_TASKS, ); const triggerCount = adoptionCount( row, 'triggerCount', MAX_AUTOMATION_ADOPTION_TRIGGERS, ); const payload = Object.freeze({ adoptionMutationId, rowOrdinal, projectId: adoptionText(row, 'projectId'), sourceDigest: adoptionText(row, 'sourceDigest', DIGEST_PATTERN), taskId: adoptionText(row, 'taskId'), taskRevision: adoptionCount(row, 'taskRevision', 1), taskMutationId: adoptionText(row, 'taskMutationId', UUID_V4_PATTERN), taskContentDigest: adoptionText( row, 'taskContentDigest', DIGEST_PATTERN, ), triggerCount, }); const itemDigest = adoptionText(row, 'itemDigest', DIGEST_PATTERN); selected && (selected.taskCount += 1); const currentState = Object.freeze({ currentRevision: row.currentRevision, storedMutationId: row.storedMutationId, storedContentDigest: row.storedContentDigest, pluginOwned: row.packageName !== null, }); if ( !selected || payload.projectId !== projectId || payload.rowOrdinal < 1 || payload.taskRevision !== 1 || legacyAdoptionTaskProvenanceDigest(payload) !== itemDigest || currentState.currentRevision !== payload.taskRevision || currentState.storedMutationId !== payload.taskMutationId || currentState.storedContentDigest !== payload.taskContentDigest || currentState.pluginOwned ) { drifted = true; } hash.update('\0task\0').update( JSON.stringify({ ...payload, itemDigest, currentState, }), ); } const triggers = target .prepare( `SELECT provenance."adoption_mutation_id" AS "adoptionMutationId", provenance."row_ordinal" AS "rowOrdinal", provenance."trigger_ordinal" AS "triggerOrdinal", provenance."project_id" AS "projectId", provenance."task_id" AS "taskId", provenance."task_revision" AS "taskRevision", provenance."trigger_id" AS "triggerId", provenance."trigger_revision" AS "triggerRevision", provenance."trigger_mutation_id" AS "triggerMutationId", provenance."trigger_content_digest" AS "triggerContentDigest", provenance."item_digest" AS "itemDigest", head."current_revision" AS "currentRevision", revision."mutation_id" AS "storedMutationId", revision."content_digest" AS "storedContentDigest", schedule."trigger_revision" AS "scheduleRevision" FROM "QingLong3LegacyAdoptionTriggers" AS provenance JOIN "QingLong3LegacyAdoptions" AS adoption ON adoption."mutation_id" = provenance."adoption_mutation_id" AND adoption."project_id" = provenance."project_id" LEFT JOIN "QingLong3Triggers" AS head ON head."project_id" = provenance."project_id" AND head."trigger_id" = provenance."trigger_id" AND head."task_id" = provenance."task_id" LEFT JOIN "QingLong3TriggerRevisions" AS revision ON revision."project_id" = provenance."project_id" AND revision."trigger_id" = provenance."trigger_id" AND revision."revision" = provenance."trigger_revision" LEFT JOIN "QingLong3LocalTriggerSchedules" AS schedule ON schedule."project_id" = provenance."project_id" AND schedule."trigger_id" = provenance."trigger_id" WHERE adoption."project_id" = ? ORDER BY adoption."created_at_ms" ASC, adoption."mutation_id" ASC, provenance."row_ordinal" ASC, provenance."trigger_ordinal" ASC`, ) .iterate(projectId) as Iterable>>; let previousTaskKey = ''; let previousTriggerOrdinal = 0; for (const row of triggers) { provenanceTriggerCount += 1; if (provenanceTriggerCount > MAX_AUTOMATION_ADOPTION_TRIGGERS) { fail('target Automation adoption Trigger provenance exceeds budget'); } const adoptionMutationId = adoptionText( row, 'adoptionMutationId', UUID_V4_PATTERN, ); const selected = records.get(adoptionMutationId); const rowOrdinal = adoptionCount( row, 'rowOrdinal', MAX_AUTOMATION_ADOPTION_TASKS, ); const triggerOrdinal = adoptionCount( row, 'triggerOrdinal', MAX_AUTOMATION_ADOPTION_TRIGGERS, ); const taskKey = `${adoptionMutationId}\0${rowOrdinal}`; if (taskKey !== previousTaskKey) { previousTaskKey = taskKey; previousTriggerOrdinal = 0; } previousTriggerOrdinal += 1; const payload = Object.freeze({ adoptionMutationId, rowOrdinal, triggerOrdinal, projectId: adoptionText(row, 'projectId'), taskId: adoptionText(row, 'taskId'), taskRevision: adoptionCount(row, 'taskRevision', 1), triggerId: adoptionText(row, 'triggerId'), triggerRevision: adoptionCount(row, 'triggerRevision', 1), triggerMutationId: adoptionText( row, 'triggerMutationId', UUID_V4_PATTERN, ), triggerContentDigest: adoptionText( row, 'triggerContentDigest', DIGEST_PATTERN, ), }); const itemDigest = adoptionText(row, 'itemDigest', DIGEST_PATTERN); selected && (selected.triggerCount += 1); const currentState = Object.freeze({ currentRevision: row.currentRevision, storedMutationId: row.storedMutationId, storedContentDigest: row.storedContentDigest, scheduleRevision: row.scheduleRevision, }); if ( !selected || payload.projectId !== projectId || payload.rowOrdinal < 1 || payload.triggerOrdinal !== previousTriggerOrdinal || payload.taskRevision !== 1 || payload.triggerRevision !== 1 || legacyAdoptionTriggerProvenanceDigest(payload) !== itemDigest || currentState.currentRevision !== payload.triggerRevision || currentState.storedMutationId !== payload.triggerMutationId || currentState.storedContentDigest !== payload.triggerContentDigest || currentState.scheduleRevision !== payload.triggerRevision ) { drifted = true; } hash.update('\0trigger\0').update( JSON.stringify({ ...payload, itemDigest, currentState, }), ); } const publicationRows = target .prepare( `SELECT adoption."mutation_id" AS "adoptionMutationId", task."row_ordinal" AS "rowOrdinal", task."source_digest" AS "sourceDigest", task."task_content_digest" AS "taskContentDigest", task."trigger_count" AS "expectedTriggerCount", task."item_digest" AS "taskItemDigest", trigger."trigger_ordinal" AS "triggerOrdinal", trigger."trigger_content_digest" AS "triggerContentDigest", trigger."item_digest" AS "triggerItemDigest" FROM "QingLong3LegacyAdoptions" AS adoption LEFT JOIN "QingLong3LegacyAdoptionTasks" AS task ON task."adoption_mutation_id" = adoption."mutation_id" AND task."project_id" = adoption."project_id" LEFT JOIN "QingLong3LegacyAdoptionTriggers" AS trigger ON trigger."adoption_mutation_id" = task."adoption_mutation_id" AND trigger."row_ordinal" = task."row_ordinal" WHERE adoption."project_id" = ? ORDER BY adoption."created_at_ms" ASC, adoption."mutation_id" ASC, task."row_ordinal" ASC, trigger."trigger_ordinal" ASC`, ) .iterate(projectId) as Iterable>>; let publicationTaskKey = ''; let publicationExpectedTriggerCount = 0; let publicationTriggerCount = 0; for (const row of publicationRows) { const adoptionMutationId = adoptionText( row, 'adoptionMutationId', UUID_V4_PATTERN, ); const selected = records.get(adoptionMutationId); if (!selected) { drifted = true; continue; } if (row.rowOrdinal === null) continue; const rowOrdinal = adoptionCount( row, 'rowOrdinal', MAX_AUTOMATION_ADOPTION_TASKS, ); const taskKey = `${adoptionMutationId}\0${rowOrdinal}`; if (taskKey !== publicationTaskKey) { if ( publicationTaskKey !== '' && publicationTriggerCount !== publicationExpectedTriggerCount ) { drifted = true; } publicationTaskKey = taskKey; publicationTriggerCount = 0; publicationExpectedTriggerCount = adoptionCount( row, 'expectedTriggerCount', MAX_AUTOMATION_ADOPTION_TRIGGERS, ); selected.publication.appendTask({ rowOrdinal, sourceDigest: adoptionText(row, 'sourceDigest', DIGEST_PATTERN), taskContentDigest: adoptionText( row, 'taskContentDigest', DIGEST_PATTERN, ), itemDigest: adoptionText(row, 'taskItemDigest', DIGEST_PATTERN), }); } if (row.triggerOrdinal === null) continue; publicationTriggerCount += 1; if ( adoptionCount( row, 'triggerOrdinal', MAX_AUTOMATION_ADOPTION_TRIGGERS, ) !== publicationTriggerCount ) { drifted = true; } selected.publication.appendTrigger({ triggerContentDigest: adoptionText( row, 'triggerContentDigest', DIGEST_PATTERN, ), itemDigest: adoptionText(row, 'triggerItemDigest', DIGEST_PATTERN), }); } if ( publicationTaskKey !== '' && publicationTriggerCount !== publicationExpectedTriggerCount ) { drifted = true; } } catch (error) { if (error instanceof LocalDeploymentConfigurationError) throw error; return fail('target Automation adoption projection is unavailable', error); } const missing = [...records.values()].some( (record) => record.taskCount !== record.expectedTaskCount || record.triggerCount !== record.expectedTriggerCount, ); if (!missing) { for (const record of records.values()) { if (record.publication.digest() !== record.expectedPublicationDigest) { drifted = true; } } } return Object.freeze({ recordCount, adoptedTaskCount, adoptedTriggerCount, provenanceTaskCount, provenanceTriggerCount, provenanceState: drifted ? ('drifted' as const) : missing ? ('missing' as const) : ('complete' as const), setDigest: hash.digest('hex'), }); } function targetSecret( target: DatabaseSync, projectId: string, secretName: string, ): LocalReconciliationSecretConfigTarget { let row: Readonly> | undefined; try { row = target .prepare( `SELECT "version", "mutation_id" AS "mutationId", "key_id" AS "keyId", "algorithm", "nonce", "ciphertext", "auth_tag" AS "authTag", "created_at_ms" AS "createdAtMs" FROM "QingLong3LocalSecretEnvelopes" WHERE "project_id" = ? AND "secret_name" = ? ORDER BY "version" DESC LIMIT 1`, ) .get(projectId, secretName) as | Readonly> | undefined; } catch (error) { return fail('target Secret projection is unavailable', error); } if (!row) return Object.freeze({ state: 'absent' as const }); if ( !Number.isSafeInteger(row.version) || (row.version as number) < 1 || typeof row.mutationId !== 'string' || row.mutationId.length < 1 || row.mutationId.length > 64 || typeof row.keyId !== 'string' || row.keyId.length < 1 || row.keyId.length > 128 || row.algorithm !== 'aes-256-gcm' || !Number.isSafeInteger(row.createdAtMs) || (row.createdAtMs as number) < 0 ) { fail('target Secret projection drifted'); } if ( !(row.ciphertext instanceof Uint8Array) || row.ciphertext.byteLength > 16 * 1024 ) { fail('target ciphertext is invalid'); } const contentDigest = cutoverDigest({ projectId, secretName, version: row.version, mutationId: row.mutationId, keyId: row.keyId, algorithm: row.algorithm, nonceDigest: bytesDigest(row.nonce, 12, 'nonce'), ciphertextDigest: bytesDigest( row.ciphertext, row.ciphertext.byteLength, 'ciphertext', ), authTagDigest: bytesDigest(row.authTag, 16, 'auth tag'), createdAtMs: row.createdAtMs, }); return Object.freeze({ state: 'occupied' as const, version: row.version as number, contentDigest, }); } function secretName(candidate: Readonly): string { const source = candidate.kind === 'active_binding' ? candidate.environmentName : `${candidate.environmentName}\0${candidate.sourceDigest}`; const suffix = createHash('sha256').update(source).digest('hex').slice(0, 32); return candidate.kind === 'active_binding' ? `legacy-db-env-${suffix}` : `legacy-db-env-disabled-${suffix}`; } function planRow( value: Readonly, ): Readonly { const payload = Object.freeze({ schemaVersion: 1 as const, kind: ROW_KIND, rowOrdinal: value.rowOrdinal, sourceDigest: value.sourceDigest, disposition: value.disposition, reasons: value.reasons, }); return Object.freeze({ ...payload, rowPlanDigest: cutoverDigest(payload) }); } function planCandidate( value: Readonly, candidateOrdinal: number, target: DatabaseSync, projectId: string, ): Readonly { const proposedSecretName = secretName(value); const selectedTarget = targetSecret(target, projectId, proposedSecretName); const sourceRowCount = value.kind === 'active_binding' ? value.sourceRowCount : 1; const sourceSetDigest = value.kind === 'active_binding' ? value.sourceSetDigest : value.sourceDigest; const requirement: LocalReconciliationSecretConfigCandidateRequirement = selectedTarget.state === 'occupied' ? 'review_skip_conflict' : value.kind === 'active_binding' ? 'review_apply_binding' : 'review_preserve_disabled'; const payload = Object.freeze({ schemaVersion: 1 as const, kind: CANDIDATE_KIND, candidateOrdinal, candidateType: value.kind, candidateDigest: value.candidateDigest, sourceRowCount, sourceSetDigest, proposedSecretName, target: selectedTarget, requirement, }); return Object.freeze({ ...payload, candidatePlanDigest: cutoverDigest(payload), }); } export function writeLocalReconciliationSecretConfigPlan( options: Readonly, ): Readonly<{ header: Readonly; footer: Readonly; fileBytes: number; fileDigest: string; }> { if ( !Number.isSafeInteger(options.maxBytes) || options.maxBytes < MAX_LINE_BYTES ) { fail('byte budget is invalid'); } const header = Object.freeze({ ...options.header, headerDigest: cutoverDigest(options.header), }); const fileHash = createHash('sha256'); const rowHash = createHash('sha256').update( 'qinglong3.local-reconciliation-secret-config-row-set.v1\0', ); const candidateHash = createHash('sha256').update( 'qinglong3.local-reconciliation-secret-config-candidate-set.v1\0', ); let fileBytes = 0; const append = ( value: unknown, set: 'none' | 'row' | 'candidate' = 'none', ): void => { const bytes = line(value); try { if (fileBytes + bytes.byteLength > options.maxBytes) { fail('exceeds profile byte budget'); } writeAll(options.descriptor, bytes); fileHash.update(bytes); if (set === 'row') rowHash.update(bytes); if (set === 'candidate') candidateHash.update(bytes); fileBytes += bytes.byteLength; } finally { bytes.fill(0); } }; append(header); let candidateOrdinal = 0; let eligibleBindingCount = 0; let eligiblePreservationCount = 0; let targetConflictCount = 0; const inventory = visitLegacyEnvironmentAdoption(options.legacy, { profile: header.profile, visitRow(value) { append(planRow(value), 'row'); }, visitCandidate(value) { candidateOrdinal += 1; const candidate = planCandidate( value, candidateOrdinal, options.target, header.projectId, ); if (candidate.requirement === 'review_apply_binding') { eligibleBindingCount += 1; } else if (candidate.requirement === 'review_preserve_disabled') { eligiblePreservationCount += 1; } else { targetConflictCount += 1; } append(candidate, 'candidate'); }, }); const automationAdoption = targetAutomationAdoptionProjection( options.target, header.projectId, header.profile, ); const summary: LocalReconciliationSecretConfigPlanSummary = Object.freeze({ tableState: inventory.tableState, rowCount: inventory.rowCount, activeRowCount: inventory.activeRowCount, disabledRowCount: inventory.disabledRowCount, manualRowCount: inventory.manualRowCount, activeGroupCount: inventory.activeGroupCount, bindingReadyCount: inventory.bindingReadyCount, preservationReadyCount: inventory.preservationReadyCount, manualGroupCount: inventory.manualGroupCount, eligibleBindingCount, eligiblePreservationCount, targetConflictCount, automationAdoptionRecordCount: automationAdoption.recordCount, adoptedLegacyTaskCount: automationAdoption.adoptedTaskCount, adoptedLegacyTriggerCount: automationAdoption.adoptedTriggerCount, adoptionProvenanceTaskCount: automationAdoption.provenanceTaskCount, adoptionProvenanceTriggerCount: automationAdoption.provenanceTriggerCount, automationAdoptionProvenanceState: automationAdoption.provenanceState, unadaptedLegacyConfigCount: header.unadaptedLegacyConfigCount, outcome: (inventory.tableState === 'absent' || inventory.rowCount === 0) && header.unadaptedLegacyConfigCount === 0 ? ('no_effect' as const) : !inventory.mutationReady || targetConflictCount > 0 || header.unadaptedLegacyConfigCount > 0 || automationAdoption.provenanceState !== 'complete' || (eligibleBindingCount > 0 && automationAdoption.adoptedTaskCount < 1) ? ('manual_required' as const) : ('ready' as const), }); const footerPayload = Object.freeze({ schemaVersion: 1 as const, kind: FOOTER_KIND, secretConfigId: header.secretConfigId, ...summary, legacyInventoryDigest: inventory.inventoryDigest, rowSetDigest: rowHash.digest('hex'), candidateSetDigest: candidateHash.digest('hex'), automationAdoptionSetDigest: automationAdoption.setDigest, }); const footer = Object.freeze({ ...footerPayload, secretConfigPlanDigest: cutoverDigest({ headerDigest: header.headerDigest, ...footerPayload, }), }); append(footer); return Object.freeze({ header, footer, fileBytes, fileDigest: fileHash.digest('hex'), }); } export function buildLocalReconciliationSecretConfigPlanReceipt( header: Readonly, footer: Readonly, planFileBytes: number, planFileDigest: string, ): Readonly { const payload = Object.freeze({ schema: RECEIPT_SCHEMA, schemaVersion: 1 as const, state: 'reconciliation_secret_config_planned' as const, secretConfigId: header.secretConfigId, applicationId: header.applicationId, applicationPlanDigest: header.applicationPlanDigest, preparedHeadDigest: header.preparedHeadDigest, legacyInventoryDigest: footer.legacyInventoryDigest, rowSetDigest: footer.rowSetDigest, candidateSetDigest: footer.candidateSetDigest, automationAdoptionSetDigest: footer.automationAdoptionSetDigest, secretConfigPlanDigest: footer.secretConfigPlanDigest, planFileBytes, planFileDigest, tableState: footer.tableState, rowCount: footer.rowCount, activeRowCount: footer.activeRowCount, disabledRowCount: footer.disabledRowCount, manualRowCount: footer.manualRowCount, activeGroupCount: footer.activeGroupCount, bindingReadyCount: footer.bindingReadyCount, preservationReadyCount: footer.preservationReadyCount, manualGroupCount: footer.manualGroupCount, eligibleBindingCount: footer.eligibleBindingCount, eligiblePreservationCount: footer.eligiblePreservationCount, targetConflictCount: footer.targetConflictCount, automationAdoptionRecordCount: footer.automationAdoptionRecordCount, adoptedLegacyTaskCount: footer.adoptedLegacyTaskCount, adoptedLegacyTriggerCount: footer.adoptedLegacyTriggerCount, adoptionProvenanceTaskCount: footer.adoptionProvenanceTaskCount, adoptionProvenanceTriggerCount: footer.adoptionProvenanceTriggerCount, automationAdoptionProvenanceState: footer.automationAdoptionProvenanceState, unadaptedLegacyConfigCount: footer.unadaptedLegacyConfigCount, outcome: footer.outcome, preparedAtMs: header.preparedAtMs, }); return Object.freeze({ ...payload, receiptDigest: cutoverDigest(payload) }); } export function normalizeLocalReconciliationSecretConfigPlanReceipt( value: unknown, ): Readonly { const receipt = exact( value, [ 'activeGroupCount', 'activeRowCount', 'adoptedLegacyTaskCount', 'adoptedLegacyTriggerCount', 'adoptionProvenanceTaskCount', 'adoptionProvenanceTriggerCount', 'applicationId', 'applicationPlanDigest', 'automationAdoptionRecordCount', 'automationAdoptionProvenanceState', 'automationAdoptionSetDigest', 'bindingReadyCount', 'candidateSetDigest', 'disabledRowCount', 'eligibleBindingCount', 'eligiblePreservationCount', 'legacyInventoryDigest', 'manualGroupCount', 'manualRowCount', 'outcome', 'planFileBytes', 'planFileDigest', 'preparedAtMs', 'preparedHeadDigest', 'preservationReadyCount', 'receiptDigest', 'rowCount', 'rowSetDigest', 'schema', 'schemaVersion', 'secretConfigId', 'secretConfigPlanDigest', 'state', 'tableState', 'targetConflictCount', 'unadaptedLegacyConfigCount', ], 'receipt', ); const { receiptDigest, ...payload } = receipt; if ( receipt.schema !== RECEIPT_SCHEMA || receipt.schemaVersion !== 1 || receipt.state !== 'reconciliation_secret_config_planned' || typeof receipt.secretConfigId !== 'string' || !UUID_V4_PATTERN.test(receipt.secretConfigId) || typeof receipt.applicationId !== 'string' || !UUID_V4_PATTERN.test(receipt.applicationId) || ![ receipt.applicationPlanDigest, receipt.preparedHeadDigest, receipt.legacyInventoryDigest, receipt.rowSetDigest, receipt.candidateSetDigest, receipt.automationAdoptionSetDigest, receipt.secretConfigPlanDigest, receipt.planFileDigest, receiptDigest, ].every( (candidate) => typeof candidate === 'string' && DIGEST_PATTERN.test(candidate), ) || ![ receipt.rowCount, receipt.activeRowCount, receipt.disabledRowCount, receipt.manualRowCount, receipt.activeGroupCount, receipt.bindingReadyCount, receipt.preservationReadyCount, receipt.manualGroupCount, receipt.eligibleBindingCount, receipt.eligiblePreservationCount, receipt.targetConflictCount, receipt.automationAdoptionRecordCount, receipt.adoptedLegacyTaskCount, receipt.adoptedLegacyTriggerCount, receipt.adoptionProvenanceTaskCount, receipt.adoptionProvenanceTriggerCount, receipt.unadaptedLegacyConfigCount, receipt.planFileBytes, receipt.preparedAtMs, ].every((count) => Number.isSafeInteger(count) && (count as number) >= 0) || !['absent', 'supported', 'unsupported_schema', 'budget_exceeded'].includes( receipt.tableState as string, ) || !['ready', 'manual_required', 'no_effect'].includes( receipt.outcome as string, ) || !['complete', 'missing', 'drifted'].includes( receipt.automationAdoptionProvenanceState as string, ) || cutoverDigest(payload) !== receiptDigest ) { fail('receipt drifted'); } return Object.freeze( receipt, ) as unknown as Readonly; } export function hashLocalReconciliationSecretConfigPlanFile( descriptor: number, expectedBytes: number, ): string { const hash = createHash('sha256'); const buffer = Buffer.allocUnsafe(HASH_BUFFER_BYTES); let offset = 0; while (offset < expectedBytes) { const count = fs.readSync( descriptor, buffer, 0, Math.min(buffer.byteLength, expectedBytes - offset), offset, ); if (count < 1) fail('plan file read stalled'); hash.update(buffer.subarray(0, count)); offset += count; } return hash.digest('hex'); }