diff --git a/apps/ai-game-creator-shell/src-tauri/Cargo.lock b/apps/ai-game-creator-shell/src-tauri/Cargo.lock index be749d083..2044029ff 100644 --- a/apps/ai-game-creator-shell/src-tauri/Cargo.lock +++ b/apps/ai-game-creator-shell/src-tauri/Cargo.lock @@ -6227,8 +6227,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ts-rs" version = "12.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756050066659291d47a554a9f558125db17428b073c5ffce1daf5dcb0f7231d8" +source = "git+https://github.com/Aleph-Alpha/ts-rs?rev=fd8679ef20f866f747c046b30bd29c15e3d7a003#fd8679ef20f866f747c046b30bd29c15e3d7a003" dependencies = [ "thiserror 2.0.18", "ts-rs-macros", @@ -6237,9 +6236,9 @@ dependencies = [ [[package]] name = "ts-rs-macros" version = "12.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d90eea51bc7988ef9e674bf80a85ba6804739e535e9cab48e4bb34a8b652aa" +source = "git+https://github.com/Aleph-Alpha/ts-rs?rev=fd8679ef20f866f747c046b30bd29c15e3d7a003#fd8679ef20f866f747c046b30bd29c15e3d7a003" dependencies = [ + "indexmap 2.14.0", "proc-macro2", "quote", "syn 2.0.118", diff --git a/apps/ai-game-creator-shell/src-tauri/Cargo.toml b/apps/ai-game-creator-shell/src-tauri/Cargo.toml index f5cc8b673..682ea0ab5 100644 --- a/apps/ai-game-creator-shell/src-tauri/Cargo.toml +++ b/apps/ai-game-creator-shell/src-tauri/Cargo.toml @@ -21,7 +21,10 @@ shared-contracts = { path = "../../../server-rs/crates/shared-contracts", defaul tauri-build = { version = "2.6.2", features = [] } [dependencies] -ts-rs = "12.0.1" +# 与 server-rs 同一口径:已发布的 ts-rs 12.0.1 解析不了 enum 级 `serde(deny_unknown_fields)` +# (见 agent/direct_codex_user_item/model.rs),上游修复 #491 未发版,故固定到该 commit; +# 等 crates.io 发布 > 12.0.1 后改回 registry 版本。不要改用 `no-serde-warnings` 关告警。 +ts-rs = { git = "https://github.com/Aleph-Alpha/ts-rs", rev = "fd8679ef20f866f747c046b30bd29c15e3d7a003" } typed_floats = { version = "1.0.7", features = ["serde"] } nalgebra = { version = "0.35.0", features = ["serde-serialize"] } agent-runtime-core = { path = "../../../server-rs/crates/agent-runtime-core" } diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 1b57435a0..2b891309d 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -2,6 +2,16 @@ > 用途:记录已经确认、会影响后续开发的长期技术/产品/协作决策。短期讨论不要写在这里。 > 当前口径:历史条目的旧路径、旧版本和已退役对象只用于追溯,不构成现行实现依据;如与当前代码或 `docs/README.md` 冲突,以当前代码和最新专题文档为准。 +## 2026-09-19 `ts-rs` 固定到上游 #491 修复 commit,保留 enum 级 `serde(deny_unknown_fields)` + +- 背景:`shared-contracts` 的 `Model3dMultiviewInputs` 用内部 tag 枚举 + enum 级 `#[serde(deny_unknown_fields)]` 严格拒绝未知字段,但每次编译都会打印一条没有 span 的 `ts-rs failed to parse this attribute` 告警。查明 ts-rs 12.0.1 只在 `StructAttr` 里静默忽略 `deny_unknown_fields`,`EnumAttr` 没有该分支;上游 PR #491 已修复但**尚未发版**(crates.io 最新仍是 12.0.1,GitHub 最新 tag 仍是 v12.0.0)。 +- 决策一:保留 enum 级 `deny_unknown_fields`,serde 侧严格性不动;新增契约用例 `server-rs/crates/shared-contracts/tests/model3d_multiview_request_contract.rs` 钉住“变体里的未知字段必须被拒绝”,防止后续为消警改成非严格模式。 +- 决策二:两个 workspace(`server-rs`、`apps/ai-game-creator-shell/src-tauri`)的 `ts-rs` 固定到上游修复 commit `fd8679ef20f866f747c046b30bd29c15e3d7a003`,而不是启用 `no-serde-warnings`:后者会把 `deserialize_with` 等**真实**未支持的 serde 属性一并静默,掩盖 Rust/TS 类型漂移。 +- 退出条件:等 crates.io 发布 > 12.0.1 后改回 `ts-rs = "12.0.1"`(或更高 registry 版本)并删除 commit 固定;届时重新跑一次 `npm run contracts:model3d:generate` + prettier 确认绑定零 diff。 +- 影响面:两个 workspace 的 `Cargo.lock` 把 `ts-rs` / `ts-rs-macros` 从 registry 源改成 git 源(新增 `indexmap` 依赖);CI 与本地构建需要能访问 GitHub(仓库已因 `tripo3d-sdk` 具备该前提)。 +- 验证:`cargo check -p shared-contracts` 与 `cargo check --locked --bin genarrative-ai-game-creator-shell` 均无 `deny_unknown_fields` 告警;`cargo test -p shared-contracts`(101+2+3+2 全绿)后 `npm run contracts:model3d:generate` + prettier 使 `packages/shared/src/contracts/model3d/` 零 diff;`npm run check:encoding`、`git diff --check`、`rustfmt --check` 通过。AGC 侧保留 1 条既有的 `deserialize_with` 告警(`ui_editor/component/image.rs`)。 +- 关联文档:[踩坑与排障记录](pitfalls.md)、[Tripo 3D模型Provider集成](../../technical/【技术方案】Tripo 3D模型Provider集成-2026-09-18.md)。 + ## 2026-09-17 图集切分模式改为显式声明 - 决策:`sliceMode` 在图标图集生成入口成为必填字段且不保留任何默认值。省略、`null` 或空字符串必须在引用解析、定价、入队和 provider / OSS 副作用之前返回 `400`(`field=sliceMode`);`grid` 必须同时提供 `gridX`/`gridY`,`connected-components` 不得携带网格尺寸,二者矛盾同样在副作用前失败关闭。 diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index d59e89037..a8c482d06 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -4,6 +4,14 @@ Vite 默认监听应用根下的 Rust `src-tauri/target`,构建产物较多时会创建大量 Windows 文件监听器。AGC 配置通过 `server.watch.ignored: ['**/src-tauri/target/**']` 排除此目录,不关闭业务源码、CSS、共享组件监听或 HMR。排查时区分后端就绪、Vite 扫描和原生窗口首绘;监听目录回归不能代替实机首绘测量,验证入口见本地开发运维文档。 +## 2026-09-19 ts-rs 的 “failed to parse serde attribute” 不代表 `deny_unknown_fields` 失效,不要靠删属性或全局静默消警 + +- **现象**:编译 `shared-contracts`(以及 AGC 壳)稳定打印一条**没有 span**的告警:`warning: failed to parse serde attribute / | deny_unknown_fields | = note: ts-rs failed to parse this attribute. It will be ignored.`。因为不带文件名和行号,容易被当成“这条属性没生效”,进而被顺手删掉。 +- **成因**:告警只来自 ts-rs 宏对 serde 属性的**解析**:`StructAttr` 里有 `"deny_unknown_fields" | "default"` 的静默分支,`EnumAttr` 没有,所以只有 enum 级 `#[serde(deny_unknown_fields)]` 会告警。**serde 侧该属性一直生效**:内部 tag 枚举的 struct 变体会真的拒绝未知字段(契约用例见 `server-rs/crates/shared-contracts/tests/model3d_multiview_request_contract.rs`)。TS 绑定本来就无法表达“拒绝未知字段”,告警与绑定内容无关。 +- **处理(现行口径)**:保留属性,禁止用 `no-serde-warnings` feature 消警(它会连同 `deserialize_with` 这类**真实**未支持的 serde 属性一起静默)。两个 workspace 的 `ts-rs` 固定到上游修复 commit `fd8679ef20f866f747c046b30bd29c15e3d7a003`(PR #491,`EnumAttr` 增加 `"deny_unknown_fields" | "default"` 分支),等 crates.io 发布 > 12.0.1 后改回 registry 版本并删掉固定。 +- **验证**:`cargo check -p shared-contracts`(server-rs)与 `cargo check --locked --bin genarrative-ai-game-creator-shell`(AGC)都不再出现 `deny_unknown_fields` 告警;`cargo test -p shared-contracts` 全绿且 `npm run contracts:model3d:generate` + `npx prettier --write packages/shared/src/contracts/model3d` 后绑定零 diff(固定 commit 不改变生成结果)。AGC 侧仍会打印 1 条 `deserialize_with = "deserialize_fill_amount"` 告警(`ui_editor/component/image.rs`,字段类型仍是 `f32`,属既有无害告警)。 +- **关联**:`server-rs/Cargo.toml`、`apps/ai-game-creator-shell/src-tauri/Cargo.toml`(两处 `ts-rs` 固定)、`server-rs/crates/shared-contracts/src/model3d/multiview_to_model/request.rs`。 + ## 2026-09-17 AGC 输入盒的「推理档」弹层被祖先裁切:要放开裁切而不是挪弹层 - **现象**:窄窗口下(视口 ≤1000px 时右侧对话面板只有 280px 宽)点开输入盒右下角的「推理档」,弹层是个**空盒子**:档位文字(默认 / 低 / 中 / 高 / 最高)整片看不见,只剩一个方框。 diff --git a/server-rs/Cargo.lock b/server-rs/Cargo.lock index e74fa84aa..89c3e8184 100644 --- a/server-rs/Cargo.lock +++ b/server-rs/Cargo.lock @@ -6314,8 +6314,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ts-rs" version = "12.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756050066659291d47a554a9f558125db17428b073c5ffce1daf5dcb0f7231d8" +source = "git+https://github.com/Aleph-Alpha/ts-rs?rev=fd8679ef20f866f747c046b30bd29c15e3d7a003#fd8679ef20f866f747c046b30bd29c15e3d7a003" dependencies = [ "thiserror 2.0.18", "ts-rs-macros", @@ -6324,9 +6323,9 @@ dependencies = [ [[package]] name = "ts-rs-macros" version = "12.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d90eea51bc7988ef9e674bf80a85ba6804739e535e9cab48e4bb34a8b652aa" +source = "git+https://github.com/Aleph-Alpha/ts-rs?rev=fd8679ef20f866f747c046b30bd29c15e3d7a003#fd8679ef20f866f747c046b30bd29c15e3d7a003" dependencies = [ + "indexmap 2.14.0", "proc-macro2", "quote", "syn 2.0.118", diff --git a/server-rs/Cargo.toml b/server-rs/Cargo.toml index 574747abf..271dbf419 100644 --- a/server-rs/Cargo.toml +++ b/server-rs/Cargo.toml @@ -117,7 +117,13 @@ tokio = "1" tokio-stream = "0.1" tokio-tungstenite = "0.27" tripo3d-sdk = { git = "https://github.com/VAST-AI-Research/tripo-rust-sdk.git", rev = "1986be88627b04b1fa582a795bf6ac15ae6e0c6d" } -ts-rs = "12.0.1" +# 已发布到 crates.io 的 ts-rs 12.0.1 解析不了 enum 级 `serde(deny_unknown_fields)`, +# 编译时会打印一条没有 span 的 “failed to parse serde attribute” 告警(该属性仍由 serde 生效)。 +# 上游修复 #491 尚未发版(最新 tag 仍为 v12.0.0,main 版本号还是 12.0.1),因此固定到修复 commit; +# 等 crates.io 发布 > 12.0.1 后改回 registry 版本并删除本固定。不要改用 +# `no-serde-warnings` 关告警:那会连同真实未支持的 serde 属性一起静默。 +# 严格性契约测试见 shared-contracts/tests/model3d_multiview_request_contract.rs。 +ts-rs = { git = "https://github.com/Aleph-Alpha/ts-rs", rev = "fd8679ef20f866f747c046b30bd29c15e3d7a003" } tower = "0.5" tower-http = "0.6" tracing = "0.1" diff --git a/server-rs/crates/shared-contracts/src/model3d/multiview_to_model/request.rs b/server-rs/crates/shared-contracts/src/model3d/multiview_to_model/request.rs index d91603a4f..33717b182 100644 --- a/server-rs/crates/shared-contracts/src/model3d/multiview_to_model/request.rs +++ b/server-rs/crates/shared-contracts/src/model3d/multiview_to_model/request.rs @@ -8,8 +8,11 @@ use crate::model3d::common::{ use super::MODEL3D_TS_EXPORT_DIR; #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -// 注:ts-rs 12 不支持 enum 级 deny_unknown_fields,构建时会打印一条 -// “failed to parse serde attribute” 告警;该属性仍由 serde 生效,生成的 TS 绑定不受影响。 +// 注:该属性由 serde 生效,JSON 侧会拒绝这些变体里的未知字段 +// (见 tests/model3d_multiview_request_contract.rs);TS 绑定无法表达这份严格性。 +// 已发布版 ts-rs 12.0.1 的 EnumAttr 解析不了它,会打印一条没有 span 的 +// “failed to parse serde attribute” 告警,因此 workspace 固定到上游修复 commit(#491)。 +// 不要为了消警删掉该属性,也不要改用 `no-serde-warnings` 一并静默其它未支持的属性。 #[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)] #[derive(ts_rs::TS)] #[ts(export, export_to = MODEL3D_TS_EXPORT_DIR)] diff --git a/server-rs/crates/shared-contracts/tests/model3d_multiview_request_contract.rs b/server-rs/crates/shared-contracts/tests/model3d_multiview_request_contract.rs new file mode 100644 index 000000000..b3802bcac --- /dev/null +++ b/server-rs/crates/shared-contracts/tests/model3d_multiview_request_contract.rs @@ -0,0 +1,78 @@ +use serde_json::json; +use shared_contracts::model3d::common::Model3dModelVersion; +use shared_contracts::model3d::multiview_to_model::{ + Model3dMultiviewInputs, Model3dMultiviewToModelRequest, +}; + +#[test] +fn multiview_inputs_accepts_declared_fields_and_camel_case_tag() { + let inputs: Model3dMultiviewInputs = serde_json::from_value(json!({ + "kind": "views", + "front": "front.png", + "left": "left.png" + })) + .expect("已声明字段应可反序列化"); + + match inputs { + Model3dMultiviewInputs::Views { front, left, .. } => { + assert_eq!(front, "front.png"); + assert_eq!(left.as_deref(), Some("left.png")); + } + Model3dMultiviewInputs::TaskId { .. } => panic!("kind=views 不应解成 taskId 变体"), + } + + let task: Model3dMultiviewInputs = serde_json::from_value(json!({ + "kind": "taskId", + "taskId": "tripo-task-1" + })) + .expect("taskId 变体使用 camelCase 字段名"); + + assert!(matches!(task, Model3dMultiviewInputs::TaskId { .. })); +} + +#[test] +fn multiview_inputs_reject_unknown_fields_inside_variants() { + // enum 级 `deny_unknown_fields` 由 serde 生效,ts-rs 12 无法在 TS 绑定里表达; + // 这条用例钉住该严格性,防止为消除 ts-rs 告警而把它改成非严格模式。 + let error = serde_json::from_value::(json!({ + "kind": "views", + "front": "front.png", + "left": "left.png", + "back": "back.png", + "right": "right.png", + "unexpected": true + })) + .expect_err("变体里出现未知字段应被拒绝"); + + assert!( + error.to_string().contains("unknown field"), + "未知字段错误应指出 unknown field,实际为:{error}" + ); + + let error = serde_json::from_value::(json!({ + "kind": "taskId", + "taskId": "tripo-task-1", + "unexpected": true + })) + .expect_err("taskId 变体里出现未知字段应被拒绝"); + + assert!( + error.to_string().contains("unknown field"), + "未知字段错误应指出 unknown field,实际为:{error}" + ); +} + +#[test] +fn multiview_request_rejects_unknown_top_level_fields() { + let error = serde_json::from_value::(json!({ + "inputs": { "kind": "taskId", "taskId": "tripo-task-1" }, + "model": Model3dModelVersion::H31.as_str(), + "unexpected": true + })) + .expect_err("请求体顶层出现未知字段应被拒绝"); + + assert!( + error.to_string().contains("unknown field"), + "未知字段错误应指出 unknown field,实际为:{error}" + ); +}