fix(worker): harden control-plane lifecycle
Все проверки выполнены успешно
CI / test (push) Successful in 2m32s
Docker / Build and publish worker image (push) Successful in 18m17s

- reconnect safely after token rotation and retry leased results
- reject malformed tasks and remove production cluster debug mutation
- validate environment files and require immutable container images

BREAKING CHANGE: Docker install, deploy, and Compose now require an
immutable repository@sha256 image reference.
Этот коммит содержится в:
Gleb Tv
2026-07-19 23:11:43 +03:00
родитель 6937674449
Коммит e987f24903
38 изменённых файлов: 2203 добавлений и 674 удалений

Просмотреть файл

@@ -2,7 +2,6 @@ package workercluster
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
@@ -325,63 +324,6 @@ func (c *Cluster) Snapshot() error {
return r.Snapshot().Error()
}
// DefaultDebugCriticalCheck returns the hardcoded CriticalCheckConfig
// the cluster admin debug endpoint and the
// --cluster-debug-apply-test-config CLI flag apply. A fresh Epoch is
// stamped on every call so repeated applies produce distinct entries
// (handy for verifying replication timing).
func DefaultDebugCriticalCheck() CriticalCheckConfig {
return CriticalCheckConfig{
ID: 9999,
Kind: "distributed_critical",
IntervalS: 30,
Target: "http://example.com",
Epoch: time.Now().UTC().UnixNano(),
}
}
// ApplyTestConfig submits a hardcoded config.adopt log entry with the
// supplied CriticalCheckConfig. Returns the applied log index. This
// is a debug convenience used by the e2e script and the
// --cluster-debug-apply-test-config CLI flag; production code should
// build entries from the real signed-config-adoption producer
// (Phase-N work).
//
// DEBUG: this exists only so the e2e shell script can verify FSM
// replication without a real producer wired in.
//
// TODO(phase-N): remove once the real producer lands.
func (c *Cluster) ApplyTestConfig(check *CriticalCheckConfig) (uint64, error) {
c.mu.Lock()
r := c.raft
c.mu.Unlock()
if r == nil {
return 0, errors.New("workercluster: not started")
}
if r.State() != raft.Leader {
return 0, errors.New("workercluster: not leader; submit on the leader")
}
payload := ConfigAdoptPayload{
Version: 1,
Actor: c.opts.NodeID,
Checks: []CriticalCheckConfig{*check},
}
raw, err := json.Marshal(payload)
if err != nil {
return 0, fmt.Errorf("workercluster: encode payload: %w", err)
}
entry, err := EncodeEntry(&Entry{Type: EntryConfigAdopt, Adopted: raw})
if err != nil {
return 0, fmt.Errorf("workercluster: encode entry: %w", err)
}
fut := r.Apply(entry, 10*time.Second)
if err := fut.Error(); err != nil {
return 0, fmt.Errorf("workercluster: apply test config: %w", err)
}
return fut.Index(), nil
}
// ClusterID returns the NodeID this cluster was constructed with. It
// is exposed so HTTP handlers can label status responses with a
// stable identifier even when the raft library's own State() reports