Revision history for IO-K8s 1.110 2026-10-01 15:57:19Z - Fix a 1.109 regression: JSONSchemaProps minimum, maximum and multipleOf (OpenAPI type: number) are now Num and go out as JSON numbers. They were declared Str, and since 1.109 serializes every Str value as a JSON string, any CRD with a numeric bound -- from to_crd, load_yaml or struct_to_object -- reached the API server as "minimum":"1" and was refused with a 400 (cannot unmarshal string into ... of type float64). A numeric string such as "0.5" is numified on the way out; a non-numeric bound is now refused. compare_to_schema describes Num, [Num] and HashRef[Num] fields as number, array and hash. 1.109 2026-10-01 02:33:28Z - Behaviour change: { Str => 1 } is now a map of strings. Every scalar value goes out as a JSON string in TO_JSON, to_json and to_yaml, so a label, annotation, ConfigMap data value or any other map[string]string value built from a numeric Perl value (labels => { v => $n + 0 }) no longer reaches the API server as a JSON number and gets a 400 back. A reference value in a { Str => 1 } map is still passed through, but warns once per class and field in the 'deprecated' category: declare a free-form map as Opaque (or a bare HashRef), which copies its values through unchanged. to_crd exports a string map as additionalProperties: {type: string} and an Opaque map as x-kubernetes-preserve-unknown-fields. Opaque takes no parameters. - New map forms HashRef[Str] (strict: a reference value is refused at construction), HashRef[Int], HashRef[Num], HashRef[Bool], HashRef[IntOrStr], HashRef[Quantity], HashRef[Time] and HashRef[InstanceOf[Class]], equivalent to the { X => 1 } sugar, which stays. { Bool => 1 } and HashRef[Bool] now accept the JSON booleans a cluster sends back. AutoGen types an additionalProperties map as HashRef[X] and a schemaless or preserve-unknown object as Opaque. - 77 shipped fields whose upstream schema is not a map of strings are now declared by what they hold: RawExtension, FieldsV1 and map[string][]string fields (ControllerRevision data, WatchEvent object, ManagedFieldsEntry fieldsV1, OpaqueDeviceConfiguration parameters, UserInfo extra, PrometheusOperator proxyConnectHeader and params, ...) are Opaque; resource lists (PodSpec overhead, allocatedResources, consumedCapacity, ...) are HashRef[Quantity] and reject a value that is not a quantity at construction; PodDisruptionBudgetStatus disruptedPods is HashRef[Time]; ExternalSecrets OnePasswordProvider vaults and Traefik ErrorPage statusRewrites are HashRef[Int]; Resource V1 DeviceCounterConsumption counters is a map of Counter objects. AgentSandbox v1alpha1 volumeClaimTemplates, conditions and env are arrays, as upstream declares them, instead of maps that refused every real value. - to_yaml, TO_YAML and save now quote every string that a JSON, YAML 1.2 core or YAML 1.1 reader would take for a boolean, null or number (True, False, yes, No, on, ~, Null, 012, 0x1F, +1, 1_000, .inf, ...). Before, only strings JSON would misread were quoted, so kubectl and the API server, which resolve YAML 1.1, read such label, annotation and env values and condition statuses as booleans, nulls or numbers, and a generated CRD's enum [True, False, Unknown] arrived as [true, false, "Unknown"]. Real booleans and numbers are still emitted bare, and the output is unchanged for every other value. Every object's to_yaml now goes through the one TO_YAML emitter. - AgentSandbox and PrometheusOperator now reuse the shipped core classes for their embedded pod and volume-claim templates: Sandbox and SandboxTemplate podTemplate is Core::V1::PodTemplateSpec, and every volumeClaimTemplates entry (plus Prometheus/PrometheusAgent's ephemeral volumeClaimTemplate) is Core::V1::PersistentVolumeClaimTemplate. The reuse heuristic now filters candidates by recursive shape compatibility before its tie-break, which resolves these previously-ambiguous {metadata,spec} shapes to the single matching core class. - A colliding k8s declaration now fails at class load instead of silently corrupting the class: two JSON keys that sanitize to one Perl accessor (x-value and x_value, also across inheritance), a field -- or an AutoGen CRD/OpenAPI property -- named like a role method that is not an attribute (label, save, spec_get, ...), or a field its own class already declared with a different type, options or required-ness (an identical repeat is a no-op). A subclass redeclaring an inherited field under the same JSON key now really replaces its type, coercion, required flag and init_arg, not only its registry entry. - ComponentStatus keeps its top-level conditions: they were dropped on inflate and new and serialized as an empty list. The condition helpers (get_condition, is_condition_true, is_ready, condition_message) read a top-level conditions field wherever a class declares one. - Serialize numeric Perl values in Str fields and [Str] elements as JSON strings: EnvVar value => 8080 now emits "8080" instead of a bare number the API server rejects. Opaque { Str => 1 } maps are unchanged. AutoGen types an array of type: number as [Num], whose elements always go out as JSON numbers, and arrays of int-or-string and date-time items as [IntOrStr] and [Time] instead of [Str], so to_crd and the emitter keep them. [IntOrStr] elements serialize like an IntOrStr field. - new_object, inflate, struct_to_object, json_to_object and FROM_HASH refuse a non-hash value (array, string, code or scalar reference) at an object position instead of silently building an empty object, with or without strict. The error names the target class and the field. - SpecBuilder element writes (spec_push, indexed spec_set, map keys and spec_merge) go through the field's own coercion and type check, like a whole-field assignment: 'false' pushed into a [Bool] stays false, and a bad element croaks at the write instead of later in to_json. A failed multi-value spec_push changes nothing. - AutoGen fails closed after a failed generation: every class the failed run began stays failed, and later requests die with the original error behind an explanatory prefix instead of returning a half-built class, even after clear_cache. This includes add_crd; load a repaired schema into a fresh IO::K8s instance. - AutoGen's reuse_core no longer types a nested schema as a shipped core class whose array, map or nested field holds a different element type (an array of objects as LabelSelectorRequirement's [Str] values), which made valid data fail to inflate, nor an int-or-string field as a Str or [Str] one, which wrote 8080 back as "8080". Element types, map values and nested fields are now compared recursively; reuse decisions for the bundled providers are unchanged. - AutoGen's reuse_core no longer types a plain type: string field as a shipped core class whose matching field is an IntOrStr, Quantity or Time -- the mirror of the int-or-string fix above. {host, port: string} no longer reuses TCPSocketAction, whose IntOrStr port sent "8080" out as 8080, and {medium, mode, sizeLimit: string} no longer reuses EmptyDirVolumeSource, whose strict Quantity rejected sizeLimit "big"; such a field is typed Str and keeps its string value. A format: date-time string still reuses a Time field and a $ref to resource.Quantity a Quantity field. - Cilium's CiliumNetworkPolicy and CiliumClusterwideNetworkPolicy toPorts.ports is now a dedicated PortProtocol class whose port is a string, not the reused Networking::V1::NetworkPolicyPort whose IntOrStr port sent a Cilium port "80" out as the number 80 that the Cilium CRD rejects. - Declare Sub::Util as a direct runtime prerequisite. - A blessed value at an object position has to come out of its TO_JSON as a hash: the JSON boolean that "metadata": true decodes to used to build an empty ObjectMeta and now dies naming its class. An IO::K8s object of another class is still converted through its TO_JSON. - An array or hash field given the wrong container (containers => 'x', labels => []), a List whose items is no array or holds a non-hash item or whose item_class is a reference or empty, and an inflate document that is no hash (inflate('[]')) die naming the class, the field or element and the expected and received shape instead of with a bare Perl dereference, Module::Runtime, type-constraint or JSON parse error. - load_yaml reads a file as UTF-8 instead of handing its bytes to YAML::PP, which mangled or died on non-ASCII text. A one-line argument that is neither an existing file nor YAML text, typically a mistyped path, now dies instead of returning an empty list, in load_yaml as in IO::K8s::CRD->load and add_crd; so does a directory. - load no longer leaves a loader package behind on every call (about 850 KB each, unbounded in a process that reloads manifests), and reads .pk8s files as UTF-8, so a non-ASCII literal gives the same characters as in YAML. An unknown option to load now dies. - load($file, vars => { ... }) hands values to a .pk8s manifest, which reads them with var($name) or var($name, $default). - Document that json_to_object and struct_to_object resolve their class name again through expand_class, so a bare one-word name is read as a Kind; an already resolved class goes in as '+Class'. - AutoGen types a $ref to the apiextensions JSON, JSONSchemaPropsOrArray, JSONSchemaPropsOrBool and JSONSchemaPropsOrStringArray definitions as the shipped classes, so a value such as default: "foo", an array or a boolean round-trips instead of failing to inflate. The v1beta1 names are carried unchanged by the v1 JSON class. - Errors in a .pk8s manifest (die, warnings, syntax errors, var() and the inflation errors of a Kind call) name its file and line instead of "(eval 273) line 1848", and the manifest no longer sees the loader's own variables ($file, $m, ...), which now fail under use strict. - Errors name the calling line instead of one inside the distribution: a missing required field or a wrongly typed value met while new_object, inflate, struct_to_object, json_to_object, load_yaml or a .pk8s Kind call builds an object (a Type::Tiny exception stays an object), and errors from add_crd, IO::K8s::CRD->generate (AutoGen's included) and to_crd, FROM_HASH and from_json (the unknown_kinds => 'unstructured' fallback included), spec_set and spec_merge, the JSONSchemaPropsOr* classes, classes AutoGen builds from an openapi_spec, load of a missing .pk8s file, inflate of a document without kind, and List errors through inflate or IO::K8s::List->FROM_STRUCT; so does a CRD file that exists but cannot be read, through add_crd and IO::K8s::CRD->load alike. - An apiextensions JSON value (a CRD schema's default, example or enum entry, a K3s HelmChart's values) is copied one level on the way in and out: inflating copies a hash or array instead of keeping the caller's own, and TO_JSON returns a copy, so editing the source structure or the serialized output no longer changes the object -- the rule every other array or hash field already follows. - A field declared nullable => 1 keeps an explicit JSON null: new, its setter and every inflate path (inflate, new_object, struct_to_object, json_to_object, FROM_HASH, nested coercion) keep it, TO_JSON writes it back as null, and an absent field stays absent. Such a field gets has_ and clear_, and spec_delete removes it. Setting a nullable field to undef therefore sends null; the ExternalSecrets refreshTime now round-trips a server's null. - use IO::K8s::APIObject takes a subresources parameter (status, scale) that to_crd and IO::K8s::CRD->new write into spec.versions[]; it is checked at use time. add_crd carries a CRD version's subresources into the generated class and IO::K8s::CRD::Emitter renders the parameter, so a CRD read in and written out again keeps them. Any other import parameter -- a typo such as subresource or resource_plurals -- dies at the use line, naming the class and the known ones, instead of being ignored; so does an odd number of import arguments, and an api_version or resource_plural that is empty, undef or a reference. - SpecBuilder paths walk through apiextensions union fields into the value they hold: spec_set('values.replicaCount', 3) on a K3s HelmChart now serializes values: {"replicaCount":3} instead of values: null, and spec_get, spec_hash, spec_array, spec_push and spec_delete reach the value as well; the same for a schema's items, additionalProperties, additionalItems, dependencies, default and example. - Fix POD where a > inside a C<> code cut it short and garbled the text: IO::K8s, IO::K8s::Resource's field options, IO::K8s::CRD->new, IO::K8s::AutoGen, IO::K8s::CRD::Emitter, IO::K8s::Cilium, IO::K8s::Types::Net, the IO::K8s::Role::* builder roles and several Resource API classes. - A field typed as an apiextensions union class takes on new, its setter and spec_set/spec_push every value inflate takes, not only a hash: HelmChartSpec->new(values => [1, 2]) and JSONSchemaProps->new(enum => ['a']) used to die on the type check. Other object fields still refuse a non-hash value. - AutoGen types a $ref to Quantity, IntOrString, Time or MicroTime in an array's items or a map's additionalProperties as that scalar, as it already did for a property: [Quantity], { Quantity => 1 } and so on. It used to generate an empty class that real values failed to inflate into, or refuse a spec that did not ship the definition. - Quantity and Time values always serialize as JSON strings, the form Kubernetes writes them in, in scalar fields, maps and arrays alike: limits => { cpu => 1 } now emits "1" instead of a bare 1. The API server accepts both, but the output for numeric Perl values changes. - Bundled provider CRD Kind classes (Cilium, GatewayAPI, AgentSandbox, CertManager, PrometheusOperator, VolumeSnapshot, ExternalSecrets) now declare their upstream subresources, so to_crd writes spec.versions[].subresources -- status, plus scale where upstream defines it (AgentSandbox SandboxWarmPool, PrometheusOperator Alertmanager and Prometheus). 1.108 2026-09-22 00:53:53Z - Track Kubernetes v1.37.0: 11 new Kinds, 103 supporting types and 48 added fields, with upstream-derived resource plurals. Ten fields newly required upstream now fail construction when omitted. - Add full-depth PrometheusOperator (v0.93.1), VolumeSnapshot (external- snapshotter v8.6.0) and ExternalSecrets (v2.10.0) providers, including all 19 ExternalSecrets generator Kinds. - Model existing CRD providers to full depth and retain compatible older API tracks. Update Cilium to v1.20.1, Traefik to v3.7.12, K3s to v1.36.4+k3s1 and AgentSandbox to v1.0.0. - Fix CertManager status conditions silently dropping live objects: Issuer, ClusterIssuer, Certificate and CertificateRequest now type their conditions with cert-manager's own condition classes, which require only type and status per the CRD, so real objects that omit message no longer fail to inflate. - Constrain cert-manager IssuerCondition, CertificateCondition and CertificateRequestCondition status to the CRD's True/False/Unknown enum, enforced client-side at construction like the other schema enums; a status outside that set now fails to construct, matching upstream. - Fix PrometheusOperator status conditions dropping live objects the same way: Alertmanager and Prometheus now use a PrometheusOperator condition class (lastTransitionTime, status and type required per the CRD, message and reason optional), so real objects that omit message no longer fail to inflate. WorkloadBinding, whose upstream condition is a distinct type with its type constrained to the Accepted enum, uses its own WorkloadBindingCondition class. - Fix AutoGen's core-class reuse (D5, reuse_core) picking a shipped class that requires a field the schema's own required list leaves optional. A nested object generated from a caller-supplied openapi_spec, or by CRD::Emitter, no longer reuses a class whose extra required field would reject or silently drop a real object that omits it. When that filter removes the only shared apimachinery or core type a nested object matched, reuse no longer falls back to an unrelated class of the same shape from another API group; the object's own class is generated instead, so a provider's condition is modelled by that provider's class. - Model Cilium and ExternalSecrets status conditions with each provider's own condition class instead of reusing Core::V1::NamespaceCondition for a look- alike shape that survives the required filter: Cilium's CiliumNetworkPolicy conditions are now NetworkPolicyCondition, and ExternalSecrets' SecretStore, ExternalSecret, PushSecret and GeneratorState status conditions each their own class. The field shape is unchanged, but a condition now blesses as the provider's class, and ExternalSecret's condition type is constrained to the CRD's Ready/Deleted enum, enforced client-side at construction. - Add the three VolumeGroupSnapshot Kinds in v1, v1beta1 and v1beta2, with typed version-specific status fields. Bare names select the pinned upstream's storage v1beta2; exact GVKs select any served track. - Add CRD loading/add_crd for objects, hashes, YAML/JSON and files. Generate every served version with exact GVKs and storage-version short names; retain the first bare-name binding when groups share a Kind. - Generate nested OpenAPI objects at full depth, expose their source metadata and safely shorten deep class names. Reject unresolved references and use process-unique namespaces so later instances cannot inherit old schemas. - Let generation keep a provider's own named type where its shape matches a core class (AutoGen reuse_core_except), and let the class emitter point a field at an absolute cross-version or core class without emitting a copy. - Add single- and multi-version CRD schema export through to_crd and IO::K8s::CRD, including field options, JSON Bool defaults and supported ECMA262 patterns. Reject duplicate versions and unsupported Perl patterns. - CRD schema round trips are not lossless: Quantity becomes Str, typed scalar maps become opaque maps, and arrays of Num, Quantity, Time or IntOrStr re-import as arrays of Str. - Add CRD::Emitter rendering with name/role overlays and canonical Quantity recognition. Shared target paths require identical functional declarations; contextual POD may differ, but conflicting aliases fail. - Extend the k8s DSL with Num, typed scalar maps, nested containers and schema/validation field options. Enforce scalar enum/range/pattern rules; defaults and schema-requiredness remain metadata, not client-side defaults. - Preserve unknown fields at every modeled depth by default; strict => 1 rejects them. Opt-in unknown_kinds => 'unstructured' also preserves unresolved resource documents instead of failing GVK resolution. - Make GVK resolution consistently exact and fail closed, including version conflicts, qualified map keys and bare names shadowed by Perl packages. kind, api_version and resource_plural reject setter arguments. - Coerce nested hashrefs through direct constructors and FROM_HASH, preserve prebuilt objects and shallow-copy input/output containers. Inner references below opaque hashes remain shared. - Make class-level JSON round trips recursive, handle fields named json and require UTF-8 bytes for from_json. Already-decoded non-ASCII character strings now fail instead of producing mojibake. - Normalize Bool values consistently, including scalar references and JSON booleans. Scalar undef is omitted; undef in Bool arrays fails at the wire boundary. Num stays numeric; Quantity accepts the n and u suffixes. - Extend generic List inflation to Kubernetes *List payloads, unknown envelope fields, strict mode and JSON round trips. item_class requires a fully qualified class; unverifiable list identity is not emitted. - Make SpecBuilder object-aware on all API objects: typed paths, container vivification, hashref coercion and non-destructive errors. Builder roles declare their requirements; calling spec_* without a spec gives an error. - Support Ingress path backends via add_path_match(service => ..., port => ...), including named ports; add_backend remains the default backend. Gateway/Traefik routing is unchanged, with Gateway path-type translation. - Limit core NetworkPolicy DNS egress to CoreDNS in kube-system. Policies generated by allow_egress_to_dns no longer permit port 53 everywhere; other DNS destinations require an explicit rule. Cilium is unchanged. - Give MiddlewareTCP a TCP-only builder. Its former HTTP-only builder methods now fail as missing methods; HTTP Middleware is unchanged. - Require owner UIDs and reject a second controller in set_owner. Adding the same UID is an idempotent no-op; controller => 0 creates a non-controller reference without the controller flag. - Remove unsupported status accessors from Traefik, K3s Addon and HelmChartConfig, and volumeClaimTemplatesPolicy from AgentSandbox v1alpha1 SandboxTemplate. Undeclared wire fields remain preserved outside strict mode. - Embedded Pod/Job/PVC template structs and ResourceClaimTemplateSpec lose APIObject identity/convenience methods (missing-method errors); metadata remains, without the incorrect apiVersion/kind output. - Remove imported helper functions accidentally exposed as public methods; genuine role methods and composition relationships remain unchanged. - Cache successful class loads and precompute core-shape lookup. Removing a cached module from %INC no longer forces a reload through load_class; failed loads are still retried, and reuse_core can be disabled. - Add pinned-source CRD drift/schema auditing and render/check/suggest tools, with data-driven exceptions and UTF-8-safe manifests. Correct API/role POD, declare Path::Tiny for tests and fix the distribution authority metadata. 1.107 2026-08-15 15:37:52Z - Resolve resource_map short-name keys on the explicit-apiVersion path (k31). expand_class('Kind', $api_version) and the qualified-string form now fall back to the resource_map's bare short-name key when no '$api_version/Kind' key exists, but only if the mapped class itself confirms the requested version via api_version() — a CRD registered as 'StaticWebSite => "+My::StaticWebSite"' inflates again from a manifest hashref instead of dying with "Cannot resolve Kubernetes GVK". The fail-closed semantics from k17 are preserved: a class that cannot verify the requested version is never substituted — whether it reports a different version, has no api_version() method, returns undef from it, or implements api_version as an instance attribute so that the class method call dies. The fallback runs before openapi_spec AutoGen, since the user explicitly registered the class. - Stop add() from dying on a provider class whose api_version is an instance attribute (k32). Merging a resource_map — via IO::K8s->new(with => [...]) or ->add(...) — asks each mapped class for its api_version() to build the domain-qualified key. A class that implements api_version as a Moo attribute rather than a constant made that class-method call die, taking the whole merge with it. Such a class is now registered under its short name and simply skipped for the qualified key, which is the only correct answer: no qualified key can be derived for it. 1.106 2026-08-13 02:59:43Z - Derive api_version through @ISA for class_namespaces subclasses. IO::K8s::Role::APIObject::api_version now falls back to walking the inheritance chain when the class name itself is not in a known namespace, so a consumer subclass (e.g. `use parent 'IO::K8s::Api::Core::V1::Pod'`) serializes apiVersion instead of silently omitting it. - Make compare_to_schema inheritance-aware. The schema comparison in IO::K8s::Role::Resource now reads the merged @ISA attribute view (_k8s_attr_info) instead of the raw registry entry, so a class_namespaces-style subclass compares its inherited attributes instead of an empty or partial set. - Make the k8s attribute lookups class_namespaces-aware via @ISA. _k8s_attr_info and _k8s_attributes in IO::K8s::Role::Resource now walk the inheritance chain, so a consumer subclass registered through class_namespaces (e.g. `use parent 'IO::K8s::Api::Core::V1::Pod'`) inflates with typed fields and serializes its parents' attributes instead of seeing an empty registry. Attribute info merges nearest-wins (a class's own declaration overrides an inherited one, in deterministic @ISA order); the attribute list is a deduplicated union of own and inherited declarations. Both merged views are cached per class and invalidated when a new k8s attribute is registered. - Made AutoGen dispatch apiVersion-aware and deterministic. expand_class()/inflate() with an explicit apiVersion now fall through to the openapi_spec AutoGen lookup when the resource map has no qualified key, but only for an exact group/version match in the spec's x-kubernetes-group-version-kind metadata; unknown or non-matching apiVersions fail closed (expand_class returns undef, inflate dies) instead of silently selecting a different version. Definition lookup no longer depends on hash order: versionless lookups sort candidate definitions lexicographically, exact lookups filter by group/version and croak on ambiguity. AutoGen picks a multi-GVK definition's entry by exact api_version (croak on ambiguity or no match) and deterministically sorted first otherwise, and a definition serving several versions now gets a GVK-specific package identity so two apiVersions of one definition yield distinct classes with the correct api_version method and wire serialization. - Fix IO::K8s::List::api_version for empty lists with an item_class. The local class-name regex derived shortened, invalid wire versions for groups with a ".k8s.io" suffix (rbac/v1, storage/v1, events/v1) and serialised them into manifests. The wire version is now derived from the item_class's own api_version class method, which knows the full group (rbac.authorization.k8s.io/v1, storage.k8s.io/v1, events.k8s.io/v1); unloadable or non-API item_classes yield undef and serialised empty lists omit apiVersion again. - Document that .pk8s manifests are Perl code executed in-process via eval and therefore must only be loaded from trusted sources, with load_yaml documented as the data-only path without code execution. - Declared Module::Runtime as a direct runtime prerequisite. IO::K8s has always loaded it in IO::K8s (for require_module) but cpanfile and the built META omitted it, so dependency installers that resolve strictly from the declared prereqs could miss it. The built META.json now lists Module::Runtime under runtime requires. - Made built-in Kubernetes resource dispatch exact for every addressable apiVersion/kind pair in the pinned v1.36.3 OpenAPI spec. This adds the missing events.k8s.io/v1 Event and autoscaling/v1 HorizontalPodAutoscaler routes while preserving the historical bare-name defaults (Core v1 Event and Autoscaling v2 HPA). Explicit unknown, malformed, empty, or mismatched apiVersions now fail closed: expand_class() returns undef and inflate()/new_object() report the requested Kind and apiVersion instead of silently selecting a different bare-name schema. Newly exposed compatibility aliases are ClusterTrustBundle, DeviceTaintRule, LeaseCandidate, PodGroup, ResourcePoolStatusRequest, StorageVersion, StorageVersionMigration and Workload; all existing aliases and targets remain pinned. An offline, SHA-256-pinned v1.36.3 fixture now exhaustively checks 98 addressable GVKs, all 14 multi-version Kind collisions, namespace scope, maintained non-dispatchable exceptions, real inflation and provider first-wins behavior. - Fixed inflate()/expand_class() to dispatch by apiVersion to the correct multi-version Kind. Pre-fix, %DEFAULT_RESOURCE_MAP mapped each short Kind name (DeviceClass, ResourceClaim, ResourceClaimTemplate, ResourceSlice, ...) to exactly one class path (always the GA v1), and only entries added externally via add() ever got domain-qualified ('$apiVersion/$Kind') entries. Two visible consequences: inflate({apiVersion=>'resource.k8s.io/v1beta1', kind=>'DeviceClass', ...}) silently returned IO::K8s::Api::Resource::V1::DeviceClass (the GA class, wrong schema), and inflate of a short-name-less Kind (DeviceTaintRule, ResourcePoolStatusRequest) died with 'Cant locate IO/K8s/DeviceTaintRule.pm in @INC' because the bare IO::K8s::$Kind fallback does not exist. %DEFAULT_RESOURCE_MAP now carries literal qualified entries for every shipped version of every addressable Kind, including the short-name-less ones. Class-method and instance dispatch therefore share the same complete static map without BUILD eagerly loading every target class; add() still derives qualified keys for externally merged providers. See k11. - Fixed api_version() in IO::K8s::Role::APIObject for the Storagemigration and Apiserverinternal groups. The fallback `lc($group) . '/' . $version` only produces the correct wire apiVersion for groups whose CamelCase lc-form equals the upstream group name (apps, batch, autoscaling, policy) -- for groups whose upstream name has a `.k8s.io` suffix, it produced a syntactically plausible but rejected-by-the-API-server string. In particular, every serialised StorageVersionMigration manifested as `apiVersion: storagemigration/v1beta1` (upstream: `storagemigration.k8s.io/v1beta1`) and every StorageVersion as `apiVersion: apiserverinternal/v1alpha1` (upstream: `internal.apiserver.k8s.io/v1alpha1`). Both groups are now in %API_GROUP_MAP, alongside the 13 already-mapped groups. The other 18 groups covered by %_class_prefix were audited and verified to either already map correctly or to fall through to the lc-fallback correctly (the upstream group name equals the lc form). - Fixed _expand_class in IO::K8s::Resource to handle CamelCase prefixes (KubeAggregator, AdmissionRegistration, ...). The single-word `[A-Z][a-z]+` regex could not match a prefix that has an internal capital, so any declared class name whose first segment was CamelCase silently fell through to the IO::K8s::Api default. In practice this meant KubeAggregator's APIService, APIServiceSpec, APIServiceStatus, APIServiceCondition and ServiceReference could not be inflated -- spec/status resolved to nonexistent IO::K8s::Api::KubeAggregator::V1 classes instead of the shipped IO::K8s::KubeAggregator::Pkg::Apis:: Apiregistration::V1 ones. The lookup now walks %_class_prefix in longest-key-first order so CamelCase wins over a hypothetical shorter substring, and uses \Q ... \E so the prefix is matched as a literal rather than as a regex. Backwards-compatible: unknown short names still fall through to the IO::K8s::Api default. - Shipped IO::K8s::Api::Apiserverinternal::V1alpha1::StorageVersionSpec. Upstream declares it as an empty struct ("StorageVersionSpec is an empty spec"), but the class was never authored, so every StorageVersion inflate died with "Can't locate .../StorageVersionSpec.pm in @INC". There was no working path through the class. This is the empty class upstream asked for; the failure mode that mattered was on the serialisation side, where an empty class round-tripped through TO_JSON could collapse to an empty hash or disappear altogether. - Added t/34_registry_guard.t: after every shipped class is loaded, walks the global attribute registry and asserts every referenced target class is loadable. This is the regression net that would have caught the apiextensions.k8s.io/v1 union types, the KubeAggregator prefix mismatch and the missing StorageVersionSpec together, rather than one at a time on the consumer side. Inline-generated structs (packages with no .pm file) are recognised via the `k8s` symbol in the target's stash. - Added t/32_kubeaggregator_apiservice.t and t/33_apiserverinternal_ storage_version.t covering the two regression cases above: that APIService and StorageVersion inflate, that every spec/status/ condition class resolves to the namespace shipped (not the wrong IO::K8s::Api fallback), and that the full object round-trips byte-for-byte through inflate -> TO_JSON -> inflate -> TO_JSON. - Added t/35_expand_class.t and t/36_storage_version_spec_unit.t for direct unit coverage of the bugfix targets: every branch of Resource::_expand_class (+FullClassName, already-qualified, longest-key-first prefix walk, default fallback) and the full prefix map; and StorageVersionSpec on its own (DOES Resource, ->new, TO_JSON as an empty hash, no api_version/kind since the class intentionally does not consume IO::K8s::APIObject). - Added the four apiextensions.k8s.io/v1 types the distribution declared but never shipped: JSON, JSONSchemaPropsOrArray, JSONSchemaPropsOrBool and JSONSchemaPropsOrStringArray. Without them, inflating any CustomResourceDefinition whose schema used `items`, `additionalItems`, `additionalProperties`, `dependencies`, `default`, `example` or `enum` died with "Can't locate IO/K8s/.../JSONSchemaPropsOrArray.pm in @INC" -- in practice nearly every real CRD, since a single array field is enough to hit it. All four are Kubernetes union types that serialize as the bare alternative rather than as a tagged wrapper, so which arm was used now survives a round trip: `additionalProperties: false` stays false instead of collapsing into an empty schema object, and a single `items` schema does not turn into a one-element array. - Added a FROM_STRUCT inflation hook to struct_to_object. A class that provides this class method takes over its own inflation completely and owns its TO_JSON in return, instead of being built field by field from a hashref. This is what makes the union types above representable; it is the general mechanism for any type that serializes as a bare value. - Shipped resource.k8s.io/v1beta1 (39 classes, entirely missing -- lib/IO/K8s/Api/Resource/ had no V1beta1/ directory at all) and filled in resource.k8s.io/v1beta2 (39 more classes; only the DeviceTaintRule/ DeviceTaintRuleSpec/DeviceTaintRuleStatus/DeviceTaintSelector quartet from an earlier fix was shipped there). Both versions now carry the full DRA surface: DeviceClass, ResourceClaim, ResourceClaimTemplate and ResourceSlice (DeviceClass/ResourceSlice cluster scoped, ResourceClaim/ ResourceClaimTemplate namespaced -- verified against the real swagger paths, not just pattern-matched from V1) plus the whole Device/ AllocationResult/DeviceRequest/Counter/CapacityRequestPolicy family. v1beta1 is the most widely deployed DRA server version; any consumer talking to a cluster that had not migrated to v1 got "Can't locate .../DeviceClass.pm in @INC" for every one of these Kinds. - Added the 9 v1.36 DRA additions to resource.k8s.io/v1alpha3 that had not been backported to the legacy "classic DRA" version also serving them: DeviceTaint, DeviceTaintRule (+ Spec/Status), DeviceTaintSelector, PoolStatus, and ResourcePoolStatusRequest (+ Spec/Status) -- the latter three were already present from a previous pass; only the DeviceTaint(Rule) family was actually missing. The 24 already-shipped "classic DRA" structural types (DeviceClass, ResourceClaim, Device, AllocationResult, etc.) are untouched, per the 1.100/1.105 decision to keep them as legacy backward compatibility. - Fixed ArrayRef[Bool] fields (DeviceAttribute.bools, present in the V1, V1beta1 and V1beta2 DRA APIs) to serialize as JSON booleans instead of plain 0/1, and to accept real decoded JSON booleans on the way back in. Previously TO_JSON emitted [1,0,1] instead of [true,false,true], and FROM_HASH on a real cluster response died with a Moo type constraint violation because JSON::PP::Boolean objects don't satisfy Types:: Standard's Bool -- this affected the already-shipped IO::K8s::Api::Resource::V1::DeviceAttribute too, not just the new v1beta1/v1beta2 copies. IO::K8s::Resource now tracks an is_array_of_bool flag with element-wise coercion, mirroring the existing scalar Bool handling. - Fixed ResourceClaimTemplateSpec (v1beta1 and v1beta2) to be built on IO::K8s::APIObject instead of IO::K8s::Resource, matching the already- shipped V1 sibling. Despite not being a Kind, it carries a real upstream `metadata: ObjectMeta` field; without APIObject the metadata attribute was never registered and silently dropped on serialization. - Added maint/spec-drift-check.pl, a repeatable coverage checker that diffs a real upstream swagger.json against what lib/IO/K8s/ actually ships and reports missing Kinds/types/fields (the tool behind k4-k8's discovery), plus a --from/--to mode that diffs two upstream releases directly to gauge whether a version bump is worth doing. Settled non-gaps (dropped *List kinds, old back-compat API tracks, apimachinery scalar/opaque types) are filtered via the maintained maint/spec-drift-exceptions.yaml. Report-only: never edits lib/ or the karr board. - Shipped storagemigration.k8s.io/v1beta1 (IO::K8s::Api::Storagemigration:: V1beta1::StorageVersionMigration, ::StorageVersionMigrationSpec and ::StorageVersionMigrationStatus), the version v1.36 clusters actually serve now that v1alpha1 has been dropped from the upstream spec. The existing V1alpha1 classes are untouched and stay shipped for old-cluster back-compat. Also added IO::K8s::Apimachinery::Pkg::Apis::Meta::V1:: GroupResource, which StorageVersionMigrationSpec.resource needs and which did not exist under any group. - Added five Core::V1 classes that existing structs gained a new $ref field for in v1.36, but whose target type was never shipped, silently swallowing the field on inflate: FileKeySelector (EnvVarSource. fileKeyRef -- read an env var's value from a file in the container), NodeSwapStatus (NodeSystemInfo.swap), PodCertificateProjection (VolumeProjection.podCertificate), and VolumeStatus + ImageVolumeStatus (VolumeMountStatus.volumeStatus). - Added t/38_storagemigration_v1beta1_and_core_v136_fields.t covering the two fixes above: a full StorageVersionMigration inflate -> TO_JSON -> inflate round-trip through GroupResource, plus round-trips for EnvVarSource.fileKeyRef, VolumeMountStatus.volumeStatus, NodeSystemInfo. swap and VolumeProjection.podCertificate. - Added 14 fields the v1.36 sync had missed on otherwise-shipped classes: Core::V1::ContainerStatus.stopSignal, Core::V1::Lifecycle.stopSignal, Core::V1::PodCondition.observedGeneration, Core::V1:: PodSecurityContext.seLinuxChangePolicy, Core::V1::ResourceHealth.message, Storage::V1::VolumeError.errorCode, ApiextensionsApiserver::...::V1:: CustomResourceDefinitionCondition.observedGeneration and :: CustomResourceDefinitionStatus.observedGeneration, Apimachinery::...:: Meta::V1::DeleteOptions.ignoreStoreReadErrorWithClusterBreakingPotential, and Apimachinery::Pkg::Version::Info.emulationMajor/emulationMinor/ minCompatibilityMajor/minCompatibilityMinor. Also shipped the new IO::K8s::Apimachinery::Pkg::Apis::Meta::V1::ShardInfo struct and wired it up as ListMeta.shardInfo, which previously had no target type at all. - Shipped coordination.k8s.io/v1alpha2 (IO::K8s::Api::Coordination:: V1alpha2::LeaseCandidate and ::LeaseCandidateSpec), which had no directory at all despite V1, V1alpha1 and V1beta1 all being shipped. Also added IO::K8s::Api::Scheduling::V1alpha2::TypedLocalObjectReference, a distinct upstream schema that WorkloadSpec.controllerRef previously pointed at Core::V1::TypedLocalObjectReference for instead -- the two happen to share the same three fields today, but they are different schemas and were drifting apart silently. - Added the meta.v1 discovery Kinds APIGroupList and APIResourceList (IO::K8s::Apimachinery::Pkg::Apis::Meta::V1::), used by the /apis and /apis/ discovery endpoints. Modelled like their already-shipped siblings APIGroup/APIVersions/Status/DeleteOptions: explicit apiVersion/ kind Str fields, not IO::K8s::APIObject. - Added t/39_v1_36_field_and_kind_gaps.t covering all of the above: field round-trips for a representative sample of the 14 additions, a full LeaseCandidate inflate -> TO_JSON -> inflate round-trip, the corrected WorkloadSpec.controllerRef target class, and APIGroupList/ APIResourceList loading with their required fields passed through. 1.105 2026-08-08 18:51:44Z - Removed the 76 individual *List classes (PodList, ServiceList, DeploymentList, etc.) that have emitted "use IO::K8s::List instead" deprecation warnings since the 1.00 Moose-to-Moo rewrite. They were never real classes in the 1.x series and this release drops them from the distribution entirely. Code that still references one of these names (e.g. via cpanfile or `use`) will now fail to install instead of silently warning; install IO::K8s::Deprecated for a clear redirect message pointing at IO::K8s::List. This also removes four namespaces that held nothing but a List stub and are now gone entirely -- IO::K8s::ApiExtensionsApiServer, IO::K8s::Api::Auditregistration, IO::K8s::Api::Extensions, IO::K8s::Api::Settings -- plus several now-empty single-version subdirectories under Apps, Autoscaling, Batch, Certificates, Coordination, Events, KubeAggregator, Policy, Rbac, and Scheduling. Full old-name list: IO::K8s::Deprecated's POD. - Restored the 8 Cilium v2alpha1/v2 classes dropped in 1.100's Cilium v1.19.2 upgrade (CiliumBGPAdvertisement, CiliumBGPClusterConfig, CiliumBGPNodeConfig, CiliumBGPNodeConfigOverride, CiliumBGPPeerConfig, CiliumCIDRGroup under Cilium::V2alpha1; CiliumBGPPeeringPolicy under Cilium::V2alpha1; CiliumExternalWorkload under Cilium::V2). These now ship alongside their current-API-version siblings for backward compatibility with clusters still running older Cilium releases, matching this dist's existing convention of keeping multiple API versions of a resource side by side (e.g. Apps V1beta1/V1beta2/V1). - Synced all API surfaces to their current upstream releases (previous target: Kubernetes v1.31 and CRD providers pinned months/years back). Kubernetes core moved v1.31 -> v1.36; CRD providers moved Cilium v1.19.2 -> v1.20.0, K3s v1.35.1+k3s1 -> v1.36.3+k3s1, Traefik v3.0.0 -> v3.7.10 (CRD set unchanged across v3.x), cert-manager v1.19.3 -> v1.21.1 (no CRD/field changes), Gateway API v1.1.0 -> v1.6.1, Agent Sandbox v0.2.1 -> v0.5.4. * Cilium: added CiliumDatapathPlugin (cilium.io/v2alpha1, new in v1.20.0's Extensible Datapath feature). * Gateway API: added BackendTLSPolicy, ListenerSet, TLSRoute, TCPRoute, UDPRoute (all gateway.networking.k8s.io/v1), and a gateway.networking.k8s.io/v1 ReferenceGrant alongside the existing v1beta1 (still the short-name/storage version); TLSRoute joins HTTPRoute/GRPCRoute in consuming IO::K8s::Role::Routable. * Agent Sandbox: all four CRDs gained a v1beta1 track (now the storage version) with real schema changes -- Sandbox drops spec/status.replicas for spec.operatingMode + spec.service + status.nodeName/podIPs; SandboxClaim drops spec.sandboxTemplateRef for a required spec.warmPoolRef and gains spec.additionalPodMetadata, spec.env, spec.lifecycle, a typed spec.volumeClaimTemplates list, and status.sandbox.name (was status.sandbox.Name); SandboxTemplate gains spec.envVarsInjectionPolicy, spec.service, spec.volumeClaimTemplates, spec.volumeClaimTemplatesPolicy; SandboxWarmPool gains spec.updateStrategy.type. v1alpha1 survives as served-but-deprecated on all four. * K3s and cert-manager: version bump only, no CRD/field-level changes upstream. * Traefik: version bump only; CRD set confirmed unchanged across the whole v3.x line. * Core Kubernetes -- workloads: added container restart rules (Container/EphemeralContainer restartPolicyRules), pod-level resources (PodSpec.resources, PodStatus.allocatedResources/ resources), PodSpec.hostnameOverride and .schedulingGroup (gang scheduling), PodStatus.observedGeneration and DRA-related extended resource claim status fields, HorizontalPodAutoscaler per-direction scaling tolerance, DeploymentStatus/ReplicaSetStatus .terminatingReplicas, NodeStatus.declaredFeatures. * Core Kubernetes -- networking: added networking.k8s.io/v1 ServiceCIDR and IPAddress (stable since v1.33; deprecated v1beta1 kept alongside), made NetworkPolicySpec.podSelector optional (upstream now defaults it), added discovery.k8s.io/v1 EndpointHints .forNodes / ForNode for PreferSameNode topology routing. * Core Kubernetes -- security/admission: added MutatingAdmissionPolicy/MutatingAdmissionPolicyBinding (GA in v1), PodCertificateRequest (certificates.k8s.io/v1beta1), and ClusterTrustBundle at v1beta1 alongside the existing v1alpha1. The older v1alpha1/v1beta1 ValidatingAdmissionPolicy and authentication.k8s.io/{v1alpha1,v1beta1} SelfSubjectReview tracks, and flowcontrol.apiserver.k8s.io/v1beta3, were evaluated for removal (no longer served by a v1.36 apiserver) but kept in this release rather than deleted, matching this dist's Cilium-precedent of preferring backward compatibility over deletion for classes that already shipped in a prior CPAN release -- deleting a previously-released module name without a tombstone would orphan it on PAUSE. Left as a maintainer decision for a future release. * Core Kubernetes -- storage/DRA: added resource.k8s.io/v1 (GA) Dynamic Resource Allocation -- DeviceClass, ResourceClaim, ResourceClaimTemplate, ResourceSlice and their full nested type graph -- replacing the old v1alpha3-only control-plane-controller allocation mode (DRA graduated to GA in Kubernetes v1.34). Also added resource.k8s.io/v1beta2 DeviceTaintRule, resource.k8s.io/v1alpha3 ResourcePoolStatusRequest, scheduling.k8s.io/v1alpha2 PodGroup/Workload (gang scheduling), storage.k8s.io/v1 VolumeAttributesClass (GA), coordination.k8s.io/v1beta1 LeaseCandidate, and new CSIDriverSpec fields (nodeAllocatableUpdatePeriodSeconds, preventPodSchedulingIfMissing, serviceAccountTokenInSecrets). Fixed a pre-existing bug found while verifying the new DRA classes: the api_version group map in IO::K8s::Role::APIObject was missing an entry for "resource", so every Api::Resource::* class (both the pre-existing v1alpha3 ones and the new ones added here) rendered apiVersion as "resource/vX" instead of the correct "resource.k8s.io/vX" -- would have produced manifests rejected by any real cluster. Fixed by adding resource => 'resource.k8s.io' to the map. Removed four resource.k8s.io/v1alpha3 classes -- PodSchedulingContext, PodSchedulingContextSpec, PodSchedulingContextStatus, ResourceClaimSchedulingStatus -- confirmed removed from the live v1.36 API (the "classic DRA" control-plane-controller allocation flow they supported was fully superseded by the new v1 structured-parameters model). Unlike the admission/auth/flowcontrol classes noted above, these had no lingering old-cluster back-compat rationale to keep them (DRA itself was alpha-only when these shipped, never GA, so there is no "still-supported old cluster" using them the way there is for a long-GA API's deprecated beta track) -- maintainer confirmed removal. Since these names already shipped in IO-K8s 1.100, PAUSE redirect tombstones for all four were added to the companion IO::K8s::Deprecated distribution rather than left to silently orphan. 1.100 2026-03-25 19:17:28Z - Added IO::K8s::AgentSandbox with 4 CRD classes for agent-sandbox (v0.2.1): Sandbox (agents.x-k8s.io/v1alpha1), SandboxClaim, SandboxTemplate, SandboxWarmPool (extensions.agents.x-k8s.io/v1alpha1) - Added inline struct support to k8s DSL: hash specs with multiple keys now auto-generate typed inner classes with hashref coercion, supporting nested structs and proper TO_JSON serialization - Upgraded IO::K8s::Cilium from ~v1.17 to v1.19.2: promoted 6 BGP CRDs and CiliumCIDRGroup from v2alpha1 to v2, removed CiliumExternalWorkload and CiliumBGPPeeringPolicy, fixed scope on CiliumEgressGatewayPolicy and CiliumGatewayClassConfig (23 → 21 CRDs) - Upgraded IO::K8s::K3s to v1.35.1+k3s1: added ETCDSnapshotFile (cluster-scoped, k3s.cattle.io/v1) (3 → 4 CRDs) - Added upstream_version() method to all CRD providers for tracking which upstream release each provider is based on 1.010 2026-03-24 13:17:59Z - Added missing Test::Deep test dependency (CPAN Testers failure) 1.009 2026-03-19 21:29:24Z - Fix: coerce \0/\1 refs to plain 0/1 for Bool attributes — Perl's \0 is a truthy reference, so users passing \0 expecting JSON false got wrong results. Bool attributes now dereference scalar refs before coercing. - Updated dist.ini authors and copyright holder 1.008 2026-03-08 23:36:05Z - Fix to_yaml handling for JSON booleans in resource hashes — TO_YAML now uses YAML::PP with JSON schema and JSON::PP boolean mode, so JSON::PP::true/false serialize as true/false instead of 1/0 or refs. - Added test for CRD to_yaml with JSON booleans in passthrough spec hash. 1.007 2026-03-08 15:01:36Z - Fix import leak: using an IO::K8s class (e.g. use IO::K8s::Api::Core::V1::Secret) no longer contaminates the caller's namespace with the k8s DSL function or IO::K8s::Role::Resource. Previously, _setup_class used extends (inheritance), causing every IO::K8s subclass to inherit Resource's import method and re-run _setup_class on any package that loaded it. Now uses Moo::Role composition (apply_roles_to_package) instead, so the import method stays in IO::K8s::Resource. - IO::K8s::Resource no longer composes IO::K8s::Role::Resource itself (it is NOT a resource, it is the DSL/setup class). - _k8s_attr_info is now called as a method on the target class (via the composed Role::Resource) instead of as a direct function call on IO::K8s::Resource. - Added regression test t/27_no_import_leak.t. 1.006 2026-03-01 02:55:20Z - Fix struct_to_object destroying pre-built objects: when a typed IO::K8s object was passed into a parent constructor (e.g. PersistentVolumeClaim in StatefulSet.volumeClaimTemplates), _inflate_struct treated the blessed object as a non-HASH ref and returned {}, producing an empty object. Now passes through objects that already match the expected class. - Harden _inflate_struct against blessed objects: if a blessed object reaches _inflate_struct despite the struct_to_object guard, it now extracts data via TO_JSON instead of silently returning {}. - Add explicit Scalar::Util import to IO::K8s (was used unimported). - Added comprehensive test suite: passthrough tests (t/23), combination tests with 35 subtests (t/24), real-world YAML manifest round-trip tests from popular projects like Kubernetes Dashboard, CoreDNS, Prometheus, Argo CD, NGINX Ingress, Grafana (t/25), and build-and-verify tests constructing manifests via Perl API and checking output (t/26). Total: 1047 assertions across 29 test files. 1.005 2026-02-28 06:43:29Z - Fixed _inflate_struct not recognizing sanitized field names (x-kubernetes-*, $ref, etc.) during JSON deserialization — Bool coercion and type inflation were skipped for these fields, causing JSON::PP::Boolean type constraint failures on e.g. x-kubernetes-preserve-unknown-fields 1.004 2026-02-28 05:42:53Z - Fixed JSONSchemaProps compilation: field names with special characters ($ref, $schema, x-kubernetes-*) are now automatically sanitized to valid Perl identifiers (e.g. _ref, _schema, x_kubernetes_embedded_resource) with init_arg mapping so constructors still accept the original JSON keys - Added comprehensive compile test (t/02_compile_all.t) loading all 697 modules 1.003 2026-02-28 00:35:50Z 1.002 2026-02-23 02:37:32Z - Added Authorization API types to default resource map (SelfSubjectRulesReview, SelfSubjectAccessReview, SubjectAccessReview, LocalSubjectAccessReview) - Added Authentication API types to default resource map (TokenReview, TokenRequest, SelfSubjectReview) 1.001 2026-02-14 19:07:12Z - Added IO::K8s::Types::Net with Net::IP-backed IP/CIDR type constraints - Added convenience methods to IO::K8s::Role::APIObject: labels, annotations, status conditions, and owner references for all API objects - Added IO::K8s::Role::SpecBuilder for deep-path spec manipulation on CRD classes - Added IO::K8s::Role::NetworkPolicy for building network policies (core K8s + Cilium) - Added IO::K8s::Role::Routable for HTTP/gRPC routing (Ingress, HTTPRoute, IngressRoute) - Added IO::K8s::Role::CertManaged for cert-manager Certificate/Issuer building - Added IO::K8s::Role::HelmManaged for K3s HelmChart/HelmChartConfig building - Added IO::K8s::Role::Loadbalanced for TraefikService traffic distribution - Added IO::K8s::Role::MiddlewareBuilder for Traefik middleware configuration - SpecBuilder is auto-applied to all CRD classes via IO::K8s::APIObject import - Added Net::IP as dependency - Added IO::K8s::Traefik with 10 Traefik CRD classes (traefik.io/v1alpha1) - Added IO::K8s::K3s with 3 K3s CRD classes (helm.cattle.io/v1 and k3s.cattle.io/v1) - Added IO::K8s::CertManager with 6 cert-manager CRD classes (cert-manager.io/v1 and acme.cert-manager.io/v1) - Added IO::K8s::GatewayAPI with 5 Gateway API CRD classes (gateway.networking.k8s.io/v1 and v1beta1) - Added IO::K8s::Cilium with 23 Cilium CRD classes (cilium.io/v2 and cilium.io/v2alpha1) - Added IO::K8s::Role::ResourceMap for external resource map providers - Added add() method to merge external resource maps (e.g. CRD packages) - Added 'with' constructor parameter for merging resource maps at construction - Added domain-qualified resource names (api_version/Kind) for collision handling - expand_class(), new_object(), inflate() now support api_version disambiguation - pk8s DSL functions accept optional api_version second argument - resource_map default now returns a copy per instance (safe for mutation) - Updated original author email and copyright holder 1.000 2026-02-13 01:52:24Z - Major refactoring: Migrated from Moose to Moo for lighter dependencies - Replaced YAML::XS with YAML::PP for pure Perl implementation - Updated to Kubernetes v1.31 API objects - New IO::K8s::List class for unified list handling - Improved attribute metadata handling with IO::K8s::Resource - Added IO::K8s::APIObject and IO::K8s::Role::APIObject for better API object support - Added support for namespaced resources via IO::K8s::Role::Namespaced - Added resource_plural support to IO::K8s::Role::APIObject for CRD classes - IO::K8s::APIObject accepts import parameters (api_version, resource_plural) for CRDs - IO::K8s::AutoGen supports CRD metadata (api_version, kind, resource_plural, is_namespaced) - expand_class recognizes already-loaded classes (needed for CRD classes) - Improved JSON serialization with canonical output - Better handling of opaque fields (fieldsV1, rawExtension, raw) - Deprecation stubs for removed List classes (backwards compatibility) - Added IO::K8s::Types for Type::Tiny type library integration 0.04 2018-12-11 00:00:00Z - Update to Kubernetes v1.14 API objects 0.03 2018-05-15 00:00:00Z - add convenience to_json method to all objects (meis) - add Test::Exception as dependency (manwar) 0.02 2018-05-08 00:00:00Z - Kubernetes API sometimes returns key => undef. We handle this not initializing the attribute to undef, since it's functionally the same to initialize it to undef or not initialize it 0.01 2018-05-04 00:00:00Z - Initial release to an unsuspecting world