fix(saa): 包级 assets 接通源 sourceProject.assets[]——闭合产消断链(兑现 engine-E2/E3 消费侧 TODO)

gen-done 出包历史上把包级 assets 硬置空(setAssets(emptyList)),丢掉源
gameDefinition 已落库的六类源资产(game_source_project.source_json 顶层
assets[6]),造成产(asset 节点写源)→消(组包)断链。

改 SaaGraphDispatcher.buildCallbackReqVO(in-JVM SAA 路,即 generic/engine
任务真实落包路):新增 extractSourceAssets(s,traceId) 从图终态 K_SOURCE_PROJECT
抽顶层 assets[] 忠实映射进 reqVO.assets;DifyCallbackTxService.buildGamePackage
读 reqVO.getAssets() 即自然带进 GamePackage.assets。

- 形态:源 assetSpec={id,category,ref,provider}(source-project.schema.json
  #/$defs/assetSpec),运行时 gd-runtime 经 gdef.assets 同形消费;geom:<cat> ref
  系几何占位资产引用,原样透传,不伪造包契约 strict AssetSpec 的
  type/url/hash/bytes/mime(无真图/真 sha256,伪造即脏数据)。
- best-effort 非阻断(严格 additive):源缺失/空白/非 JSON/无 assets 键/非数组/
  异常一律回退空集,绝不外抛、绝不打断 SAA 生成;存量无源回调字节零变化。
- 复用既有 parseTrace + TRACE_MAPPER(只读,无共享可变状态),不引新依赖。

不动 AigcGenerateExecutor:440:该处为进程内 clicker/merge 旧路(generic/engine
经 dispatchGeneric 早返,不达此行;worker 回调由独立 reqVO 驱动),作用域无
sourceProject,改它即凭空伪造,故按现状保留(对齐 run_batch.py assets:[])。

验证:mvn -o clean install -pl game-module-aigc-server -am 编译 BUILD SUCCESS;
DifyCallbackServiceImplTest+AigcGenerateExecutorTest 41 tests 0 fail(含
in-process 路 getAssets().isEmpty() 断言仍过,确认未回归)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lili 2026-06-21 10:50:40 +08:00
parent 9d463f0873
commit 490dc6293d

View File

@ -609,7 +609,14 @@ public class SaaGraphDispatcher implements GenerationDispatcher {
DifyCallbackReqVO reqVO = new DifyCallbackReqVO();
reqVO.setTraceId(traceId);
reqVO.setTemplateId(templateId);
reqVO.setAssets(java.util.Collections.emptyList());
// U2/B8 消费侧§5.2 产消接通忠实取已落库源 assets[]六类源资产填充包级 assets替代历史硬置空
// 兑现 SaaStudioNodes 处自标的engine-E2/E3 消费侧本期不做TODO asset 节点已产 assets[6]
// schemaVersion+gameDefinition+顶层 assets game_source_project.source_json此处把同批源 assets
// 忠实带进 GamePackage.assetsDifyCallbackTxService.buildGamePackage reqVO.getAssets()闭合产消断链
// 形态注记 assetSpec={id,category,ref,provider}source-project.schema.json #/$defs/assetSpec运行时
// gd-runtime gdef.assets 同形消费geom:<cat> ref 为几何占位资产引用原样透传不伪造 type/hash/bytes/mime
// //非数组源 assets 回退空集不抛严格非破坏存量无源回调字节零变
reqVO.setAssets(extractSourceAssets(s, traceId));
// gameConfig 最小合法占位engine 路游戏逻辑全在 engineBundlegameConfig 仍不可空GamePackage required
Map<String, Object> gameConfig = new LinkedHashMap<>();
gameConfig.put("templateId", templateId); // 须与包级 templateId 一致一致性校验
@ -763,6 +770,55 @@ public class SaaGraphDispatcher implements GenerationDispatcher {
}
}
/**
* U2/B8 消费侧§5.2 产消接通从图终态 {@code K_SOURCE_PROJECT} 抽顶层 {@code assets[]} 映射为包级 assets
*
* <p><b>兑现 TODO</b> asset 节点已把六类源资产写入 {@code sourceProject} 顶层 {@code assets}
* {@code source-project.schema.json #/$defs/assetSpec}形态 {@code {id,category,ref,provider}}历史上组包侧
* 硬置空集导致产消断链本方法把<b>同一批</b> assets 忠实搬进 {@link DifyCallbackReqVO#getAssets()}
* {@code DifyCallbackTxService.buildGamePackage} 落进 {@code GamePackage.assets}运行时 {@code gd-runtime}
* {@code gdef.assets} source assetSpec 形态消费<b>原样透传不重塑</b>{@code geom:<cat>} 为几何占位
* 资产引用不伪造包契约 strict AssetSpec {@code type/url/hash/bytes/mime}无真图无真 sha256伪造即脏数据
*
* <p><b>best-effort 非阻断严格 additive</b>源缺失/空白/ JSON/ assets /assets 非数组/任意异常
* 一律返回<b>空集</b>绝不外抛绝不打断 SAA 生成与历史恒空集对存量无源回调字节零变化
*
* @param s 图终态可能 null
* @param traceId 贯穿键仅日志
* @return assets 映射出的 {@code List<Map<String,Object>>}忠实透传字段/异常时空集 null
*/
private static java.util.List<Map<String, Object>> extractSourceAssets(OverAllState s, String traceId) {
if (s == null) {
return java.util.Collections.emptyList();
}
try {
// 复用 best-effort 宽松解析null/空白/非法 JSON null源串即 §5.2 落库的 source_json
JsonNode root = parseTrace(s.value(SaaStudioNodes.K_SOURCE_PROJECT, String.class).orElse(""));
if (root == null) {
return java.util.Collections.emptyList();
}
JsonNode assets = root.path("assets");
if (!assets.isArray() || assets.isEmpty()) {
// assets / 非数组 / 空数组 回退空集不抛存量无源回调字节零变
return java.util.Collections.emptyList();
}
// 逐项忠实映射为 Map {id,category,ref,provider} 等全部源字段geom: ref 原样非对象项跳过防御脏数据
java.util.List<Map<String, Object>> out = new java.util.ArrayList<>(assets.size());
for (JsonNode a : assets) {
if (a != null && a.isObject()) {
out.add(TRACE_MAPPER.convertValue(a, new com.fasterxml.jackson.core.type.TypeReference<Map<String, Object>>() {}));
}
}
// 可追溯日志接通了几个源 assets0 表示源里确无对象项等价历史空集不告警
log.info("[saa-dispatch] 包级 assets 接通源 assets[] 共 {} 个(产消接通,兑现 engine-E2/E3 消费侧 TODOtraceId={}", out.size(), traceId);
return out;
} catch (Exception e) {
// 命门抽取/映射异常一律吞 + warn 返空集绝不打断 SAA 生成生成成败由 status/engineBundle 既有逻辑决定
log.warn("[saa-dispatch] 源 assets 抽取失败,包级 assets 回退空集不阻断生成traceId={}", traceId, e);
return java.util.Collections.emptyList();
}
}
/**
* 派生 models 并集{@code K_TOKENS_BY_MODEL} model 名键集 {@code K_ATTEMPTS[].{role:model}}组同形 {role:model} map
*