Makes kubectl get and datumctl get say what an IPAM object actually holds.
What was wrong
Every kind used the default table conversion, whose only columns are the name and an RFC3339 creation timestamp. Pool names are long and generated, so a listing was a column of digests beside a column of timestamps:
NAME CREATED AT
datum-subnet-ipv6-datum-cloud-us-central-1-global-mesh-282a69ab 2026-09-22T20:25:15Z
The change
Each kind now defines its own columns, mirroring what the milo-ipam plugin prints for the same resources, with age in the elapsed-time form every other Kubernetes resource uses. The absolute timestamp is still in -o yaml.
NAME CIDR FAMILY CLASS UTILIZATION AGE
datum-fabric-identity-root fd30::/32 IPv6 datum-fabric-identity <0.1% 26d
datum-subnet-ipv6-datum-cloud-us-central-1-default-72362560 fd20:0:f::/64 IPv6 datum-subnet-ipv6 <0.1% 12d
Fields that answer a follow-up rather than identify the object — parent, scope, phase — appear under -o wide.
Two details that only real data exposed. A pool's class comes from spec.classNames on an operator-authored pool and spec.classRef on a cascade-provisioned one, and exactly one is ever set, so the column falls back. And status.utilizationPercent is rounded to four decimal places, which an IPv6 allocation rounds straight through — whether a pool has been drawn on is read from the exact capacity counts, so a pool holding a /48 out of a /32 reports <0.1% rather than claiming to be empty.
This reaches every client at once, including the portal: they all ask the server for the table.
Upgrade
No API types change and no migrations. Printing only.