fix(visionA-backend): DownloadStream 移除 dead ensurePromoted call (Bug #11)
ADR-016 v0.6 後 visionA download 直接從 converter MinIO 拿 NEF、不需先 promote 推上 FAA; 原 Phase 0.8 / v0.4-v0.5 設計的 ensurePromoted 是 dead call。 stage e2e 證實: - visionA DownloadStream → f.ensurePromoted → converter.Promote - converter Promote → faa.putFile (OAuthClientError 401 — converter↔FAA OAuth 鏈獨立 bug) - converter Promote 回 500、visionA DownloadStream 因 ensurePromoted 失敗回 502 - user 看到下載失敗 修法:flow.go DownloadStream 移除 ensurePromoted call、直接 GetResult(converter MinIO 已在 worker `_upload_output` 寫進 NEF、不需 promote 把 NEF 推 FAA)。 PromoteToModels(line 531)流程仍會呼叫 Promote、這是「加到模型庫」的合理步驟、不動。 驗證: - 17 packages race -count=3 全綠 - 新 test TestDownloadStream_DoesNotCallPromote 取代舊 TestDownloadStream_PromoteError_Propagation (故意把 promoteFunc 設成 t.Fatalf、確保 download path 完全不打 promote) 關聯 follow-up(未在本 commit 修): - converter↔FAA OAuth 401 仍是 promote-to-models 流程的 bug、需 converter / MC scope debug - 但 download 解耦後 user 至少能下載 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
78c1343e9a
commit
2d629f3ba2
@ -756,15 +756,20 @@ func (f *flow) DownloadStream(ctx context.Context, userID, jobID string) (io.Rea
|
||||
return nil, nil, fmt.Errorf("%w: status=%s", ErrJobNotCompleted, cj.Status)
|
||||
}
|
||||
|
||||
// 3. ensurePromoted — 自動觸發 promote 確保 converter MinIO 內有 NEF(converter 端冪等)
|
||||
// 回傳的 targetObjectKey 在 v0.6 只用於 log(visionA 端不再用它打 FAA)
|
||||
targetObjectKey, err := f.ensurePromoted(ctx, userID, jobID, cj)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// 2026-05-18 Bug #11 移除 ensurePromoted call:
|
||||
// 原 Phase 0.8 / v0.4-v0.5 設計需要先 promote(拿 target_object_key for FAA path)
|
||||
// 再 stream from FAA。v0.6 + ADR-016 後 visionA 直接從 converter MinIO 拿、不再
|
||||
// 走 FAA、target_object_key 無用、ensurePromoted 是 dead call。
|
||||
// 實際 stage e2e 也證實:converter promote → FAA put 撞 OAuth 401(converter ↔ FAA
|
||||
// OAuth 鏈獨立 bug、跟 download 不該綁),visionA download 卡 502。
|
||||
// download 直接 GetResult 即可——converter MinIO 在 worker 跑完 nef stage 就有 NEF
|
||||
// (worker `_upload_output` line 121)、不需要 promote。
|
||||
//
|
||||
// PromoteToModels 流程仍會呼叫 ensurePromoted(line 603 直接呼叫 f.converter.Promote)、
|
||||
// 這是「加到模型庫」流程的合理步驟、本次不動。
|
||||
|
||||
// 4. converter.GetResult — 從 converter MinIO streaming pull NEF
|
||||
// (v0.6:取代原 faa.GetFile(targetObjectKey);visionA 端不再直接打 FAA)
|
||||
// 3. converter.GetResult — 從 converter MinIO streaming pull NEF
|
||||
// (v0.6:取代原 faa.GetFile;visionA 端完全不打 FAA)
|
||||
stream, resultMeta, err := f.converter.GetResult(ctx, jobID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -786,10 +791,11 @@ func (f *flow) DownloadStream(ctx context.Context, userID, jobID string) (io.Rea
|
||||
ContentLength: resultMeta.ContentLength,
|
||||
}
|
||||
|
||||
// 2026-05-18 Bug #11:原 log 含 hashObjectKey(targetObjectKey) — Bug #11 移除
|
||||
// ensurePromoted 後 targetObjectKey 不再可用;改記 job_id(已可 cross-ref)+ meta info。
|
||||
f.logger.InfoContext(ctx, "conversion.flow.download_stream_opened",
|
||||
slog.String("user_hash", hashUserID(userID)),
|
||||
slog.String("job_id", jobID),
|
||||
slog.String("object_key_hash", hashObjectKey(targetObjectKey)),
|
||||
slog.Int64("content_length", resultMeta.ContentLength),
|
||||
slog.String("filename", meta.Filename),
|
||||
)
|
||||
|
||||
@ -1220,24 +1220,31 @@ func TestDownloadStream_JobNotCompleted(t *testing.T) {
|
||||
assert.Nil(t, meta)
|
||||
}
|
||||
|
||||
// TestDownloadStream_PromoteError_Propagation:promote 5xx 透傳。
|
||||
func TestDownloadStream_PromoteError_Propagation(t *testing.T) {
|
||||
// TestDownloadStream_DoesNotCallPromote:2026-05-18 Bug #11 後 download 不再呼叫 converter.Promote。
|
||||
// 設計理由:ADR-016 v0.6 後 visionA download 直接從 converter MinIO 拿 NEF、不需先 promote 推上 FAA;
|
||||
// ensurePromoted 是 dead call。
|
||||
// 此 test 故意把 promoteFunc 設成 fail——download 不該打到、test 仍應成功(GetResult 走完)。
|
||||
func TestDownloadStream_DoesNotCallPromote(t *testing.T) {
|
||||
t.Parallel()
|
||||
fix := newFlowFixture(t)
|
||||
|
||||
fix.converter.setJob(&ConverterJob{JobID: "j1", Status: "completed", CreatedAt: time.Now()})
|
||||
fix.ownership.Set("j1", "user-alice")
|
||||
// 故意把 promote 設成 fail——download 不該打到、test 應該仍成功
|
||||
fix.converter.promoteFunc = func(ctx context.Context, jobID string, req PromoteReq) (*ConverterPromoteResult, error) {
|
||||
return nil, fmt.Errorf("%w: promote 502", ErrConverterUnavailable)
|
||||
t.Fatalf("download 不該呼叫 promote (Bug #11 已移除 ensurePromoted)")
|
||||
return nil, errors.New("unreachable")
|
||||
}
|
||||
|
||||
_, _, err := fix.svc.DownloadStream(context.Background(), "user-alice", "j1")
|
||||
require.Error(t, err)
|
||||
assert.True(t, errors.Is(err, ErrConverterUnavailable))
|
||||
stream, meta, err := fix.svc.DownloadStream(context.Background(), "user-alice", "j1")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, stream)
|
||||
require.NotNil(t, meta)
|
||||
_ = stream.Close()
|
||||
|
||||
// converter.GetResult 不該被打到(promote 失敗在 GetResult 之前)
|
||||
assert.Equal(t, int32(0), fix.converter.getResultCalls.Load(),
|
||||
"promote 失敗應在 converter.GetResult 之前短路")
|
||||
// converter.GetResult 應該被打到 1 次(promote 沒被打、直接 GetResult)
|
||||
assert.Equal(t, int32(1), fix.converter.getResultCalls.Load(),
|
||||
"download 應直接 GetResult、不繞 promote")
|
||||
}
|
||||
|
||||
// TestDownloadStream_ConverterGetResultError_Propagation:converter.GetResult 5xx 透傳
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user