feat(monetizeapi): controller-gen as canonical CRD schema source - #525

Closed
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen
Closed

feat(monetizeapi): controller-gen as canonical CRD schema source#525
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Why

CRD-vs-Go drift is a documented recurring bug class. PurchaseAutoRefill.MaxTotal existed in purchaserequest-crd.yaml for months while types.go didn't read it — fixed by hand in PR #513 with no enforcement to prevent the next instance. This commit also adds back the missing MaxTotal and MaxSpendPerDay fields to the Go struct so the next reconcile actually reads what the CRD declares.

Before

 internal/monetizeapi/types.go (Go struct, hand-edited)
↕ drift (no enforcement)
*-crd.yaml (OpenAPI schema, hand-edited)
Result: fields appear/disappear independently. Caught at runtime
when apiserver rejects a CR with "unknown field" or controller
reads zero from a field that was supposed to be populated.

After

 internal/monetizeapi/types.go
(kubebuilder markers are the source of truth)
│
▼ just generate
│
*-crd.yaml + zz_generated.deepcopy.go
(machine-generated; CI fails if not in sync with markers)
Result: one editable source. Schema, validations, printer columns,
subresources, DeepCopy methods all derive from Go markers.
`git diff` after `just generate` MUST be empty — CI enforces.

What changed

  • tools/tools.go — anchors controller-gen as a build dependency (v0.16.5, compatible with k8s.io/client-go v0.34.x)
  • justfilegenerate recipe runs controller-gen + renames obol.org_<plural>.yaml to existing <singular>-crd.yaml naming
  • hack/boilerplate.go.txt — generated-file marker (force-added past the repo's *.txt gitignore)
  • internal/monetizeapi/doc.go — package-level +groupName=obol.org / +versionName=v1alpha1 / +kubebuilder:object:generate=true
  • internal/monetizeapi/types.go — kubebuilder markers on every CRD-backed type, plus the previously-missing MaxTotal / MaxSpendPerDay fields on PurchaseAutoRefill
  • internal/monetizeapi/deepcopy_manual.go — hand-written DeepCopy for PreSignedAuth (its Payment map[string]interface{} is opaque to controller-gen)
  • internal/monetizeapi/zz_generated.deepcopy.go — generated DeepCopy methods for the rest of the package
  • internal/embed/infrastructure/base/templates/*-crd.yaml — regenerated (see diff section below)
  • .github/workflows/lint-test.yaml — new generate-check job: runs just generate and fails if git status --porcelain is non-empty

CRD diff after generation

Bit-exact round-trip wasn't possible because controller-gen normalises layout. The unavoidable differences between hand-written and generated CRDs are:

  1. Top-of-file comments dropped. controller-gen does not preserve the leading narrative comments (e.g. # ServiceOffer CRD\n# Defines a compute service...). All structural descriptions are preserved as description: fields on the matching property; the narrative comments are reproduced as Go doc comments on the corresponding types in types.go.
  2. controller-gen.kubebuilder.io/version: v0.16.5 annotation added on every generated CRD — required for CI to detect drift via diff and lets future operators see which controller-gen version produced the YAML.
  3. YAML key ordering and indentation changed. controller-gen alphabetises keys within objects (e.g. additionalPrinterColumns before name, singular after shortNames) and uses 2-space indentation throughout. The hand-written files used 4-space and a CRD-conventional ordering. The OpenAPI schemas are semantically identical; kubectl apply is order-insensitive.
  4. apiVersion + kind + metadata properties appear in every CR's openAPIV3Schema. controller-gen always emits these to match what apiserver expects; they were elided in the hand-written files. No behaviour change.
  5. PurchaseAutoRefill gained maxTotal (integer) and maxSpendPerDay (string) on the Go side. Both fields already existed in the prior hand-written CRD but were silently missing from types.go — this is the bug class the PR exists to close.
  6. Top-of-file --- separator on the (previously-missing) purchaserequest-crd.yaml added by controller-gen; harmless.

No fields were dropped. No validations were loosened. The ^0x[0-9a-fA-F]{40}$ pattern on payTo, the eip3009;permit2 enum on transferMethod, the ^/[a-zA-Z0-9/_.-]*$ path pattern, the 1-65535 port range, the 1-2500 count range, the inference;fine-tuning;http;agent type enum, all printer columns, and all subresources.status declarations are preserved.

Test plan

  • just generate (executed as the inlined shell script since just isn't installed locally) produces zero diff on a clean re-run
  • go build ./... clean
  • go test ./internal/embed/... green — the embed CRD parse + schema tests still pass against the regenerated YAML
  • go test ./internal/monetizeapi/... ./internal/serviceoffercontroller/... ./internal/x402/... ./internal/x402/buyer/... green
  • go vet ./... clean (only pre-existing internal/enclave/enclave_darwin.go unsafe-pointer warnings remain)
  • internal/stackTestWarnIfNoChatModel_EmitsWarnWhenNoModels failure verified as pre-existing on origin/main (unrelated)

Future

Unblocks any future CRD changes (e.g. spec.paused + metav1.Condition) — those become "edit Go markers, run just generate, commit" instead of hand-editing CRDs twice and drifting.

Closes the entire class of "CRD YAML and Go struct drifted" bugs.
PurchaseAutoRefill.MaxTotal was the most recent instance — it existed
in purchaserequest-crd.yaml for months while internal/monetizeapi/
types.go didn't have the corresponding field. Without this commit,
that pattern recurs by design: two sources of truth, one hand-
maintained, no enforcement of agreement.
Now Go is the single source of truth:
- kubebuilder markers on every CRD-backed struct in types.go
(validation, required, enum, pattern, printer columns, subresources)
- `just generate` regenerates *-crd.yaml from those markers
+ zz_generated.deepcopy.go from object:generate=true
- CI fails if `git status` is non-empty after `just generate` runs
This commit also fixes the documented MaxTotal / MaxSpendPerDay drift
by adding both fields to PurchaseAutoRefill — the generated CRD now
matches the prior hand-written one and the controller can read them.
Pinned controller-tools at v0.16.5 in tools/tools.go (compatible with
client-go v0.34.x; a newer release would force prometheus/common
through a panicking validation-scheme change). Generation is
deterministic; running locally produces no diff after a clean
checkout.
For future CRD edits:
1. Edit types.go (add/change a field, update markers)
2. `just generate`
3. Commit both the Go and YAML diffs
4. CI verifies the YAML was committed
PreSignedAuth.Payment is map[string]interface{} (opaque x402
payload), which controller-gen cannot deep-copy automatically; a
hand-written DeepCopy lives in deepcopy_manual.go and the type is
flagged object:generate=false.
The hack/boilerplate.go.txt file is force-added past *.txt gitignore;
it's an empty marker for now — add a copyright header later if the
repo settles on one.
Comment on lines +48 to +73
name: CRD generation up-to-date
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: 'go.mod'

- name: Set up just
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2.0.2

- name: Regenerate CRDs + DeepCopy
run: just generate

- name: Fail if regeneration changed any tracked files
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "::error::CRD manifests or DeepCopy methods are out of date."
echo "::error::Run 'just generate' locally and commit the result."
git status
git --no-pager diff
exit 1
fi
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded by bundle PR #536 — closing in favor of the consolidated merge target. Original branch and history preserved.

@bussyjdbussyjd closed this May 24, 2026
@OisinKyne
OisinKyne deleted the feat/controller-gen-codegen branch July 1, 2026 12:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(monetizeapi): controller-gen as canonical CRD schema source - #525

Closed
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen
Closed

feat(monetizeapi): controller-gen as canonical CRD schema source#525
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Why

CRD-vs-Go drift is a documented recurring bug class. PurchaseAutoRefill.MaxTotal existed in purchaserequest-crd.yaml for months while types.go didn't read it — fixed by hand in PR #513 with no enforcement to prevent the next instance. This commit also adds back the missing MaxTotal and MaxSpendPerDay fields to the Go struct so the next reconcile actually reads what the CRD declares.

Before

 internal/monetizeapi/types.go (Go struct, hand-edited)
↕ drift (no enforcement)
*-crd.yaml (OpenAPI schema, hand-edited)
Result: fields appear/disappear independently. Caught at runtime
when apiserver rejects a CR with "unknown field" or controller
reads zero from a field that was supposed to be populated.

After

 internal/monetizeapi/types.go
(kubebuilder markers are the source of truth)
│
▼ just generate
│
*-crd.yaml + zz_generated.deepcopy.go
(machine-generated; CI fails if not in sync with markers)
Result: one editable source. Schema, validations, printer columns,
subresources, DeepCopy methods all derive from Go markers.
`git diff` after `just generate` MUST be empty — CI enforces.

What changed

  • tools/tools.go — anchors controller-gen as a build dependency (v0.16.5, compatible with k8s.io/client-go v0.34.x)
  • justfilegenerate recipe runs controller-gen + renames obol.org_<plural>.yaml to existing <singular>-crd.yaml naming
  • hack/boilerplate.go.txt — generated-file marker (force-added past the repo's *.txt gitignore)
  • internal/monetizeapi/doc.go — package-level +groupName=obol.org / +versionName=v1alpha1 / +kubebuilder:object:generate=true
  • internal/monetizeapi/types.go — kubebuilder markers on every CRD-backed type, plus the previously-missing MaxTotal / MaxSpendPerDay fields on PurchaseAutoRefill
  • internal/monetizeapi/deepcopy_manual.go — hand-written DeepCopy for PreSignedAuth (its Payment map[string]interface{} is opaque to controller-gen)
  • internal/monetizeapi/zz_generated.deepcopy.go — generated DeepCopy methods for the rest of the package
  • internal/embed/infrastructure/base/templates/*-crd.yaml — regenerated (see diff section below)
  • .github/workflows/lint-test.yaml — new generate-check job: runs just generate and fails if git status --porcelain is non-empty

CRD diff after generation

Bit-exact round-trip wasn't possible because controller-gen normalises layout. The unavoidable differences between hand-written and generated CRDs are:

  1. Top-of-file comments dropped. controller-gen does not preserve the leading narrative comments (e.g. # ServiceOffer CRD\n# Defines a compute service...). All structural descriptions are preserved as description: fields on the matching property; the narrative comments are reproduced as Go doc comments on the corresponding types in types.go.
  2. controller-gen.kubebuilder.io/version: v0.16.5 annotation added on every generated CRD — required for CI to detect drift via diff and lets future operators see which controller-gen version produced the YAML.
  3. YAML key ordering and indentation changed. controller-gen alphabetises keys within objects (e.g. additionalPrinterColumns before name, singular after shortNames) and uses 2-space indentation throughout. The hand-written files used 4-space and a CRD-conventional ordering. The OpenAPI schemas are semantically identical; kubectl apply is order-insensitive.
  4. apiVersion + kind + metadata properties appear in every CR's openAPIV3Schema. controller-gen always emits these to match what apiserver expects; they were elided in the hand-written files. No behaviour change.
  5. PurchaseAutoRefill gained maxTotal (integer) and maxSpendPerDay (string) on the Go side. Both fields already existed in the prior hand-written CRD but were silently missing from types.go — this is the bug class the PR exists to close.
  6. Top-of-file --- separator on the (previously-missing) purchaserequest-crd.yaml added by controller-gen; harmless.

No fields were dropped. No validations were loosened. The ^0x[0-9a-fA-F]{40}$ pattern on payTo, the eip3009;permit2 enum on transferMethod, the ^/[a-zA-Z0-9/_.-]*$ path pattern, the 1-65535 port range, the 1-2500 count range, the inference;fine-tuning;http;agent type enum, all printer columns, and all subresources.status declarations are preserved.

Test plan

  • just generate (executed as the inlined shell script since just isn't installed locally) produces zero diff on a clean re-run
  • go build ./... clean
  • go test ./internal/embed/... green — the embed CRD parse + schema tests still pass against the regenerated YAML
  • go test ./internal/monetizeapi/... ./internal/serviceoffercontroller/... ./internal/x402/... ./internal/x402/buyer/... green
  • go vet ./... clean (only pre-existing internal/enclave/enclave_darwin.go unsafe-pointer warnings remain)
  • internal/stackTestWarnIfNoChatModel_EmitsWarnWhenNoModels failure verified as pre-existing on origin/main (unrelated)

Future

Unblocks any future CRD changes (e.g. spec.paused + metav1.Condition) — those become "edit Go markers, run just generate, commit" instead of hand-editing CRDs twice and drifting.

Closes the entire class of "CRD YAML and Go struct drifted" bugs.
PurchaseAutoRefill.MaxTotal was the most recent instance — it existed
in purchaserequest-crd.yaml for months while internal/monetizeapi/
types.go didn't have the corresponding field. Without this commit,
that pattern recurs by design: two sources of truth, one hand-
maintained, no enforcement of agreement.
Now Go is the single source of truth:
- kubebuilder markers on every CRD-backed struct in types.go
(validation, required, enum, pattern, printer columns, subresources)
- `just generate` regenerates *-crd.yaml from those markers
+ zz_generated.deepcopy.go from object:generate=true
- CI fails if `git status` is non-empty after `just generate` runs
This commit also fixes the documented MaxTotal / MaxSpendPerDay drift
by adding both fields to PurchaseAutoRefill — the generated CRD now
matches the prior hand-written one and the controller can read them.
Pinned controller-tools at v0.16.5 in tools/tools.go (compatible with
client-go v0.34.x; a newer release would force prometheus/common
through a panicking validation-scheme change). Generation is
deterministic; running locally produces no diff after a clean
checkout.
For future CRD edits:
1. Edit types.go (add/change a field, update markers)
2. `just generate`
3. Commit both the Go and YAML diffs
4. CI verifies the YAML was committed
PreSignedAuth.Payment is map[string]interface{} (opaque x402
payload), which controller-gen cannot deep-copy automatically; a
hand-written DeepCopy lives in deepcopy_manual.go and the type is
flagged object:generate=false.
The hack/boilerplate.go.txt file is force-added past *.txt gitignore;
it's an empty marker for now — add a copyright header later if the
repo settles on one.
Comment on lines +48 to +73
name: CRD generation up-to-date
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: 'go.mod'

- name: Set up just
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2.0.2

- name: Regenerate CRDs + DeepCopy
run: just generate

- name: Fail if regeneration changed any tracked files
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "::error::CRD manifests or DeepCopy methods are out of date."
echo "::error::Run 'just generate' locally and commit the result."
git status
git --no-pager diff
exit 1
fi
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded by bundle PR #536 — closing in favor of the consolidated merge target. Original branch and history preserved.

@bussyjdbussyjd closed this May 24, 2026
@OisinKyne
OisinKyne deleted the feat/controller-gen-codegen branch July 1, 2026 12:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(monetizeapi): controller-gen as canonical CRD schema source - #525

Closed
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen
Closed

feat(monetizeapi): controller-gen as canonical CRD schema source#525
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Why

CRD-vs-Go drift is a documented recurring bug class. PurchaseAutoRefill.MaxTotal existed in purchaserequest-crd.yaml for months while types.go didn't read it — fixed by hand in PR #513 with no enforcement to prevent the next instance. This commit also adds back the missing MaxTotal and MaxSpendPerDay fields to the Go struct so the next reconcile actually reads what the CRD declares.

Before

 internal/monetizeapi/types.go (Go struct, hand-edited)
↕ drift (no enforcement)
*-crd.yaml (OpenAPI schema, hand-edited)
Result: fields appear/disappear independently. Caught at runtime
when apiserver rejects a CR with "unknown field" or controller
reads zero from a field that was supposed to be populated.

After

 internal/monetizeapi/types.go
(kubebuilder markers are the source of truth)
│
▼ just generate
│
*-crd.yaml + zz_generated.deepcopy.go
(machine-generated; CI fails if not in sync with markers)
Result: one editable source. Schema, validations, printer columns,
subresources, DeepCopy methods all derive from Go markers.
`git diff` after `just generate` MUST be empty — CI enforces.

What changed

  • tools/tools.go — anchors controller-gen as a build dependency (v0.16.5, compatible with k8s.io/client-go v0.34.x)
  • justfilegenerate recipe runs controller-gen + renames obol.org_<plural>.yaml to existing <singular>-crd.yaml naming
  • hack/boilerplate.go.txt — generated-file marker (force-added past the repo's *.txt gitignore)
  • internal/monetizeapi/doc.go — package-level +groupName=obol.org / +versionName=v1alpha1 / +kubebuilder:object:generate=true
  • internal/monetizeapi/types.go — kubebuilder markers on every CRD-backed type, plus the previously-missing MaxTotal / MaxSpendPerDay fields on PurchaseAutoRefill
  • internal/monetizeapi/deepcopy_manual.go — hand-written DeepCopy for PreSignedAuth (its Payment map[string]interface{} is opaque to controller-gen)
  • internal/monetizeapi/zz_generated.deepcopy.go — generated DeepCopy methods for the rest of the package
  • internal/embed/infrastructure/base/templates/*-crd.yaml — regenerated (see diff section below)
  • .github/workflows/lint-test.yaml — new generate-check job: runs just generate and fails if git status --porcelain is non-empty

CRD diff after generation

Bit-exact round-trip wasn't possible because controller-gen normalises layout. The unavoidable differences between hand-written and generated CRDs are:

  1. Top-of-file comments dropped. controller-gen does not preserve the leading narrative comments (e.g. # ServiceOffer CRD\n# Defines a compute service...). All structural descriptions are preserved as description: fields on the matching property; the narrative comments are reproduced as Go doc comments on the corresponding types in types.go.
  2. controller-gen.kubebuilder.io/version: v0.16.5 annotation added on every generated CRD — required for CI to detect drift via diff and lets future operators see which controller-gen version produced the YAML.
  3. YAML key ordering and indentation changed. controller-gen alphabetises keys within objects (e.g. additionalPrinterColumns before name, singular after shortNames) and uses 2-space indentation throughout. The hand-written files used 4-space and a CRD-conventional ordering. The OpenAPI schemas are semantically identical; kubectl apply is order-insensitive.
  4. apiVersion + kind + metadata properties appear in every CR's openAPIV3Schema. controller-gen always emits these to match what apiserver expects; they were elided in the hand-written files. No behaviour change.
  5. PurchaseAutoRefill gained maxTotal (integer) and maxSpendPerDay (string) on the Go side. Both fields already existed in the prior hand-written CRD but were silently missing from types.go — this is the bug class the PR exists to close.
  6. Top-of-file --- separator on the (previously-missing) purchaserequest-crd.yaml added by controller-gen; harmless.

No fields were dropped. No validations were loosened. The ^0x[0-9a-fA-F]{40}$ pattern on payTo, the eip3009;permit2 enum on transferMethod, the ^/[a-zA-Z0-9/_.-]*$ path pattern, the 1-65535 port range, the 1-2500 count range, the inference;fine-tuning;http;agent type enum, all printer columns, and all subresources.status declarations are preserved.

Test plan

  • just generate (executed as the inlined shell script since just isn't installed locally) produces zero diff on a clean re-run
  • go build ./... clean
  • go test ./internal/embed/... green — the embed CRD parse + schema tests still pass against the regenerated YAML
  • go test ./internal/monetizeapi/... ./internal/serviceoffercontroller/... ./internal/x402/... ./internal/x402/buyer/... green
  • go vet ./... clean (only pre-existing internal/enclave/enclave_darwin.go unsafe-pointer warnings remain)
  • internal/stackTestWarnIfNoChatModel_EmitsWarnWhenNoModels failure verified as pre-existing on origin/main (unrelated)

Future

Unblocks any future CRD changes (e.g. spec.paused + metav1.Condition) — those become "edit Go markers, run just generate, commit" instead of hand-editing CRDs twice and drifting.

Closes the entire class of "CRD YAML and Go struct drifted" bugs.
PurchaseAutoRefill.MaxTotal was the most recent instance — it existed
in purchaserequest-crd.yaml for months while internal/monetizeapi/
types.go didn't have the corresponding field. Without this commit,
that pattern recurs by design: two sources of truth, one hand-
maintained, no enforcement of agreement.
Now Go is the single source of truth:
- kubebuilder markers on every CRD-backed struct in types.go
(validation, required, enum, pattern, printer columns, subresources)
- `just generate` regenerates *-crd.yaml from those markers
+ zz_generated.deepcopy.go from object:generate=true
- CI fails if `git status` is non-empty after `just generate` runs
This commit also fixes the documented MaxTotal / MaxSpendPerDay drift
by adding both fields to PurchaseAutoRefill — the generated CRD now
matches the prior hand-written one and the controller can read them.
Pinned controller-tools at v0.16.5 in tools/tools.go (compatible with
client-go v0.34.x; a newer release would force prometheus/common
through a panicking validation-scheme change). Generation is
deterministic; running locally produces no diff after a clean
checkout.
For future CRD edits:
1. Edit types.go (add/change a field, update markers)
2. `just generate`
3. Commit both the Go and YAML diffs
4. CI verifies the YAML was committed
PreSignedAuth.Payment is map[string]interface{} (opaque x402
payload), which controller-gen cannot deep-copy automatically; a
hand-written DeepCopy lives in deepcopy_manual.go and the type is
flagged object:generate=false.
The hack/boilerplate.go.txt file is force-added past *.txt gitignore;
it's an empty marker for now — add a copyright header later if the
repo settles on one.
Comment on lines +48 to +73
name: CRD generation up-to-date
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: 'go.mod'

- name: Set up just
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2.0.2

- name: Regenerate CRDs + DeepCopy
run: just generate

- name: Fail if regeneration changed any tracked files
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "::error::CRD manifests or DeepCopy methods are out of date."
echo "::error::Run 'just generate' locally and commit the result."
git status
git --no-pager diff
exit 1
fi
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded by bundle PR #536 — closing in favor of the consolidated merge target. Original branch and history preserved.

@bussyjdbussyjd closed this May 24, 2026
@OisinKyne
OisinKyne deleted the feat/controller-gen-codegen branch July 1, 2026 12:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(monetizeapi): controller-gen as canonical CRD schema source - #525

Closed
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen
Closed

feat(monetizeapi): controller-gen as canonical CRD schema source#525
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Why

CRD-vs-Go drift is a documented recurring bug class. PurchaseAutoRefill.MaxTotal existed in purchaserequest-crd.yaml for months while types.go didn't read it — fixed by hand in PR #513 with no enforcement to prevent the next instance. This commit also adds back the missing MaxTotal and MaxSpendPerDay fields to the Go struct so the next reconcile actually reads what the CRD declares.

Before

 internal/monetizeapi/types.go (Go struct, hand-edited)
↕ drift (no enforcement)
*-crd.yaml (OpenAPI schema, hand-edited)
Result: fields appear/disappear independently. Caught at runtime
when apiserver rejects a CR with "unknown field" or controller
reads zero from a field that was supposed to be populated.

After

 internal/monetizeapi/types.go
(kubebuilder markers are the source of truth)
│
▼ just generate
│
*-crd.yaml + zz_generated.deepcopy.go
(machine-generated; CI fails if not in sync with markers)
Result: one editable source. Schema, validations, printer columns,
subresources, DeepCopy methods all derive from Go markers.
`git diff` after `just generate` MUST be empty — CI enforces.

What changed

  • tools/tools.go — anchors controller-gen as a build dependency (v0.16.5, compatible with k8s.io/client-go v0.34.x)
  • justfilegenerate recipe runs controller-gen + renames obol.org_<plural>.yaml to existing <singular>-crd.yaml naming
  • hack/boilerplate.go.txt — generated-file marker (force-added past the repo's *.txt gitignore)
  • internal/monetizeapi/doc.go — package-level +groupName=obol.org / +versionName=v1alpha1 / +kubebuilder:object:generate=true
  • internal/monetizeapi/types.go — kubebuilder markers on every CRD-backed type, plus the previously-missing MaxTotal / MaxSpendPerDay fields on PurchaseAutoRefill
  • internal/monetizeapi/deepcopy_manual.go — hand-written DeepCopy for PreSignedAuth (its Payment map[string]interface{} is opaque to controller-gen)
  • internal/monetizeapi/zz_generated.deepcopy.go — generated DeepCopy methods for the rest of the package
  • internal/embed/infrastructure/base/templates/*-crd.yaml — regenerated (see diff section below)
  • .github/workflows/lint-test.yaml — new generate-check job: runs just generate and fails if git status --porcelain is non-empty

CRD diff after generation

Bit-exact round-trip wasn't possible because controller-gen normalises layout. The unavoidable differences between hand-written and generated CRDs are:

  1. Top-of-file comments dropped. controller-gen does not preserve the leading narrative comments (e.g. # ServiceOffer CRD\n# Defines a compute service...). All structural descriptions are preserved as description: fields on the matching property; the narrative comments are reproduced as Go doc comments on the corresponding types in types.go.
  2. controller-gen.kubebuilder.io/version: v0.16.5 annotation added on every generated CRD — required for CI to detect drift via diff and lets future operators see which controller-gen version produced the YAML.
  3. YAML key ordering and indentation changed. controller-gen alphabetises keys within objects (e.g. additionalPrinterColumns before name, singular after shortNames) and uses 2-space indentation throughout. The hand-written files used 4-space and a CRD-conventional ordering. The OpenAPI schemas are semantically identical; kubectl apply is order-insensitive.
  4. apiVersion + kind + metadata properties appear in every CR's openAPIV3Schema. controller-gen always emits these to match what apiserver expects; they were elided in the hand-written files. No behaviour change.
  5. PurchaseAutoRefill gained maxTotal (integer) and maxSpendPerDay (string) on the Go side. Both fields already existed in the prior hand-written CRD but were silently missing from types.go — this is the bug class the PR exists to close.
  6. Top-of-file --- separator on the (previously-missing) purchaserequest-crd.yaml added by controller-gen; harmless.

No fields were dropped. No validations were loosened. The ^0x[0-9a-fA-F]{40}$ pattern on payTo, the eip3009;permit2 enum on transferMethod, the ^/[a-zA-Z0-9/_.-]*$ path pattern, the 1-65535 port range, the 1-2500 count range, the inference;fine-tuning;http;agent type enum, all printer columns, and all subresources.status declarations are preserved.

Test plan

  • just generate (executed as the inlined shell script since just isn't installed locally) produces zero diff on a clean re-run
  • go build ./... clean
  • go test ./internal/embed/... green — the embed CRD parse + schema tests still pass against the regenerated YAML
  • go test ./internal/monetizeapi/... ./internal/serviceoffercontroller/... ./internal/x402/... ./internal/x402/buyer/... green
  • go vet ./... clean (only pre-existing internal/enclave/enclave_darwin.go unsafe-pointer warnings remain)
  • internal/stackTestWarnIfNoChatModel_EmitsWarnWhenNoModels failure verified as pre-existing on origin/main (unrelated)

Future

Unblocks any future CRD changes (e.g. spec.paused + metav1.Condition) — those become "edit Go markers, run just generate, commit" instead of hand-editing CRDs twice and drifting.

Closes the entire class of "CRD YAML and Go struct drifted" bugs.
PurchaseAutoRefill.MaxTotal was the most recent instance — it existed
in purchaserequest-crd.yaml for months while internal/monetizeapi/
types.go didn't have the corresponding field. Without this commit,
that pattern recurs by design: two sources of truth, one hand-
maintained, no enforcement of agreement.
Now Go is the single source of truth:
- kubebuilder markers on every CRD-backed struct in types.go
(validation, required, enum, pattern, printer columns, subresources)
- `just generate` regenerates *-crd.yaml from those markers
+ zz_generated.deepcopy.go from object:generate=true
- CI fails if `git status` is non-empty after `just generate` runs
This commit also fixes the documented MaxTotal / MaxSpendPerDay drift
by adding both fields to PurchaseAutoRefill — the generated CRD now
matches the prior hand-written one and the controller can read them.
Pinned controller-tools at v0.16.5 in tools/tools.go (compatible with
client-go v0.34.x; a newer release would force prometheus/common
through a panicking validation-scheme change). Generation is
deterministic; running locally produces no diff after a clean
checkout.
For future CRD edits:
1. Edit types.go (add/change a field, update markers)
2. `just generate`
3. Commit both the Go and YAML diffs
4. CI verifies the YAML was committed
PreSignedAuth.Payment is map[string]interface{} (opaque x402
payload), which controller-gen cannot deep-copy automatically; a
hand-written DeepCopy lives in deepcopy_manual.go and the type is
flagged object:generate=false.
The hack/boilerplate.go.txt file is force-added past *.txt gitignore;
it's an empty marker for now — add a copyright header later if the
repo settles on one.
Comment on lines +48 to +73
name: CRD generation up-to-date
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: 'go.mod'

- name: Set up just
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2.0.2

- name: Regenerate CRDs + DeepCopy
run: just generate

- name: Fail if regeneration changed any tracked files
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "::error::CRD manifests or DeepCopy methods are out of date."
echo "::error::Run 'just generate' locally and commit the result."
git status
git --no-pager diff
exit 1
fi
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded by bundle PR #536 — closing in favor of the consolidated merge target. Original branch and history preserved.

@bussyjdbussyjd closed this May 24, 2026
@OisinKyne
OisinKyne deleted the feat/controller-gen-codegen branch July 1, 2026 12:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(monetizeapi): controller-gen as canonical CRD schema source - #525

Closed
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen
Closed

feat(monetizeapi): controller-gen as canonical CRD schema source#525
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Why

CRD-vs-Go drift is a documented recurring bug class. PurchaseAutoRefill.MaxTotal existed in purchaserequest-crd.yaml for months while types.go didn't read it — fixed by hand in PR #513 with no enforcement to prevent the next instance. This commit also adds back the missing MaxTotal and MaxSpendPerDay fields to the Go struct so the next reconcile actually reads what the CRD declares.

Before

 internal/monetizeapi/types.go (Go struct, hand-edited)
↕ drift (no enforcement)
*-crd.yaml (OpenAPI schema, hand-edited)
Result: fields appear/disappear independently. Caught at runtime
when apiserver rejects a CR with "unknown field" or controller
reads zero from a field that was supposed to be populated.

After

 internal/monetizeapi/types.go
(kubebuilder markers are the source of truth)
│
▼ just generate
│
*-crd.yaml + zz_generated.deepcopy.go
(machine-generated; CI fails if not in sync with markers)
Result: one editable source. Schema, validations, printer columns,
subresources, DeepCopy methods all derive from Go markers.
`git diff` after `just generate` MUST be empty — CI enforces.

What changed

  • tools/tools.go — anchors controller-gen as a build dependency (v0.16.5, compatible with k8s.io/client-go v0.34.x)
  • justfilegenerate recipe runs controller-gen + renames obol.org_<plural>.yaml to existing <singular>-crd.yaml naming
  • hack/boilerplate.go.txt — generated-file marker (force-added past the repo's *.txt gitignore)
  • internal/monetizeapi/doc.go — package-level +groupName=obol.org / +versionName=v1alpha1 / +kubebuilder:object:generate=true
  • internal/monetizeapi/types.go — kubebuilder markers on every CRD-backed type, plus the previously-missing MaxTotal / MaxSpendPerDay fields on PurchaseAutoRefill
  • internal/monetizeapi/deepcopy_manual.go — hand-written DeepCopy for PreSignedAuth (its Payment map[string]interface{} is opaque to controller-gen)
  • internal/monetizeapi/zz_generated.deepcopy.go — generated DeepCopy methods for the rest of the package
  • internal/embed/infrastructure/base/templates/*-crd.yaml — regenerated (see diff section below)
  • .github/workflows/lint-test.yaml — new generate-check job: runs just generate and fails if git status --porcelain is non-empty

CRD diff after generation

Bit-exact round-trip wasn't possible because controller-gen normalises layout. The unavoidable differences between hand-written and generated CRDs are:

  1. Top-of-file comments dropped. controller-gen does not preserve the leading narrative comments (e.g. # ServiceOffer CRD\n# Defines a compute service...). All structural descriptions are preserved as description: fields on the matching property; the narrative comments are reproduced as Go doc comments on the corresponding types in types.go.
  2. controller-gen.kubebuilder.io/version: v0.16.5 annotation added on every generated CRD — required for CI to detect drift via diff and lets future operators see which controller-gen version produced the YAML.
  3. YAML key ordering and indentation changed. controller-gen alphabetises keys within objects (e.g. additionalPrinterColumns before name, singular after shortNames) and uses 2-space indentation throughout. The hand-written files used 4-space and a CRD-conventional ordering. The OpenAPI schemas are semantically identical; kubectl apply is order-insensitive.
  4. apiVersion + kind + metadata properties appear in every CR's openAPIV3Schema. controller-gen always emits these to match what apiserver expects; they were elided in the hand-written files. No behaviour change.
  5. PurchaseAutoRefill gained maxTotal (integer) and maxSpendPerDay (string) on the Go side. Both fields already existed in the prior hand-written CRD but were silently missing from types.go — this is the bug class the PR exists to close.
  6. Top-of-file --- separator on the (previously-missing) purchaserequest-crd.yaml added by controller-gen; harmless.

No fields were dropped. No validations were loosened. The ^0x[0-9a-fA-F]{40}$ pattern on payTo, the eip3009;permit2 enum on transferMethod, the ^/[a-zA-Z0-9/_.-]*$ path pattern, the 1-65535 port range, the 1-2500 count range, the inference;fine-tuning;http;agent type enum, all printer columns, and all subresources.status declarations are preserved.

Test plan

  • just generate (executed as the inlined shell script since just isn't installed locally) produces zero diff on a clean re-run
  • go build ./... clean
  • go test ./internal/embed/... green — the embed CRD parse + schema tests still pass against the regenerated YAML
  • go test ./internal/monetizeapi/... ./internal/serviceoffercontroller/... ./internal/x402/... ./internal/x402/buyer/... green
  • go vet ./... clean (only pre-existing internal/enclave/enclave_darwin.go unsafe-pointer warnings remain)
  • internal/stackTestWarnIfNoChatModel_EmitsWarnWhenNoModels failure verified as pre-existing on origin/main (unrelated)

Future

Unblocks any future CRD changes (e.g. spec.paused + metav1.Condition) — those become "edit Go markers, run just generate, commit" instead of hand-editing CRDs twice and drifting.

Closes the entire class of "CRD YAML and Go struct drifted" bugs.
PurchaseAutoRefill.MaxTotal was the most recent instance — it existed
in purchaserequest-crd.yaml for months while internal/monetizeapi/
types.go didn't have the corresponding field. Without this commit,
that pattern recurs by design: two sources of truth, one hand-
maintained, no enforcement of agreement.
Now Go is the single source of truth:
- kubebuilder markers on every CRD-backed struct in types.go
(validation, required, enum, pattern, printer columns, subresources)
- `just generate` regenerates *-crd.yaml from those markers
+ zz_generated.deepcopy.go from object:generate=true
- CI fails if `git status` is non-empty after `just generate` runs
This commit also fixes the documented MaxTotal / MaxSpendPerDay drift
by adding both fields to PurchaseAutoRefill — the generated CRD now
matches the prior hand-written one and the controller can read them.
Pinned controller-tools at v0.16.5 in tools/tools.go (compatible with
client-go v0.34.x; a newer release would force prometheus/common
through a panicking validation-scheme change). Generation is
deterministic; running locally produces no diff after a clean
checkout.
For future CRD edits:
1. Edit types.go (add/change a field, update markers)
2. `just generate`
3. Commit both the Go and YAML diffs
4. CI verifies the YAML was committed
PreSignedAuth.Payment is map[string]interface{} (opaque x402
payload), which controller-gen cannot deep-copy automatically; a
hand-written DeepCopy lives in deepcopy_manual.go and the type is
flagged object:generate=false.
The hack/boilerplate.go.txt file is force-added past *.txt gitignore;
it's an empty marker for now — add a copyright header later if the
repo settles on one.
Comment on lines +48 to +73
name: CRD generation up-to-date
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: 'go.mod'

- name: Set up just
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2.0.2

- name: Regenerate CRDs + DeepCopy
run: just generate

- name: Fail if regeneration changed any tracked files
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "::error::CRD manifests or DeepCopy methods are out of date."
echo "::error::Run 'just generate' locally and commit the result."
git status
git --no-pager diff
exit 1
fi
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded by bundle PR #536 — closing in favor of the consolidated merge target. Original branch and history preserved.

@bussyjdbussyjd closed this May 24, 2026
@OisinKyne
OisinKyne deleted the feat/controller-gen-codegen branch July 1, 2026 12:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(monetizeapi): controller-gen as canonical CRD schema source - #525

Closed
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen
Closed

feat(monetizeapi): controller-gen as canonical CRD schema source#525
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Why

CRD-vs-Go drift is a documented recurring bug class. PurchaseAutoRefill.MaxTotal existed in purchaserequest-crd.yaml for months while types.go didn't read it — fixed by hand in PR #513 with no enforcement to prevent the next instance. This commit also adds back the missing MaxTotal and MaxSpendPerDay fields to the Go struct so the next reconcile actually reads what the CRD declares.

Before

 internal/monetizeapi/types.go (Go struct, hand-edited)
↕ drift (no enforcement)
*-crd.yaml (OpenAPI schema, hand-edited)
Result: fields appear/disappear independently. Caught at runtime
when apiserver rejects a CR with "unknown field" or controller
reads zero from a field that was supposed to be populated.

After

 internal/monetizeapi/types.go
(kubebuilder markers are the source of truth)
│
▼ just generate
│
*-crd.yaml + zz_generated.deepcopy.go
(machine-generated; CI fails if not in sync with markers)
Result: one editable source. Schema, validations, printer columns,
subresources, DeepCopy methods all derive from Go markers.
`git diff` after `just generate` MUST be empty — CI enforces.

What changed

  • tools/tools.go — anchors controller-gen as a build dependency (v0.16.5, compatible with k8s.io/client-go v0.34.x)
  • justfilegenerate recipe runs controller-gen + renames obol.org_<plural>.yaml to existing <singular>-crd.yaml naming
  • hack/boilerplate.go.txt — generated-file marker (force-added past the repo's *.txt gitignore)
  • internal/monetizeapi/doc.go — package-level +groupName=obol.org / +versionName=v1alpha1 / +kubebuilder:object:generate=true
  • internal/monetizeapi/types.go — kubebuilder markers on every CRD-backed type, plus the previously-missing MaxTotal / MaxSpendPerDay fields on PurchaseAutoRefill
  • internal/monetizeapi/deepcopy_manual.go — hand-written DeepCopy for PreSignedAuth (its Payment map[string]interface{} is opaque to controller-gen)
  • internal/monetizeapi/zz_generated.deepcopy.go — generated DeepCopy methods for the rest of the package
  • internal/embed/infrastructure/base/templates/*-crd.yaml — regenerated (see diff section below)
  • .github/workflows/lint-test.yaml — new generate-check job: runs just generate and fails if git status --porcelain is non-empty

CRD diff after generation

Bit-exact round-trip wasn't possible because controller-gen normalises layout. The unavoidable differences between hand-written and generated CRDs are:

  1. Top-of-file comments dropped. controller-gen does not preserve the leading narrative comments (e.g. # ServiceOffer CRD\n# Defines a compute service...). All structural descriptions are preserved as description: fields on the matching property; the narrative comments are reproduced as Go doc comments on the corresponding types in types.go.
  2. controller-gen.kubebuilder.io/version: v0.16.5 annotation added on every generated CRD — required for CI to detect drift via diff and lets future operators see which controller-gen version produced the YAML.
  3. YAML key ordering and indentation changed. controller-gen alphabetises keys within objects (e.g. additionalPrinterColumns before name, singular after shortNames) and uses 2-space indentation throughout. The hand-written files used 4-space and a CRD-conventional ordering. The OpenAPI schemas are semantically identical; kubectl apply is order-insensitive.
  4. apiVersion + kind + metadata properties appear in every CR's openAPIV3Schema. controller-gen always emits these to match what apiserver expects; they were elided in the hand-written files. No behaviour change.
  5. PurchaseAutoRefill gained maxTotal (integer) and maxSpendPerDay (string) on the Go side. Both fields already existed in the prior hand-written CRD but were silently missing from types.go — this is the bug class the PR exists to close.
  6. Top-of-file --- separator on the (previously-missing) purchaserequest-crd.yaml added by controller-gen; harmless.

No fields were dropped. No validations were loosened. The ^0x[0-9a-fA-F]{40}$ pattern on payTo, the eip3009;permit2 enum on transferMethod, the ^/[a-zA-Z0-9/_.-]*$ path pattern, the 1-65535 port range, the 1-2500 count range, the inference;fine-tuning;http;agent type enum, all printer columns, and all subresources.status declarations are preserved.

Test plan

  • just generate (executed as the inlined shell script since just isn't installed locally) produces zero diff on a clean re-run
  • go build ./... clean
  • go test ./internal/embed/... green — the embed CRD parse + schema tests still pass against the regenerated YAML
  • go test ./internal/monetizeapi/... ./internal/serviceoffercontroller/... ./internal/x402/... ./internal/x402/buyer/... green
  • go vet ./... clean (only pre-existing internal/enclave/enclave_darwin.go unsafe-pointer warnings remain)
  • internal/stackTestWarnIfNoChatModel_EmitsWarnWhenNoModels failure verified as pre-existing on origin/main (unrelated)

Future

Unblocks any future CRD changes (e.g. spec.paused + metav1.Condition) — those become "edit Go markers, run just generate, commit" instead of hand-editing CRDs twice and drifting.

Closes the entire class of "CRD YAML and Go struct drifted" bugs.
PurchaseAutoRefill.MaxTotal was the most recent instance — it existed
in purchaserequest-crd.yaml for months while internal/monetizeapi/
types.go didn't have the corresponding field. Without this commit,
that pattern recurs by design: two sources of truth, one hand-
maintained, no enforcement of agreement.
Now Go is the single source of truth:
- kubebuilder markers on every CRD-backed struct in types.go
(validation, required, enum, pattern, printer columns, subresources)
- `just generate` regenerates *-crd.yaml from those markers
+ zz_generated.deepcopy.go from object:generate=true
- CI fails if `git status` is non-empty after `just generate` runs
This commit also fixes the documented MaxTotal / MaxSpendPerDay drift
by adding both fields to PurchaseAutoRefill — the generated CRD now
matches the prior hand-written one and the controller can read them.
Pinned controller-tools at v0.16.5 in tools/tools.go (compatible with
client-go v0.34.x; a newer release would force prometheus/common
through a panicking validation-scheme change). Generation is
deterministic; running locally produces no diff after a clean
checkout.
For future CRD edits:
1. Edit types.go (add/change a field, update markers)
2. `just generate`
3. Commit both the Go and YAML diffs
4. CI verifies the YAML was committed
PreSignedAuth.Payment is map[string]interface{} (opaque x402
payload), which controller-gen cannot deep-copy automatically; a
hand-written DeepCopy lives in deepcopy_manual.go and the type is
flagged object:generate=false.
The hack/boilerplate.go.txt file is force-added past *.txt gitignore;
it's an empty marker for now — add a copyright header later if the
repo settles on one.
Comment on lines +48 to +73
name: CRD generation up-to-date
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: 'go.mod'

- name: Set up just
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2.0.2

- name: Regenerate CRDs + DeepCopy
run: just generate

- name: Fail if regeneration changed any tracked files
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "::error::CRD manifests or DeepCopy methods are out of date."
echo "::error::Run 'just generate' locally and commit the result."
git status
git --no-pager diff
exit 1
fi
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded by bundle PR #536 — closing in favor of the consolidated merge target. Original branch and history preserved.

@bussyjdbussyjd closed this May 24, 2026
@OisinKyne
OisinKyne deleted the feat/controller-gen-codegen branch July 1, 2026 12:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(monetizeapi): controller-gen as canonical CRD schema source - #525

Closed
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen
Closed

feat(monetizeapi): controller-gen as canonical CRD schema source#525
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Why

CRD-vs-Go drift is a documented recurring bug class. PurchaseAutoRefill.MaxTotal existed in purchaserequest-crd.yaml for months while types.go didn't read it — fixed by hand in PR #513 with no enforcement to prevent the next instance. This commit also adds back the missing MaxTotal and MaxSpendPerDay fields to the Go struct so the next reconcile actually reads what the CRD declares.

Before

 internal/monetizeapi/types.go (Go struct, hand-edited)
↕ drift (no enforcement)
*-crd.yaml (OpenAPI schema, hand-edited)
Result: fields appear/disappear independently. Caught at runtime
when apiserver rejects a CR with "unknown field" or controller
reads zero from a field that was supposed to be populated.

After

 internal/monetizeapi/types.go
(kubebuilder markers are the source of truth)
│
▼ just generate
│
*-crd.yaml + zz_generated.deepcopy.go
(machine-generated; CI fails if not in sync with markers)
Result: one editable source. Schema, validations, printer columns,
subresources, DeepCopy methods all derive from Go markers.
`git diff` after `just generate` MUST be empty — CI enforces.

What changed

  • tools/tools.go — anchors controller-gen as a build dependency (v0.16.5, compatible with k8s.io/client-go v0.34.x)
  • justfilegenerate recipe runs controller-gen + renames obol.org_<plural>.yaml to existing <singular>-crd.yaml naming
  • hack/boilerplate.go.txt — generated-file marker (force-added past the repo's *.txt gitignore)
  • internal/monetizeapi/doc.go — package-level +groupName=obol.org / +versionName=v1alpha1 / +kubebuilder:object:generate=true
  • internal/monetizeapi/types.go — kubebuilder markers on every CRD-backed type, plus the previously-missing MaxTotal / MaxSpendPerDay fields on PurchaseAutoRefill
  • internal/monetizeapi/deepcopy_manual.go — hand-written DeepCopy for PreSignedAuth (its Payment map[string]interface{} is opaque to controller-gen)
  • internal/monetizeapi/zz_generated.deepcopy.go — generated DeepCopy methods for the rest of the package
  • internal/embed/infrastructure/base/templates/*-crd.yaml — regenerated (see diff section below)
  • .github/workflows/lint-test.yaml — new generate-check job: runs just generate and fails if git status --porcelain is non-empty

CRD diff after generation

Bit-exact round-trip wasn't possible because controller-gen normalises layout. The unavoidable differences between hand-written and generated CRDs are:

  1. Top-of-file comments dropped. controller-gen does not preserve the leading narrative comments (e.g. # ServiceOffer CRD\n# Defines a compute service...). All structural descriptions are preserved as description: fields on the matching property; the narrative comments are reproduced as Go doc comments on the corresponding types in types.go.
  2. controller-gen.kubebuilder.io/version: v0.16.5 annotation added on every generated CRD — required for CI to detect drift via diff and lets future operators see which controller-gen version produced the YAML.
  3. YAML key ordering and indentation changed. controller-gen alphabetises keys within objects (e.g. additionalPrinterColumns before name, singular after shortNames) and uses 2-space indentation throughout. The hand-written files used 4-space and a CRD-conventional ordering. The OpenAPI schemas are semantically identical; kubectl apply is order-insensitive.
  4. apiVersion + kind + metadata properties appear in every CR's openAPIV3Schema. controller-gen always emits these to match what apiserver expects; they were elided in the hand-written files. No behaviour change.
  5. PurchaseAutoRefill gained maxTotal (integer) and maxSpendPerDay (string) on the Go side. Both fields already existed in the prior hand-written CRD but were silently missing from types.go — this is the bug class the PR exists to close.
  6. Top-of-file --- separator on the (previously-missing) purchaserequest-crd.yaml added by controller-gen; harmless.

No fields were dropped. No validations were loosened. The ^0x[0-9a-fA-F]{40}$ pattern on payTo, the eip3009;permit2 enum on transferMethod, the ^/[a-zA-Z0-9/_.-]*$ path pattern, the 1-65535 port range, the 1-2500 count range, the inference;fine-tuning;http;agent type enum, all printer columns, and all subresources.status declarations are preserved.

Test plan

  • just generate (executed as the inlined shell script since just isn't installed locally) produces zero diff on a clean re-run
  • go build ./... clean
  • go test ./internal/embed/... green — the embed CRD parse + schema tests still pass against the regenerated YAML
  • go test ./internal/monetizeapi/... ./internal/serviceoffercontroller/... ./internal/x402/... ./internal/x402/buyer/... green
  • go vet ./... clean (only pre-existing internal/enclave/enclave_darwin.go unsafe-pointer warnings remain)
  • internal/stackTestWarnIfNoChatModel_EmitsWarnWhenNoModels failure verified as pre-existing on origin/main (unrelated)

Future

Unblocks any future CRD changes (e.g. spec.paused + metav1.Condition) — those become "edit Go markers, run just generate, commit" instead of hand-editing CRDs twice and drifting.

Closes the entire class of "CRD YAML and Go struct drifted" bugs.
PurchaseAutoRefill.MaxTotal was the most recent instance — it existed
in purchaserequest-crd.yaml for months while internal/monetizeapi/
types.go didn't have the corresponding field. Without this commit,
that pattern recurs by design: two sources of truth, one hand-
maintained, no enforcement of agreement.
Now Go is the single source of truth:
- kubebuilder markers on every CRD-backed struct in types.go
(validation, required, enum, pattern, printer columns, subresources)
- `just generate` regenerates *-crd.yaml from those markers
+ zz_generated.deepcopy.go from object:generate=true
- CI fails if `git status` is non-empty after `just generate` runs
This commit also fixes the documented MaxTotal / MaxSpendPerDay drift
by adding both fields to PurchaseAutoRefill — the generated CRD now
matches the prior hand-written one and the controller can read them.
Pinned controller-tools at v0.16.5 in tools/tools.go (compatible with
client-go v0.34.x; a newer release would force prometheus/common
through a panicking validation-scheme change). Generation is
deterministic; running locally produces no diff after a clean
checkout.
For future CRD edits:
1. Edit types.go (add/change a field, update markers)
2. `just generate`
3. Commit both the Go and YAML diffs
4. CI verifies the YAML was committed
PreSignedAuth.Payment is map[string]interface{} (opaque x402
payload), which controller-gen cannot deep-copy automatically; a
hand-written DeepCopy lives in deepcopy_manual.go and the type is
flagged object:generate=false.
The hack/boilerplate.go.txt file is force-added past *.txt gitignore;
it's an empty marker for now — add a copyright header later if the
repo settles on one.
Comment on lines +48 to +73
name: CRD generation up-to-date
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: 'go.mod'

- name: Set up just
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2.0.2

- name: Regenerate CRDs + DeepCopy
run: just generate

- name: Fail if regeneration changed any tracked files
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "::error::CRD manifests or DeepCopy methods are out of date."
echo "::error::Run 'just generate' locally and commit the result."
git status
git --no-pager diff
exit 1
fi
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded by bundle PR #536 — closing in favor of the consolidated merge target. Original branch and history preserved.

@bussyjdbussyjd closed this May 24, 2026
@OisinKyne
OisinKyne deleted the feat/controller-gen-codegen branch July 1, 2026 12:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(monetizeapi): controller-gen as canonical CRD schema source - #525

Closed
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen
Closed

feat(monetizeapi): controller-gen as canonical CRD schema source#525
bussyjd wants to merge 1 commit into
mainfrom
feat/controller-gen-codegen

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Why

CRD-vs-Go drift is a documented recurring bug class. PurchaseAutoRefill.MaxTotal existed in purchaserequest-crd.yaml for months while types.go didn't read it — fixed by hand in PR #513 with no enforcement to prevent the next instance. This commit also adds back the missing MaxTotal and MaxSpendPerDay fields to the Go struct so the next reconcile actually reads what the CRD declares.

Before

 internal/monetizeapi/types.go (Go struct, hand-edited)
↕ drift (no enforcement)
*-crd.yaml (OpenAPI schema, hand-edited)
Result: fields appear/disappear independently. Caught at runtime
when apiserver rejects a CR with "unknown field" or controller
reads zero from a field that was supposed to be populated.

After

 internal/monetizeapi/types.go
(kubebuilder markers are the source of truth)
│
▼ just generate
│
*-crd.yaml + zz_generated.deepcopy.go
(machine-generated; CI fails if not in sync with markers)
Result: one editable source. Schema, validations, printer columns,
subresources, DeepCopy methods all derive from Go markers.
`git diff` after `just generate` MUST be empty — CI enforces.

What changed

  • tools/tools.go — anchors controller-gen as a build dependency (v0.16.5, compatible with k8s.io/client-go v0.34.x)
  • justfilegenerate recipe runs controller-gen + renames obol.org_<plural>.yaml to existing <singular>-crd.yaml naming
  • hack/boilerplate.go.txt — generated-file marker (force-added past the repo's *.txt gitignore)
  • internal/monetizeapi/doc.go — package-level +groupName=obol.org / +versionName=v1alpha1 / +kubebuilder:object:generate=true
  • internal/monetizeapi/types.go — kubebuilder markers on every CRD-backed type, plus the previously-missing MaxTotal / MaxSpendPerDay fields on PurchaseAutoRefill
  • internal/monetizeapi/deepcopy_manual.go — hand-written DeepCopy for PreSignedAuth (its Payment map[string]interface{} is opaque to controller-gen)
  • internal/monetizeapi/zz_generated.deepcopy.go — generated DeepCopy methods for the rest of the package
  • internal/embed/infrastructure/base/templates/*-crd.yaml — regenerated (see diff section below)
  • .github/workflows/lint-test.yaml — new generate-check job: runs just generate and fails if git status --porcelain is non-empty

CRD diff after generation

Bit-exact round-trip wasn't possible because controller-gen normalises layout. The unavoidable differences between hand-written and generated CRDs are:

  1. Top-of-file comments dropped. controller-gen does not preserve the leading narrative comments (e.g. # ServiceOffer CRD\n# Defines a compute service...). All structural descriptions are preserved as description: fields on the matching property; the narrative comments are reproduced as Go doc comments on the corresponding types in types.go.
  2. controller-gen.kubebuilder.io/version: v0.16.5 annotation added on every generated CRD — required for CI to detect drift via diff and lets future operators see which controller-gen version produced the YAML.
  3. YAML key ordering and indentation changed. controller-gen alphabetises keys within objects (e.g. additionalPrinterColumns before name, singular after shortNames) and uses 2-space indentation throughout. The hand-written files used 4-space and a CRD-conventional ordering. The OpenAPI schemas are semantically identical; kubectl apply is order-insensitive.
  4. apiVersion + kind + metadata properties appear in every CR's openAPIV3Schema. controller-gen always emits these to match what apiserver expects; they were elided in the hand-written files. No behaviour change.
  5. PurchaseAutoRefill gained maxTotal (integer) and maxSpendPerDay (string) on the Go side. Both fields already existed in the prior hand-written CRD but were silently missing from types.go — this is the bug class the PR exists to close.
  6. Top-of-file --- separator on the (previously-missing) purchaserequest-crd.yaml added by controller-gen; harmless.

No fields were dropped. No validations were loosened. The ^0x[0-9a-fA-F]{40}$ pattern on payTo, the eip3009;permit2 enum on transferMethod, the ^/[a-zA-Z0-9/_.-]*$ path pattern, the 1-65535 port range, the 1-2500 count range, the inference;fine-tuning;http;agent type enum, all printer columns, and all subresources.status declarations are preserved.

Test plan

  • just generate (executed as the inlined shell script since just isn't installed locally) produces zero diff on a clean re-run
  • go build ./... clean
  • go test ./internal/embed/... green — the embed CRD parse + schema tests still pass against the regenerated YAML
  • go test ./internal/monetizeapi/... ./internal/serviceoffercontroller/... ./internal/x402/... ./internal/x402/buyer/... green
  • go vet ./... clean (only pre-existing internal/enclave/enclave_darwin.go unsafe-pointer warnings remain)
  • internal/stackTestWarnIfNoChatModel_EmitsWarnWhenNoModels failure verified as pre-existing on origin/main (unrelated)

Future

Unblocks any future CRD changes (e.g. spec.paused + metav1.Condition) — those become "edit Go markers, run just generate, commit" instead of hand-editing CRDs twice and drifting.

Closes the entire class of "CRD YAML and Go struct drifted" bugs.
PurchaseAutoRefill.MaxTotal was the most recent instance — it existed
in purchaserequest-crd.yaml for months while internal/monetizeapi/
types.go didn't have the corresponding field. Without this commit,
that pattern recurs by design: two sources of truth, one hand-
maintained, no enforcement of agreement.
Now Go is the single source of truth:
- kubebuilder markers on every CRD-backed struct in types.go
(validation, required, enum, pattern, printer columns, subresources)
- `just generate` regenerates *-crd.yaml from those markers
+ zz_generated.deepcopy.go from object:generate=true
- CI fails if `git status` is non-empty after `just generate` runs
This commit also fixes the documented MaxTotal / MaxSpendPerDay drift
by adding both fields to PurchaseAutoRefill — the generated CRD now
matches the prior hand-written one and the controller can read them.
Pinned controller-tools at v0.16.5 in tools/tools.go (compatible with
client-go v0.34.x; a newer release would force prometheus/common
through a panicking validation-scheme change). Generation is
deterministic; running locally produces no diff after a clean
checkout.
For future CRD edits:
1. Edit types.go (add/change a field, update markers)
2. `just generate`
3. Commit both the Go and YAML diffs
4. CI verifies the YAML was committed
PreSignedAuth.Payment is map[string]interface{} (opaque x402
payload), which controller-gen cannot deep-copy automatically; a
hand-written DeepCopy lives in deepcopy_manual.go and the type is
flagged object:generate=false.
The hack/boilerplate.go.txt file is force-added past *.txt gitignore;
it's an empty marker for now — add a copyright header later if the
repo settles on one.
Comment on lines +48 to +73
name: CRD generation up-to-date
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: 'go.mod'

- name: Set up just
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2.0.2

- name: Regenerate CRDs + DeepCopy
run: just generate

- name: Fail if regeneration changed any tracked files
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "::error::CRD manifests or DeepCopy methods are out of date."
echo "::error::Run 'just generate' locally and commit the result."
git status
git --no-pager diff
exit 1
fi
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded by bundle PR #536 — closing in favor of the consolidated merge target. Original branch and history preserved.

@bussyjdbussyjd closed this May 24, 2026
@OisinKyne
OisinKyne deleted the feat/controller-gen-codegen branch July 1, 2026 12:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@github-advanced-security