Merge remote-tracking branch 'refs/remotes/origin/master' into refactor/extract-dep-from-ref-inputer
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m25s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m56s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m51s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m30s
Project CI / Backend tests (pull_request) Successful in 7m17s
Project CI / Native shell tests (pull_request) Successful in 7m34s
Project CI / Frontend tests (pull_request) Successful in 4m4s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m9s
Project CI / Repository checks (pull_request) Successful in 3m36s

# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
This commit is contained in:
2026-09-22 18:51:27 +08:00
14 changed files with 382 additions and 109 deletions
@@ -13,8 +13,18 @@
- 未纳入本次:粘贴解析(未来接入点是 provider 的 `mentionToken` 与既有 `buildContentFromTextTokens`)、`resource-reference-*` CSS 类名重命名(独立机械提交)、扩展变更事件的即时失效。
- 验证方式:定向 `npx vitest run apps/ai-game-creator-shell/tests/resourceReferenceInput.test.tsx` 与受影响宿主用例、`npm run typecheck``npm run check:encoding``npm run check:doc-index``git diff --check`;行为零变化按「改名后芯片显示名自动刷新、打开面板前重读清单、`@`/`$` 候选与键盘交互、附件上限与失败提示文案」逐条对照核验,唯一例外是已裁决的 Skill 可见性缺陷修复。
## 2026-09-22 release 每日调度纳入正式 Full Build 并收集结果
- 背景:原 `Genarrative-Scheduled-Release-Trigger` 只处理 AGC Windows/macOS,且对下游使用 `wait: false` 后立即推进 revision;正式服务端 release 仍只能人工触发,客户端构建失败也不会回传调度器。
- 决策:release 调度同时计算 `AGC``FullBuild` 两条 scope。服务端相关路径变化时以 `DEPLOY_TARGET=release``CONFIRM_RELEASE_DEPLOY_AGENT=true` 触发 `Genarrative-Full-Build-And-Deploy`;客户端相关路径变化时保持统一发号并触发 Windows/macOS release。Full Build、Windows、macOS 三路均等待结果并汇总,只有成功的 lane 才推进对应 `.jenkins-last-release-full-revision` / `.jenkins-last-release-agc-revision`,失败 lane 在下一轮单独补发,避免重复部署已成功的服务端版本。`DATABASE_BACKUP_MODE` 默认 `async`,并显式提供 `STDB_API_ROLLOUT_MODE` / approvers 以保留正式维护门禁。
- 原因:正式 release 需要与 dev 小时调度隔离,同时不能继续依赖人工触发服务端;lane-level 成功状态可区分“已成功但另一 lane 失败”的部分发布,避免下一次调度重复发布 Full Build。
- 影响范围:`jenkins/Jenkinsfile.scheduled-release-trigger``jenkins/scheduled-release-trigger-job-config.xml``scripts/check-production-ops-guardrails.mjs`、开发运维文档与共享开发工作流。
- 验证方式:生产运维门禁检查 Full Build release 参数、双 scope、`wait: true` 结果聚合、三路分支和成功后才写 lane revision;并用 Jenkins 具体构建验证 Full Build/AGC 的 build number 与结果能回传到调度 Job。
## 2026-09-22 AGC release 增加每日调度,dev 调度保持双平台
> 已被上一条决策取代:release 调度现已纳入正式服务端 Full Build。
- 背景:原有 `Genarrative-Scheduled-Revision-Trigger` 每小时跟随 revision 发布 dev 客户端,但没有对应的 release 渠道自动入口;Mac 节点此前已纳入小时 dev 调度,需要避免新增 release 调度时再退回 Windows-only。
- 决策:新增 `Genarrative-Scheduled-Release-Trigger`,每天 04:00 检查 `SOURCE_BRANCH`,使用独立的 `.jenkins-last-release-revision` 与客户端相关路径白名单;上一轮 release 调度后有客户端变更时,先经 `Genarrative-Agc-Global-Version-Issue` 发统一总号,再以同一固定 `COMMIT_HASH` 触发 `Genarrative-Agc-Windows-Build``Genarrative-Agc-MacOS-Build``AGC_UPDATE_CHANNEL=release` 分区,Mac 继续带 `SKIP_IF_SUPERSEDED=true`。小时 dev 调度保持同时触发 Windows 与 macOS dev。
- 原因:release 与 dev 是不同渠道和发布节奏,不能靠同一个小时 Job 隐式切换;独立 Job 能分别去重、记录状态和审计发号。release 调度不触发 Full Build,避免把桌面客户端发布与线上全栈部署绑定。
@@ -92,7 +92,7 @@ SpacetimeDB 任务统一先读取 `.codex/skills/genarrative-spacetimedb/SKILL.m
## Jenkins 定时版本调度
定时与版本比较收口到两条调度器:`Genarrative-Scheduled-Revision-Trigger` 每小时处理 dev 渠道,变化时触发 Full Build、AGC Windows/macOS dev,并让两个客户端平台共用同一个总版本号;`Genarrative-Scheduled-Release-Trigger` 每天 04:00 处理 AGC release 渠道,只在上一轮 release 调度后出现客户端相关路径变化时`Genarrative-Agc-Global-Version-Issue` 发同一个总号并触发 AGC Windows/macOS release。各下游 Job 都不得自带 `triggers` / `cron`,也不得在管线内再做一套版本去重;`npm run check:production-ops` 会拦住回退。macOS 节点是日常办公机,两条调度触发它时都置 `SKIP_IF_SUPERSEDED=true`:节点离线期间排队的旧构建在恢复后会自行让位,不发布过期版本。调度状态与生效步骤见 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
定时与版本比较收口到两条调度器:`Genarrative-Scheduled-Revision-Trigger` 每小时处理 dev 渠道,变化时触发 Full Build、AGC Windows/macOS dev,并让两个客户端平台共用同一个总版本号;`Genarrative-Scheduled-Release-Trigger` 每天 04:00 按服务端与客户端两条独立 scope 处理 release,服务端变化时用 `DEPLOY_TARGET=release` 触发正式 Full Build,客户端变化时经 `Genarrative-Agc-Global-Version-Issue` 发同一个总号并触发 AGC Windows/macOS release。两条调度都等待并汇总下游,按成功 lane 推进 revision;失败 lane 下一轮单独补发。各下游 Job 都不得自带 `triggers` / `cron`,也不得在管线内再做一套版本去重;`npm run check:production-ops` 会拦住回退。macOS 节点是日常办公机,两条调度触发它时都置 `SKIP_IF_SUPERSEDED=true`:节点离线期间排队的旧构建在恢复后会自行让位,不发布过期版本。调度状态与生效步骤见 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## Gitea CI 依赖闭合
@@ -100,6 +100,8 @@ Gitea Rust 缓存自动维护由宿主 `genarrative-ci-cache.timer` 收集同一
修改 Gitea workflow 的 job 显示名称、ID 或缓存导出组时,必须同步维护器的 `JOBS` / `RUST_JOB_IDS``test_gitea_cache_maintenance.py` 直接对照实际 workflow 检查全集和导出映射,避免自动刷新或镜像验收因名单漂移长期等待。维护器 `Api.request``method` 是必填关键字参数,GET 也必须显式指定,不根据 body 推断请求方法。
Gitea 缓存部署必须区分网络:runner 的 RPC 走 `gitea-runner-fetch-gate:8080`;内层 job 的 checkout/上传走映射到 `172.30.0.3``http://genarrative-station/git`;宿主专用 clone 走 `http://127.0.0.1:3003`。不要把 runner 可达的 `gitea:3000` 配给 job。内层 Docker 使用 `10.240.0.0/16`、每 job `/24` 的默认地址池,避开外层 `172.30/172.31` 网段;恢复领取前必须在真实 job 网络里验证 checkout 与 Gitea API,不能只验证 FetchTask。具体配置与遗留空网络处理见 `deploy/container/README.md`
AGC Rust 两条 lane、crates、smoke、Backend 和桌面壳测试使用镜像内可信 sccache 对象快照;Native shell release step 显式清空双 wrapper,前端/repository checks 不启用。仅首次人工 bootstrap 时,维护者通过 `scripts/build-gitea-rust-cache.sh` 从远端 master 在限额、无宿主挂载的临时容器中按实际 cwd/profile/目标预热全部测试组,仅编译、不执行测试/应用;后端 workspace 与 spacetime-module 保持独立,AGC 的三个 cwd 入口之间清理预热 target,防止 fresh 判断漏产缓存键。最终镜像只追加 sccache、对象和来源元数据,不包含源码或 target。容量上限 4 GiB,不替代宿主旧镜像/归档清理。PR 只写当前容器层、不回传,不开放 Docker API/发布权限;继续禁用 incremental。`ci-rust-cache.sh` 在快照缺失、工具链不符或 wrapper 探测失败时直接编译,并隔离远程缓存配置和 daemon。分片日志记录编译耗时,收尾输出命中统计;两个 lane 的测试和前置检查不同,耗时差不是严格 A/B。线上存在活跃 CI 时不得重启 runner 或切换标签;全组启用前须刷新完整快照并逐组验证,详见开发运维文档。
`.gitea/workflows/project-ci.yml` 的客户端门禁拆成 lane 与功能 job,每个 job 只预热自己会构建的那几份依赖:`AI game creator shell Rust lane 1/2``lane 2/2` 各自预取一次 AGC 壳 manifest,并顺序运行两片 Rust bin 单测;`AI game creator shell Rust smoke` 同样只预取 AGC 壳 manifest`agent-run` smoke 会用 `src-tauri/Cargo.toml` spawn `cargo run`),`AI game creator shell Rust crates` 预取 `server-rs/Cargo.toml` 与独立 crate`Native shell tests` 预取桌面壳与 AGC 壳 manifest`AI game creator shell web tests` 不触碰 Cargo,不预热。两条 Rust lane、smoke job 与 crates job 只用 cargo 与 node 内建模块,因此不执行 `npm ci`。两个被 `server-rs/Cargo.toml` 排除、且没有提交 `Cargo.lock` 的独立 crate`agent-runtime-core``agent-runtime-orchestration`)只能在 `AI game creator shell Rust crates` 里用不带锁标志的 fetch。AGC 壳的 bin target 单测(约 2466 条)由 `apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs` 编译后按 `--list` 名单分 4 片:每次分片调用用 `--shard-index=<i>` 只跑自己那片,片内保持 `--test-threads=1` 并使用独立 `TMPDIR`;两条 lane 之间并发,lane 内顺序运行两片,避免重复依赖预热和同一容器内多进程争抢。不要改回「一个 job 内多进程并行这几片」——同一容器里它们会争抢共享 `HOME`、target 目录与固定临时路径,实测比整套串行还慢。每个分片调用都会自校验「片并集等于全集且互斥」,因此改分片规则不会静默漏跑。Backend host workspace tests 使用 `cargo test --locked --workspace --exclude spacetime-module --no-fail-fast`,避免 `spacetime-module``spacetime-types` feature 统一污染普通领域 crate 的 host 测试;随后单独执行 `cargo test --locked -p spacetime-module --no-fail-fast`,由 `spacetime-module/src/active.rs` 在 host 测试构建期间提供仅测试期的 SpacetimeDB ABI 链接支持,使该 crate 的纯单元测试也纳入 Backend 门禁。`spacetime-module` 的 reducer / procedure 运行时行为仍必须通过真实 SpacetimeDB runtime/integration harness 验证,host 链接支持不得被当作运行时替身。Backend 另外执行 `cargo check --locked -p spacetime-module` 验证模块源码。AGC 壳检查还会运行 `platform-llm``shared-contracts` 的 server-rs workspace 测试,这些命令以及 AGC 壳测试必须带 `--locked`,避免在测试阶段重新解析 registry index;锁文件发生变化时应先更新受信任 CI 镜像缓存,再重跑门禁。
@@ -1,5 +1,12 @@
# 踩坑与排障记录
## AGC 素材直传的 OSS 权限必须同步到 Native shell 契约检查
- **现象**Native shell CI 在 `check:native-shells:contract` 的 HTTP scope 检查失败,尚未准备 Rust 缓存;后续导出步骤正常退出但实际跳过,导致 master 缓存产物缺组、自动镜像刷新等待。
- **原因**`capabilities/main.json` 已为封面与截图直传加入 `https://*.aliyuncs.com/*``scripts/check-native-shells.mjs` 的精确白名单仍只有五项,且误以为更新下载迁至原生 updater 后就不再需要 OSS 权限。`assetDirectUpload.ts` 仍通过 Tauri HTTP 插件执行素材直传。
- **处理**:契约期望同步包含现有 OSS HTTPS 项,保留完整白名单精确比较;不得删除业务所需权限或改成任意 URL 放行。更新下载与素材直传是不同调用链,变更能力配置时同步检查调用方和契约。
- **验证**:运行 `npm run check:native-shells:contract``apps/ai-game-creator-shell/tests/assetDirectUpload.test.ts`;缓存完整性以实际 artifact 和导出日志为准,不能仅看上传 step 是否成功。
## release 安装包文件名中的编码空格不能按控制字符拒绝
- **现象**:release 环境点击“下载客户端”只显示无法获取最新版本,`GET /api/client-downloads` 返回 `502 UPSTREAM_ERROR`dev 环境正常。
@@ -50,6 +57,14 @@ Copy Artifact 插件在**非 SYSTEM 认证**下按「认证用户」判权:只
`Genarrative-Manual-Build-And-Deploy``DEPLOY_TARGET=release` 只控制 Stdb / API / Web 全量发布,不会自动成为 AGC 的 `AGC_UPDATE_CHANNEL`。2026-09-21 的手工发布 #10 就因此让 Windows #107 与 macOS #16 使用默认 `dev`,把 `0.1.95` 上传到 `agc/dev-win``agc/dev-mac`,而 `agc/release-win/latest.json``agc/release-mac/latest.json` 保持 404。现行口径:手工入口按 `release -> release``development -> dev` 同时给 Windows 与 macOS AGC Build 传 `AGC_UPDATE_CHANNEL`;补发已烧号的同一版本时用相同 `AGC_RELEASE_VERSION` 直接重跑两条 AGC Job,不重新发号。OSS 发布对象是 `agc/<channel>-win|mac/`,不存在 `agc/release/` 这一层。
## Jenkins 渠道参数会进入 Node 测试进程,默认值测试必须隔离环境
- **现象**release 渠道的 macOS Job 在 Tauri 构建前的 `build-release.test.mjs` 失败,唯一差异是 `no-bundle smoke skips version writes and manifest generation` 期望 `dev`、实际得到 `release`;构建尚未进入 Rust/Tauri 编译。
- **原因**Jenkins Job 参数 `AGC_UPDATE_CHANNEL=release` 会成为子进程环境变量,而该测试直接依赖未设置的默认渠道,却只显式传了 `--target``--no-bundle`
- **处理(现行口径)**:断言默认渠道、默认目标等环境派生值的测试,必须用现有 `withEnv` 显式清除相关变量;构建入口继续按 `AGC_UPDATE_CHANNEL` 选择渠道,不为迁就测试改生产逻辑。
- **验证**:用 `AGC_UPDATE_CHANNEL=release node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs apps/ai-game-creator-shell/scripts/cargo-features.test.mjs` 复现并验证修复,未设置变量时也必须通过。
- **关联**`apps/ai-game-creator-shell/scripts/build-release.test.mjs``jenkins/Jenkinsfile.ai-game-creator-shell-macos-build`
## 同一条链路两处上限不一致:平台合法产出被客户端整条丢弃
- 现象:客户端报「生成素材失败:platform-generation-result-unknown: 异步生成完成结果无法绑定到 operationIdExternal Editor 旧同步结果的图集切片超过 64 个」,而平台侧这次生成**其实已经成功并切完图**(任务账本耗时正常、`assetId` 为空、没有任何素材落盘,付费产物被丢)。
@@ -137,8 +137,8 @@
- 发布入口只解析一次目标,优先级为 CLI `--target value` / `--target=value` / `-t value``AGC_BUILD_TARGET`、Windows 默认值;重复/空目标与不支持目标失败关闭。版本高水位、构建 feature/渠道端点、bundle 路径、产物后缀、清单平台键及摘要必须消费同一个发布上下文,不能分别回读默认目标。
- 渠道由 `AGC_UPDATE_CHANNEL` 显式指定,默认 devWindows 与 macOS 目标均支持 dev、release 和自定义渠道,目标校验独立进行。
- 渠道 `--config` 在 Tauri 构建前最后合并,同时注入 `productName``identifier` 与 updater 端点:安装身份与更新端点必须来自同一个渠道,不能各自回读默认值。macOS 发布入口构建 `*.app`、updater 归档与 DMG 前先按发布渠道解析产品名,产物名一律派生而不写死。
- 定时调度只在本轮到达的提交含 AGC 相关路径(客户端、共享包、`server-rs/crates`、AGC 插件、桌面壳图标、根依赖清单)时才触发渠道发布;纯文档或流水线自身的提交只跑 Full Build,不推高客户端版本号。判定失败或勾选强制触发时按"需要发布"处理。
- 更新摘要自动生成:发布脚本用渠道清单里的 `commit` 字段(上一次发布的提交)到本次提交之间、且只覆盖客户端相关路径的提交列表生成 `notes`(每条 `- 提交标题(短 SHA`,最多 12 条、主题 80 字、整体 900 字,超出折叠或截断),同时写入旧协议清单的 `releaseNotes` 和归档文件 `release-notes.txt``AGC_UPDATE_RELEASE_NOTES` 非空时以手动文案为准;无法判定起点(缺少上次 `commit` 或本地没有该提交)时不写摘要。清单缺少 `commit` 时回退用上一次成功构建的 `COMMIT_HASH`CI 通过 `AGC_UPDATE_PREVIOUS_COMMIT` 传入)作为锚点,因此首次启用摘要或更换渠道后也能立即产出摘要。锚点仍不可得(清单读取失败或没有 CI 锚点)时降级为「最近客户端改动」列表并注明可能与上一版重复 —— 摘要属于附注,任何情况下都不允许因为它让发布失败。
- 定时调度分别判断服务端与客户端 scope:dev 小时调度在提交含 AGC 相关路径时发布对应渠道,纯文档或流水线自身的提交只跑 Full Build;release 每日调度在服务端相关路径变化时发布正式 Full Build,在 AGC 相关路径变化时发布 release 客户端,并在同一调度内等待、汇总各 lane 结果,失败 lane 下一轮补发。判定失败或勾选强制触发时按"需要发布"处理。
- 更新摘要自动生成:发布脚本用渠道清单里的 `commit` 字段(上一次发布的提交)到本次提交之间、且只覆盖客户端相关路径的提交列表生成 `notes`(每条 `- 提交标题`标题只取 commit message 第一行并忽略后续说明行;最多 12 条、主题 80 字、整体 900 字,超出折叠或截断),同时写入旧协议清单的 `releaseNotes` 和归档文件 `release-notes.txt``AGC_UPDATE_RELEASE_NOTES` 非空时以手动文案为准;无法判定起点(缺少上次 `commit` 或本地没有该提交)时不写摘要。清单缺少 `commit` 时回退用上一次成功构建的 `COMMIT_HASH`CI 通过 `AGC_UPDATE_PREVIOUS_COMMIT` 传入)作为锚点,因此首次启用摘要或更换渠道后也能立即产出摘要。锚点仍不可得(清单读取失败或没有 CI 锚点)时降级为「最近客户端改动」列表并注明可能与上一版重复 —— 摘要属于附注,任何情况下都不允许因为它让发布失败。
- 清单里的 `commit` 是非标准字段:更新插件忽略未知字段,发布脚本用它定位下一次摘要的起点。
- 上传:安装包与 `.sig` 上传到 `agc/<channel>-win|mac/<version>/`,清单以 `--force` 覆盖上传到对应分区的 `latest.json`,保证 latest 指针与清单内 URL 指向已存在的对象。
- 首装发布:发布脚本生成 `downloads`Windows 复用已选 NSIS `.exe`,Mac 选择本次版本和目标架构匹配的非空 `.dmg`;缺失、歧义或版本/架构不匹配时失败,不发布带悬空地址的清单。上传顺序为更新包、签名及首装包全部成功后再更新渠道清单,Windows 相同对象只上传一次。`dry-run` 不写 OSS。各渠道独立写自己的清单,由 BFF 汇总,Windows 与 Mac 发布不会覆盖彼此的下载项;Mac 跨架构合并仍遵循现有单架构发布约束。
@@ -39,7 +39,7 @@ AGC 客户端此前按「渠道各自比高水位自增」发号:`dev-win` 与
- 发号模块:`apps/ai-game-creator-shell/scripts/agc-global-version.mjs`(读总号 / 播种 / 发号 / 写后回读 / 渠道高水位断言 / dry-run 预览)。
- 发号入口:`apps/ai-game-creator-shell/scripts/issue-global-version.mjs`CI 与本地共用,输出固定为 `AGC_GLOBAL_VERSION=<version>`)。
- 构建侧:`build-release.mjs``prepareReleaseVersion()` 优先采用传入总号,未传入时现场发号;`resolveRemoteHighWaterVersion()` 降级为断言来源,只用于「请求号低于本渠道清单版本即失败关闭」。
- CI`jenkins/Jenkinsfile.agc-global-version-issue` + `jenkins/agc-global-version-issue-job-config.xml`dev 调度管线 `jenkins/Jenkinsfile.scheduled-revision-trigger`、每日 release 调度管线 `jenkins/Jenkinsfile.scheduled-release-trigger` 与手动管线先调发号 Job,再把号透传给 AGC Build。
- CI`jenkins/Jenkinsfile.agc-global-version-issue` + `jenkins/agc-global-version-issue-job-config.xml`dev 调度管线 `jenkins/Jenkinsfile.scheduled-revision-trigger`、每日 release 调度管线 `jenkins/Jenkinsfile.scheduled-release-trigger` 与手动管线先调发号 Job,再把号透传给 AGC Buildrelease 调度的服务端 Full Build 不使用 AGC 总号,只使用固定 `COMMIT_HASH` 与 Full Build 自身版本
- 发号 Job 的 Copy Artifact 采用生产权限模式,`options` 里的 `copyArtifactPermission(...)` 必须显式列出**全部**消费者:小时 dev 调度 `Genarrative-Scheduled-Revision-Trigger`、每日 release 调度 `Genarrative-Scheduled-Release-Trigger` 与手动发布 `Genarrative-Manual-Build-And-Deploy`。用户触发的构建按「认证用户」判权(SYSTEM 定时构建短路放行),漏列时手动发布会报 `Unable to find project for artifact copy: Genarrative-Agc-Global-Version-Issue`,且失败点在发号之后。改完该 `options` 后必须先单独跑一次发号 Job,让 Declarative Pipeline 把 Job property 写回 Jenkins。
## 不改的东西
File diff suppressed because one or more lines are too long