fix(runner): 修复图像结果转存与任务租约失效

修正非语义字段长 Base64 元数据被误判为媒体的问题,并允许按文件签名识别真实媒体后转存 OSS;同时将网关本地存储失败与上游失败分离,避免重复调用和错误降权。

异步任务复用已分配并发名额前立即刷新租约,租约已丢失时重新排队获取,避免排队耗时侵蚀租约后错误调用上游。生产环境关闭 server-main 尚未实现的进度回调,保留 Gateway 任务详情与事件查询。

验证:API 全量 go test、go vet、gofmt、Kubernetes 渲染、迁移安全检查及真实阿里云 OSS 读写/生命周期验收通过。
This commit is contained in:
2026-08-04 13:48:44 +08:00
parent 6a64d3936a
commit 394cc6288f
8 changed files with 127 additions and 18 deletions
@@ -495,3 +495,37 @@ func TestResolveCandidateFailureUnmatchedRetryableUsesSameThenNext(t *testing.T)
t.Fatalf("unmatched exhausted error should rotate without side effects, got %+v", decision)
}
}
func TestGatewayStorageFailureNeverRetriesProvider(t *testing.T) {
err := &clients.ClientError{
Code: "storage_write_failed",
Message: "generated binary result could not be written to object storage",
StatusCode: 503,
Retryable: true,
}
candidate := store.RuntimeModelCandidate{ModelRetryPolicy: map[string]any{
"enabled": true,
"allowKeywords": []any{"5xx"},
}}
retry := retryDecisionForCandidate(candidate, err)
if retry.Retry || retry.Reason != "result_persistence_failed" {
t.Fatalf("Gateway storage failure must not repeat an upstream request: %+v", retry)
}
failover := failoverDecisionForCandidate(store.RunnerPolicy{
Status: "active",
FailoverPolicy: map[string]any{"enabled": true, "allowCategories": []any{"provider_5xx"}},
}, candidate, err)
if failover.Retry || failover.Reason != "result_persistence_failed" {
t.Fatalf("Gateway storage failure must not fail over to another provider: %+v", failover)
}
decision := resolveCandidateFailure(resolveCandidateFailureInput{
RunnerPolicy: store.RunnerPolicy{Status: "active"},
Err: err,
HasNextCandidate: true,
Async: true,
})
if decision.Route != "stop" || decision.Reason != "result_persistence_failed" || decision.Info.Category != "gateway_storage" {
t.Fatalf("Gateway storage failure classification is incorrect: %+v", decision)
}
}