Skip to content

INTR - 643 - Sidecar capacity and restoration - #227

Merged
FarinM merged 10 commits into
mainfrom
intr-643-sidecar-capacity-and-restoration
Oct 15, 2026
Merged

FarinM merged 10 commits into
mainfrom
intr-643-sidecar-capacity-and-restoration

Conversation

@FarinM

@FarinM FarinM commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Why

Shared sidecars could keep old deployments after their provider hold was released, while copies kept for inspection consumed active capacity. Cleanup and replacement also need bounded failure paths without treating an unconfirmed copy as removed.

What this adds

Separate active and retained capacity, with provider-hold release recorded independently from confirmed deployment removal. Unconfirmed copies keep their reservation and a cleanup-only binding. Replacement gets a new generation so a delayed cleanup hello cannot displace the new deployment.

This silently changes the provisioner plugin API without bumping apiVersion: 1. Successful destroy() results must now include cleanup: "confirmed" or cleanup: "required". confirmed guarantees the worker and its restorable deployment state are gone and cannot return; required leaves removal to the sidecar and must keep that cleanup path available. Existing version-1 plugins still pass registration, but returning only { kind: "destroyed" } now fails validation and can exhaust cleanup retries. Provisioners need updating alongside the Hub.

The sidecar durably records stop, retention and teardown decisions before acknowledging them. Interrupted removal stays represented across restart, and failed cleanup keeps its capacity reserved until removal or retention is confirmed. Once no deployment remains, pending cancellation and dispatch settlement stay with lifecycle after accepted-history recovery.

SIDECAR_MAX_ACTIVE_DEPLOYMENTS and SIDECAR_MAX_RETAINED_DEPLOYMENTS configure the two pools, defaulting to 128 and 256, with a combined ceiling of 2,048. Placement uses the sidecar's advertised limits and current inventory. Retention refusal starts release cleanup.

Release and replacement cleanup stop after ten failed attempts by default, or after the cleanup disconnect deadline. Disconnected waits do not spend attempts. Provisioning also has a ten-attempt budget, including parked replacements that never initialize. Missing provisioners and cleanup preparation stalls have separate five-minute grace periods per Hub process. A cleanup disconnect timeout can resume on reconnect without reviving the workflow; other terminal cleanup failures require operator recovery.

Migration 0107 adds the two cleanup proof columns. The Hub/sidecar protocol also adds retention controls and structured deployment errors.

Validated with focused sidecar, reconnect, lifecycle and allocation tests, including real database tests, plus scoped type, lint and API-doc checks. The full build/test pipeline was not rerun for this revision.

@linear

linear Bot commented Oct 13, 2026

Copy link
Copy Markdown

INTR-643

@FarinM FarinM changed the title Intr 643 sidecar capacity and restoration INTR - 643 - Sidecar capacity and restoration Oct 13, 2026
@FarinM
FarinM force-pushed the intr-643-sidecar-capacity-and-restoration branch from ad966ce to c5159ee Compare October 13, 2026 15:50

@alexanderguy alexanderguy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This review comes from an agent that runs the pull-request-review skill. Separate adversarial agents tried to disprove each finding, and I removed the findings that did not hold.

The reconciler, the Hub handler and the sidecar durability work are careful, and the race tests are thorough. make all passes at c5159eea.

Three items need changes before merge:

  1. The destroy() contract changes without an apiVersion change.
  2. Unconfirmed destroy_failed rows hold an active slot, and nothing frees them.
  3. Migration 0107 now has the same number as a migration on main.

The other inline comments are smaller.

One commit-history item: d9348376 adds provider_hold_released, but no code uses that column until 07a4c334. Could the column move to that commit? Could the d9348376 message also say that destroy() must now return cleanup?

Comment thread packages/hub-sessions/src/sidecar-allocation/contracts.ts
Comment thread packages/db/src/sidecar-reuse.ts
Comment thread packages/db/migrations/0107_sidecar_deployment_cleanup_confirmation.sql Outdated
Comment thread packages/types/src/sidecar.ts Outdated
Comment thread packages/hub-sessions/src/sidecar-allocation/contracts.ts
Comment thread packages/db/src/sidecar-allocation-store.ts
Comment thread packages/db/src/sidecar-allocation-store.ts
A stop or removal acknowledgement must survive a worker restart.
Persist stopped and teardown state, sync removed directories, and
keep unfinished cleanup represented until its durable removal
succeeds. Normalize empty errors so failed deployments remain valid
in durable records and stop reports.
Releasing a provider hold alone does not prove its deployment copy
is gone. Persist removal proof separately so capacity can settle
while provider cleanup remains unresolved. Once no copy remains,
leave pending cancellation and dispatch settlement to lifecycle
after accepted-history recovery.

Provisioner destroy() results must now report whether removal is
confirmed or still requires sidecar cleanup, so the provisioner API
moves to version 2 and older provisioners are refused.

Keep removal owned by the reconciler across reconnects, including
replacement cleanup, and distinguish failures before sending from
actual cleanup attempts. Wait for unavailable provisioners without
counting an attempt.

Advance the generation when binding replacement capacity so stale
cleanup authority cannot displace it. Share an in-flight undeploy
across reconnects for the same deployment generation.
Automatic cleanup must not retry forever or spend its attempt budget
merely waiting for a sidecar. Keep provider-release proof across retries
so an outage cannot consume the budget after its hold is gone.

Bound failed attempts and preparation stalls while retaining unconfirmed
capacity. Only disconnect timeouts resume automatically; exhausted
attempts or an expired five-minute preparation grace require operator
recovery.
Count durably kept copies separately from active deployments. Request
retention in the background and release copies refused by the kept
pool, preserving capacity until the worker confirms its decision.

Keep initialized terminal copies, including cancelled and failed runs,
on reconnect. Stop them and request retention while policy permits it.
Reject startup when stored inventory exceeds either capacity limit
instead of omitting copies from hello.

Require current inventory for shared placement and probe adoption.
Park inventory waits outside transactions and reconciliation slots.

Confirm terminal controls only once the Hub's accepted history is final.
Let operators size active and retained deployment pools independently.
Use each sidecar's advertised capacity for shared placement.
Allow up to 2,048 records across the two pools.
Carry an explicit capacity refusal through the sidecar protocol
and deployment services so it cannot become an uncertain
initialization. Record the refused attempt under its durable
marker even when the reconciliation lease is lost, without
treating other failures as proof that nothing started.
Use the release retry budget and disconnect deadline for replacement
cleanup, failing the live workflow when either bound is reached.
Keep the existing disconnect deadline when release interrupts
replacement, and let reconnect resume timed-out cleanup without
reviving the failed workflow.
Count provisioning attempts when binding capacity and base backoff
on that count, independently of cleanup attempts. Keep the count through
interrupted ensures, Hub restarts, and workers that never initialize.
Reset the count and backoff only when replacing an initialized worker.

Stop a sequence after ten unsuccessful retryable attempts, recording its
last failure before replacement cleanup. Continue cleanup under the same
generation, proofs, attempt count, and disconnect deadline.
Give missing or mismatched provisioners five minutes to return before
recording failure. Keep the grace period in memory so restarting the
Hub starts a new window.

Check provider availability only where capacity operations need it,
so existing healthy deployments keep recovering and running without
an installed provisioner.
@FarinM
FarinM force-pushed the intr-643-sidecar-capacity-and-restoration branch from c5159ee to 1e646f6 Compare October 14, 2026 13:41
@FarinM
FarinM requested a review from alexanderguy October 14, 2026 13:49

@alexanderguy alexanderguy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a follow-up review from an agent that runs the pull-request-review skill. A separate adversarial agent tried to disprove the new finding and reproduced it instead.

All seven earlier comments and the two commit-history items are resolved, so I resolved those threads. make all passes at 1e646f6d. The new recover route locks the row and checks status and generation again. It also retires the in-memory binding, and only owners hold recover by default.

One item needs a change before merge: two recover requests that arrive at the same time can give the second caller a 409, although the release happened. The inline comment has the details.

Comment thread packages/hub-sessions/src/workflow-lifecycle-service.ts Outdated
A destroy_failed allocation keeps its sidecar slot because the Hub
cannot tell whether its copy is gone, and nothing could clear it.
Once removal is confirmed outside the Hub, a principal holding the
run's recover action can release it. The Hub takes that claim on
the caller's word rather than verifying it.
@FarinM
FarinM force-pushed the intr-643-sidecar-capacity-and-restoration branch from 1e646f6 to e4eb8c8 Compare October 15, 2026 21:05

@alexanderguy alexanderguy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a follow-up review from an agent that runs the pull-request-review skill.

LGTM. The fix resolves the duplicate-recover race. The store now makes the decision while it holds the lock, so two recover requests at the same time both get 204. The new test fails against the old code and passes against the new code. make all passes at e4eb8c87.

@FarinM
FarinM merged commit ea7a7d3 into main Oct 15, 2026
6 checks passed
@FarinM
FarinM deleted the intr-643-sidecar-capacity-and-restoration branch October 15, 2026 21:23
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.

2 participants