//#region subgraphs/vetra-housekeeping/policy.ts
/**
* User-Agent the observability `clint-pull-worker` stamps on its `/_proxy/routes`
* poll so the idle signal + wake activator can exclude it as automation. Defined
* here (the dependency-free policy module) and re-exported by the worker, so the
* slim housekeeping-service build doesn't drag in the observability subgraph.
*/
const OBSERVABILITY_PULL_USER_AGENT = "vetra-observability-pull";
/**
* Studio housekeeping policy — pure decision functions shared by the
* housekeeping subgraph (sleep/wake mutations, power-state query) and the
* standalone housekeeping service (idle detector + wake activator).
*
* Keeping these pure and dependency-free means both the in-reactor subgraph and
* the out-of-process activator apply identical rules, and they're trivially
* unit-testable. See docs/superpowers/specs/2026-06-30-studio-housekeeping-…
*/
/**
* Request paths that are automation, never a real user interaction. Matched as
* exact paths or path prefixes (a trailing `/` marks a prefix family).
*/
const AUTOMATION_PATHS = [
	"/_proxy/routes",
	"/health",
	"/healthz",
	"/ready",
	"/readyz",
	"/livez",
	"/metrics",
	"/favicon.ico",
	"/.well-known/acme-challenge/"
];
/**
* User-Agent substrings (lowercased) that mark automation: our own pollers and
* common uptime/health monitors. Deliberately conservative — generic clients
* like curl/wget are NOT here, since a real user's CLI uses them.
*/
const AUTOMATION_USER_AGENTS = [
	OBSERVABILITY_PULL_USER_AGENT.toLowerCase(),
	"kube-probe",
	"uptime-kuma",
	"uptimerobot",
	"pingdom",
	"statuscake",
	"betteruptime",
	"prometheus",
	"blackbox",
	"grafana"
];
/**
* True if the env runs an enabled CLINT (studio) agent. Housekeeping only sleeps
* CLINT studios, which are served at the apex host `<subdomain>.vetra.io` — the
* host the idle detector checks. CONNECT/SWITCHBOARD apps serve at
* `connect.*`/`switchboard.*` sub-hosts, so the apex is always empty for them and
* the idle signal would (wrongly) read every such env as idle → slept. Scoping
* to CLINT is what keeps the detector off non-studio envs.
*/
function hasClintService(services) {
	if (!services) return false;
	try {
		const parsed = JSON.parse(services);
		return Array.isArray(parsed) && parsed.some((s) => s?.type === "CLINT" && s?.enabled === true);
	} catch {
		return false;
	}
}
const TRANSITIONAL_STATUSES = new Set([
	"DEPLOYING",
	"CHANGES_PENDING",
	"CHANGES_APPROVED",
	"CHANGES_PUSHED"
]);
/**
* Map an environment's document status to a coarse power state. In the
* activator's context a host is only caught while disabled, so `STOPPED`
* (housekeeping sleep) reads as SLEEPING and a subsequent deploy reads as
* WAKING; `READY` is AWAKE.
*/
function deriveStudioPowerState(row) {
	const status = row?.status ?? "";
	if (status === "STOPPED") return "SLEEPING";
	if (status === "READY") return "AWAKE";
	if (TRANSITIONAL_STATUSES.has(status)) return "WAKING";
	return "UNKNOWN";
}
/**
* Deploy-failed status string in the environments read-model. Note the
* lowercase `t` — a known typo baked into the read-model that must be matched
* exactly. The document reducer already permits SLEEP_ENVIRONMENT from this
* state (shipped in 0.0.21); this makes housekeeping reclaim stuck failed
* deploys instead of leaving them running forever.
*/
const DEPLOYMENT_FAILED_STATUS = "DEPLOYMENt_FAILED";
/**
* Tenants that must never be slept by housekeeping — the hand-managed core
* tenants (also excluded from the `powerhouse-tenants` ApplicationSet).
*/
const DEFAULT_NEVER_SLEEP_TENANTS = [
	"renown",
	"vetra",
	"staging",
	"rfp-hub",
	"defiunited",
	"defiunited-staging"
];
/**
* Whether an environment is eligible to be put to sleep:
*  - it's a claimed studio (`owner` set; not a warm-pool WARMING/AVAILABLE/FAILED env),
*  - it's currently live (`READY`) or stuck in `DEPLOYMENt_FAILED` (never
*    re-sleep one already stopping/deploying),
*  - it has a routable subdomain,
*  - it's not a core tenant and not on the never-sleep allowlist.
*/
function isEligibleForSleep(row, opts = {}) {
	if (!row) return false;
	if (row.status !== "READY" && row.status !== "DEPLOYMENt_FAILED") return false;
	if (!row.owner) return false;
	if (!row.subdomain) return false;
	if (row.poolState != null && row.poolState !== "CLAIMED") return false;
	if (!hasClintService(row.services)) return false;
	const never = new Set([...opts.neverSleepTenants ?? DEFAULT_NEVER_SLEEP_TENANTS, ...opts.allowlist ?? []]);
	if (row.tenantId && never.has(row.tenantId)) return false;
	if (row.subdomain && never.has(row.subdomain)) return false;
	return true;
}
//#endregion
export { deriveStudioPowerState as a, OBSERVABILITY_PULL_USER_AGENT as i, AUTOMATION_USER_AGENTS as n, isEligibleForSleep as o, DEPLOYMENT_FAILED_STATUS as r, AUTOMATION_PATHS as t };

//# sourceMappingURL=policy-BJOoyypM.js.map