/** * K8s environment→cluster binding — chant #1100. * * Every cloud lexicon binds an environment to a scope: AWS resolves `` * to a CloudFormation stack, Azure treats `` as the resource group, * Temporal looks up `temporal.profiles.` in `chant.config.ts`. K8s (and * GCP-via-Config-Connector, which observes through the same kubectl path) * bound nothing — `describeResources` shelled out to `kubectl get` with no * `--context`, so it read whatever cluster `kubectl config current-context` * happened to point at. Point `prod` at a dev cluster and every declared * resource reads as missing — a wrong-cluster diff that looks like a * confident list of deletions. * * This module is the shared resolver both the k8s and gcp lexicons' * `describeResources` call, so they resolve a cluster identity the same way * (see `lexicons/k8s/src/config.ts`'s `K8sChantConfig` for the declared * shape). It is intentionally provider-agnostic and lives in core (like * `./ownership.ts`) rather than in the k8s lexicon package, since gcp's * Config Connector observation needs it too without taking a dependency on * the k8s lexicon. */ import type { UnobservedReason } from "./observation.js"; /** A single environment's cluster binding — see `K8sChantConfig` in the k8s lexicon. */ export interface K8sClusterProfile { /** kubectl context name this environment is bound to. */ context: string; } /** Shape of the `k8s` passthrough key in `chant.config.ts` that this resolver reads. */ export interface K8sConfigShape { profiles?: Record; } /** * Historically thrown when an environment declared a cluster binding but the * ambient kubectl context disagreed with it (#1100/#1155). As of #1488 the * resolver no longer polices ambient at all — a declared binding is USED, not * checked — so {@link resolveClusterTarget} never throws this anymore. The * class stays exported because the k8s lexicon's read/write paths still catch * it (converting a refusal into NOT-OBSERVED), and a custom connector may * still throw it to get that classification. */ export declare class ClusterBindingMismatchError extends Error { readonly environment: string; readonly expectedContext: string; readonly ambientContext: string; constructor(environment: string, expectedContext: string, ambientContext: string); } export interface ResolvedClusterTarget { /** * Explicit `--context` value to pass to every kubectl invocation. Present * only when the environment has a declared binding — undefined means * "no binding, keep today's ambient-context behavior". */ context?: string; /** Where the target came from. */ source: "bound" | "ambient"; } /** * How the resolver learns which context is ambient. The default shells * `kubectl config current-context`; the k8s lexicon's typed API client * (chant #1074) supplies one that reads the parsed kubeconfig instead, so a * client that never needs the `kubectl` binary does not acquire a dependency * on it just to check the binding. * * As of #1488 the resolver itself no longer reads ambient (a declared binding * is used directly), so this hook only matters to callers that surface the * ambient context in their own messages. */ export type AmbientContextReader = () => Promise; /** Options for {@link resolveClusterTarget}. */ export interface ResolveClusterTargetOptions { /** * Override how the ambient context is read. Defaults to * `kubectl config current-context`. */ ambientContext?: AmbientContextReader; } /** * Resolve the kubectl context an environment should be observed/applied * against, reading `k8s.profiles..context` from `chant.config.ts` * (the `config` passed in is the passthrough `ChantConfig`, cast loosely since * the `k8s` key isn't declared on the core schema — same pattern as * `temporal.profiles`). * * - No binding declared: returns `{ source: "ambient" }` — unchanged * behavior — but logs a visible warning identifying the caller and * environment, so the fallback is never silent (#1100 acceptance). * - Binding declared: returns `{ context: bound, source: "bound" }`, * regardless of what is ambient (#1488). The declared binding is the * selection, not a check: callers pass this context explicitly on every * read and write, so whichever cluster `kubectl` happens to be pointed at * is irrelevant. Any k3d cluster another project creates mid-session used * to steal the ambient context and turn a healthy estate grey with * `read-failed` as the only explanation; now it cannot. A bound context * that is absent from the kubeconfig fails downstream in the typed client * with an error naming the context and the `k8s.profiles..context` * binding — never by falling back to ambient. */ export declare function resolveClusterTarget(config: Record, environment: string, lexiconName: string, options?: ResolveClusterTargetOptions): Promise; /** * What a failed `kubectl get` actually proved. Shared by the k8s and gcp * lexicons, which read through the same kubectl path and used to collapse every * non-zero exit into "not there" — so an expired token, a downed API server, or * an uninstalled CRD all classified as `create`. */ export type KubectlReadOutcome = /** The API server answered and the object is not there. Safe to plan a create. */ { kind: "absent"; } /** The read proved nothing about the object's existence. */ | { kind: "unobserved"; reason: UnobservedReason; detail: string; }; /** * Classify a `kubectl get` failure into the observation tri-state (#1089). * * Only a genuine `NotFound` from the API server — or a kind the server does not * serve at all, where no instance can exist — establishes absence. Auth, * connectivity, and unresolvable contexts establish nothing, and must reach the * change set as NOT-OBSERVED rather than as an empty result. */ export declare function classifyKubectlFailure(err: unknown): KubectlReadOutcome; //# sourceMappingURL=kubectl-context.d.ts.map