fix: allow retry after definitive publish failures
Frontend CI / Frontend (push) Successful in 3m18s
Backend CI / Backend (push) Successful in 16m16s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 26s
Desktop Client Build / Resolve Build Metadata (push) Successful in 26s
Desktop Client Build / Build Desktop Client (push) Successful in 25m45s

This commit is contained in:
2026-06-25 00:22:59 +08:00
parent 2ec266201c
commit ed48674ab5
13 changed files with 473 additions and 162 deletions
@@ -37,37 +37,38 @@ type CreateDesktopPublishJobParams struct {
}
type DesktopTask struct {
DesktopID uuid.UUID
JobID uuid.UUID
TenantID int64
WorkspaceID int64
TargetAccountID uuid.UUID
TargetClientID uuid.UUID
Platform string
Kind string
Payload []byte
Status string
Priority int
Lane string
LaneWeight int
Source string
SchedulerGroupKey *string
MonitorTaskID *int64
SupersedesTaskID *uuid.UUID
ControlFlags []byte
InterruptGeneration int
DedupKey *string
ActiveAttemptID *uuid.UUID
LeaseExpiresAt *time.Time
Attempts int
Result []byte
Error []byte
StartedAt *time.Time
InterruptedAt *time.Time
InterruptReason *string
EnqueuedAt time.Time
CreatedAt time.Time
UpdatedAt time.Time
DesktopID uuid.UUID
JobID uuid.UUID
TenantID int64
WorkspaceID int64
TargetAccountID uuid.UUID
TargetClientID uuid.UUID
Platform string
Kind string
Payload []byte
Status string
Priority int
Lane string
LaneWeight int
Source string
SchedulerGroupKey *string
MonitorTaskID *int64
SupersedesTaskID *uuid.UUID
ControlFlags []byte
InterruptGeneration int
DedupKey *string
ActiveAttemptID *uuid.UUID
LeaseExpiresAt *time.Time
Attempts int
Result []byte
Error []byte
StartedAt *time.Time
InterruptedAt *time.Time
InterruptReason *string
EnqueuedAt time.Time
PublishSubmitStartedAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
type CreateDesktopTaskParams struct {
@@ -355,37 +356,38 @@ func desktopPublishJobFromGenerated(row generated.DesktopPublishJob) *DesktopPub
func desktopTaskFromGenerated(row generated.DesktopTask) *DesktopTask {
return &DesktopTask{
DesktopID: uuidFromPG(row.DesktopID),
JobID: uuidFromPG(row.JobID),
TenantID: row.TenantID,
WorkspaceID: row.WorkspaceID,
TargetAccountID: uuidFromPG(row.TargetAccountID),
TargetClientID: uuidFromPG(row.TargetClientID),
Platform: row.PlatformID,
Kind: row.Kind,
Payload: row.Payload,
Status: row.Status,
Priority: int(row.Priority),
Lane: row.Lane,
LaneWeight: int(row.LaneWeight),
Source: row.Source,
SchedulerGroupKey: nullableText(row.SchedulerGroupKey),
MonitorTaskID: nullableInt64(row.MonitorTaskID),
SupersedesTaskID: nullableUUID(row.SupersedesTaskID),
ControlFlags: row.ControlFlags,
InterruptGeneration: int(row.InterruptGeneration),
DedupKey: nullableText(row.DedupKey),
ActiveAttemptID: nullableUUID(row.ActiveAttemptID),
LeaseExpiresAt: optionalTime(row.LeaseExpiresAt),
Attempts: int(row.Attempts),
Result: row.Result,
Error: row.Error,
StartedAt: optionalTime(row.StartedAt),
InterruptedAt: optionalTime(row.InterruptedAt),
InterruptReason: nullableText(row.InterruptReason),
EnqueuedAt: timeFromTimestamp(row.EnqueuedAt),
CreatedAt: timeFromTimestamp(row.CreatedAt),
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
DesktopID: uuidFromPG(row.DesktopID),
JobID: uuidFromPG(row.JobID),
TenantID: row.TenantID,
WorkspaceID: row.WorkspaceID,
TargetAccountID: uuidFromPG(row.TargetAccountID),
TargetClientID: uuidFromPG(row.TargetClientID),
Platform: row.PlatformID,
Kind: row.Kind,
Payload: row.Payload,
Status: row.Status,
Priority: int(row.Priority),
Lane: row.Lane,
LaneWeight: int(row.LaneWeight),
Source: row.Source,
SchedulerGroupKey: nullableText(row.SchedulerGroupKey),
MonitorTaskID: nullableInt64(row.MonitorTaskID),
SupersedesTaskID: nullableUUID(row.SupersedesTaskID),
ControlFlags: row.ControlFlags,
InterruptGeneration: int(row.InterruptGeneration),
DedupKey: nullableText(row.DedupKey),
ActiveAttemptID: nullableUUID(row.ActiveAttemptID),
LeaseExpiresAt: optionalTime(row.LeaseExpiresAt),
Attempts: int(row.Attempts),
Result: row.Result,
Error: row.Error,
StartedAt: optionalTime(row.StartedAt),
InterruptedAt: optionalTime(row.InterruptedAt),
InterruptReason: nullableText(row.InterruptReason),
EnqueuedAt: timeFromTimestamp(row.EnqueuedAt),
PublishSubmitStartedAt: optionalTime(row.PublishSubmitStartedAt),
CreatedAt: timeFromTimestamp(row.CreatedAt),
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
}
}
@@ -849,6 +849,10 @@ SET status = CASE
active_attempt_id = NULL,
lease_token_hash = NULL,
lease_expires_at = NULL,
publish_submit_started_at = CASE
WHEN $1::text = 'retry' THEN NULL
ELSE publish_submit_started_at
END,
attempts = CASE WHEN $1::text = 'retry' THEN 0 ELSE attempts END,
updated_at = now()
WHERE desktop_id = $4
@@ -39,6 +39,10 @@ func TestReconcileDesktopTaskRetryResetsAttempts(t *testing.T) {
if !strings.Contains(query, "attempts = CASE WHEN $1::text = 'retry' THEN 0 ELSE attempts END") {
t.Fatalf("retry reconcile must reset attempts so manual retry can be leased; query:\n%s", query)
}
if !strings.Contains(query, "publish_submit_started_at = CASE") ||
!strings.Contains(query, "WHEN $1::text = 'retry' THEN NULL") {
t.Fatalf("retry reconcile must clear stale publish submit marker; query:\n%s", query)
}
if strings.Contains(query, "attempts + CASE") {
t.Fatalf("retry reconcile must not increment attempts; query:\n%s", query)
}
@@ -225,6 +225,10 @@ SET status = CASE
active_attempt_id = NULL,
lease_token_hash = NULL,
lease_expires_at = NULL,
publish_submit_started_at = CASE
WHEN sqlc.arg(status)::text = 'retry' THEN NULL
ELSE publish_submit_started_at
END,
attempts = CASE WHEN sqlc.arg(status)::text = 'retry' THEN 0 ELSE attempts END,
updated_at = now()
WHERE desktop_id = sqlc.arg(desktop_id)