import { n as childLogger } from "./dist-COZZnek9.js";
import { a as markChangesPushed } from "./creators-BQ2MyC7h.js";
import { n as OpenBaoTransitClient, t as createSecretsService } from "./secrets-service-CgMFc9sS.js";
import { t as up$1 } from "./migrations-CYRGWKah.js";
import { a as syncEnvironment, i as getTenantId, n as deleteEnvironmentFromGitops, t as removeEnvironmentRecord } from "./cleanup-CXaZPU9h.js";
//#region processors/vetra-cloud-environment/migrations.ts
async function up(db) {
	await db.schema.createTable("environments").addColumn("id", "varchar(255)").addColumn("name", "varchar(255)").addColumn("subdomain", "varchar(255)").addColumn("tenantId", "varchar(255)").addColumn("customDomain", "varchar(255)").addColumn("packages", "text").addColumn("services", "text").addColumn("status", "varchar(50)").addColumn("deployingSince", "varchar(255)").addColumn("createdBy", "varchar(255)").addPrimaryKeyConstraint("environments_pkey", ["id"]).ifNotExists().execute();
	for (const column of [
		"subdomain",
		"tenantId",
		"customDomain"
	]) try {
		await db.schema.alterTable("environments").addColumn(column, "varchar(255)").execute();
	} catch {}
	try {
		await db.schema.alterTable("environments").dropColumn("domain").execute();
	} catch {}
	try {
		await db.schema.alterTable("environments").addColumn("deployingSince", "varchar(255)").execute();
	} catch {}
	try {
		await db.schema.alterTable("environments").addColumn("createdBy", "varchar(255)").execute();
	} catch {}
	try {
		await db.schema.alterTable("environments").addColumn("owner", "varchar(255)").execute();
	} catch {}
	try {
		await db.schema.alterTable("environments").addColumn("autoUpdateChannel", "varchar(32)").execute();
	} catch {}
	try {
		await db.schema.alterTable("environments").addColumn("studioInstanceId", "varchar(255)").execute();
	} catch {}
	for (const column of [
		"poolState",
		"claimedBy",
		"claimedAt",
		"pinnedVersion"
	]) try {
		await db.schema.alterTable("environments").addColumn(column, "varchar(255)").execute();
	} catch {}
	try {
		await db.schema.createIndex("environments_pool_available_idx").on("environments").column("poolState").where("poolState", "=", "AVAILABLE").ifNotExists().execute();
	} catch {}
	try {
		await db.schema.dropTable("clint_announce_tokens").execute();
	} catch {}
}
//#endregion
//#region processors/vetra-cloud-environment/factory.ts
const logger$1 = childLogger(["vetra-cloud-environment-factory"]);
const DEFAULT_TRANSIT_ROLE = "vetra-secrets";
/**
* Wire a SecretsService for the processor. Returns null when OPENBAO_ADDR
* is unset (test mode, local dev without OpenBao) — the processor will
* keep emitting env vars inline in values.yaml in that case, same as
* before the refactor. In production OPENBAO_ADDR is required.
*/
async function createSecretsServiceForProcessor(module) {
	const openbaoAddr = process.env.OPENBAO_ADDR;
	if (!openbaoAddr) {
		logger$1.warn("[secrets] OPENBAO_ADDR is not set — env entries will be emitted inline in values.yaml. Set OPENBAO_ADDR to route secrets through the encrypted vetra-secrets-controller path.");
		return null;
	}
	const secretsDb = await module.relationalDb.createNamespace("vetra-cloud-secrets");
	await up$1(secretsDb);
	return createSecretsService({
		db: secretsDb,
		transit: new OpenBaoTransitClient({
			addr: openbaoAddr,
			role: process.env.OPENBAO_TRANSIT_ROLE ?? DEFAULT_TRANSIT_ROLE,
			keyNamePrefix: process.env.OPENBAO_TRANSIT_KEY_PREFIX
		})
	});
}
const vetraCloudEnvironmentProcessorFactory = (module) => async (driveHeader) => {
	logger$1.info(`Creating processor for drive ${driveHeader.id}`);
	const db = await module.relationalDb.createNamespace("vetra-cloud-environments");
	await up(db);
	const documentView = module.getReadModel("document-view");
	const secretsService = await createSecretsServiceForProcessor(module);
	return [{
		processor: new VetraCloudEnvironmentProcessor(db, module.dispatch, documentView, secretsService),
		filter: {
			branch: ["main"],
			documentId: ["*"],
			documentType: ["powerhouse/vetra-cloud-environment", "powerhouse/document-drive"],
			scope: ["global"]
		}
	}];
};
//#endregion
//#region processors/vetra-cloud-environment/index.ts
const logger = childLogger(["vetra-cloud-environment-processor"]);
var VetraCloudEnvironmentProcessor = class {
	relationalDb;
	dispatch;
	documentView;
	/**
	* Optional — null when OPENBAO_ADDR is unset (local dev, tests). When
	* present, gitops sync routes secret env entries through the encrypted
	* tenant_secrets table so they never land in values.yaml plaintext.
	*/
	secretsService;
	constructor(relationalDb, dispatch, documentView, secretsService = null) {
		this.relationalDb = relationalDb;
		this.dispatch = dispatch;
		this.documentView = documentView;
		this.secretsService = secretsService;
	}
	async dispatchAction(documentId, action) {
		try {
			await this.dispatch.execute(documentId, "main", [action]);
			logger.info(`Dispatched ${action.type} for ${documentId}`);
		} catch (err) {
			logger.error(`Failed to dispatch ${action.type} for ${documentId}: ${String(err)}`);
		}
	}
	async onOperations(operations) {
		if (operations.length === 0) return;
		logger.info(`Received ${operations.length} operations`);
		const deletedIds = /* @__PURE__ */ new Set();
		for (const { operation, context } of operations) if (context.documentType === "powerhouse/document-drive") {
			const deletedId = await this.handleDriveOperation(operation);
			if (deletedId) deletedIds.add(deletedId);
		}
		const lastByDocument = /* @__PURE__ */ new Map();
		const firstUserSignerByDocument = /* @__PURE__ */ new Map();
		for (const entry of operations) if (entry.context.documentType === "powerhouse/vetra-cloud-environment" && !deletedIds.has(entry.context.documentId)) {
			lastByDocument.set(entry.context.documentId, entry);
			if (!firstUserSignerByDocument.has(entry.context.documentId)) {
				const userAddress = extractUserSignerAddress(entry.operation);
				if (userAddress) firstUserSignerByDocument.set(entry.context.documentId, userAddress);
			}
		}
		if (lastByDocument.size > 0) logger.info(`Processing ${lastByDocument.size} environment(s) (deduplicated from ${operations.filter((o) => o.context.documentType === "powerhouse/vetra-cloud-environment").length} operations)`);
		for (const [documentId, { operation, context }] of lastByDocument) {
			let phState = context.resultingState ? JSON.parse(context.resultingState) : void 0;
			if (!phState) try {
				phState = (await this.documentView.get(documentId)).state;
				if (phState) logger.info(`Fetched current state for ${documentId} (resultingState was missing)`);
			} catch (err) {
				logger.warn(`Failed to fetch state for ${documentId}: ${String(err)}`);
			}
			if (!phState) {
				logger.warn(`No state available for operation ${operation.index} on ${documentId}`);
				continue;
			}
			const state = phState.global;
			const { owner, label: envLabel, genericSubdomain, customDomain, packages, services, status, autoUpdateChannel, studioInstanceId } = state;
			const label = envLabel ?? documentId;
			const tenantId = genericSubdomain ? getTenantId(genericSubdomain, documentId) : null;
			logger.info(`Processing document ${label} (op ${operation.index}): label=${envLabel ?? "unset"}, status=${status ?? "unset"}, subdomain=${genericSubdomain ?? "unset"}, tenantId=${tenantId ?? "unset"}, owner=${owner ?? "unset"}, services=[${services?.map((s) => `${s.type}:${s.enabled}`).join(", ") ?? ""}], packages=[${packages?.map((p) => `${p.name}@${p.version}`).join(", ") ?? ""}]`);
			const ownerNormalized = owner ? owner.toLowerCase() : null;
			const row = {
				name: envLabel ?? null,
				subdomain: genericSubdomain ?? null,
				tenantId,
				customDomain: customDomain?.domain ?? null,
				packages: JSON.stringify(packages ?? []),
				services: JSON.stringify(services ?? []),
				status: status ?? null,
				owner: ownerNormalized,
				autoUpdateChannel: autoUpdateChannel ?? null,
				studioInstanceId: studioInstanceId ?? null
			};
			const createdBy = firstUserSignerByDocument.get(documentId) ?? null;
			logger.info(`Upserting environment record for "${label}"${createdBy ? ` (createdBy=${createdBy})` : ""}${ownerNormalized ? ` (owner=${ownerNormalized})` : ""}`);
			await this.relationalDb.insertInto("environments").values({
				id: documentId,
				...row,
				createdBy
			}).onConflict((oc) => oc.column("id").doUpdateSet(row)).execute();
			if (status === "CHANGES_APPROVED") {
				logger.info(`Triggering gitops sync for "${label}"`);
				try {
					await syncEnvironment(this.relationalDb, state, documentId, this.secretsService);
					logger.info(`Gitops sync completed for "${label}"`);
					const freshStatus = (await this.documentView.get(documentId))?.state?.global?.status;
					if (freshStatus !== "CHANGES_APPROVED") logger.info(`Skipping MARK_CHANGES_PUSHED for "${label}" — status already changed to ${freshStatus}`);
					else await this.dispatchAction(documentId, markChangesPushed({}));
				} catch (error) {
					logger.error(`Gitops sync failed for "${label}": ${String(error)}`);
				}
			} else if (status === "STOPPED") {
				logger.info(`Triggering gitops sync for "${label}" (sleep → disabled)`);
				try {
					await syncEnvironment(this.relationalDb, state, documentId, this.secretsService);
					logger.info(`Gitops sleep sync completed for "${label}"`);
				} catch (error) {
					logger.error(`Gitops sleep sync failed for "${label}": ${String(error)}`);
				}
			} else logger.info(`Skipping gitops sync for "${label}" (status: ${status})`);
		}
	}
	/**
	* Handle a drive-level operation. Returns the deleted document ID if a
	* DELETE_NODE was processed, so the caller can skip syncing it.
	*/
	async handleDriveOperation(operation) {
		if (operation.action.type !== "DELETE_NODE") return void 0;
		const deletedNodeId = operation.action.input?.id;
		if (!deletedNodeId) return void 0;
		const removed = await removeEnvironmentRecord(this.relationalDb, deletedNodeId);
		if (!removed) return deletedNodeId;
		const label = removed.name ?? deletedNodeId;
		logger.info(`Deleting environment record for "${label}"`);
		if (removed.subdomain) {
			const tenantId = getTenantId(removed.subdomain, deletedNodeId);
			logger.info(`Removing gitops tenant "${tenantId}" for deleted environment "${label}"`);
			try {
				await deleteEnvironmentFromGitops(tenantId);
				logger.info(`Gitops cleanup completed for "${label}"`);
			} catch (error) {
				logger.error(`Gitops cleanup failed for "${label}": ${String(error)}`);
			}
		}
		return deletedNodeId;
	}
	async onDisconnect() {}
};
/**
* Extract a user's EthereumAddress from a signed action's signer context.
* Returns null for unsigned actions, or for actions signed by system identities
* (e.g. switchboard) where there is no human user.
*
* The returned address is lowercased to match the convention used by
* reactor-api's AuthService when comparing against the configured ADMINS list.
*/
function extractUserSignerAddress(operation) {
	const signer = operation.action?.context?.signer;
	if (!signer) return null;
	const userAddress = signer.user?.address;
	if (!userAddress) return null;
	if (typeof userAddress !== "string" || userAddress.length === 0) return null;
	return userAddress.toLowerCase();
}
//#endregion
export { vetraCloudEnvironmentProcessorFactory as n, VetraCloudEnvironmentProcessor as t };

//# sourceMappingURL=vetra-cloud-environment-BszY6l7Z.js.map