Repository navigation
test(kube): compose the save path against a real API server - #58
Merged
Merged
Conversation
…e conditional-save host Against a real API server, a ConfigMap deleted and recreated between the host's preflight GET and its PATCH fails the captured resourceVersion check first, so it arrives as an ordinary 409 Conflict. Only a UID check tells a replacement from a stale write. The handler also flattened every Kubernetes error to bare status text, losing reasons and field causes. - Errors are answered with the Kubernetes Status as JSON, keeping its code, reason and causes; a non-API failure is reported without its text. - After a PATCH 409, or a 422 whose cause is metadata.uid, one GET classifies it: a different UID answers 409 "object was replaced" with Details.UID naming the replacement, a deleted object answers 404, and anything else is forwarded as Kubernetes reported it. Every other 422 stays a validation error, never a conflict. - The example editor reports a PATCH 404 as unavailable rather than throwing a host error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Implement priority 3 of proposal 0006. The TestRealAPI cases run a real API server, the real gateway over the real backend, a host save endpoint and the real browser store and conditional editor. The browser runs under node (packages/krm-stream/e2e/store-driver.ts), and Go decides when each stream event reaches it, so races are staged on purpose. - Status churn under krm-spec/v1, using a real status subresource from a test-installed Widget CRD: the write advances resourceVersion, the gateway suppresses it, the captured save is refused with 409, the guarded projected GET advances the base without field conflicts, and a fresh intent saves while the controller's status survives. - The contrasts: the same write is delivered under krm-full/v1, and a bookkeeping-only write (a new server-side-apply manager, which does advance the version, unlike a no-op reapply) is suppressed there. - Overlapping recovery: the reconciliation read is held until a closed upstream watch has begun a new snapshot. The guard refuses it, the base and draft are unchanged, the editor reports recovering, and progress resumes with a read and then one deliberate write. - The UID race: a transport barrier deletes and recreates the ConfigMap between the host's preflight GET and its PATCH. The host names the replacement in a structured Status, the replacement is untouched, the editor reports unavailable, and the replacement opens without the old draft. A real 422 keeps its causes and is never a conflict. task test-real-api runs them on the spike cluster; the full suite stays task test-cluster. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
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 59 minutes. View limit detailsLimit details: You’ve used the included review currently available. Review configuration: ⚙️ Run configuration
📒 Files selected for processing (12)
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #57 (→ #55 → #54 → #53). This PR adds two commits; the PRs below it are unchanged. A third commit, the CI workflow, is not pushed yet: see Making it merge evidence.
Implements priority 3 of proposal 0006: real-API save composition and identity races.
Harness
The
TestRealAPIcases (gateway/kube/composition_e2e_test.go, build tage2e) compose:packages/krm-stream/e2e/store-driver.ts.The Go side decides exactly when each stream event reaches the browser, so the races are staged on purpose rather than hoped for. No browser logic is reimplemented in Go.
Cases
TestRealAPIStatusChurnUnderSpecProjectionkrm-spec/v1: the status write advances RV (checked numerically); the gateway suppresses it (observation, and the stream stays quiet); the captured save gets a real 409; the guarded projected GET advances the base with no field conflicts (version-stale) and no status leak; a fresh intent saves; the controller's status survives; the echo settles the draft.TestRealAPIStatusChangeIsDeliveredUnderFullProjectionPATCH 204only).TestRealAPIBookkeepingOnlyWriteUnderFullProjectionkrm-full/v1suppresses it, and the save goes 409 →version-stale→saved.TestRealAPIOverlappingRecoveryreset), and the read lands beforesynced. The guard refuses it (the base is still the delivered version), the draft is kept, and the editor reportsrecovering. Aftersynced, Save does only a GET (version-stale), then one deliberatePATCH 204. There are no blind PATCH retries.TestRealAPIReplacementBetweenPreflightAndPatchconditionalsavehandler's preflight GET and its PATCH. The response is a structured KubernetesStatusnaming the replacement indetails.uid. The replacement is unchanged, the editor reportsunavailable, and the replacement opens with no old draft.TestRealAPIValidationErrorIsNotAConflictmetadata.labelscauses, and the object is unchanged.What the race revealed, and the fix (
8d67d44)Against a real API server, the replaced object's PATCH fails the captured
resourceVersioncheck before the UID check. So it arrives as a plain409 Conflict, indistinguishable from a stale write. Only when the RV matches does the uid precondition give422 field is immutable, and that can't happen in a race. The example host also flattened every error to bare status text. Now:StatusJSON, keeping code, reason and causes. A non-API failure is reported without its text.metadata.uid, one GET classifies the failure: a different UID becomes 409"object was replaced"withdetails.uid; a vanished object becomes 404. Every other 422 passes through unchanged.unavailableinstead of throwing a host error.Fake-client unit tests cover each branch.
Making it merge evidence
task test-real-apiruns the cases on the spike cluster.task test-clusterstill runs everything taggede2e.Pending push: commit
bc78e95(ci: run the real-API save composition on pull requests and on demand) adds.github/workflows/real-api.ymland the doc lines that point at it. GitHub refused it from this machine because the token here lacks theworkflowscope; it is on the localtest/real-api-save-racesbranch, on top of what is pushed. The workflow:src, the driver, the conditional-save example, the workflow), and on demand;ci.ymland the release gate stay cluster-free. Aworkflow_dispatch-only workflow can't run before it exists onmain, so the PR trigger is what lets it produce evidence for this PR. Until it is pushed, the evidence is the local run below.Evidence from this run
2268916cf8e08650de1468d5832e61b125de2391(the pushed head; the 3× repeat,task test-clusterandtask verifyran on a commit with identical code that also contained the workflow)v1.36.4+k3s1(k3dkrm-stream-spike)go test -tags e2e -count=1 -run '^TestRealAPI' -v .ingateway/kubeResults:
task test-clusterpassed 43 tests with 0 failures and 0 skips, including the aggregated-API fallback and the shared-host example.task verifypasses: client 144/144, browser 20/20.The cases were also checked by breaking the code on purpose:
base=<new RV>).Not run: the CI workflow, pending the push above. Nothing was skipped.
🤖 Generated with Claude Code