Skip to content

feat(nodes): add guarded worker commissioning and retirement - #2093

Closed
fluxis wants to merge 3 commits into
Silo-Server:mainfrom
fluxis:feat/v2-worker-drain-disabled-commissioning
Closed

fluxis wants to merge 3 commits into
Silo-Server:mainfrom
fluxis:feat/v2-worker-drain-disabled-commissioning

Conversation

@fluxis

@fluxis fluxis commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Problem

External deployment controllers cannot safely commission or retire workers through the current node API. Creation enables placement immediately, and ordinary disable does not prove that execution or client traffic has stopped. This change adds disabled registration and a guarded drain acknowledgment.

Related issue: #1176
Validation tasks: changes #1177 C1/C3; changes #1206 C2

Approach

Native API v2 advertises disabled_creation and worker_drain. Controllers can register a disabled worker, verify it, and activate it under the original configuration validator. Identical create retries match the requested enabled state and cannot undo later activation.

Drain disables placement and publishes an admission fence in one guarded transaction. Existing admitted work and authenticated GET/HEAD requests for prepared downloads can finish. The fence blocks new preparation and cached preparation reuse. Delivery keeps the original artifact expiry; successful pre-fence preparation or reuse can extend it. No known permit renews after fencing. Successful deletion revokes the exact artifact permit. Workers count requests, executing jobs, detached FFmpeg work and bounded media permits through completion and cleanup, then seal admission when all counters reach zero. Each acknowledgment binds the node, fence, process and persisted server identity. Missing fields, stale configuration or unavailable authority cannot produce a retirement receipt.

Controllers must confirm zero, delete the exact registration under its current validator, and acknowledge deletion before stopping the process they own. Explicit cancellation invalidates earlier receipts and leaves placement disabled. Missing or recreated registrations cannot reopen an old worker. Database checks run outside the request-accounting mutex; identity epochs reject results from checks that raced cancellation, fencing or registration loss.

Ordinary disable and main-listener v1 business contracts retain their behavior. Apple and Android need no new product surface for these administrator operations; Jellyfin compatibility needs no separate management endpoint. The existing web create request explicitly keeps enabled=true without changing its presentation.

Validation

  • All ten registered PostgreSQL contracts executed without skips, including concurrent publication, fenced identity changes, assembled media refusal after registration deletion and transaction rollback. Actual Goose Down refused an active fence; Down/Up after cancellation preserved the latest validator and configuration.
  • Focused native API, proxy and transcode listener tests passed under the race detector. Lifetime regressions cover detached subtitle fills, canceled copy-seek recovery, session stop, tone-map probes and media-capability work. Mutation checks reproduced false zero, prepared-download refusal, stale identity restoration and media admission after registration deletion and indefinite permit renewal under polling before the fixes. Clock-driven routed GET/HEAD checks establish expiry, including delayed successful preparation, without waiting for the production 24-hour bound.
  • Affected Go vet and changed-line Go lint passed; lint reported zero issues. Generated OpenAPI/router fixtures, the complete migration ledger and route/playback contract gates passed. Semantic comparison found five additive changes and no breaking changes.
  • Generated web types, TypeScript, scoped lint/format checks and all 12 affected admin-node request/authority tests passed.
  • The full local Go/web suites, real GPU matrix and cross-host playback failover were not run. These scoped checks do not establish human acceptance of Distributed transcode nodes — Server #1177 or Active sessions / now-playing / remote control — Server #1206.

Evidence

The server-only commissioning contract changes; no current client renders the new administrator fields. These excerpts came from the actual registered v2 handler, production NodeHandler and AdminConfigurationStore against migrated private PostgreSQL. Only the changed field is shown.

Before: a controlled private-copy mutation of dfef30df omitted forwarding the requested Enabled value (Enabled: nil). This is a fault reproduction, not a screenshot of a released build.

Request field: {"enabled":false}
Response: HTTP 201 {"enabled":true}

After: the frozen dfef30df source was restored byte-for-byte.

Request field: {"enabled":false}
Response: HTTP 201 {"enabled":false}

Both phases acknowledged guarded native disable/delete and proved the exact fixture registration absent.

The assembled transcode listener also verifies artifact continuity and expiry over HTTP, using synthetic artifacts and a controllable accounting clock:

Request Before fault reproduction After restored source
Authenticated HEAD for an admitted artifact after fencing HTTP 503 before artifact keys were retained HTTP 200 within its original permit
HEAD at the admitted expiry after repeated polling HTTP 200 with the original renewal branch HTTP 503; GET also returns 503 and private drain reaches zero

These are controlled private-copy regressions, not captures of released builds. Restored cases preserve artifact bytes and modification time, then delete the exact artifact and partial file. The private listeners and disposable fixtures establish protocol behavior, not human acceptance or fleet playback.

Risks

Controllers may retire only workers supporting the new private protocol. Missing or incompatible acknowledgments leave workers running. Media permits can remain until positive revocation or token expiry, up to 24 hours. Unknown admission and retirement observations require current PostgreSQL authority; already admitted, unsealed playback can continue on its original bounded permit during a transient outage.

Rolling back the migration requires canceling all drains and preserves the latest configuration validator. An uncertain registration deletion requires retaining the process and explicit recovery. Deployment-controller custody and real fleet playback remain separate integration checks. Manual follow-up: siloserver.org #125, covering API-driven commissioning and retirement after this change merges.

Checklist

  • I read and can explain the complete diff.
  • This pull request addresses one concern.
  • The Evidence section shows every change a user can see, or says there is none.

AI Disclosure

  • Harness: OpenAI Codex
  • Tool(s): Codex tools, GitHub CLI and repository Kody Code Review (its model identifier was not reported)
  • Model(s): gpt-6.1-sol (configured Codex model; exact runtime identifier is not separately exposed)
  • Involvement: AI-generated; human review pending
  • Adversarial review: independent read-only review of fencing, validator continuity, identity binding, detached subprocess lifetimes and retirement ordering. Subtitle-fill, copy-seek, rollback-validator, artifact-continuity and request-accounting findings were fixed and covered by focused regressions.

@coderabbitai

coderabbitai Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@silo-kody

silo-kody Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Silo Kody — review complete

Review finished. Check the inline comments for findings and verify each suggestion against the code and tests.

Reviewing changes in Silo
  • Include the related issue, expected behavior, and validation steps in the PR description.
  • For API changes, describe the effect on Apple and Android clients and Jellyfin compatibility.
  • For plugin changes, identify the affected SDK contract, plugin, and catalog entry.
  • Follow this repository's AGENTS.md and CONTRIBUTING.md.
  • Request another review with @kody start-review in a PR comment.
  • React with 👍 or 👎 to give feedback on individual suggestions.
Review settings
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ❌

Comment on lines +44 to +48
key := ""
parts := strings.Split(strings.Trim(path, "/"), "/")
if len(parts) >= 2 && (parts[0] == drainTranscodeSegment || parts[0] == "remux") && parts[1] != drainStartSegment {
key = parts[1]
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Bug high

Admission failure in internal/transcodenode/drain.go gives existing prepared-download deliveries an empty admission key because only transcode and remux paths populate it, causing Gate.Begin to reject every GET/HEAD to /downloads/artifacts/{artifact_id} after fence publication, including already admitted downloads continuing through HTTPPreparer.Open. Identify and retain artifact delivery permits before fencing so existing transfers can continue without admitting new artifacts.

Prompt for LLM

File internal/transcodenode/drain.go:

Line 44 to 48:

Admission failure in internal/transcodenode/drain.go gives existing prepared-download deliveries an empty admission key because only transcode and remux paths populate it, causing Gate.Begin to reject every GET/HEAD to /downloads/artifacts/{artifact_id} after fence publication, including already admitted downloads continuing through HTTPPreparer.Open. Identify and retain artifact delivery permits before fencing so existing transfers can continue without admitting new artifacts.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Comment on lines +123 to +126
g.mu.Lock()
defer g.mu.Unlock()
authorityConfirmed := true
if err := g.syncLocked(ctx); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Performance high

Request serialization in internal/workerdrain/gate.go occurs because Gate.Begin performs a durable PostgreSQL lookup while holding the process-wide gate mutex on every media request, making concurrent manifest, segment, subtitle, and download requests wait on database round-trip latency and allowing slow lookups to block request releases and drain observations. Move authority I/O outside the shared state lock while preserving an ordering mechanism that prevents stale admissions after a sealed observation.

Prompt for LLM

File internal/workerdrain/gate.go:

Line 123 to 126:

Request serialization in internal/workerdrain/gate.go occurs because Gate.Begin performs a durable PostgreSQL lookup while holding the process-wide gate mutex on every media request, making concurrent manifest, segment, subtitle, and download requests wait on database round-trip latency and allowing slow lookups to block request releases and drain observations. Move authority I/O outside the shared state lock while preserving an ordering mechanism that prevents stale admissions after a sealed observation.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

@silo-kody

silo-kody Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Silo Kody — review complete

Review finished. Check the inline comments for findings and verify each suggestion against the code and tests.

Reviewing changes in Silo
  • Include the related issue, expected behavior, and validation steps in the PR description.
  • For API changes, describe the effect on Apple and Android clients and Jellyfin compatibility.
  • For plugin changes, identify the affected SDK contract, plugin, and catalog entry.
  • Follow this repository's AGENTS.md and CONTRIBUTING.md.
  • Request another review with @kody start-review in a PR comment.
  • React with 👍 or 👎 to give feedback on individual suggestions.
Review settings
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ❌

Comment on lines +61 to +62
if len(parts) == 3 && parts[0] == "downloads" && parts[1] == "artifacts" && downloadprepare.ValidArtifactID(parts[2]) && (r.Method == http.MethodGet || r.Method == http.MethodHead) {
key = artifactDrainKey(parts[2])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Bug high

Unbounded permit renewal in internal/transcodenode/drain.go occurs because artifact GET/HEAD admission sends the new artifact key through Gate.Begin's renewal branch, resetting the retained permit's expiry to now + MaxTokenTTL on every request, even after fencing; repeated HEAD polling therefore prevents bounded permit expiry and drain acknowledgment indefinitely. Preserve an existing artifact permit's expiry during delivery and extend it only through successful preparation or reuse before fencing.

Prompt for LLM

File internal/transcodenode/drain.go:

Line 61 to 62:

Unbounded permit renewal in `internal/transcodenode/drain.go` occurs because artifact GET/HEAD admission sends the new artifact key through `Gate.Begin`'s renewal branch, resetting the retained permit's expiry to `now + MaxTokenTTL` on every request, even after fencing; repeated HEAD polling therefore prevents bounded permit expiry and drain acknowledgment indefinitely. Preserve an existing artifact permit's expiry during delivery and extend it only through successful preparation or reuse before fencing.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

@silo-kody

silo-kody Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Silo Kody — review complete

Review finished. Check the inline comments for findings and verify each suggestion against the code and tests.

Reviewing changes in Silo
  • Include the related issue, expected behavior, and validation steps in the PR description.
  • For API changes, describe the effect on Apple and Android clients and Jellyfin compatibility.
  • For plugin changes, identify the affected SDK contract, plugin, and catalog entry.
  • Follow this repository's AGENTS.md and CONTRIBUTING.md.
  • Request another review with @kody start-review in a PR comment.
  • React with 👍 or 👎 to give feedback on individual suggestions.
Review settings
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ❌

@fluxis fluxis closed this Oct 8, 2026
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