feat(saas): pre-claimed checkout, the gateway hands out an already-activated sandbox#896
feat(saas): pre-claimed checkout, the gateway hands out an already-activated sandbox#896stubbi wants to merge 13 commits into
Conversation
The gateway serves eligible creates from a buffer of already-activated, org-less sandboxes; checkout is one resourceVersion-guarded label patch that is both the mutual exclusion between gateway replicas and the claim-time org attribution. Decision record, failure behavior, threat notes, and the per-org-namespace migration note are in the spec. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jannes Stubbemann <jannes@openclaw.rocks>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jannes Stubbemann <jannes@openclaw.rocks>
…ut refill Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jannes Stubbemann <jannes@openclaw.rocks>
…attribution patch Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jannes Stubbemann <jannes@openclaw.rocks>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jannes Stubbemann <jannes@openclaw.rocks>
…ile loop A zero CreationTimestamp (which a real apiserver never produces) reads as age-unknown, not infinitely old, so the reap can never fire on it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jannes Stubbemann <jannes@openclaw.rocks>
…out billing The gateway's checkout patch stamps the org on a formerly buffered sandbox's CR; the reconcile that patch triggers copies it to the backing husk pod, whose TRUSTED label is what the usage scraper bills. Idempotent and a no-op for classic claims labeled at claim time; an org-less (still-buffered) claim never stamps anything. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jannes Stubbemann <jannes@openclaw.rocks>
…kout Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jannes Stubbemann <jannes@openclaw.rocks>
… overrides The threat-model invariant is that control-plane-derived attribution (namespace-derived, or stamped at claim time) is never overridden by a claim label; a tenant-set CR label must not rebill a pod to another org. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jannes Stubbemann <jannes@openclaw.rocks>
…-model row Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jannes Stubbemann <jannes@openclaw.rocks>
📝 WalkthroughWalkthroughAdds an optional pre-claimed checkout buffer for eligible sandbox creates, including control-plane reconciliation, atomic claims, gateway and Helm configuration, RBAC patch access, and husk pod organization-label propagation. ChangesPre-claimed checkout
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Gateway
participant K8sControlPlane
participant CheckoutBuffer
participant KubernetesAPI
participant SandboxController
Gateway->>K8sControlPlane: submit eligible sandbox.create
K8sControlPlane->>CheckoutBuffer: claim pool entry
CheckoutBuffer->>KubernetesAPI: resourceVersion-guarded label patch
KubernetesAPI-->>CheckoutBuffer: claimed sandbox metadata
CheckoutBuffer-->>Gateway: cached endpoint and token
CheckoutBuffer->>K8sControlPlane: refill below floor
K8sControlPlane->>SandboxController: create buffered sandbox
SandboxController-->>CheckoutBuffer: Ready sandbox status and token Secret
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cmd/gateway/main.go`:
- Around line 103-134: Add focused unit tests for splitNonEmpty, envInt, and
envDuration. Cover comma-separated values with whitespace and empty entries,
valid and malformed integer environment values, and valid, empty, and invalid
duration values, asserting invalid or unset inputs return the supplied defaults;
isolate environment changes per test.
In `@docs/superpowers/specs/2026-07-11-preclaimed-checkout-design.md`:
- Around line 88-104: Update the Checkout and Janitor data-flow descriptions to
match the controller-owned design: remove the gateway’s async husk pod org-label
patch and refill kick from Checkout step 4, and remove Janitor’s pod re-patching
responsibility. State that the controller reconcile, triggered by the CR patch
watch event, propagates the pod label, while refill runs only on its periodic
ticker; preserve that the gateway requires no pod RBAC.
In `@internal/saas/controlplane/checkout.go`:
- Around line 313-354: In refillOne, wrap the error returned by
createSandboxAndAwait with contextual information instead of returning it
directly; update that branch to use fmt.Errorf with a descriptive message and %w
while preserving the original error.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: bfb79fb2-3488-4ddb-9f90-92f417ba7008
📒 Files selected for processing (15)
cmd/gateway/main.godeploy/charts/mitos/templates/gateway-rbac.yamldeploy/charts/mitos/templates/gateway.yamldeploy/charts/mitos/values.schema.jsondeploy/charts/mitos/values.yamldocs/superpowers/plans/2026-07-11-preclaimed-checkout.mddocs/superpowers/specs/2026-07-11-preclaimed-checkout-design.mddocs/threat-model.mdinternal/controller/husk_orgpropagation_test.gointernal/controller/huskdrain.gointernal/controller/sandboxclaim_controller.gointernal/saas/controlplane/checkout.gointernal/saas/controlplane/checkout_test.gointernal/saas/controlplane/controlplane.gointernal/saas/controlplane/forward.go
…ta-flow sync Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jannes Stubbemann <jannes@openclaw.rocks>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cmd/gateway/checkout_flags_test.go`:
- Around line 53-56: Make the unset-environment assertion in the envInt test
deterministic by explicitly clearing TEST_CHECKOUT_UNSET_KEY before calling
envInt, using the test’s cleanup-aware environment helper if available; then
retain the assertion that envInt returns 5.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 1ab83927-bfc9-48c7-8ad4-38d70e6a03e9
📒 Files selected for processing (3)
cmd/gateway/checkout_flags_test.godocs/superpowers/specs/2026-07-11-preclaimed-checkout-design.mdinternal/saas/controlplane/checkout.go
| if got := envInt("TEST_CHECKOUT_UNSET_KEY", 5); got != 5 { | ||
| t.Fatalf("envInt(unset) = %d, want 5", got) | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make the “unset” environment assertion deterministic.
envInt reads the process environment, so an externally defined TEST_CHECKOUT_UNSET_KEY can make this test fail. Clear the key explicitly before asserting the default.
Proposed fix
+ t.Setenv("TEST_CHECKOUT_UNSET_KEY", "")
if got := envInt("TEST_CHECKOUT_UNSET_KEY", 5); got != 5 {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if got := envInt("TEST_CHECKOUT_UNSET_KEY", 5); got != 5 { | |
| t.Fatalf("envInt(unset) = %d, want 5", got) | |
| } | |
| } | |
| t.Setenv("TEST_CHECKOUT_UNSET_KEY", "") | |
| if got := envInt("TEST_CHECKOUT_UNSET_KEY", 5); got != 5 { | |
| t.Fatalf("envInt(unset) = %d, want 5", got) | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cmd/gateway/checkout_flags_test.go` around lines 53 - 56, Make the
unset-environment assertion in the envInt test deterministic by explicitly
clearing TEST_CHECKOUT_UNSET_KEY before calling envInt, using the test’s
cleanup-aware environment helper if available; then retain the assertion that
envInt returns 5.
Thinking Path
Linked Issues or Issue Description
Related: #871 (the TTI gap is control-plane cost). Design spec: docs/superpowers/specs/2026-07-11-preclaimed-checkout-design.md (decision record, failure behavior, per-org-namespace migration note); implementation plan: docs/superpowers/plans/2026-07-11-preclaimed-checkout.md. Both are in this PR.
What Changed
Verification
Risks
Model Used
Checklist
🤖 Generated with Claude Code
Summary by CodeRabbit