Skip to content

Route channel 156 real-person verification through Seedance Gateway - #839

Open
think-back wants to merge 2 commits into
mainfrom
fix/channel-156-bound-asset-rewrite
Open

Route channel 156 real-person verification through Seedance Gateway#839
think-back wants to merge 2 commits into
mainfrom
fix/channel-156-bound-asset-rewrite

Conversation

@think-back

@think-back think-back commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Route channel 156 when explicitly configured with asset_materialization.provider=seedance_proxy to the Seedance Gateway stateful face-verification API.
  • Keep verification_id encrypted in the existing platform session, activate the profile from Gateway group_id, and use that verified liveness-face group for real-person assets.
  • Cover Gateway create/status/list/create-asset/get-asset/delete-asset paths; failed and expired Gateway statuses now settle local terminal states instead of retrying forever.
  • Require exactly one enabled channel key for this provider to preserve per-user/per-key isolation.

Verification

  • Targeted service/model real-person tests pass.
  • Seedance adaptor package and channel-156 asset-binding regressions pass.
  • Compile-only checks for service, model, controller, and router pass.
  • go vet ./... remains blocked by the existing missing web/classic/dist embed directory; full service/model suites retain pre-existing parallel SQLite contention.

No production deployment or merge is included.

Prevent channel 156 from forwarding upstream asset identifiers that no longer exist, while retaining retryable provider failures for recovery.

Constraint: Existing active bindings and per-channel credential scopes must remain reusable when the upstream asset is still active.

Rejected: Blindly trust cached active bindings | stale upstream IDs caused Seedance requests to fail and could not recover automatically.

Confidence: high

Scope-risk: narrow

Directive: Keep provider-specific revalidation limited to Seedance proxy bindings and preserve CAS guards for concurrent requests.

Tested: go test -count=1 ./service -run 'TestSeedanceProxyMaterializeSetRematerializesStaleActiveBinding|TestSeedanceProxyAssetBindingReusesActiveBindingAcrossSeedanceModelsOnSameKey|TestAssetBinding'; go test -count=1 ./model -run 'AssetBinding|Asset'; go test -count=1 ./relay/channel/task/modelapiseedance; go vet ./service ./model ./relay/channel/task/modelapiseedance; git diff --cached --check

Not-tested: Full ./service and ./model suites time out on pre-existing SQLite parallel-test failures.
@KingCesc

Copy link
Copy Markdown

🤖 OpenCodeReview · 评审 commit 768974a6 · 共 3 条

model/asset.go

  • L579-607: [阻塞] 这里把“重新校验”逻辑直接挂在所有 active binding 上,但 RefreshActiveAssetBindingCAS 只在 status = active 时更新;对于 MaterializeAssetBinding 的几条命中路径,如果 GetAsset 返回的状态不是 active/processing(例如上游已失败或返回了其他中间态),当前代码会把 DB 中原本可复用的 active binding 直接刷成 failed,并且不会再回退创建/续租新 binding,导致本次 materialize 失败且状态被永久污染。建议把“仅对 Seedance Proxy 且命中可复用的 active binding 才 revalidate”收敛到更明确的分支,并在非可复用状态下保持原 binding 不变或走显式重建流程。
if activeAssetBinding(existing) && seedanceProxyActiveBindingRequiresRevalidation(request.Channel) {
		result, reusable, err := revalidateSeedanceProxyActiveAssetBinding(ctx, asset, request.Channel, bindingScope, request.Model, request.APIKey, existing)
		if err != nil {
			return AssetBindingResult{}, err
		}
		if reusable {
			return result, nil
		}
	}

service/asset_binding.go

  • L355-374: [阻塞] 这里在 Seedance 活跃绑定需要重验证时,直接把 MaterializeAssetBinding 的错误向上返回;但重验证路径里一旦命中 ErrAssetBindingInitializing(例如对端仍在 processing),MaterializeAssetBindingsForChannel 会立刻终止整批引用的 rewrite 生成。这样单个资产的临时状态就可能拖垮同一 channel 的所有资产输出,造成页面/接口整体不可用。建议对“初始化中/处理中”的结果仅跳过当前引用或保留旧映射,不要中断整个循环。
if !seedanceProxyActiveBindingRequiresRevalidation(channel) {
		rewriteMap["asset://"+reference.PublicID] = assetBindingRewriteURI(binding.UpstreamAssetID)
		continue
	}
	result, err := MaterializeAssetBinding(ctx, AssetBindingRequest{
		UserID:       userID,
		PublicID:     reference.PublicID,
		Channel:      channel,
		LeaseOwner:   assetBindingLeaseOwner(),
		PollLimit:    assetBindingDefaultPollLimit,
		PollDelay:    assetBindingDefaultPollDelay,
		LeaseTTL:     assetBindingDefaultLeaseTTL,
		ExpectedType: reference.ExpectedAssetType,
		Model:        materializeOptions.Model,
		APIKey:       materializeOptions.APIKey,
	})
	if err != nil {
		if errors.Is(err, ErrAssetBindingInitializing) || errors.Is(err, ErrAssetBindingUnavailable) {
			continue
		}
		return nil, err
	}
	rewriteMap[result.PublicURI] = result.RewriteURI
  • L1002-1005: [严重] seedanceProxyActiveBindingRequiresRevalidationassetMaterializationConfigForChannel 的任何错误都吞掉并返回 false。当 Seedance 配置解析/校验失败时,这里会静默跳过重验证,继续复用旧的活跃绑定,等于绕过了这次新增的失效检查,可能把过期或已失效的资产继续对外返回。建议对显式配置但读取失败的场景采取保守策略:要么直接返回错误,要么默认要求重验证,而不是放行复用。
func seedanceProxyActiveBindingRequiresRevalidation(channel *model.Channel) bool {
	config, explicit, err := assetMaterializationConfigForChannel(channel)
	if err != nil {
		return explicit && config.Provider == assetMaterializationProviderSeedanceProxy
	}
	return explicit && config.Provider == assetMaterializationProviderSeedanceProxy
}

Keep the existing /v1/real-persons contract while binding channel 156 to the stateful Gateway workflow and its verified liveness-face group. Terminal Gateway verification states now settle local sessions instead of being retried forever.

Constraint: Preserve native BytePlus and TokenSpace providers, encrypted verification state, and the existing public API surface.
Rejected: Reusing the ordinary materialization group or native BytePlus callback/storage flow | those scopes do not represent Gateway-owned verified person groups.
Confidence: high
Scope-risk: moderate
Directive: Keep seedance_proxy real-person routing pinned to exactly one enabled channel key and never log upstream credentials or signed URLs.
Tested: go test ./service -run 'Test.*RealPerson|TestSeedanceProxyRealPerson|TestSeedanceProxyVerificationJob' -count=1; go test ./model -run 'TestBytePlusRealPerson|TestBytePlusVisualValidation|TestBytePlusAsset' -count=1; go test ./relay/channel/task/modelapiseedance -count=1; go test ./service -run 'TestSeedanceProxyMaterializeSetRematerializesStaleActiveBinding|TestSeedanceProxyAssetBindingReusesActiveBindingAcrossSeedanceModelsOnSameKey|TestAssetBinding' -count=1; compile-only go tests for service/model/controller/router; git diff --cached --check
Not-tested: Full ./service and ./model suites remain affected by pre-existing parallel SQLite test contention; go vet ./... is blocked by the existing missing web/classic/dist embed directory.
@think-back think-back changed the title Revalidate stale Seedance asset bindings before reuse Route channel 156 real-person verification through Seedance Gateway Aug 25, 2026
@KingCesc

Copy link
Copy Markdown

🤖 OpenCodeReview · 评审 增量 768974a6..6a74cdff · 共 2 条

service/seedance_proxy_real_person.go

  • L206-221: [严重] 这里创建了 allowedGroups 但没有把 request.GroupIDs 传给网关,实际只下推了 GroupType。这样如果网关返回同一类型下的其他分组资产,或者分页结果里混入了非目标分组,当前实现会直接把整页请求判失败,导致 156 渠道的资产列表在正常数据下也可能不可用。建议把分组条件同步下推到网关;如果协议暂不支持,也应改为本地过滤后返回可用项,而不是单条越界记录就让整页失败。
query := url.Values{}
query.Set("GroupType", "LivenessFace")
for _, groupID := range request.GroupIDs {
	if groupID = strings.TrimSpace(groupID); groupID != "" {
		query.Add("GroupIDs", groupID)
	}
}
if request.PageNumber > 0 {
	query.Set("PageNumber", strconv.Itoa(request.PageNumber))
}
if request.PageSize > 0 {
	query.Set("PageSize", strconv.Itoa(request.PageSize))
}
if name := strings.TrimSpace(request.Name); name != "" {
	query.Set("Name", name)
}
for _, status := range request.Statuses {
	if status = strings.TrimSpace(status); status != "" {
		query.Add("Statuses", status)
	}
}

service/byteplus_real_person.go

  • L274-287: [严重] 这里把终态同步失败直接升级成了 500,但 finishSeedanceProxyVerificationTerminal 底层会因为并发/重复推进返回 model.ErrAPIIdempotencyCASLost。对于这个“只是状态已被别的请求/任务先推进”的场景,当前实现会把用户请求误报成存储错误,导致已完成的验证页偶发返回 500。建议像 job 逻辑一样把 CAS lost 视为可接受的并发结果,必要时重载 profile 后直接返回 nil。
if terminalStatus := seedanceProxyVerificationTerminalStatus(err); terminalStatus != "" {
			changed, transitionErr := finishSeedanceProxyVerificationTerminal(profile.Id, claimed.Id, terminalStatus, bytePlusAssetNow())
			if transitionErr != nil && !errors.Is(transitionErr, model.ErrAPIIdempotencyCASLost) {
				return realPersonError(types.ErrorCodeRealPersonStorageError, http.StatusInternalServerError)
			}
			if changed {
				reloaded, reloadErr := model.GetBytePlusRealPersonProfileByIDForUser(userID, profile.Id)
				if reloadErr != nil {
					return realPersonError(types.ErrorCodeRealPersonStorageError, http.StatusInternalServerError)
				}
				*profile = *reloaded
			}
			return nil
		}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants