Skip to content

Admit obo in MCPExternalAuthConfig CRD enum#5361

Merged
tgrunnagle merged 3 commits into
mainfrom
oss-obo-5_issue_5329
May 22, 2026
Merged

Admit obo in MCPExternalAuthConfig CRD enum#5361
tgrunnagle merged 3 commits into
mainfrom
oss-obo-5_issue_5329

Conversation

@tgrunnagle
Copy link
Copy Markdown
Contributor

@tgrunnagle tgrunnagle commented May 21, 2026

Summary

Until this PR, the apiserver enum on MCPExternalAuthConfig.spec.type did
not list obo, so kubectl apply of an obo-typed CR was rejected by
admission 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:

  • Append ;obo to the +kubebuilder:validation:Enum marker on
    MCPExternalAuthConfigSpec.Type and add the matching XValidation CEL
    rule (self.type == 'obo' ? has(self.obo) : !has(self.obo)).
  • Declare an opaque OBOConfig struct{} placeholder and an
    OBO *OBOConfig field on MCPExternalAuthConfigSpec. The inner schema
    is intentionally empty in this revision; sub-fields land in a follow-up
    RFC.
  • Extend the unauthenticated CEL rule and the corresponding Go-level
    validateTypeConfigConsistency check to also reject self.obo /
    Spec.OBO, so the "no configuration when unauthenticated" invariant
    holds for the new type.
  • Add a no-op case ExternalAuthTypeOBO arm to the Validate() switch
    and document the two-tier validation pattern: structural validation
    here, semantic validation behind the controllerutil.OBOValidate
    function-pointer hook at reconcile time.
  • Regenerate the operator-crds chart YAML, the v1beta1 DeepCopy
    artifacts, and the public CRD reference docs.
  • Update existing OBO-typed test fixtures across operator and vMCP
    packages to populate Spec.OBO: &OBOConfig{} so they continue to pass
    the new biconditional check.

Closes #5329

Type of change

  • New feature

Test plan

  • Unit tests (task test)
  • Linting (task lint-fix)
  • Manual testing (describe below)

Manual testing performed:

  • Applied the regenerated CRD YAML to a local Kind cluster and confirmed
    kubectl explain mcpexternalauthconfig.spec.type lists obo.
  • kubectl apply of an obo-typed MCPExternalAuthConfig with
    spec.obo: {} succeeds for the first time. The resource transitions
    to status.conditions[Valid] = False / Reason: EnterpriseRequired
    via the dispatch wiring from the prior task (Wire OBO dispatch arms and reconciler branch; add integration tests #5328).
  • kubectl apply of an obo-typed config that omits spec.obo is
    rejected at admission with the new CEL message.
  • kubectl apply of a non-obo-typed config that sets spec.obo: {}
    is rejected at admission with the new CEL message.
  • Existing integration tests in
    cmd/thv-operator/test-integration/mcp-external-auth/ pass
    unchanged.

API Compatibility

  • This PR does not break the v1beta1 API. 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

File Change
cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.go Extend enum, add CEL rule, declare OBOConfig, add OBO field, add Validate() no-op arm, extend validateTypeConfigConsistency (incl. unauthenticated guard), document two-tier validation and gocyclo suppression.
cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types_test.go Add four Validate() cases covering the OBO biconditional and the unauthenticated guard.
cmd/thv-operator/api/v1beta1/zz_generated.deepcopy.go Regenerated DeepCopyInto for OBOConfig and updated MCPExternalAuthConfigSpec copier.
deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcpexternalauthconfigs.yaml (and cmd/thv-operator/... copy) Regenerated via task operator-manifests: new enum value, new CEL rule, new obo object property, extended unauthenticated rule.
docs/operator/crd-api.md Regenerated via task crdref-gen.
cmd/thv-operator/controllers/*_test.go, cmd/thv-operator/pkg/controllerutil/tokenexchange_test.go, pkg/vmcp/auth/converters/obo_test.go Populate Spec.OBO: &OBOConfig{} on existing OBO-typed fixtures so they satisfy the new biconditional check.

Does this introduce a user-facing change?

Yes. MCPExternalAuthConfig.spec.type now admits obo as a valid enum
value at the apiserver layer. On an upstream-only build, an
obo-typed CR is admitted, reconciled, and transitions to
status.conditions[Valid] = False with Reason: EnterpriseRequired
(via the dispatch wiring landed in #5328). Out-of-tree builds that
register a real OBO handler via controllerutil.RegisterOBOHandler
short-circuit the sentinel and run their own protocol-level checks.

The OBOConfig struct itself is an intentionally empty placeholder in
this revision; sub-fields land in a follow-up RFC.

Special notes for reviewers

  • Stacked branch caveat. GitHub will show
    this branch's diff against main as 7 commits / ~12 files. Only
    ef00b85c and d1c3c817 are in scope here; the other 5 are the
    consumer-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-fixture
    additions.
  • Two-tier validation is the load-bearing design choice. The
    Validate() arm for OBO is deliberately a no-op. Structural validation
    (OBO field set iff type is obo) runs in validateTypeConfigConsistency
    and mirrors the new CEL rule. Semantic validation (is an OBO handler
    registered for this build?) runs at reconcile time via the
    controllerutil.OBOValidate function-pointer hook. Splitting the tiers
    this 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.
  • gocyclo suppression on validateTypeConfigConsistency. The
    per-type if shape mirrors the CEL rules on MCPExternalAuthConfigSpec
    one-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.
  • The unauthenticated guard is shape-parity, not load-bearing. The
    per-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.

@github-actions github-actions Bot added the size/XL Extra large PR: 1000+ lines changed label May 21, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 transformation

Alternative:

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.

@tgrunnagle tgrunnagle changed the base branch from main to oss-obo-4_issue_5347 May 21, 2026 18:13
@github-actions github-actions Bot added size/S Small PR: 100-299 lines changed and removed size/XL Extra large PR: 1000+ lines changed labels May 21, 2026
@github-actions github-actions Bot dismissed their stale review May 21, 2026 18:14

PR size has been reduced below the XL threshold. Thank you for splitting this up!

@github-actions
Copy link
Copy Markdown
Contributor

✅ 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!

@github-actions github-actions Bot added size/S Small PR: 100-299 lines changed and removed size/S Small PR: 100-299 lines changed labels May 21, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 21, 2026

Codecov Report

❌ Patch coverage is 75.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 68.72%. Comparing base (e37e336) to head (569f7e4).

Files with missing lines Patch % Lines
...perator/api/v1beta1/mcpexternalauthconfig_types.go 75.00% 0 Missing and 1 partial ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor Author

@tgrunnagle tgrunnagle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.go Outdated
Comment thread cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.go
Comment thread cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.go Outdated
Comment thread cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.go Outdated
Comment thread cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types_test.go Outdated
Comment thread cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types_test.go Outdated
Comment thread cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.go Outdated
Comment thread cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.go Outdated
Comment thread cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types_test.go Outdated
tgrunnagle added a commit that referenced this pull request May 21, 2026
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"
@github-actions github-actions Bot added size/S Small PR: 100-299 lines changed and removed size/S Small PR: 100-299 lines changed labels May 21, 2026
@tgrunnagle tgrunnagle marked this pull request as ready for review May 21, 2026 18:47
jhrozek
jhrozek previously approved these changes May 21, 2026
Base automatically changed from oss-obo-4_issue_5347 to main May 22, 2026 15:35
@tgrunnagle tgrunnagle dismissed jhrozek’s stale review May 22, 2026 15:35

The base branch was changed.

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"
@tgrunnagle tgrunnagle force-pushed the oss-obo-5_issue_5329 branch from ac9cec5 to 569f7e4 Compare May 22, 2026 15:45
@github-actions github-actions Bot added size/S Small PR: 100-299 lines changed and removed size/S Small PR: 100-299 lines changed labels May 22, 2026
@tgrunnagle tgrunnagle merged commit 4587fc8 into main May 22, 2026
46 checks passed
@tgrunnagle tgrunnagle deleted the oss-obo-5_issue_5329 branch May 22, 2026 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/S Small PR: 100-299 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Admit obo in MCPExternalAuthConfig CRD enum and regenerate manifests

3 participants