feat(podspec): find PodSpecs with a CUE schema (option C) - #83
Open
MPV wants to merge 3 commits into
Open
Conversation
This was referenced Aug 9, 2026
MPV
force-pushed
the
claude/podspec-c-cue-schema
branch
2 times, most recently
from
August 9, 2026 20:42
d4b67e2 to
0aaa86b
Compare
This was referenced Aug 9, 2026
MPV
force-pushed
the
claude/podspec-c-cue-schema
branch
from
August 10, 2026 06:46
0aaa86b to
cb6d9e5
Compare
Option C of four candidate answers to #26, raised side by side for comparison, and the one the issue itself suggested. Each document is decoded into plain Go values and walked; a node is a PodSpec when one of its container lists unifies with an embedded CUE schema (k8s/podspec.cue). #Container is a CUE definition and therefore closed, so a container carrying an unlisted field is a mismatch — the same lookalike rejection option B gets from strict Go decoding. What is unique here is that the schema is data, so it can be replaced without a rebuild: $ kir manifests/ # embedded schema $ kir --schema my-containers.cue ... # your own TestCustomSchema pins both halves: a resource whose containers carry an unknown field yields nothing by default and its image under an extended schema. The k8s unit tests are the same corpus as option B, so the two engines are compared on identical cases. The costs are why this may not be the answer. Over 1000 documents: 164ms -> 406ms, about twice option B's walk. cuelang.org/go adds 35 go.sum lines and takes the binary from 12.8 MB to 23.7 MB. It requires Go 1.25, so .tool-versions moves from 1.24.3 (CI reads its version from there). And podspec.cue restates part of the container API by hand, where option B reuses k8s.io/api directly. See ADR 0008. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013RYRsLAGHPcRuwhodXXmWP
The hand-written podspec.cue restated part of the container API, which
could drift from k8s.io/api and was the weakest part of option C. Reuse
the official types instead: `cue get go k8s.io/api/core/v1` generates the
definitions (make schema, checked in under k8s/cue.mod/gen/), and the
schema kir maintains is now the import that names them.
import corev1 "k8s.io/api/core/v1"
#Containers: [...corev1.#Container]
#EphemeralContainers: [...corev1.#EphemeralContainer]
A --schema file is loaded inside the same module, so a user schema can
embed corev1.#Container and add a field rather than transcribe it; the
TestCustomSchema fixture now does exactly that.
Two things the official types surfaced that the hand-written schema hid:
- Manifests decode YAML->JSON->Go, so every number is a float64.
Offering that to a schema saying int32 rejects every container
declaring a port — TestKind/StatefulSet caught it. Candidates now
reach CUE as JSON, which keeps whole numbers whole;
TestFindImagesIntegerFields pins it.
- Unifying against the full closed #Container is expensive: 2469ms per
1000 documents, against 357ms for the hand-written schema. Calling
Eval() on the definitions recovers ~7x of that and silently discards
closedness, so every lookalike starts matching; only the Lookalike
fixture catches it. Left un-evaluated, with a comment saying why.
Both are recorded in ADR 0009: reusing the official types removes the
fidelity objection and sharpens the cost one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013RYRsLAGHPcRuwhodXXmWP
Re-measured on the 0.4.3 base: 105ms -> 2163ms over 1000 documents. The ratio worsens rather than improves, since master got faster on Go 1.26 while CUE unification did not: ~21x today and ~16x option B. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013RYRsLAGHPcRuwhodXXmWP
MPV
force-pushed
the
claude/podspec-c-cue-schema
branch
from
August 13, 2026 07:51
cb6d9e5 to
4eab721
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Option C of four candidate answers to #26, raised side by side so they can be compared — this is the one the issue itself suggested. Scope
feat(podspec)finds the set — #81 #82 #83 #84. Only one should be merged.Rebased onto
2abd515(0.4.3) — Kubernetes v0.36.3, Go 1.26. All numbers re-measured, and the CUE schema regenerated against the new Kubernetes API. That regeneration turned out to be the interesting part of this rebase; see below.masterRollout)--configWorkflow)--configReplicationController,PodTemplatecontainerslookalikego.sumlinesmake schema--schema--configAll four keep the existing goldens green and produce byte-identical output on the shared 1000-document corpus. Timings are best-of-7 in a single run on one machine.
Approach
Decode each document into plain Go values, walk it, and ask a CUE schema whether each node is PodSpec-shaped.
The schema is generated from
k8s.io/api/core/v1bycue get go(make schema, checked in underk8s/cue.mod/gen/) rather than written by hand — the official types, not a restatement of them. What kir maintains is the import that names them:What it buys over B
Both reach custom resources, both reject lookalikes, both drop the
Listspecial case. The one thing unique to CUE is that the schema is replaceable without a rebuild, and a--schemafile loads inside the same module, so it can extend the official type rather than restate it (TestCustomSchema).What the rebase cost — the honest headline
Regenerating the schema for Kubernetes v0.36.3 broke in two ways, and neither was visible until the bump:
go mod tidydeletedk8s.io/apifromgo.mod. kir's Go code does not import it — only the generated CUE references it, and CUE imports are invisible to the Go toolchain. So the module the schema is generated from silently disappeared, andmake schemathen failed with "no required module provides package". Fixed with a build-tagged blank import (k8s/schema_source.go) whose only job is to keep it pinned.cue get gocould not read the new Kubernetes source.go run cuelang.org/go/cmd/cue@v0.17.1builds the generator with the Go version in CUE'sgo.mod(1.25), whilek8s.io/apiv0.36.3 requires 1.26 — so generation died withpackage requires newer Go version go1.26 (application built with go1.25). Fixed by pinningGOTOOLCHAIN=go1.26.5in the target.Both are one-line fixes once diagnosed. The point is that this is the maintenance surface options A, B and D do not have — B rode the same bump with no code change at all.
One cost did go away: CUE needs Go 1.25 and
masteris now on 1.26.5, so this no longer moves.tool-versions.What it still costs
cue.Value.Eval()on the definitions is ~7× faster and silently discards closedness, so every lookalike starts matching. Nothing errors; onlyTestSkipsNonWorkloads.Lookalikecatches it. Left un-evaluated with a comment saying why.float64, and offering that to a schema sayingint32rejects every container with a port.TestKind/StatefulSetcaught this. Candidates now reach CUE as JSON;TestFindImagesIntegerFieldspins it..cuefiles checked in (up from 45 on the older API).Note on a golden
TestFailure.BadYAML's stderr gains anerror converting YAML to JSON:prefix — same class, same exit 1, different parser wrapping the message. Shared with B and D.The question this PR poses
Is a user-replaceable schema worth ~16× option B's runtime plus a code-generation step that needs care on every Kubernetes bump — when B gets the same reach and the same lookalike rejection from
k8s.io/api, a dependency kir already has?gofmt,go vet,go mod tidyno-op,go test -race ./...green. ADR 0009 records the decision as proposed, and now records both regeneration hazards.