feat(httpapi): harden control plane and speed up prefix refresh
Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
924f111e48
commit
f8e0520817
197 files changed
+18655
-1416
No files matched your search
@@ -0,0 +1,83 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SystemJobTenantID owns global maintenance jobs outside user tenant scope.
|
||||
const SystemJobTenantID = "00000000-0000-0000-0000-000000000000"
|
||||
|
||||
// JobRequestHash identifies the immutable requested operation, excluding result metadata.
|
||||
func JobRequestHash(in JobEnqueue) (string, error) {
|
||||
meta := in.Meta
|
||||
if in.Request != nil {
|
||||
meta = in.Request
|
||||
}
|
||||
b, err := json.Marshal(struct {
|
||||
Kind string `json:"kind"`
|
||||
ModuleID *string `json:"module_id"`
|
||||
Meta map[string]any `json:"meta"`
|
||||
}{in.Kind, in.ModuleID, meta})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h := sha256.Sum256(b)
|
||||
return hex.EncodeToString(h[:]), nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrJobNotFound = errors.New("store: job not found")
|
||||
ErrJobConflict = errors.New("store: idempotency key conflicts with another request")
|
||||
ErrJobQueueFull = errors.New("store: job queue is full")
|
||||
ErrJobLeaseLost = errors.New("store: job lease lost")
|
||||
)
|
||||
|
||||
// JobRecord is the shared, tenant-scoped job lifecycle record.
|
||||
type JobRecord struct {
|
||||
RequestHash string
|
||||
ID, TenantID, Kind, Status string
|
||||
IdempotencyKey, ModuleID *string
|
||||
Meta map[string]any
|
||||
CreatedAt time.Time
|
||||
StartedAt, FinishedAt *time.Time
|
||||
LeaseUntil *time.Time
|
||||
Error *string
|
||||
OwnerID string
|
||||
FencingToken int64
|
||||
RequestSequence int64
|
||||
Attempt int
|
||||
CancelRequested bool
|
||||
}
|
||||
|
||||
// JobEnqueue is the immutable request used for idempotent insertion.
|
||||
type JobEnqueue struct {
|
||||
TenantID, Kind string
|
||||
IdempotencyKey, ModuleID *string
|
||||
Meta map[string]any
|
||||
// Request optionally supplies the original selector before dynamic resolution.
|
||||
Request map[string]any
|
||||
}
|
||||
|
||||
// JobQueue provides durable operations; PostgreSQL is authoritative in production.
|
||||
type JobQueue interface {
|
||||
CountJobsByStatus(context.Context) (map[string]int64, error)
|
||||
FindJobByIdempotencyKey(context.Context, string, string) (JobRecord, error)
|
||||
EnqueueJob(context.Context, JobEnqueue) (JobRecord, bool, error)
|
||||
ClaimJobs(context.Context, string, int, time.Duration) ([]JobRecord, error)
|
||||
// ReapExpiredJobs marks orphaned cancellations and exhausted retries as terminal.
|
||||
// Production callers run it about every 15 seconds, not on every claim.
|
||||
ReapExpiredJobs(context.Context) error
|
||||
HeartbeatJob(context.Context, string, string, int64, time.Duration) (bool, error)
|
||||
CheckpointJob(context.Context, JobRecord) error
|
||||
FinishJob(context.Context, JobRecord) error
|
||||
ReleaseJob(context.Context, string, string, int64) error
|
||||
GetJob(context.Context, string, string) (JobRecord, error)
|
||||
ListJobs(context.Context, string, string, string, string, int) ([]JobRecord, string, bool, error)
|
||||
CancelJob(context.Context, string, string) (JobRecord, error)
|
||||
RetryJob(context.Context, string, string) (JobRecord, error)
|
||||
}
|
||||
Reference in new issue
Block a user