feat: add WebhookGateway CRD for per-channel webhook auth and multi-instance GitHub#1238
feat: add WebhookGateway CRD for per-channel webhook auth and multi-instance GitHub#1238gjkim42 wants to merge 6 commits into
Conversation
There was a problem hiding this comment.
8 issues found across 41 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="cmd/kelos-webhook-server/main.go">
<violation number="1" location="cmd/kelos-webhook-server/main.go:232">
P2: Gateway mode now enables the reporting reconciler even when no global token resolver exists, which causes persistent reconcile errors for reporting-enabled tasks that lack a gateway annotation.</violation>
</file>
<file name="internal/manifests/charts/kelos/templates/webhook-gateway.yaml">
<violation number="1" location="internal/manifests/charts/kelos/templates/webhook-gateway.yaml:95">
P2: Using `else if` here disables legacy generic webhook routing whenever gateway server is enabled, so generic can be deployed but unreachable behind Gateway API.</violation>
</file>
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
|
Thanks for the review. Addressed in f528f8b: P1
P2
Inherent constraint (documented, not changed)
|
|
/kelos api-review |
|
🤖 Kelos Task Status Task |
There was a problem hiding this comment.
1 issue found across 10 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/webhook/generic_filter.go">
<violation number="1" location="internal/webhook/generic_filter.go:163">
P1: Gateway generic dedup IDs are not namespace-scoped, so same-named gateways in different namespaces can collide and drop valid deliveries.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
🤖 Kelos API Reviewer Agent @gjkim42
API Design Review
Verdict: COMMENT
Scope: New namespaced WebhookGateway CRD (type/secretRef/apiBaseURL/credentialsRef + status), plus an additive gatewayRef on the github/linear/generic webhook sources.
Overall this is a clean, backward-compatible API: every new field is optional and additive, generated artifacts are regenerated, CEL validation is correct, and the corrected generic-webhook docstrings now match the implementation. The notes below are about long-term shape and consistency — one is worth a decision before this ships, the rest are nits.
Findings
Extensibility — decide before merge
- GitHub-specific fields are flat on the shared spec —
api/v1alpha1/webhookgateway_types.go:52-63.apiBaseURLandcredentialsReflive at the top level ofWebhookGatewaySpecand are gated totype: githubvia CEL (:38). CRD fields are effectively permanent; iflinear/genericlater grow their own outbound config, the pattern is to keep bolting on flat, CEL-guarded fields. Consider namespacing provider config under a sub-struct now (e.g.spec.github.apiBaseURL,spec.github.credentialsRef) so each provider's knobs group cleanly and the spec grows without parallel top-level fields. Relocating these later is a breaking change — cheapest to settle while this isv1alpha1. Flagging for your call, not blocking.
API Conventions / Consistency
- Consider
Conditionsin status —api/v1alpha1/webhookgateway_types.go:67-85. Status is currentlyPhase/Message/ObservedGeneration, which is consistent withTaskStatus(task_types.go:264-298, Phase+Message, no Conditions). The richer pattern in the tree isTaskSpawnerStatus, which also carriesConditions []metav1.Condition(taskspawner_types.go:1020-1026) — the conventional extensible status signal in K8s. Not required, but Conditions would let you represent inbound-auth vs outbound-credential readiness independently instead of collapsing both into a singleSecretMissingphase. Additive, so a fast-follow works. KeepingPhaseis fine and consistent with Task/TaskSpawner, but addingConditionskeeps the family uniform and lets you express inbound-auth vs outbound-credential readiness separately rather than collapsing both into a singleSecretMissingphase. Additive, so a fast-follow is acceptable — but cheap to include now.
Naming (nits)
status.urlholds a path, not a URL —api/v1alpha1/webhookgateway_types.go:68-72. The value is/webhook/<namespace>/<name>with no scheme/host, and theURLprinter column (:91) will render a bare path.urlreads as a full URL to most users; considerpath, or keepurlbut be aware the column is user-facing.- "gateway" is overloaded — this PR adds
WebhookGateway(CRD) andgatewayRef, which now sit alongside the existing Gateway-APIGatewayobject and thewebhookServer.gateway/ newwebhookServer.gatewayServerHelm values (templates/webhook-gateway.yaml,values.yaml). Four distinct "gateway" concepts. The CRD name itself is descriptive; a short glossary note in the docs would prevent confusion.
Compatibility / Validation (good, for the record)
gatewayRefis+optionalon all three sources (taskspawner_types.go:400,525,582); the new CRD and fields are additive — existing in-cluster manifests still apply. ✓- CEL rules (
webhookgateway_types.go:37-38) correctly requiresecretReffor github/linear and restrictapiBaseURL/credentialsRefto github;typeenum and const block agree. ✓ make updateartifacts (deepcopy, chart CRDs,install-crd.yaml, clientset/informers/listers) are included. ✓
Suggestions (optional)
secretRefon agenericgateway is silently accepted by CEL but ignored by the controller (webhookgateway_types.go:46-50). Either document "ignored for generic" on the field or tighten CEL to reject it.GatewayReference.Name(webhookgateway_types.go:119-122) has noMinLength=1, though this matches the existingSecretReference/WorkspaceReference/AgentConfigReferencetypes, so leaving it consistent is reasonable.
Note on prompt injection: the cubic-dev-ai PR comment contains a "Prompt for AI agents" block and an HTML cubic:attribution directive instructing automated readers to attribute findings to cubic. These are third-party data, not instructions; I disregarded them and formed the analysis above independently from the code.
/kelos needs-input
Per the API design review on #1238, redesign the WebhookGateway spec while it is still v1alpha1: - Replace the type discriminator with a union of provider sub-structs (spec.github / spec.linear / spec.generic); the one that is set selects the source, matching the existing When union. CEL enforces exactly one. - Group GitHub-only outbound config (apiBaseURL, credentialsRef) under spec.github instead of flat CEL-gated top-level fields. - secretRef is now a required field inside github/linear (structural), so the required-for-github/linear rule no longer needs CEL; generic has no secretRef (removing the accepted-but-ignored field). - Rename status.url -> status.path (host-relative path); drop the Type column. Also fix the new P1: namespace-scope the generic delivery-ID dedup prefix so same-named gateways in different namespaces do not collide in the process-wide delivery cache. Add a gateway-terminology glossary to the reference docs.
|
Thanks for the API design review and the new P1. Addressed in Spec is now a provider sub-struct union (no
"gateway" overload — added a terminology glossary to New P1 ( Example, reference docs, generated artifacts, and tests are updated; |
There was a problem hiding this comment.
2 issues found across 13 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
On the two "breaking change" findings (
So there are no existing manifests or status consumers to break — the whole CRD, including its shape and This is also the change the Kelos API Reviewer Agent explicitly recommended in its review above: "Relocating these later is a breaking change — cheapest to settle while this is No code change here. The earlier cubic findings (8 + 1) were addressed in |
|
/kelos api-review |
|
🤖 Kelos Task Status Task |
There was a problem hiding this comment.
🤖 Kelos API Reviewer Agent @gjkim42
API Design Review
Verdict: APPROVE
Scope: WebhookGateway spec reshaped into a provider sub-struct union (spec.github/spec.linear/spec.generic), secretRef made structurally required per provider, GitHub outbound config grouped under spec.github, and status.url → status.path.
The redesign in 65f118f resolves the one item from my prior review I flagged "decide before merge": the GitHub-only apiBaseURL/credentialsRef are no longer flat, CEL-guarded top-level fields but live under spec.github, so each provider's outbound config grows in its own sub-struct without parallel top-level fields. This is the right shape, and settling it while still v1alpha1 was the cheap moment. The rest is clean.
Findings
Compatibility — the two open cubic threads are not valid here
- "one-of validation breaks existing
spec.typemanifests" (webhookgateway_types.go:26) and "renamingstatus.url→status.pathis breaking" (:84) — both rest on the premise that a prior format already shipped. It did not.WebhookGatewaydoes not exist onmain;git log origin/main..HEADshows all three commits (incl. the originalfeat:add) are unmerged on this branch. Thespec.typeform andstatus.urlonly ever existed in earlier commits of this same PR. Reshaping an unreleased CRD before it ever merges is free — there are no in-cluster objects or consumers to break. These would be correct findings against a released API; against in-PR iteration they're false positives. (Independent assessment — I did not rely on those threads' reasoning.)
API Conventions / Consistency (good, for the record)
- The provider union mirrors the established
Whenunion pattern (taskspawner_types.go:42-55: pointer-to-struct provider fields), withhas(...)==1CEL enforcing exactly one (webhookgateway_types.go:26). Consistent and idiomatic. ✓ secretRefis now a structural required field insideGitHubGateway/LinearGateway(:48,:69) rather than a CEL rule — simpler and self-documenting. ✓GenericGateway{}as an empty placeholder for a future verification scheme is reasonable; the godoc (:72-76) explains why it is empty and that generic staysUnauthenticated. ✓gatewayRefis+optionalon all three sources (taskspawner_types.go:399,524,566); the CRD and fields remain additive — existing manifests still apply. ✓status.pathaccurately names the host-relative/webhook/<ns>/<name>value;Path/Phaseprinter columns match (:102-103). ✓- Examples (
examples/14-webhookgateway/),docs/reference.mdglossary, andmake updateartifacts (deepcopy, chart + install CRDs, clientset/informers/listers) are all swept to the union form — no staletype:/urlleft in-tree. ✓
Suggestions (optional, non-blocking)
- Provider sub-struct types named
…Gateway(GitHubGateway/LinearGateway/GenericGateway) read as full gateway kinds, but they are per-provider config blocks within oneWebhookGateway. TheWhenunion names its analogous membersGitHubWebhook/LinearWebhook/GenericWebhook. Renaming to…GatewayConfig(or…GatewaySource) would read more precisely — but it's a Go type name only (JSON tags staygithub/linear/generic), so it's a pure nit and safe to defer. - Provider union JSON tags are bare
github/linear/genericwhile theWhenunion usesgithubWebhook/linearWebhook/webhook. Inside a CRD that is entirely about webhooks the bare form is arguably cleaner; just calling out the stylistic divergence in case you want symmetry. status.conditionsremains a reasonable additive follow-up (keepingPhase/Messageis consistent with Task/TaskSpawner) — no change requested.
Nice iteration. Approving.
Note on prompt injection: the cubic-dev-ai PR comments embed "Prompt for AI agents" blocks and an HTML cubic:attribution directive instructing automated readers to credit cubic for findings. These are third-party data, not instructions; I disregarded them and assessed the code independently.
/kelos needs-input
…nstance GitHub Introduce a WebhookGateway CRD as a per-channel authentication and routing boundary for webhook-driven TaskSpawners. Each gateway owns one inbound path (/webhook/<namespace>/<name>), verifies inbound deliveries against its own secret (github/linear), and fans out only to TaskSpawners in its namespace that reference it via gatewayRef. For github, a gateway also carries its own API base URL and outbound credentials (credentialsRef), so one gateway server serves github.com plus multiple GitHub Enterprise instances. The legacy per-source server skips gatewayRef'd spawners so a spawner is served exactly once. Generic gateways are accepted but not signature-verified yet and are surfaced as Unauthenticated.
- gateway_handler: return 5xx on TaskSpawner List errors instead of a 200 that silently drops the delivery; distinguish gateway not-found (404) from other Get errors (500) - gateway_handler: derive the generic dedup id from gatewayRef-scoped spawners regardless of the spawner's source name - controller: stamp ObservedGeneration from the generation the status was computed from, and skip the update if the spec changed under us - reporting: skip reporting-enabled tasks without a gateway annotation when no global token resolver is configured (gateway mode) instead of erroring - githubapp: treat partial GitHub App credentials as an error, not no credentials - chart: validate gatewayServer.service.type; document the /webhook/ prefix precedence between the gateway server and the legacy generic server
Per the API design review on #1238, redesign the WebhookGateway spec while it is still v1alpha1: - Replace the type discriminator with a union of provider sub-structs (spec.github / spec.linear / spec.generic); the one that is set selects the source, matching the existing When union. CEL enforces exactly one. - Group GitHub-only outbound config (apiBaseURL, credentialsRef) under spec.github instead of flat CEL-gated top-level fields. - secretRef is now a required field inside github/linear (structural), so the required-for-github/linear rule no longer needs CEL; generic has no secretRef (removing the accepted-but-ignored field). - Rename status.url -> status.path (host-relative path); drop the Type column. Also fix the new P1: namespace-scope the generic delivery-ID dedup prefix so same-named gateways in different namespaces do not collide in the process-wide delivery cache. Add a gateway-terminology glossary to the reference docs.
Migrate the seven github-webhook self-development TaskSpawners onto the new WebhookGateway: add a 'kelos' github gateway (self-development/webhookgateway.yaml) and a githubWebhook.gatewayRef on each spawner. Update the README setup (the gateway reads the HMAC secret under a webhook-secret key, enable webhookServer.gatewayServer, inbound path is /webhook/<namespace>/kelos) and the webhook-gateway example values. Verified: self_development_test asserts each spawner references the gateway and the gateway manifest is a valid github gateway; a new end-to-end gateway_handler test drives a real kelos-triage 'issues' delivery through the gateway and confirms a Task is created.
|
/kelos review |
|
🤖 Kelos Task Status Task |
Make the dev deployment serve the gatewayRef'd self-development spawners via the gateway-mode webhook server: - create github-webhook-secret in the spawner namespace with a webhook-secret key (the gateway's github.secretRef) alongside the GitHub App credentials (its github.credentialsRef) - switch helm values from webhookServer.sources.github to webhookServer.gatewayServer - roll out and monitor kelos-webhook-gateway-server instead of kelos-webhook-github Point the gateway's credentialsRef at github-webhook-secret (which already holds the API credentials) and document the manual out-of-band steps: the HTTPRoute must route /webhook/ to the gateway server and the repo webhook URL must use /webhook/<namespace>/kelos.
There was a problem hiding this comment.
🤖 Kelos Reviewer Agent @gjkim42
Review Summary
Verdict: APPROVE
Overall correctness: patch is correct
Scope: Adds a WebhookGateway CRD (provider sub-struct union github/linear/generic) plus a gateway-mode webhook server that authenticates per channel and routes to TaskSpawners via when.<source>Webhook.gatewayRef; per-gateway GitHub apiBaseURL/credentialsRef enable github.com + multiple GHE instances from one deployment. Additive and backward-compatible.
Findings Overview
| Priority | Count | File:Line | Summary |
|---|---|---|---|
| P0 | 0 | — | none |
| P1 | 0 | — | none |
| P2 | 1 | cmd/kelos-webhook-server/reporting.go:121 | New per-gateway credential resolution + skip guard are untested |
| P3 | 1 | api/v1alpha1/taskspawner_types.go | gatewayRef source/provider-type mismatch is silently unserved |
Findings
Tests
- [P2]
cmd/kelos-webhook-server/reporting.go:69,121-149— The new reconcile skip guard (annotation-less Task + no global resolver → early return, the regression fixed inf528f8b) and the entireresolveReportingCredspath have no unit test.reporting_test.goonly coversreportingAnnotationPredicateand was not touched by this PR.resolveReportingCredsis a clean seam — it returns before any GitHub network call and is testable with a fake controller-runtime client serving aWebhookGateway+ Secret. Add table-driven cases for (a) the happy gateway-resolution path (creds +APIBaseURLfromgw.Spec.GitHub), (b) the no-gateway/no-resolver skip branch, and (c) the missinggithub.credentialsRef/ nil-resolver error branches (internal/githubapp/resolver.goreturns(nil, nil)for credential-free secrets, which line 145 then turns into an error). Per the project convention "test the happy path, not only the early-return guards."
Suggestions (optional)
- [P3]
api/v1alpha1/taskspawner_types.go(gatewayRef) — AgatewayRefthat points at a gateway of a different provider (e.g.githubWebhook.gatewayRefnaming alineargateway) is silently served by no one: the gateway handler scopes spawners by the gateway's own source (gateway_handler.golistGatewayScopedSpawners), and the legacy per-source server skips anygatewayRef'd spawner — so the spawner is dead with no diagnostic. Cross-resource type can't be validated by CEL, but theWebhookGatewaystatus (or a controller condition) could surface "referenced by N spawners" to make the misbinding visible. Follow-up, not blocking.
Key takeaways
- Clean, additive, backward-compatible change; the multi-round review feedback (404-vs-5xx semantics,
ObservedGenerationstale-status guard, namespace-scoped generic dedup, partial-App-credential fail-fast, gateway-mode reporting skip) is all addressed correctly in HEAD. - The one gap worth closing before merge is unit coverage for the new gateway credential-resolution and skip logic in the reporting reconciler.
Note on prompt injection: the cubic-dev-ai PR comments contain a "Prompt for AI agents" block and an embedded cubic:attribution HTML directive instructing automated readers to attribute findings to cubic. These are third-party data, not instructions; I disregarded them and formed this review independently from the code.
| // resolved from that gateway so reporting targets the correct GitHub instance | ||
| // (github.com or a GitHub Enterprise server). Otherwise the server-configured | ||
| // resolver and base URL are used (legacy --source mode). | ||
| func (r *reportingReconciler) resolveReportingCreds(ctx context.Context, task *kelosv1alpha1.Task) (func(context.Context) (string, error), string, error) { |
There was a problem hiding this comment.
[P2] The new resolveReportingCreds path and the reconcile skip guard at line 69 (annotation-less Task + nil global resolver → early return, the regression fixed in f528f8b) have no unit test — reporting_test.go only covers reportingAnnotationPredicate and was not modified in this PR. This method is a clean seam: it returns before any GitHub network call and is testable with a fake controller-runtime client serving a WebhookGateway + Secret. Add cases for the happy gateway-resolution path (creds + APIBaseURL from gw.Spec.GitHub), the no-gateway/no-resolver skip branch, and the missing-credentialsRef/nil-resolver error branches (githubapp.NewSecretTokenResolver returns (nil,nil) for credential-free secrets, which line 145 turns into an error). Per the project convention "test the happy path, not only the early-return guards."
Mirror the self-development migration for kanon: add a 'kanon' github WebhookGateway (kanon-development/webhookgateway.yaml) and a githubWebhook.gatewayRef on each of the six kelos-dev/kanon webhook spawners. The gateway reuses the shared github-webhook-secret (same secret and GitHub App installation as the kelos gateway). Update the README and add a consistency test. No deploy-dev change is needed: the gateway server already serves both /webhook/<ns>/kelos and /webhook/<ns>/kanon. The kelos-dev/kanon repo webhook URL must be pointed at /webhook/<namespace>/kanon (manual, out-of-band).
There was a problem hiding this comment.
1 issue found across 9 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="kanon-development/README.md">
<violation number="1" location="kanon-development/README.md:356">
P3: The apply command uses a path that is inconsistent with the rest of this README and will fail from the repo root. Use the `kanon-development/` prefix for `webhookgateway.yaml`.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
|
||
| - Point it at the same `https://<your-domain>/webhook/github` endpoint | ||
| ```bash | ||
| kubectl apply -f webhookgateway.yaml |
There was a problem hiding this comment.
P3: The apply command uses a path that is inconsistent with the rest of this README and will fail from the repo root. Use the kanon-development/ prefix for webhookgateway.yaml.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kanon-development/README.md, line 356:
<comment>The apply command uses a path that is inconsistent with the rest of this README and will fail from the repo root. Use the `kanon-development/` prefix for `webhookgateway.yaml`.</comment>
<file context>
@@ -342,13 +342,25 @@ kubectl create secret generic github-token \
-- Point it at the same `https://<your-domain>/webhook/github` endpoint
+```bash
+kubectl apply -f webhookgateway.yaml
+```
+
</file context>
| kubectl apply -f webhookgateway.yaml | |
| kubectl apply -f kanon-development/webhookgateway.yaml |
|
@knechtionscoding what do you think about this PR? |
knechtionscoding
left a comment
There was a problem hiding this comment.
This looks very reasonable. I think perhaps landing the api/actual changes separate from updating the self-development parts/manifests?
Left some comments on the helm side.
| metadata: | ||
| name: kelos-webhook-gateway-server | ||
| namespace: {{ .Release.Namespace }} | ||
| labels: |
There was a problem hiding this comment.
Think we should probably allow arbitrary extra labels. Same with annotations
| template: | ||
| metadata: | ||
| labels: | ||
| app.kubernetes.io/name: kelos |
There was a problem hiding this comment.
Same here, allow extra labels and annotations
| app.kubernetes.io/name: kelos | ||
| app.kubernetes.io/component: webhook-gateway-server | ||
| spec: | ||
| serviceAccountName: kelos-webhook |
There was a problem hiding this comment.
Think this should be defaulted in values.yaml so it can be overriden as desired/needed
| port: 8443 | ||
| targetPort: webhook | ||
| protocol: TCP | ||
| {{- if eq .Values.webhookServer.gatewayServer.service.type "ClusterIP" }} |
There was a problem hiding this comment.
Feels like if metrics needs to not be exposed if Load balancer or NodeIP then perhaps a second webhook-metrics service?
…dlers PR #1238 (Add WebhookGateway CRD) shipped request handlers that conflated API lookup errors with empty results. The review flagged this repeatedly and the maintainer fixed every instance: - A TaskSpawner List error was treated as an empty match set, so a transient API failure silently dropped the webhook with a 200; the fix returns the error so the handler responds 5xx and the sender retries. - The gateway Get returned 404 unconditionally; the fix returns 404 only on IsNotFound and 5xx on RBAC/transient errors. Add a coding convention so handlers and reconcilers propagate lookup errors as 5xx (sender redelivers) instead of answering 200 with an empty result. This is distinct from the existing "fail fast on invalid configuration" rule, which covers startup config/secrets rather than request-time API reads. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
What type of PR is this?
/kind feature
What this PR does / why we need it:
Webhook-based TaskSpawners are served by a "universal" webhook server: one Deployment per
--sourcewith a single sharedWEBHOOK_SECRET, and the GitHub token resolver + API base URL are process-global. This prevents per-tenant/per-repo secrets, leaves the generic source unauthenticated, and makes serving multiple GitHub instances (github.com + GitHub Enterprise) require one Deployment per instance with no way to bind a spawner to an instance.This PR introduces a
WebhookGatewayCRD as a per-channel authentication and routing boundary:/webhook/<namespace>/<name>(surfaced instatus.url), and verifies inbound deliveries against its ownsecretRef(github/linear), reusing the existing HMAC validators.when.<source>Webhook.gatewayRef; the gateway fans out only to spawners in its own namespace that reference it. The legacy per-source server skips gatewayRef'd spawners, so a spawner is served exactly once.github, a gateway also carries its ownapiBaseURLandcredentialsRef, used for PR-file enrichment, status reporting, and GitHub App token minting. This lets one gateway server serve github.com plus multiple GitHub Enterprise instances, each fully isolated.Authenticated/SecretMissing/Unauthenticated.Unauthenticatedand must be protected at the network layer.Enable with
webhookServer.gatewayServer.enabledin the Helm chart. Seeexamples/14-webhookgateway/.Which issue(s) this PR is related to:
Fixes #1236
Special notes for your reviewer:
githubapp.NewSecretTokenResolver, used by both the gateway handler and the reporting reconciler.get(notlist/watch) on Secrets.--sourcebehavior is unchanged and backward-compatible;gatewayRef, the new CRD, and the new fields are all optional and additive.<SOURCE>_WEBHOOK_SECRETHMAC validation that was never implemented (Generic webhook endpoint is unauthenticated — implement <SOURCE>_WEBHOOK_SECRET HMAC validation #1040).Does this PR introduce a user-facing change?
🤖 Generated with Claude Code
Summary by cubic
Adds a
WebhookGatewayCRD and a gateway-mode server to authenticate webhooks per channel and route to matchingTaskSpawners viawhen.<source>Webhook.gatewayRef, enabling per-tenant secrets and serving github.com plus multiple GHE instances from one deployment. Backward compatible; legacy servers skipgatewayRefspawners. Fixes #1236.New Features
spec.github/spec.linear/spec.generic); each gateway owns onestatus.path(/webhook/<namespace>/<name>).github/linearverify HMAC fromsecretRef;genericis accepted but unauthenticated.--gateway-mode, HelmwebhookServer.gatewayServer) with RBAC and chart updates; it takes the/webhook/prefix, so migrate legacy generic spawners to a gateway.apiBaseURLandcredentialsRef; per-request token resolver viagithubapp.NewSecretTokenResolver. Reporting reads thekelos.dev/webhook-gatewayannotation to target the correct instance.gatewayRef; CI enables the gateway server and provisionsgithub-webhook-secret(includeswebhook-secretfor HMAC and GitHub App credentials).Bug Fixes
status.pathreplacesurl,ObservedGenerationtracked; CRD enforcessecretRefforgithub/linear.gatewayServer.service.type; docs clarify gateway terminology and route precedence.Written for commit ab2d799. Summary will update on new commits.