Skip to content

Retire hosted tenants before account deletion - #131

Merged
lawrencecchen merged 27 commits into
mainfrom
fix/hosted-tenant-lifecycle
Aug 3, 2026
Merged

lawrencecchen merged 27 commits into
mainfrom
fix/hosted-tenant-lifecycle

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

Retires Stack-owned hosted tenants before identity deletion, revokes keys before draining in-flight requests, and leaves durable non-secret tombstones. Also streams release ancestry verification so large GitHub comparisons cannot deadlock the continuity harness.

Tests: go test ./...
Tests: go test -race ./internal/proxy ./internal/tenant
Tests: Windows cross-compile for internal/tenant and internal/proxy


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Retires Stack-owned hosted tenants before identity deletion. Revokes keys, blocks new requests, drains in-flight traffic, and deletes state with durable tombstones and crash-safe recovery (including when the tenant never existed). Also streams release ancestry verification and clearly classifies failures.

  • New Features

    • Added DELETE /_subrouter/auth/stack/tenant: requires X-Subrouter-Tenant-Delete-Token (set via --stack-tenant-delete-token/SUBROUTER_STACK_TENANT_DELETE_TOKEN) and Stack team membership; token must be ≥32 bytes. Returns 202 while draining, then deletes; idempotent, resumes pending deletions on restart, and writes durable tombstones for both existing and absent tenants.
    • Coordinated tenant-use locking and fresh resolution: per-request shared locks with ResolveFresh; exclusive locks (AcquireUse, TryAcquireExclusiveUse) gate deletion; registry exposes RetireExternal, DeleteRetired, PendingDeletionIDs, and ErrTenantRetired. /_subrouter/auth/stack returns 410 Gone for retired tenants; unknown srt_ credentials always return 401.
  • Bug Fixes

    • Completed restart recovery for tenant deletions: scan retired/retiring markers, retry with exponential backoff, respect process drain; background worker finishes transcript cleanup and leaves deletions pending (without dropping credentials) if transcript removal fails.
    • Rejected new key creation for retired tenants.
    • Kept hosted login working when SUBROUTER_STACK_TENANT_DELETE_TOKEN is unset; the delete endpoint is disabled unless configured.
    • Stabilized deployment/test harnesses: added deploy/gcp/verify-release-on-main.sh to stream large GitHub comparisons into jq and accurately classify transport vs. processing failures; allowed test-only probe/egress tolerance overrides to reduce CI jitter.

Written for commit fedb1d0. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added Stack tenant deletion with authorization checks and safe coordination with active requests.
    • Retired tenants reject new requests, return 410 Gone, and support crash recovery and cleanup.
    • Added consistent tenant resolution and usage coordination across supported platforms.
    • Added release verification for confirming revisions are on the main branch.
  • Bug Fixes

    • Prevented stale or unknown tenant credentials from reaching the wrong tenant.
    • Improved repeated deletion and unauthorized request handling.
    • Release verification now handles large responses and reports transport failures clearly.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request adds shared release verification with streamed GitHub comparison parsing. It also adds tenant retirement, Stack tenant deletion, cross-platform usage locks, fresh resolution, stale-request protection, authorization, recovery, and lifecycle tests.

Changes

Release verification

Layer / File(s) Summary
Release verification workflow
deploy/gcp/verify-release-on-main.sh
The script validates inputs and dependencies, resolves release tags, checks revisions, streams comparison data, and reports the verified revision.
Deployment integration and regression coverage
deploy/gcp/golden-local-mac-production-continuity.sh, cmd/subrouter/deploy_scripts_test.go
The continuity script delegates verification. Tests cover large comparison responses and comparison transport failures.

Tenant lifecycle management

Layer / File(s) Summary
Retirement state and registry workflows
internal/tenant/tenant.go, internal/tenant/tenant_test.go
The registry stores retirement state and tombstones, blocks provisioning and key creation, supports recovery, and resolves active tenants without cached metadata.
Cross-platform tenant-use coordination
internal/tenant/use_lock_*.go, internal/tenant/lock_windows.go, internal/tenant/*lock*test.go
Unix and Windows implementations coordinate shared tenant use with exclusive deletion locks.
Stack deletion and request dispatch
internal/proxy/multitenant.go
The proxy adds authorized deletion, fresh resolution, fail-closed routing, request draining, recovery, and handler cleanup.
Deletion-token wiring and lifecycle validation
cmd/subrouter/main.go, cmd/subrouter/main_test.go, internal/proxy/multitenant_test.go
The serve command loads and validates the deletion token. Tests cover compatibility, authorization, retries, draining, cleanup, and idempotency.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant StackClient
  participant MultitenantProxy
  participant TenantRegistry
  participant UseLock

  StackClient->>MultitenantProxy: Request tenant deletion
  MultitenantProxy->>TenantRegistry: Authorize and retire tenant
  MultitenantProxy->>UseLock: Acquire exclusive tenant-use lock
  UseLock-->>MultitenantProxy: Lock acquired or pending
  MultitenantProxy->>TenantRegistry: Delete retired tenant
  TenantRegistry-->>MultitenantProxy: Deletion result
  MultitenantProxy-->>StackClient: Deletion response
Loading
sequenceDiagram
  participant DeploymentScript
  participant VerifyReleaseOnMain
  participant GitHub

  DeploymentScript->>VerifyReleaseOnMain: Repository, tag, expected revision
  VerifyReleaseOnMain->>GitHub: Resolve tag and request comparison
  GitHub-->>VerifyReleaseOnMain: Commit and comparison data
  VerifyReleaseOnMain-->>DeploymentScript: Verified revision or error
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: retiring hosted tenants before account deletion.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hosted-tenant-lifecycle

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 96118f2e26

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/proxy/multitenant.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (4)
internal/proxy/multitenant.go (3)

384-385: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the shared team authorization rule.

The same two-part check now exists here and at Lines 485-486 in handleStackTenantDelete, including the StackTeams fallback. One helper that takes teamID, claims, and the token keeps both handlers aligned when the rule changes.

🤖 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 `@internal/proxy/multitenant.go` around lines 384 - 385, Extract the shared
two-part team authorization check from the current handler and
handleStackTenantDelete into a helper accepting teamID, claims, and the token,
including the StackTeams fallback. Replace both inline checks with the helper so
both handlers use the same authorization rule.

511-519: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unreachable ErrTenantRetired branch.

RetireExternal in internal/tenant/tenant.go:402-433 returns only nil, a validation error, or an I/O error. It never returns ErrTenantRetired; an already-retired tenant returns (true, nil). The 409 branch is dead code.

♻️ Proposed cleanup
 	retired, err := m.Registry.RetireExternal(teamID)
 	if err != nil {
-		if errors.Is(err, tenant.ErrTenantRetired) {
-			http.Error(w, "tenant is retired", http.StatusConflict)
-			return
-		}
 		http.Error(w, "tenant retirement failed", http.StatusInternalServerError)
 		return
 	}
🤖 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 `@internal/proxy/multitenant.go` around lines 511 - 519, Remove the unreachable
errors.Is check for tenant.ErrTenantRetired in the retirement handler around
Registry.RetireExternal, including its 409 response, and retain the generic
internal-server-error response for all returned errors.

532-540: 🩺 Stability & Availability | 🔵 Trivial

Add a server-side sweep for tenants stuck in the retired state.

When TryAcquireExclusiveUse fails, the tenant stays retired with its credential-bearing directory intact, and cleanup depends on the client sending another DELETE. If the client never retries, the state persists. A background sweep that retries DeleteRetired for retired, keyless tenants makes the deletion durable without client cooperation. Add a metric for the count of tenants in the retired-pending state so the backlog is observable.

[operational_advice]

🤖 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 `@internal/proxy/multitenant.go` around lines 532 - 540, Add a background
cleanup sweep that identifies retired, keyless tenants and retries
Registry.DeleteRetired for each, including tenants left behind when
TryAcquireExclusiveUse fails; ensure the sweep runs independently of subsequent
DELETE requests and safely handles concurrent cleanup. Expose a metric counting
tenants currently in the retired-pending state, and update it as the sweep
observes changes. Integrate this with the existing multitenant lifecycle near
forgetTenant and DeleteRetired without changing successful DELETE behavior.
internal/tenant/use_lock_unix.go (1)

18-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider pruning stale lock files.

Every tenant ID creates a permanent file under tenant-use-locks. DeleteRetired removes r.Dir(id) but not the lock file, so entries accumulate for the lifetime of the state directory. A periodic sweep of lock files whose tenant has a tombstone keeps the directory bounded. Do not delete a lock file while a holder can still be inside AcquireUse, because a new opener would then get a different inode.

🤖 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 `@internal/tenant/use_lock_unix.go` around lines 18 - 27, Update the
tenant-use-lock maintenance flow around Registry.openUseLock and DeleteRetired
to periodically remove lock files only for tenants with tombstones, while
retaining locks for active or non-tombstoned tenants. Coordinate pruning with
AcquireUse so a lock file is never deleted while an existing holder may still be
inside the critical section; preserve inode continuity for concurrent openers.
🤖 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 `@deploy/gcp/verify-release-on-main.sh`:
- Around line 47-53: Update the comparison pipeline in the release verification
check to inspect PIPESTATUS[0] after gh api and jq run. If gh api fails, report
the transport/API failure distinctly and exit nonzero; otherwise retain the
existing "${tag} is not on main" message for a valid comparison that fails the
jq condition, without introducing response buffering or a here-string.

In `@internal/tenant/tenant.go`:
- Around line 455-479: Update the tenant deletion logic around the found/kept
filtering loop to build kept as a new independent slice rather than using
file.Tenants[:0]. Preserve validation and deletion behavior while ensuring
validation failures and the interval before save cannot mutate or expose cached
tenant state.
- Around line 467-489: Update the tenant retirement flow around RetireExternal
to remove the tenant state before creating the tombstone, and only write the
tombstone when found or hadState is true. Ensure os.RemoveAll failures return
without creating a tombstone, while preserving the existing save behavior and
idempotent repeated-deletion result.

In `@internal/tenant/use_lock_windows.go`:
- Around line 36-42: Update Registry.TryAcquireExclusiveUse so Windows reports
existing lock contention instead of always returning acquired=true. Implement
actual Windows-exclusive locking with LockFileEx, or defer tombstone creation in
DeleteRetired until os.RemoveAll succeeds and retry while the tenant directory
remains; preserve the 202 pending path for blocked tenants.

---

Nitpick comments:
In `@internal/proxy/multitenant.go`:
- Around line 384-385: Extract the shared two-part team authorization check from
the current handler and handleStackTenantDelete into a helper accepting teamID,
claims, and the token, including the StackTeams fallback. Replace both inline
checks with the helper so both handlers use the same authorization rule.
- Around line 511-519: Remove the unreachable errors.Is check for
tenant.ErrTenantRetired in the retirement handler around
Registry.RetireExternal, including its 409 response, and retain the generic
internal-server-error response for all returned errors.
- Around line 532-540: Add a background cleanup sweep that identifies retired,
keyless tenants and retries Registry.DeleteRetired for each, including tenants
left behind when TryAcquireExclusiveUse fails; ensure the sweep runs
independently of subsequent DELETE requests and safely handles concurrent
cleanup. Expose a metric counting tenants currently in the retired-pending
state, and update it as the sweep observes changes. Integrate this with the
existing multitenant lifecycle near forgetTenant and DeleteRetired without
changing successful DELETE behavior.

In `@internal/tenant/use_lock_unix.go`:
- Around line 18-27: Update the tenant-use-lock maintenance flow around
Registry.openUseLock and DeleteRetired to periodically remove lock files only
for tenants with tombstones, while retaining locks for active or non-tombstoned
tenants. Coordinate pruning with AcquireUse so a lock file is never deleted
while an existing holder may still be inside the critical section; preserve
inode continuity for concurrent openers.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4443aa19-a890-4991-a034-476cac512ad2

📥 Commits

Reviewing files that changed from the base of the PR and between ae783ba and 96118f2.

📒 Files selected for processing (8)
  • cmd/subrouter/deploy_scripts_test.go
  • deploy/gcp/golden-local-mac-production-continuity.sh
  • deploy/gcp/verify-release-on-main.sh
  • internal/proxy/multitenant.go
  • internal/proxy/multitenant_test.go
  • internal/tenant/tenant.go
  • internal/tenant/use_lock_unix.go
  • internal/tenant/use_lock_windows.go

Comment thread deploy/gcp/verify-release-on-main.sh Outdated
Comment thread internal/tenant/tenant.go
Comment thread internal/tenant/tenant.go Outdated
Comment thread internal/tenant/use_lock_windows.go

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc9a0e4b3e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/proxy/multitenant.go Outdated
Comment thread internal/tenant/tenant.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (4)
internal/tenant/lock_windows.go (1)

19-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Share one set of Win32 lock bindings.

lock_windows.go and use_lock_windows.go both bind kernel32.dll, LockFileEx, and UnlockFileEx, and both repeat the same six-argument Call shape and the same unlock-then-close sequence. Extract one Windows-only helper file with the procedure bindings and small lockWholeFile(file, flags) and unlockWholeFile(file, overlapped) functions. Both lock types can then use it. This removes the duplicated flag values and keeps one place to fix if the failure-code handling changes.

🤖 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 `@internal/tenant/lock_windows.go` around lines 19 - 23, Consolidate the
duplicated Win32 bindings and lock/unlock calls from lock_windows.go and
use_lock_windows.go into one Windows-only helper file. Define shared
kernel32.dll procedure bindings plus lockWholeFile(file, flags) and
unlockWholeFile(file, overlapped), then update both lock implementations to use
these helpers while preserving existing failure-code handling and
unlock-before-close behavior.
internal/tenant/tenant_test.go (1)

163-183: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a unit test for PendingDeletionIDs.

The new tests cover retirement intent and key rejection. They do not cover PendingDeletionIDs, which drives crash recovery. Add a test that leaves only a retiring-tenants marker and asserts the ID is returned, and a test that asserts a retired registry entry is returned. This also pins the deduplication and sort order.

🤖 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 `@internal/tenant/tenant_test.go` around lines 163 - 183, Add unit coverage for
PendingDeletionIDs by testing a registry containing only a retiring-tenants
marker and asserting that marker’s tenant ID is returned, then testing a retired
registry entry and asserting its ID is returned. Include both sources together
to verify duplicate IDs are returned once and results are sorted
deterministically, using the existing registry setup and tenant identifiers.
internal/proxy/multitenant.go (1)

583-612: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Give the retry loop a stop signal that does not depend on Lifecycle.

The loop exits only on success or when m.Base.Lifecycle != nil && m.Base.Lifecycle.Draining() is true. When Lifecycle is nil, the worker retries forever and logs an error every 30 seconds. cmd/subrouter/main.go always sets Lifecycle, so production is covered. Tests and any other embedding are not: TestStackTenantDeletionRetriesAfterExclusiveLockSetupFailure builds Server{MaxBodyBytes: 1024} with no Lifecycle, so a worker that keeps failing outlives the test and keeps writing to the test logger.

Add a stop channel on MultiTenant, or accept a context.Context, and select on it beside the backoff timer. This also lets a caller cancel recovery deterministically.

🤖 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 `@internal/proxy/multitenant.go` around lines 583 - 612, Add a cancellation
signal to the background tenant-deletion retry loop surrounding
Registry.AcquireExclusiveUse and DeleteRetired, using a MultiTenant stop channel
or context.Context; select on that signal alongside the backoff timer and return
when cancellation occurs, without relying on Base.Lifecycle. Ensure the signal
can be triggered by callers or teardown so retries stop deterministically when
Lifecycle is nil.
internal/tenant/lock_windows_test.go (1)

10-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add Windows execution coverage for the lock tests.

.github/workflows/ci.yml, .github/workflows/release.yml, and release build coverage all run on ubuntu-latest and use GOOS cross-builds. The new //go:build windows tests compile only on Windows, so the LockFileEx behavior is not executed by the existing CI jobs. Run these tests on a Windows runner, or document that Windows lock behavior is unverified. Also rename TestWindowsRegistryLockSerializesProcesses to avoid implying separate processes; this test uses two handles in one process.

🤖 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 `@internal/tenant/lock_windows_test.go` around lines 10 - 13, Rename
TestWindowsRegistryLockSerializesProcesses to reflect that it verifies
serialization between two handles in one process, not separate processes. Add
Windows-runner execution coverage for the Windows-only lock tests in the CI or
release workflow, ensuring LockFileEx behavior is exercised; if workflow changes
are out of scope, explicitly document that Windows lock behavior remains
unverified.
🤖 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 `@deploy/gcp/verify-release-on-main.sh`:
- Around line 47-58: Update the comparison failure handling after the gh api/jq
pipeline to inspect comparison_status[1] for jq processing failures. Report a
comparison-fetch/processing error when jq fails, and reserve the "${tag} is not
on main" message for successfully processed responses that do not satisfy the
ancestor check.

In `@internal/proxy/multitenant_test.go`:
- Around line 687-712: Update the two wait loops in the test to use independent
deadlines, assigning a fresh, longer timeout immediately before the second loop
that checks multi.deletions. Keep the existing polling and timeout assertions
unchanged while increasing the timeout budget so background deletion retries can
complete.

In `@internal/proxy/multitenant.go`:
- Around line 519-529: Update the RetireExternal error path in the tenant
retirement handler to call scheduleTenantDeletion(teamID) before returning the
500 response, matching the subsequent TryAcquireExclusiveUse failure path. Keep
the existing error response and control flow unchanged after scheduling.
- Around line 95-97: Update the proxied-request flow around serveResolvedTenant
so AcquireUse and useLock.Close cover only the short tenant validation, then
release the lock before handler initialization and forwarding. Ensure
serveResolvedTenant no longer holds the per-tenant use lock across the
downstream handler lifetime, while preserving the existing resolved-tenant
validation behavior.

---

Nitpick comments:
In `@internal/proxy/multitenant.go`:
- Around line 583-612: Add a cancellation signal to the background
tenant-deletion retry loop surrounding Registry.AcquireExclusiveUse and
DeleteRetired, using a MultiTenant stop channel or context.Context; select on
that signal alongside the backoff timer and return when cancellation occurs,
without relying on Base.Lifecycle. Ensure the signal can be triggered by callers
or teardown so retries stop deterministically when Lifecycle is nil.

In `@internal/tenant/lock_windows_test.go`:
- Around line 10-13: Rename TestWindowsRegistryLockSerializesProcesses to
reflect that it verifies serialization between two handles in one process, not
separate processes. Add Windows-runner execution coverage for the Windows-only
lock tests in the CI or release workflow, ensuring LockFileEx behavior is
exercised; if workflow changes are out of scope, explicitly document that
Windows lock behavior remains unverified.

In `@internal/tenant/lock_windows.go`:
- Around line 19-23: Consolidate the duplicated Win32 bindings and lock/unlock
calls from lock_windows.go and use_lock_windows.go into one Windows-only helper
file. Define shared kernel32.dll procedure bindings plus lockWholeFile(file,
flags) and unlockWholeFile(file, overlapped), then update both lock
implementations to use these helpers while preserving existing failure-code
handling and unlock-before-close behavior.

In `@internal/tenant/tenant_test.go`:
- Around line 163-183: Add unit coverage for PendingDeletionIDs by testing a
registry containing only a retiring-tenants marker and asserting that marker’s
tenant ID is returned, then testing a retired registry entry and asserting its
ID is returned. Include both sources together to verify duplicate IDs are
returned once and results are sorted deterministically, using the existing
registry setup and tenant identifiers.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fa66da5a-9aa3-43b3-b5fd-7cfac8bde8a3

📥 Commits

Reviewing files that changed from the base of the PR and between 96118f2 and b64b8c2.

📒 Files selected for processing (13)
  • cmd/subrouter/deploy_scripts_test.go
  • cmd/subrouter/main.go
  • cmd/subrouter/main_test.go
  • deploy/gcp/verify-release-on-main.sh
  • internal/proxy/multitenant.go
  • internal/proxy/multitenant_test.go
  • internal/tenant/lock_windows.go
  • internal/tenant/lock_windows_test.go
  • internal/tenant/tenant.go
  • internal/tenant/tenant_test.go
  • internal/tenant/use_lock_unix.go
  • internal/tenant/use_lock_windows.go
  • internal/tenant/use_lock_windows_test.go

Comment thread deploy/gcp/verify-release-on-main.sh
Comment thread internal/proxy/multitenant_test.go Outdated
Comment thread internal/proxy/multitenant.go
Comment thread internal/proxy/multitenant.go
@lawrencecchen
lawrencecchen merged commit f488941 into main Aug 3, 2026
7 checks passed
@lawrencecchen
lawrencecchen deleted the fix/hosted-tenant-lifecycle branch August 3, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant