Repository navigation
feat!: map upstream errors, keep error text off the wire, and let the client own retries - #40
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 43 minutes. View limit detailsLimit details: You’ve used the included review currently available. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (1)
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (33)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe changes update gateway scope admission and error handling, classify Kubernetes failures, add shared-watch backoff, and pass retry hints through the managed client. Documentation and tests cover these behaviors, along with integration and release guidance. ChangesGateway recovery and client retry behavior
Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~50 minutes Change: Feature Sequence Diagram(s)sequenceDiagram
participant ManagedConnector as connectManagedResourceStream
participant Gateway
participant SharedBackend
participant KubeBackend as gateway/kube.Backend
participant KubernetesAPI
ManagedConnector->>Gateway: Open stream
Gateway->>SharedBackend: Request scope watch
SharedBackend->>KubeBackend: Open upstream watch
KubeBackend->>KubernetesAPI: List or watch resources
KubernetesAPI-->>KubeBackend: Return upstream failure
KubeBackend-->>SharedBackend: Return classified error and retry hint
SharedBackend-->>Gateway: Return upstream-unavailable error
Gateway-->>ManagedConnector: Send error event and close stream
ManagedConnector->>ManagedConnector: Wait at least the retry hint
ManagedConnector->>Gateway: Reconnect
Merge Risk: ⚪ Minimal · up to The identified concerns do not block the documented retry behavior or supported builds. The PR is ready for normal merge checks. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to Authorization remains mandatory, unexpected error details are withheld, and retries are bounded. The broader resource-access option depends on correct caller credential binding, which has not been established for deployed integrations. Retained concerns Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
… client own retries Implements the five asks in docs/field-reports/first-fieldreport.md from krm-foyer. - gateway/kube maps API-server failures to protocol codes on every path (streaming list, fallback LIST and WATCH, watch ERROR events): 403 FORBIDDEN, 401 UNAUTHENTICATED, 404 SCOPE_INVALID, 429/5xx/unreachable UPSTREAM_UNAVAILABLE with Retry-After as retryAfterMs; 410 still resyncs. - The stream loop recovers only RESYNC_REQUIRED in band. Any other non-terminal error is sent with its code and hint and the connection closes; the client retries. SharedBackend backs off a scope whose upstream will not open. - A generated INTERNAL says "internal error". Raw errors, and the new StreamError.Cause, go only to Options.Diagnostics. - ScopePolicy.AnyResource delegates resource admission to the upstream's RBAC. - Principal may return a *StreamError; other errors are UNAUTHENTICATED. - The managed client honours Retry-After and retryAfterMs, and shows a Kubernetes Status message on a non-200 refusal. - spec §4.3 states who recovers; §8 and conformance item 10 allow a host-approved GVR. - README points to krm-foyer for a quick start. BREAKING CHANGE: Principal errors and kube.SubjectAccessReviewAuthorizer's unmapped callers are UNAUTHENTICATED instead of FORBIDDEN; unexpected error text no longer reaches the browser; non-terminal errors other than RESYNC_REQUIRED now end the connection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ads, keep SAR hints Review follow-ups for the krm-foyer field report. - SharedBackend records a retryable failure when an established upstream watch dies, atomically with forgetting the scope, so repeated watch-error events can no longer bypass the backoff or its hint. The backoff now resets only after a completed upstream snapshot, not on merely obtaining a watcher. - The client's refusal-body read stops when the stream closes and after a 2-second budget, falling back to "stream: HTTP <status>"; close() during the read no longer hangs, and a closed stream reports nothing. - A throttled or unreachable SubjectAccessReview keeps its classified error, including the Retry-After hint and cause. - Record the field-report changes in the unreleased changelogs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
30f2dc4 to
401768b
Compare
…urrent recovered(key) cleared a scope's backoff without checking which watch had recovered. Called after the scope lock is released, it could arrive after the watch was forgotten and a replacement had failed, erasing the newer backoff and allowing an immediate reopen. Clear it only while that watch is still the scope's current one, under the backend lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The adapter now uses gateway.Unauthenticated, gateway.UpstreamUnavailable and StreamError.Cause, and relies on the stream loop closing after a retryable error. Its go.mod still required gateway v0.2.1, so go.work hid a build failure that every consumer would hit, and an adopter on an older core would have retried UPSTREAM_UNAVAILABLE in a tight loop. Require the core at the pushed commit that has both. docs/releasing.md says how to make such a change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The check treated only v0.0.0-… as a pseudo-version. When an adapter change needs unreleased core API, Go chooses the canonical v0.4.1-0.… form once a tag precedes the commit, and the check skipped it as "not tagged yet" without building anything. Recognise any version ending in a timestamp and revision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Release Please inserts each version below the hand-written "Unreleased" section and leaves it in place, so released notes stayed labelled Unreleased. File them under the version that shipped them: the #35 notes under 0.4.0, the #40 notes under 0.5.0. Point gateway/kube at main's 891e93e rather than the PR-branch commit e723903. The core module is identical; the released adapter now requires a commit on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Implements the five asks from krm-foyer's feedback, which is committed here as
docs/field-reports/first-fieldreport.md. The README now points to krm-foyer for a quick start.Changes
1. Map Kubernetes errors, and recover from them (
gateway/kube,gateway/stream.go,gateway/shared.go)FORBIDDENwith Kubernetes' own message, 401 →UNAUTHENTICATED, 404 →SCOPE_INVALID, and 429, 5xx or an unreachable server →UPSTREAM_UNAVAILABLE. On a 429,Retry-AfterbecomesretryAfterMs. A 410 still starts a new snapshot.ERRORevents on an open watch.RESYNC_REQUIREDon the same connection. Any other non-terminal error is sent with its code and hint, and then the connection closes. A newretryable_errorobservation reports these.SharedBackendbacks off a scope whose upstream won't open (1s, doubling up to 30s). However many subscribers reconnect, the upstream sees one attempt per backoff period.UPSTREAM_UNAVAILABLE. That includes timed reauthorization checks, which therefore end the stream without making the error terminal.2. Keep unexpected error text off the wire
INTERNALsays "internal error". The raw error goes to a newOptions.Diagnosticshook, with the principal and scope it belongs to.StreamErrorhas a newCausefield, which is never sent.3.
ScopePolicy.AnyResourceAnyResourceandResourcespanics inHandler, andValidaterefuses every scope.4.
Principalcan say why it failed*StreamErrorto choose the code. Any other error is nowUNAUTHENTICATED.5. Client retry hints (
@configbutler/krm-stream)Retry-AfterorretryAfterMs, withinmaxRetryDelayMs, and drops the hint once a snapshot completes.onErrorreceives the hint as a fourth argument.Status,onErrorshows itsmessage. The read is capped at 16 KiB.Spec
retryAfterMs.Review follow-ups
stream: HTTP <status>. Before the fix,close()during a quiet body never resolved.Retry-Afterall the way to the SSE event.Breaking changes
Principalerrors, andkube.SubjectAccessReviewAuthorizercallers it can't map to a subject, now giveUNAUTHENTICATEDinstead ofFORBIDDEN.RESYNC_REQUIREDnow end the connection.Testing
task verifypasses on the rebased branch: lint, Go (-race) and TypeScript suites, wire and browser e2e, pack. The conformance goldens are unchanged.ERROReventsCauseSharedBackend's backoff holds under a reconnect stormAnyResourceadmission and refusalsStatusparsingNext()error andWatchErrorevent, 100 concurrent reconnects), and reset only after a snapshotretryAfterMs: 7000to the browsertest-cluster).🤖 Generated with Claude Code
Summary by CodeRabbit