/** * Kubernetes-object-shape utilities shared by every lexicon whose live model * is a Kubernetes API object — the k8s lexicon itself (chant #1076) and GCP's * Config Connector custom resources (chant #1087). * * A Config Connector custom resource *is* a Kubernetes object: it carries the * same envelope (`status`, `metadata.{uid,resourceVersion,generation, * creationTimestamp,managedFields,selfLink}`), the same SSA `fieldsV1` * encoding for `metadata.managedFields`, and some CNRM kinds even embed * genuinely k8s-shaped substructures (Cloud Run's `RunService` wraps a * Knative pod spec with `containers`/`env`/`ports`, keyed the same way a * Deployment's are). None of that is specific to chant's k8s *lexicon* — it * is a fact about the Kubernetes API that any reader of a Kubernetes-shaped * object needs, regardless of which lexicon is doing the reading. * * This module lives in core rather than in the k8s lexicon for the same * reason `./kubectl-context.ts`'s `resolveClusterTarget` does (chant #1100): * GCP's observation needs it too, without taking a dependency on the k8s * lexicon package. Nothing here is keyed by chant's own k8s entityType * catalog (`K8s::Apps::Deployment`, …) or by any lexicon's service-default * table — that stays lexicon-specific, layered on top of what's here * (`lexicons/k8s/src/deep-observe-hooks.ts`'s `K8S_SERVICE_DEFAULTS`, * `lexicons/gcp/src/deep-observe.ts`'s CNRM-specific annotation noise). */ import type { DeepArrayElement } from "./deep-observation.js"; /** * Paths every Kubernetes API object carries regardless of kind, matched on * the exact index-erased pattern (there is exactly one `status`, one * `metadata.managedFields`, per object — no per-type variation the way AWS's * `Arn`/`RoleId` repeat at every nesting depth). * * - `status` — the whole subtree is server-computed; no declarative source * (chant's k8s manifests, chant's Config Connector CRs) ever authors it. * - `metadata.uid`/`resourceVersion`/`generation`/`creationTimestamp` — minted * and incremented by the API server, never authored. * - `metadata.managedFields` — the bookkeeping the ownership walk below reads * to decide everything else. Left in the tree it would report as permanent * drift (a timestamp changes on every write) and would recurse into the * encoded `fieldsV1` structure as if it were ordinary properties. * - `metadata.selfLink` — deprecated API-server bookkeeping some clusters * still echo; never a declared field. */ export declare const K8S_OBJECT_ENVELOPE_PRUNE_PATTERNS: ReadonlySet; /** * Kubernetes' own well-known list-map-key conventions for the substructures * that recur across kinds and across lexicons: containers/initContainers/ * ephemeralContainers and `env`/`volumes` keyed by `name` — the same field * Kubernetes' strategic-merge-patch and SSA's `list-map-keys` key on for * these lists — and container ports keyed by `containerPort`+`protocol`, * Service ports keyed by `port`+`protocol` (Kubernetes' own SSA * `list-map-keys` for each). Both port shapes are handled under one `ports` * branch by checking which field is present. * * Entity-type-agnostic on purpose: whether an array named `containers` * belongs to a `K8s::Apps::Deployment` or to a GCP `RunService`'s embedded * pod spec, the identity Kubernetes assigns each element is the same. */ export declare function k8sListMapOrderKey(element: DeepArrayElement): string | undefined; /** * The structural shape of one `metadata.managedFields` entry this module * needs. Matches `@intentius/chant-k8s-client`'s `ManagedFieldsEntry` * (chant #1075) field-for-field, but is declared independently here rather * than imported from that package: core must stay reachable from any * lexicon's build path, and the k8s client package is deliberately *not* * reachable from one (chant #1074's structural boundary, * `examples/k8s-client-boundary.test.ts`). A caller that already has a real * `ManagedFieldsEntry[]` (the k8s lexicon) passes it straight through — * TypeScript's structural typing accepts it with no cast. */ export interface ManagedFieldsEntryLike { manager?: string; operation?: string; subresource?: string; fieldsV1?: Record; } /** One live object's managed-fields ownership, resolved to chant dot-paths. */ export interface OwnershipSets { /** Paths any chant field manager owns on this object. */ chantOwned: ReadonlySet; /** Paths owned by a manager that is not chant. */ foreignOwned: ReadonlySet; /** The subset of `foreignOwned` where the declared manifest also sets the path — a contested field. */ foreignContested: ReadonlySet; /** * Path → the name of the manager that owns it (#1189). * * The three sets above answer *which category* owns a path. A reader needs * the other question — "owned by `kubectl-client-side-apply`" and "owned by * `hpa-controller`" are the same category and mean very different things to * an operator. Since chant #1191 no category is pruned from the diff, so * this map is what tells the two apart in the report. Last writer wins * where several managers touch one path, matching what the API server * itself reports. */ owners: ReadonlyMap; } /** * Build the three ownership sets for one live object. `entries` is * `metadata.managedFields`, already decoded (`@intentius/chant-k8s-client`'s * `managedFieldsOf` for the k8s lexicon; a plain `JSON.parse` of `kubectl get * -o json` for gcp); `isChantManager` classifies each entry's manager name * — matched on the `chant`/`chant:` family per chant #1075, but the * matcher itself is supplied by the caller rather than fixed here, because * what counts as "chant" is not the same fact on every lexicon's apply path * (see gcp's `deep-observe.ts` module doc for why that matters there). * * Subresource entries (`status`, `scale`) are excluded: a controller writing * a Deployment's `status` is not competing for the spec chant declared, the * same reasoning `@intentius/chant-k8s-client`'s `fieldsOwnedBy` default * already encodes. */ export declare function buildOwnershipSets(entries: readonly ManagedFieldsEntryLike[], liveRoot: Record, declaredRoot: Record, isChantManager: (manager: string | undefined) => boolean): OwnershipSets; /** * Well-known metadata the control plane writes on an object regardless of * who applied it — matched on the exact index-erased pattern, like * {@link K8S_OBJECT_ENVELOPE_PRUNE_PATTERNS}, but gated by the caller on * `side === "live" && counterpart === "absent"` so a manifest that *does* * declare one of these (a hand-pinned `change-cause`, say) is still compared. * * This list is the noise valve that replaced the managed-fields prune * (chant #1191). Until then a foreign-owned, undeclared path was dropped * before the diff ever saw it, which silenced `kubectl label deploy web * team=platform` — the exact console-edit class the `undeclared` drift kind * exists for. Now a foreign-owned path nobody declared is reported as * `undeclared` (with its owner named, #1189) unless the accepted baseline * already carries it — and what remains to subtract statically is only the * handful of annotations and labels Kubernetes' own controllers stamp on * every object of a kind. Widening this set is additive; each entry must be * something *no* human writes out of band. * * - `kubectl.kubernetes.io/last-applied-configuration` — client-side apply's * bookkeeping, a JSON copy of whatever was last applied. * - `deployment.kubernetes.io/revision` (+ `revision-history`, * `desired-replicas`, `max-replicas`) — the Deployment controller's * rollout counters, written to Deployments and their ReplicaSets. * - `pod-template-hash` / `controller-revision-hash` / * `statefulset.kubernetes.io/pod-name` — the selector labels the * ReplicaSet, DaemonSet and StatefulSet controllers add to what they own. */ export declare const K8S_SYSTEM_METADATA_PRUNE_PATTERNS: ReadonlySet; //# sourceMappingURL=managed-fields.d.ts.map