Admit obo in MCPExternalAuthConfig CRD enum#5361
Conversation
There was a problem hiding this comment.
Large PR Detected
This PR exceeds 1000 lines of changes and requires justification before it can be reviewed.
How to unblock this PR:
Add a section to your PR description with the following format:
## Large PR Justification
[Explain why this PR must be large, such as:]
- Generated code that cannot be split
- Large refactoring that must be atomic
- Multiple related changes that would break if separated
- Migration or data transformationAlternative:
Consider splitting this PR into smaller, focused changes (< 1000 lines each) for easier review and reduced risk.
See our Contributing Guidelines for more details.
This review will be automatically dismissed once you add the justification section.
PR size has been reduced below the XL threshold. Thank you for splitting this up!
|
✅ PR size has been reduced below the XL threshold. The size review has been dismissed and this PR can now proceed with normal review. Thank you for splitting this up! |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5361 +/- ##
==========================================
- Coverage 68.72% 68.72% -0.01%
==========================================
Files 626 626
Lines 63495 63498 +3
==========================================
Hits 43637 43637
- Misses 16610 16616 +6
+ Partials 3248 3245 -3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
tgrunnagle
left a comment
There was a problem hiding this comment.
Multi-Agent Consensus Review
Agents consulted: pr-reviewer (CRD/CEL specialist), pr-reviewer (Go API/validation), pr-reviewer (test coverage), pr-reviewer (generated artifacts), pr-reviewer (general quality + intent vs issue)
Codex cross-review: skipped (codex CLI not installed)
Consensus Summary
| # | Finding | Consensus | Severity | Action |
|---|---|---|---|---|
| F1 | Unauthenticated-guard OBO disjunct is unreachable; comment should say so | 10/10 | LOW | Fix |
| F2 | Stale lines 44-49 reference in Validate() doc comment |
10/10 | LOW | Fix |
| F3 | OBOConfig doc justifies the struct with a speculative downstream consumer |
10/10 | LOW | Fix |
| F4 | OBO Validate() arm comment doesn't note structural validation already ran |
10/10 | LOW | Fix |
| F5 | Test #4 name implies coverage of unauthenticated guard; OBO biconditional intercepts first | 10/10 | LOW | Fix |
| F6 | New OBO test cases appended at end of table instead of grouped per-type | 10/10 | LOW | Optional |
| F7 | //nolint:gocyclo rationale and directive are separated by ~5 lines of doc |
10/10 | LOW | Optional |
| F8 | kubectl explain spec.type won't surface build-requires-handler note |
10/10 | LOW | Optional |
| F9 | "Empty config" wording in test #1 reads as zero-valued rather than placeholder | 10/10 | INFO | Optional |
| F10 | Stale type: obo fixtures elsewhere |
dropped (verified vacuous) | INFO | None — grep returned zero matches |
Overall
This is a textbook execution of a stacked task — surgical edit to one Go source file plus complete regeneration of the generated artifacts and a thorough fixture sweep. Every acceptance criterion from #5329 is met: enum extended, OBOConfig placeholder struct declared, OBO *OBOConfig field added, new CEL biconditional rule paired with the matching Go-level row in validateTypeConfigConsistency, unauthenticated CEL rule and Go guard extended, generated YAML / deepcopy / docs all updated consistently across the four insertion points. The eight-type symmetry is preserved row-for-row across kubebuilder markers, CEL rules, Go bicond, error messages, and deepcopy.
There are no HIGH-severity findings and no specialist disputes. All ten findings are LOW or INFO and focus on making implicit invariants explicit — primarily around the new unauthenticated-guard disjunct (F1/F5: it's unreachable today and the test that appears to cover it actually exercises the per-type biconditional that intercepts first) and a few comment-accuracy items (F2/F3/F4) that the project's .claude/rules/go-style.md "Keep Comments Synchronized With Code" rule highlights.
The most impactful change to bundle is F1 + F5 together: clarify the unauthenticated-guard comment so it explicitly says the disjunct is shape-parity-for-future-types (not catching a live case today), and rename test #4 to reflect that the OBO biconditional row is what fires. The remaining items are nice-to-have polish.
Event type is COMMENT because (a) the authenticated gh user matches the PR author — GitHub would 422 on APPROVE or REQUEST_CHANGES, and (b) the PR is in draft status.
Generated with Claude Code
Addresses #5361 review comments: - LOW types.go (3283528614): F1 — say unauthenticated-guard OBO disjunct is unreachable; retained for forward-compat - LOW types.go (3283528635): F2 — replace stale "lines 44-49" ref in Validate() doc with structural reference - LOW types.go (3283528640): F3 — concrete OBOConfig rationale (compiles, admits spec.obo: {}); drop speculative downstream-consumer phrasing - LOW types.go (3283528649): F4 — note structural validation already ran in OBO Validate() arm comment - LOW types_test.go (3283528655): F5 — rename test #4 to note OBO biconditional intercepts first - LOW types_test.go (3283528662): F6 — move OBO test cases next to upstreamInject group - LOW types.go (3283528670): F7 — copy nolint:gocyclo rationale onto the directive line - LOW types.go (3283528675): F8 — extend Type field doc with obo build-requires-handler note (regenerated YAML + crd-api.md) - INFO types_test.go (3283528682): F9 — rename "empty config" to "placeholder OBOConfig"
Until this commit, the apiserver enum on MCPExternalAuthConfig.spec.type did not list "obo", so applying an obo-typed CR was rejected at admission before the previously-landed dispatch wiring could run. Implements changes for issue #5329: - Extend the CRD enum, CEL rules, and Go-level validateTypeConfigConsistency check on MCPExternalAuthConfigSpec to admit and validate the new "obo" type alongside the existing seven - Declare an opaque OBOConfig placeholder struct and spec.obo field; the inner schema lands in a follow-up - Document the two-tier validation pattern on the Validate() OBO arm: structural validation here, semantic validation behind the controllerutil.OBOValidate function-pointer hook at reconcile time - Regenerate operator-crds chart YAML, deepcopy, and public CRD reference - Add unit tests covering the OBO validation matrix and update existing OBO-typed test fixtures across operator and vMCP packages
Address code-review feedback on the CRD admission change: - Expand the doc comment on validateTypeConfigConsistency so the //nolint:gocyclo directive meets the "confirmed false positive" bar from .claude/rules/go-style.md. The per-type if shape mirrors the CEL rules on MCPExternalAuthConfigSpec one-to-one; collapsing into a table-driven loop would obscure that correspondence. - Add an explanatory comment on the unauthenticated guard so it reads as shape-parity / belt-and-braces, not load-bearing validation. The per-type biconditionals above always intercept a populated config field first; the guard exists so a future contributor adding a type cannot forget to extend the invariant.
Addresses #5361 review comments: - LOW types.go (3283528614): F1 — say unauthenticated-guard OBO disjunct is unreachable; retained for forward-compat - LOW types.go (3283528635): F2 — replace stale "lines 44-49" ref in Validate() doc with structural reference - LOW types.go (3283528640): F3 — concrete OBOConfig rationale (compiles, admits spec.obo: {}); drop speculative downstream-consumer phrasing - LOW types.go (3283528649): F4 — note structural validation already ran in OBO Validate() arm comment - LOW types_test.go (3283528655): F5 — rename test #4 to note OBO biconditional intercepts first - LOW types_test.go (3283528662): F6 — move OBO test cases next to upstreamInject group - LOW types.go (3283528670): F7 — copy nolint:gocyclo rationale onto the directive line - LOW types.go (3283528675): F8 — extend Type field doc with obo build-requires-handler note (regenerated YAML + crd-api.md) - INFO types_test.go (3283528682): F9 — rename "empty config" to "placeholder OBOConfig"
ac9cec5 to
569f7e4
Compare
Summary
Until this PR, the apiserver enum on
MCPExternalAuthConfig.spec.typedidnot list
obo, sokubectl applyof anobo-typed CR was rejected byadmission before any of the previously-landed dispatch wiring could run.
This is the fourth and final flat task under the parent OSS story
(#5325) — the moment the OBO feature becomes user-visible end-to-end on an
upstream-only build.
What changed:
;oboto the+kubebuilder:validation:Enummarker onMCPExternalAuthConfigSpec.Typeand add the matchingXValidationCELrule (
self.type == 'obo' ? has(self.obo) : !has(self.obo)).OBOConfig struct{}placeholder and anOBO *OBOConfigfield onMCPExternalAuthConfigSpec. The inner schemais intentionally empty in this revision; sub-fields land in a follow-up
RFC.
unauthenticatedCEL rule and the corresponding Go-levelvalidateTypeConfigConsistencycheck to also rejectself.obo/Spec.OBO, so the "no configuration when unauthenticated" invariantholds for the new type.
case ExternalAuthTypeOBOarm to theValidate()switchand document the two-tier validation pattern: structural validation
here, semantic validation behind the
controllerutil.OBOValidatefunction-pointer hook at reconcile time.
DeepCopyartifacts, and the public CRD reference docs.
packages to populate
Spec.OBO: &OBOConfig{}so they continue to passthe new biconditional check.
Closes #5329
Type of change
Test plan
task test)task lint-fix)Manual testing performed:
kubectl explain mcpexternalauthconfig.spec.typelistsobo.kubectl applyof anobo-typedMCPExternalAuthConfigwithspec.obo: {}succeeds for the first time. The resource transitionsto
status.conditions[Valid] = False/Reason: EnterpriseRequiredvia the dispatch wiring from the prior task (Wire OBO dispatch arms and reconciler branch; add integration tests #5328).
kubectl applyof anobo-typed config that omitsspec.oboisrejected at admission with the new CEL message.
kubectl applyof a non-obo-typed config that setsspec.obo: {}is rejected at admission with the new CEL message.
cmd/thv-operator/test-integration/mcp-external-auth/passunchanged.
API Compatibility
v1beta1API. The change is additive:one new enum value, one new optional spec field, one new CEL rule, one
new Go-level validation row. Existing CRs and existing CEL rules are
unaffected.
Changes
cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.goOBOConfig, addOBOfield, addValidate()no-op arm, extendvalidateTypeConfigConsistency(incl.unauthenticatedguard), document two-tier validation andgocyclosuppression.cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types_test.goValidate()cases covering the OBO biconditional and theunauthenticatedguard.cmd/thv-operator/api/v1beta1/zz_generated.deepcopy.goDeepCopyIntoforOBOConfigand updatedMCPExternalAuthConfigSpeccopier.deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcpexternalauthconfigs.yaml(andcmd/thv-operator/...copy)task operator-manifests: new enum value, new CEL rule, newoboobject property, extendedunauthenticatedrule.docs/operator/crd-api.mdtask crdref-gen.cmd/thv-operator/controllers/*_test.go,cmd/thv-operator/pkg/controllerutil/tokenexchange_test.go,pkg/vmcp/auth/converters/obo_test.goSpec.OBO: &OBOConfig{}on existing OBO-typed fixtures so they satisfy the new biconditional check.Does this introduce a user-facing change?
Yes.
MCPExternalAuthConfig.spec.typenow admitsoboas a valid enumvalue at the apiserver layer. On an upstream-only build, an
obo-typed CR is admitted, reconciled, and transitions tostatus.conditions[Valid] = FalsewithReason: EnterpriseRequired(via the dispatch wiring landed in #5328). Out-of-tree builds that
register a real OBO handler via
controllerutil.RegisterOBOHandlershort-circuit the sentinel and run their own protocol-level checks.
The
OBOConfigstruct itself is an intentionally empty placeholder inthis revision; sub-fields land in a follow-up RFC.
Special notes for reviewers
this branch's diff against
mainas 7 commits / ~12 files. Onlyef00b85candd1c3c817are in scope here; the other 5 are theconsumer-CR mirror work under PR for Mirror MCPExternalAuthConfig.Valid=False/EnterpriseRequired to consumer CRs #5347. Easiest review path: wait
for the mirror PR to merge, then re-render this diff. Alternative:
scope your review to
cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.go,the regenerated YAML/docs, and the small
&OBOConfig{}test-fixtureadditions.
Validate()arm for OBO is deliberately a no-op. Structural validation(OBO field set iff type is
obo) runs invalidateTypeConfigConsistencyand mirrors the new CEL rule. Semantic validation (is an OBO handler
registered for this build?) runs at reconcile time via the
controllerutil.OBOValidatefunction-pointer hook. Splitting the tiersthis way is what keeps the upstream CRD schema stable across out-of-tree
builds — please push back if you'd prefer a different split, but note
that the prior tasks in this story (Add default OBO handler hooks and vMCP/proxy converter stubs #5327, Wire OBO dispatch arms and reconciler branch; add integration tests #5328) are already wired to
this contract.
gocyclosuppression onvalidateTypeConfigConsistency. Theper-type
ifshape mirrors the CEL rules onMCPExternalAuthConfigSpecone-to-one. A table-driven loop would lower the score but obscure that
correspondence, which is the property a reviewer relies on to audit the
structural-validation contract. The suppression is documented in the
function's doc comment per the "confirmed false positive" bar in
.claude/rules/go-style.md.unauthenticatedguard is shape-parity, not load-bearing. Theper-type biconditionals always intercept a populated config field
first. The guard exists so a future contributor adding a new type
cannot forget to extend the "no configuration must be set" invariant.
Documented in the function so it doesn't read as dead code.