From fdc14404b2090881c0b3de264fe264398f00c25e Mon Sep 17 00:00:00 2001 From: kdletters Date: Tue, 22 Sep 2026 16:31:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20AGC=20release=20=E6=AF=8F?= =?UTF-8?q?=E6=97=A5=E8=B0=83=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 Genarrative-Scheduled-Release-Trigger,每天 04:00 发布 Windows/macOS release 渠道 release 调度复用统一发号源,并为 macOS 启用过期构建让位 现有小时 dev 调度保持同时触发 Windows 与 macOS 更新生产运维门禁、发号权限、技术方案与共享记忆 --- .../shared-memory/decision-log.md | 8 + .../shared-memory/development-workflow.md | 2 +- ...技术方案】AGC总版本号与发号-2026-09-20.md | 8 +- ...发运维】本地开发验证与生产运维-2026-05-15.md | 8 +- jenkins/Jenkinsfile.agc-global-version-issue | 13 +- jenkins/Jenkinsfile.scheduled-release-trigger | 181 ++++++++++++++++++ .../agc-global-version-issue-job-config.xml | 7 +- .../scheduled-release-trigger-job-config.xml | 55 ++++++ scripts/check-production-ops-guardrails.mjs | 82 ++++++++ 9 files changed, 347 insertions(+), 17 deletions(-) create mode 100644 jenkins/Jenkinsfile.scheduled-release-trigger create mode 100644 jenkins/scheduled-release-trigger-job-config.xml diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index ccdb61e08..6a0af194d 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -1,5 +1,13 @@ # 决策记录 +## 2026-09-22 AGC release 增加每日调度,dev 调度保持双平台 + +- 背景:原有 `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,避免把桌面客户端发布与线上全栈部署绑定。 +- 影响范围:`jenkins/Jenkinsfile.scheduled-release-trigger`、`jenkins/scheduled-release-trigger-job-config.xml`、`jenkins/Jenkinsfile.agc-global-version-issue`、`scripts/check-production-ops-guardrails.mjs`、开发运维文档、共享开发工作流与 AGC 总版本号技术方案。 +- 验证方式:`npm run check:production-ops` 校验 release Job 的 cron、发号、双平台 release 参数、Mac 让位和 Copy Artifact 授权;`npm run check:encoding` 与 `git diff --check` 校验文件与补丁。 + ## 2026-09-21 合并 origin/master:Supervisor 永久退役,策划 V1V2 退役落到当前两条产品路径 - 背景:`refactor/split-direct-project`(DirectProject 独立聊天容器)与 `origin/master`(#355 退役策划 Agent V1/V2)在 2026-09-18 之后各走一条线:本分支删掉 Supervisor 前端链路、把立项策划收敛到 `view/project-development/planning/`,master 删掉整套策划 V1/V2(前端会话 / 审批卡 / 适配器 / 类型与 Rust `planning_*_v2` 命令、`planning_gdd_model.rs`、`planning_policy_v2.rs`、`planning_session_v2.rs`)只保留 Design Agent。两边都在删 Supervisor,冲突集中在 `App.tsx`、聊天视图(`PlanningChatView`、`DirectProjectTurn`、`ToolCallGroup`)、Direct composer / 引用输入区、`styles.css`、Rust direct user item 与 appSurface 用例。 diff --git a/docs/project-memory/shared-memory/development-workflow.md b/docs/project-memory/shared-memory/development-workflow.md index fc9354c63..d37feedb2 100644 --- a/docs/project-memory/shared-memory/development-workflow.md +++ b/docs/project-memory/shared-memory/development-workflow.md @@ -92,7 +92,7 @@ SpacetimeDB 任务统一先读取 `.codex/skills/genarrative-spacetimedb/SKILL.m ## Jenkins 定时版本调度 -定时与版本比较只保留在 `Genarrative-Scheduled-Revision-Trigger` 一处:每小时用 `git ls-remote` 解析 `SOURCE_BRANCH` 远端 HEAD,与上一次触发过的 revision 比较,变化时才把同一个 `COMMIT_HASH` 传给 `Genarrative-Full-Build-And-Deploy`,并先经 `Genarrative-Agc-Global-Version-Issue` 发号、再把同一个总版本号透传给 `Genarrative-Agc-Windows-Build` 与 `Genarrative-Agc-MacOS-Build`,保证两个客户端的平台分区发布同一个版本。这三个下游 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 处理 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`。 ## Gitea CI 依赖闭合 diff --git a/docs/technical/【技术方案】AGC总版本号与发号-2026-09-20.md b/docs/technical/【技术方案】AGC总版本号与发号-2026-09-20.md index fad9d5cef..fdef27adb 100644 --- a/docs/technical/【技术方案】AGC总版本号与发号-2026-09-20.md +++ b/docs/technical/【技术方案】AGC总版本号与发号-2026-09-20.md @@ -1,6 +1,6 @@ # AGC 总版本号与发号 -更新时间:`2026-09-20` +更新时间:`2026-09-22` ## 背景 @@ -18,7 +18,7 @@ AGC 客户端此前按「渠道各自比高水位自增」发号:`dev-win` 与 - `next = 总号 + 1`;总号不存在时先一次性播种,见下节。 - 顺序固定为「先写总号 → 再构建 → 再发渠道清单」;任何一步失败都不回滚,只烧号。 -- 统一构建:发一次号,通过 `AGC_RELEASE_VERSION` 同时传给 `dev-win`、`dev-mac`(以及后续渠道),各渠道共用同一个号。 +- 统一构建:发一次号,通过 `AGC_RELEASE_VERSION` 同时传给同一渠道组的两个平台;dev 调度写 `dev-win` / `dev-mac`,每日 release 调度写 `release-win` / `release-mac`,组内共用同一个号。 - 单渠道热修:发一次号,只传给该渠道;其他渠道清单保持原值。 - 显式传入 `AGC_RELEASE_VERSION` 的构建不再自行发号,只做高水位断言。 @@ -39,8 +39,8 @@ 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=`)。 - 构建侧:`build-release.mjs` 的 `prepareReleaseVersion()` 优先采用传入总号,未传入时现场发号;`resolveRemoteHighWaterVersion()` 降级为断言来源,只用于「请求号低于本渠道清单版本即失败关闭」。 -- CI:`jenkins/Jenkinsfile.agc-global-version-issue` + `jenkins/agc-global-version-issue-job-config.xml`;调度管线 `jenkins/Jenkinsfile.scheduled-revision-trigger` 与手动管线先调发号 Job,再把号透传给 AGC Build。 -- 发号 Job 的 Copy Artifact 采用生产权限模式,`options` 里的 `copyArtifactPermission(...)` 必须显式列出**全部**消费者:定时调度 `Genarrative-Scheduled-Revision-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。 +- 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。 +- 发号 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。 ## 不改的东西 diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index 60f267a61..de0f4211c 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -141,9 +141,9 @@ BgFilter 对已经落入私有 OSS 的生成原图、动作抽取帧和手动去 普通图片错误素材类型清理使用 `npm run spacetime:editor-image-asset-kind:clean -- --database --server-url `,只能由已授权 migration operator 执行。先进入维护模式并发布包含 `clean_editor_image_asset_kind_and_return` 的 SpacetimeDB module,并保持旧版本 API / controller / worker 停止;随后运行默认全量 dry-run,核对 `asset → project-resource → showcase → canvas` 各 scope 的扫描数、命中行数、字段数和 blocker 均符合预期,再追加 `--apply`。脚本对每批重新 dry-run、绑定包含画布迁移摘要、结构化 layer 与 generation-dialog 权威 JSON 的 SHA-256,最后自动从头复核零命中;任一画布数据异常都会只输出哈希化 ID、scope 与原因并停止,不能跳过。清理只处理精确业务旧值,不修改 `asset_object.asset_kind`、MIME 或媒体类型;project-resource scope 在清行前验证同工程 migration 并将其状态纳入批次 hash,layout version 0 的 legacy 画布可以没有 migration,但 structured 画布缺 migration 必须立即形成 blocker,资源行不得先被清空;清行后能保持原 status 不变量时立即刷新摘要,否则只允许留给后续精确 canvas 字段清理收口。canvas scope 在任何布局写入前再次按 active / backfilled / rolled_back 状态验证原 migration 凭证和双份 legacy / structured 不变量,将 `editor_canvas_generation_dialog.dialog_json` 与 layer rows 一并扫描并在同一事务 patch;只允许本批资源清零及精确字段删除造成的差异,写入后从全部结构化权威行重建 layout、再次复核新状态才受控重签摘要,同时保持业务 revision、migration status 与全部时间戳不变。新版本 API、SpacetimeDB storage 创建入口、legacy 画布元数据提取和项目资源落表边界都会将 trim 后精确等于 `image` 的 `assetKind` 归一为 `NULL`,防止旧页面、滞留请求或 legacy 保存重新制造废弃值。完成零残留复核,并分别确认 cleaned backfilled 可激活、active 可继续保存、rolled_back 可重复复检后恢复应用版本,最后退出维护。Stdb build artifact 和完整 release 包必须同时包含 `scripts/spacetime-clean-editor-image-asset-kind.mjs` 与 `scripts/spacetime-migration-common.mjs`。 -`Genarrative-Scheduled-Revision-Trigger` 是唯一的定时入口,每小时检查一次(`H * * * *`,分钟由 Jenkins 按 Job 名散列,不等同于整点)。它只用 `git ls-remote` 解析 `SOURCE_BRANCH`(默认 `master`)的远端 HEAD,不 checkout 工作区;解析出的完整 commit 与上一次触发过的 revision 相同则标记 `NOT_BUILT` 并结束,不触发任何下游。 +`Genarrative-Scheduled-Revision-Trigger` 是 dev 渠道的小时调度入口,每小时检查一次(`H * * * *`,分钟由 Jenkins 按 Job 名散列,不等同于整点)。`Genarrative-Scheduled-Release-Trigger` 是 AGC release 渠道的每日调度入口,固定在每天 04:00(`0 4 * * *`)检查同一个源码分支。两条调度器都只用 `git ls-remote` 解析 `SOURCE_BRANCH`(默认 `master`)的远端 HEAD,不 checkout 工作区;解析出的完整 commit 与各自上一次触发过的 revision 相同则标记 `NOT_BUILT` 并结束,不触发任何下游。 -revision 变化时,调度管线把同一个完整 commit 通过 `COMMIT_HASH` 同时传给 `Genarrative-Full-Build-And-Deploy`、`Genarrative-Agc-Windows-Build` 与 `Genarrative-Agc-MacOS-Build`,各条管线都按这个 commit 检出(Full Job 继续把 `env.SOURCE_COMMIT` 透传给 Web / API / Stdb 的 Build、Publish、Deploy),因此两个产物必然来自同一个版本,不会各自解析分支 HEAD 造成漂移。这些下游管线自身不带任何定时触发器,也不在管线内部做版本比较。Windows 客户端发布额外按路径过滤:调度管线比较「上一轮已触发的 revision」与本次 revision 之间的变更路径,只有出现 `apps/ai-game-creator-shell/`、`packages/`、`server-rs/crates/`、`plugins/agc-cocos-editor/`、`apps/desktop-shell/src-tauri/icons/`、`package.json` 或 `package-lock.json` 时才触发 `Genarrative-Agc-Windows-Build` 与 `Genarrative-Agc-MacOS-Build`(两个平台分区发布同一个发号 Job 下发的总版本号),纯文档或流水线自身的提交只触发 Full Build、不推高客户端版本号;判定取消或失败一律按「需要发布」处理,勾选 `FORCE_TRIGGER` 可强制两条都触发。两条下游各自判定:AGC Windows Build 采用「客户端相关路径白名单」,Full Build 采用「与线上站点 / 后端无关的路径黑名单」(`docs/`、`.codex/`、`jenkins/`、`apps/ai-game-creator-shell/`、`apps/mobile-shell/`、`apps/desktop-shell/`、`apps/preview-deployer-web/`、`tools/`、根级 `*.md`),改动只要落在黑名单之外就会照常部署,避免漏发线上站点或后端;两条同时被判为跳过时调度管线只推进 revision 状态、不触发任何发布。AGC 客户端版本号不再由渠道各自递增:唯一发号源是 OSS 对象 `agc/global-version.json`,发号收口到 `Genarrative-Agc-Global-Version-Issue`(`disableConcurrentBuilds()` + 写后回读校验;集群未装 `lockable-resources` 插件)。调度管线与手动发布管线都先调用该 Job 发号,再用归档产物 `agc-global-version.txt` 读取总号并作为 `AGC_RELEASE_VERSION` 透传给客户端构建;统一构建只发一次号供各渠道共用,单渠道热修只把号传给该渠道。显式传入的号低于本渠道当前清单版本时构建失败关闭;`AGC_RELEASE_DRY_RUN` 只预览下一位,不写回、不烧号。一次性播种用 `SEED_ONLY`:基线取「仓库版本 / 各渠道清单 / 旧迁移指针」的最大值,播种本身不烧号。仓库里的 5 个版本文件仍由构建改写,只作构建输入参考,不是事实源。客户端渠道清单的更新摘要同样自动生成:发布脚本读取上一份渠道清单的 `commit` 字段,把该提交到本次提交之间触及客户端相关路径的提交标题逐条写进 `notes`(旧协议清单写入 `releaseNotes`,并落盘归档文件 `release-notes.txt`);`AGC_UPDATE_RELEASE_NOTES` 非空时以手动文案为准,缺少上一份 `commit` 时不写摘要。Full Job 默认以 `DEPLOY_TARGET=development`、`STDB_API_ROLLOUT_MODE=normal` 对仅供开发使用的 dev 服务器执行 Stdb → API → Web 完整发布,不进入人工 rollout gate;三个下游 Build 都由 Full Job 显式传 `PUBLISH_AFTER_BUILD=false`,统一 Build 完成后仍由 Full Job 按固定顺序发布。人工维护窗口才选择 `pause-after-stdb`,且必须配置 `STDB_API_ROLLOUT_APPROVERS`。 `Genarrative-Agc-Windows-Build` 与 `Genarrative-Agc-MacOS-Build` 结束后都会触发 `Genarrative-Notify-Email`;只有正式 OSS 发布成功时,通知正文才附加本次生成的 `latest.json` 首装包 URL,演练、失败或跳过构建时留空,避免把未写入或不完整的对象地址发给收件人。 +dev 调度器发现 revision 变化时,把同一个完整 commit 通过 `COMMIT_HASH` 同时传给 `Genarrative-Full-Build-And-Deploy`、`Genarrative-Agc-Windows-Build` 与 `Genarrative-Agc-MacOS-Build`,各条管线都按这个 commit 检出(Full Job 继续把 `env.SOURCE_COMMIT` 透传给 Web / API / Stdb 的 Build、Publish、Deploy),因此两个产物必然来自同一个版本,不会各自解析分支 HEAD 造成漂移。这些下游管线自身不带任何定时触发器,也不在管线内部做版本比较。Windows 客户端发布额外按路径过滤:调度管线比较「上一轮已触发的 revision」与本次 revision 之间的变更路径,只有出现 `apps/ai-game-creator-shell/`、`packages/`、`server-rs/crates/`、`plugins/agc-cocos-editor/`、`apps/desktop-shell/src-tauri/icons/`、`package.json` 或 `package-lock.json` 时才触发 `Genarrative-Agc-Windows-Build` 与 `Genarrative-Agc-MacOS-Build`(两个平台分区发布同一个发号 Job 下发的总版本号),纯文档或流水线自身的提交只触发 Full Build、不推高客户端版本号;判定取消或失败一律按「需要发布」处理,勾选 `FORCE_TRIGGER` 可强制两条都触发。每日 release 调度器不触发 Full Build;它使用独立的 `.jenkins-last-release-revision` 与客户端路径白名单,只在上一轮 release 调度后出现 `apps/ai-game-creator-shell/`、`packages/`、`server-rs/crates/`、`plugins/agc-cocos-editor/`、`plugins/agc-unity-editor/`、`plugins/agc-godot-editor/`、`apps/desktop-shell/src-tauri/icons/`、`package.json` 或 `package-lock.json` 变更时,先经发号 Job 获取统一总号,再以同一个固定 commit 触发 AGC Windows 与 macOS 的 `release` 分区构建;macOS 仍带 `SKIP_IF_SUPERSEDED=true`,无客户端变化时只推进 release revision 状态、不烧号。dev 调度器的下游各自判定:AGC Windows Build 采用「客户端相关路径白名单」,Full Build 采用「与线上站点 / 后端无关的路径黑名单」(`docs/`、`.codex/`、`jenkins/`、`apps/ai-game-creator-shell/`、`apps/mobile-shell/`、`apps/desktop-shell/`、`apps/preview-deployer-web/`、`tools/`、根级 `*.md`),改动只要落在黑名单之外就会照常部署,避免漏发线上站点或后端;两条同时被判为跳过时调度管线只推进 revision 状态、不触发任何发布。AGC 客户端版本号不再由渠道各自递增:唯一发号源是 OSS 对象 `agc/global-version.json`,发号收口到 `Genarrative-Agc-Global-Version-Issue`(`disableConcurrentBuilds()` + 写后回读校验;集群未装 `lockable-resources` 插件)。dev / release 两条调度管线与手动发布管线都先调用该 Job 发号,再用归档产物 `agc-global-version.txt` 读取总号并作为 `AGC_RELEASE_VERSION` 透传给客户端构建;统一构建只发一次号供各渠道共用,单渠道热修只把号传给该渠道。显式传入的号低于本渠道当前清单版本时构建失败关闭;`AGC_RELEASE_DRY_RUN` 只预览下一位,不写回、不烧号。一次性播种用 `SEED_ONLY`:基线取「仓库版本 / 各渠道清单 / 旧迁移指针」的最大值,播种本身不烧号。仓库里的 5 个版本文件仍由构建改写,只作构建输入参考,不是事实源。客户端渠道清单的更新摘要同样自动生成:发布脚本读取上一份渠道清单的 `commit` 字段,把该提交到本次提交之间触及客户端相关路径的提交标题逐条写进 `notes`(旧协议清单写入 `releaseNotes`,并落盘归档文件 `release-notes.txt`);`AGC_UPDATE_RELEASE_NOTES` 非空时以手动文案为准,缺少上一份 `commit` 时不写摘要。Full Job 默认以 `DEPLOY_TARGET=development`、`STDB_API_ROLLOUT_MODE=normal` 对仅供开发使用的 dev 服务器执行 Stdb → API → Web 完整发布,不进入人工 rollout gate;三个下游 Build 都由 Full Job 显式传 `PUBLISH_AFTER_BUILD=false`,统一 Build 完成后仍由 Full Job 按固定顺序发布。人工维护窗口才选择 `pause-after-stdb`,且必须配置 `STDB_API_ROLLOUT_APPROVERS`。 `Genarrative-Agc-Windows-Build` 与 `Genarrative-Agc-MacOS-Build` 结束后都会触发 `Genarrative-Notify-Email`;只有正式 OSS 发布成功时,通知正文才附加本次生成的 `latest.json` 首装包 URL,演练、失败或跳过构建时留空,避免把未写入或不完整的对象地址发给收件人。 手工发布入口 `Genarrative-Manual-Build-And-Deploy` 的 `DEPLOY_TARGET` 与 AGC 更新渠道是两个独立维度。手工入口必须把 `release` 映射为 `AGC_UPDATE_CHANNEL=release`、把 `development` 映射为 `dev`,并把该参数同时透传给 `Genarrative-Agc-Windows-Build` 与 `Genarrative-Agc-MacOS-Build`;只传 `AGC_RELEASE_VERSION` 时下游会落回各自默认 `dev`。统一号 `0.1.95` 的 release 包应分别位于 `agc/release-win/0.1.95/` 与 `agc/release-mac/0.1.95/`,渠道清单为对应目录下的 `latest.json`,不存在 `agc/release/` 这一层。补发本轮已烧号的版本时,直接以相同 `AGC_RELEASE_VERSION` 重跑两条 AGC Job,不重新发号。 @@ -730,7 +730,7 @@ cat /var/lib/genarrative/health-patrol/status.json 如需接外部告警,可在 `/etc/genarrative/health-patrol.env` 配置 `GENARRATIVE_HEALTH_PATROL_WEBHOOK_URL`;脚本只会在 `WARNING` 或 `CRITICAL` 时向该 webhook 发送 JSON。未配置 webhook 时,告警来源是 systemd 失败状态、journal 和状态文件。 -Jenkins Copy Artifact 必须保持 `Production` 权限模式;产物生产者要在 Jenkinsfile 中用 `copyArtifactPermission` 精确授权消费者,不能依赖 Migration 模式或全局 `Job/Read`。固定映射为 `Genarrative-Stdb-Module-Build` → `Genarrative-Stdb-Module-Publish`、`Genarrative-Api-Build` → `Genarrative-Api-Deploy`、`Genarrative-Web-Build` → `Genarrative-Web-Deploy`、`Genarrative-Database-Export` → `Genarrative-Database-Import`、`Genarrative-Agc-Global-Version-Issue` → `Genarrative-Scheduled-Revision-Trigger`(发号 Job 必须授权调度器读 `agc-global-version.txt`;缺这条会让整轮调度在发号之后失败——号已烧、渠道构建没被触发,2026-09-20 #103 即为此)。如果 `copyArtifacts` 报 `Unable to find project for artifact copy`,但来源 Job、指定构建号和归档都实际存在,先检查来源 Job 的 `CopyArtifactPermissionProperty`;修复 Jenkinsfile 后必须先运行一次产物生产者,让 Declarative Pipeline 把 Job property 写回 Jenkins,再重跑 Deploy / Publish / Import。`npm run check:production-ops` 会防止四条白名单再次丢失。 +Jenkins Copy Artifact 必须保持 `Production` 权限模式;产物生产者要在 Jenkinsfile 中用 `copyArtifactPermission` 精确授权消费者,不能依赖 Migration 模式或全局 `Job/Read`。固定映射为 `Genarrative-Stdb-Module-Build` → `Genarrative-Stdb-Module-Publish`、`Genarrative-Api-Build` → `Genarrative-Api-Deploy`、`Genarrative-Web-Build` → `Genarrative-Web-Deploy`、`Genarrative-Database-Export` → `Genarrative-Database-Import`、`Genarrative-Agc-Global-Version-Issue` → `Genarrative-Scheduled-Revision-Trigger` / `Genarrative-Scheduled-Release-Trigger`(发号 Job 必须授权两条调度器读取 `agc-global-version.txt`;缺这条会让整轮调度在发号之后失败——号已烧、渠道构建没被触发,2026-09-20 #103 即为此)。如果 `copyArtifacts` 报 `Unable to find project for artifact copy`,但来源 Job、指定构建号和归档都实际存在,先检查来源 Job 的 `CopyArtifactPermissionProperty`;修复 Jenkinsfile 后必须先运行一次产物生产者,让 Declarative Pipeline 把 Job property 写回 Jenkins,再重跑 Deploy / Publish / Import。`npm run check:production-ops` 会防止这些 Copy Artifact 授权再次丢失。 `Genarrative-Web-Build` 的主站构建失败若出现 Rollup 报错 `"xxx" is not exported by "src/services/publicWorkCode.ts"`,优先按前端公开作品号工具缺失处理,而不是排查 Jenkins 节点环境。修复时要让 `publicWorkCode.ts` 的 `buildPublicWorkCode` 与 `isSamePublicWorkCode` 成对导出,并补 `src/services/publicWorkCode.test.ts` 覆盖对应玩法前缀;随后用 `npm run build:production-release -- --component web --name <临时名>` 复现 Jenkins web 构建路径。 @@ -760,7 +760,7 @@ Pingora current release 自审脚本 `scripts/ops/pingora-current-release-audit. ### AGC macOS 手动构建节点 -Mac 单架构(arm64)构建脚本位于 `jenkins/Jenkinsfile.ai-game-creator-shell-macos-build`,只对专用 `genarrative-agc-macos` 标签运行。节点按 EXCLUSIVE、单 executor 配置,Job 禁止并发、自身不设 cron(定时只来自调度 Job);它已接入每小时版本调度:`Genarrative-Scheduled-Revision-Trigger` 先调发号 Job 拿同一个总号,再把 `SOURCE_BRANCH`、固定 `COMMIT_HASH`、`AGC_UPDATE_CHANNEL=dev`、`AGC_RELEASE_VERSION=<总号>` 与 `SKIP_IF_SUPERSEDED=true` 透传给本 Job,**因此正常发布不需要(也不应该)手工点构建**;手工触发只是排障手段。它不改 Windows 发布职责。它使它只在专用 Agent 目录下构建:默认按约定匹配 `$HOME/Library/Jenkins/agents//workspace/`(不写死节点名,节点改名后仍成立),也可用 `AGC_AGENT_ROOT` 显式覆盖;禁止指向开发 checkout 或共享其可写 target/node_modules。 +Mac 单架构(arm64)构建脚本位于 `jenkins/Jenkinsfile.ai-game-creator-shell-macos-build`,只对专用 `genarrative-agc-macos` 标签运行。节点按 EXCLUSIVE、单 executor 配置,Job 禁止并发、自身不设 cron(定时只来自调度 Job);它同时接入每小时 dev 与每日 04:00 release 两条版本调度:`Genarrative-Scheduled-Revision-Trigger` / `Genarrative-Scheduled-Release-Trigger` 先调发号 Job 拿同一个总号,再把 `SOURCE_BRANCH`、固定 `COMMIT_HASH`、对应 `AGC_UPDATE_CHANNEL=dev|release`、`AGC_RELEASE_VERSION=<总号>` 与 `SKIP_IF_SUPERSEDED=true` 透传给本 Job,**因此正常发布不需要(也不应该)手工点构建**;手工触发只是排障手段。它不改 Windows 发布职责。它使它只在专用 Agent 目录下构建:默认按约定匹配 `$HOME/Library/Jenkins/agents//workspace/`(不写死节点名,节点改名后仍成立),也可用 `AGC_AGENT_ROOT` 显式覆盖;禁止指向开发 checkout 或共享其可写 target/node_modules。 Job 名为 `Genarrative-Agc-MacOS-Build`,SCM 直接读取仓库内上述 Jenkinsfile,参数为 `SOURCE_BRANCH`、`COMMIT_HASH`、`AGC_UPDATE_CHANNEL`、`AGC_RELEASE_VERSION`、`AGC_RELEASE_DRY_RUN`、`AGC_UPDATE_RELEASE_NOTES`、`OSSUTIL_BIN`、`CARGO_BUILD_JOBS`、`NOTIFICATION_EMAILS`。渠道参数是基础名(不含系统,默认 `dev`),脚本不接受 `dev-mac` 这类系统后缀,写入分区固定推导为 `-mac`:这与 Windows Job 的 `-win` 对称,也延续已发布客户端的端点。 diff --git a/jenkins/Jenkinsfile.agc-global-version-issue b/jenkins/Jenkinsfile.agc-global-version-issue index 824394f7c..83277e23c 100644 --- a/jenkins/Jenkinsfile.agc-global-version-issue +++ b/jenkins/Jenkinsfile.agc-global-version-issue @@ -16,14 +16,15 @@ pipeline { skipDefaultCheckout(true) buildDiscarder(logRotator(numToKeepStr: '100', artifactNumToKeepStr: '20')) // Copy Artifact 保持 Production 权限模式:生产者必须显式授权消费者。 - // 调度管线与手动发布管线都要 copy 本 Job 的 agc-global-version.txt 才能把总号 + // 所有调度与手动发布管线都要 copy 本 Job 的 agc-global-version.txt 才能把总号 // 透传给渠道构建;缺授权时 copyArtifacts 会报「Unable to find project for // artifact copy」(用户触发的构建按「认证用户」判权,SYSTEM 定时构建会短路放行, // 所以定时调度能过、手动发布必挂;2026-09-20 #103 与 2026-09-21 手动发布 - // #6/#7 均命中)。这里必须同时列出两条消费者:Genarrative-Scheduled-Revision-Trigger - // 与 Genarrative-Manual-Build-And-Deploy。改完本文件后必须先单独跑一次本 Job, - // 让 Declarative Pipeline 把 Job property 写回 Jenkins,再重跑调度或手动发布。 - copyArtifactPermission('Genarrative-Scheduled-Revision-Trigger,Genarrative-Manual-Build-And-Deploy') + // #6/#7 均命中)。这里必须列出全部消费者:Genarrative-Scheduled-Revision-Trigger、 + // Genarrative-Scheduled-Release-Trigger 与 Genarrative-Manual-Build-And-Deploy。 + // 改完本文件后必须先单独跑一次本 Job,让 Declarative Pipeline 把 Job property + // 写回 Jenkins,再重跑调度或手动发布。 + copyArtifactPermission('Genarrative-Scheduled-Revision-Trigger,Genarrative-Scheduled-Release-Trigger,Genarrative-Manual-Build-And-Deploy') } environment { @@ -34,7 +35,7 @@ pipeline { } parameters { - choice(name: 'AGC_CHANNEL', choices: ['unified', 'dev-win', 'dev-mac'], description: '发号用途:unified 为统一构建(一个号同时给 dev-win 与 dev-mac),其余为单渠道热修') + choice(name: 'AGC_CHANNEL', choices: ['unified', 'dev-win', 'dev-mac', 'release-unified', 'release-win', 'release-mac'], description: '发号用途标签:unified / release-unified 为一个号同时给两个平台,其余为单渠道热修') string(name: 'SOURCE_BRANCH', defaultValue: 'master', description: '取发号脚本的源码分支;默认 master') string(name: 'COMMIT_HASH', defaultValue: '', description: '可选:固定提交;留空则解析 SOURCE_BRANCH 当前 revision') booleanParam(name: 'SEED_ONLY', defaultValue: false, description: '仅做一次性播种:写入渠道与仓库版本的最大值作为基线,不递增、不烧号') diff --git a/jenkins/Jenkinsfile.scheduled-release-trigger b/jenkins/Jenkinsfile.scheduled-release-trigger new file mode 100644 index 000000000..044bd0883 --- /dev/null +++ b/jenkins/Jenkinsfile.scheduled-release-trigger @@ -0,0 +1,181 @@ +// AGC release 每日调度管线:每天 04:00 检查源码分支, +// 只有上一轮 release 调度之后出现客户端相关路径变更时,才用同一个固定 commit +// 触发 AGC Windows/macOS 两个 release 分区构建。 +pipeline { + agent { + label 'linux && genarrative-build' + } + + options { + disableConcurrentBuilds() + skipDefaultCheckout(true) + buildDiscarder(logRotator(numToKeepStr: '100', artifactNumToKeepStr: '10')) + } + + triggers { + cron('0 4 * * *') + } + + environment { + GIT_REMOTE_URL = 'ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git' + GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-ssh' + AGC_WINDOWS_BUILD_JOB_NAME = 'Genarrative-Agc-Windows-Build' + AGC_MACOS_BUILD_JOB_NAME = 'Genarrative-Agc-MacOS-Build' + AGC_GLOBAL_VERSION_JOB_NAME = 'Genarrative-Agc-Global-Version-Issue' + AGC_GLOBAL_VERSION_ARTIFACT = 'agc-global-version.txt' + REVISION_STATE_FILE = '.jenkins-last-release-revision' + AGC_SCOPE_CACHE_DIR = '.agc-release-scope-cache' + } + + parameters { + string(name: 'SOURCE_BRANCH', defaultValue: 'master', description: '被检查的源码分支;默认 master') + booleanParam(name: 'FORCE_TRIGGER', defaultValue: false, description: '勾选后忽略版本与路径比较,强制发布 AGC release 渠道') + } + + stages { + stage('Resolve Revision') { + steps { + withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_CREDENTIAL_ID, keyFileVariable: 'GENARRATIVE_GIT_SSH_KEY')]) { + sh '''#!/usr/bin/env bash + set -euo pipefail + export GIT_SSH_COMMAND="ssh -i ${GENARRATIVE_GIT_SSH_KEY:?缺少 Git SSH 凭据} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" + revision="$(git ls-remote "${GIT_REMOTE_URL:?缺少 GIT_REMOTE_URL}" "refs/heads/${SOURCE_BRANCH}" | awk 'NR == 1 { print $1 }')" + if [[ ! "${revision}" =~ ^[0-9a-f]{40}$ ]]; then + echo "无法解析远端分支版本: branch=${SOURCE_BRANCH} revision=${revision:-}" >&2 + exit 1 + fi + printf '%s' "${revision}" > .jenkins-remote-revision + echo "release 远端版本: branch=${SOURCE_BRANCH} revision=${revision}" + ''' + } + script { + env.REMOTE_REVISION = readFile('.jenkins-remote-revision').trim() + env.LAST_TRIGGERED_REVISION = fileExists(env.REVISION_STATE_FILE) ? readFile(env.REVISION_STATE_FILE).trim() : '' + env.REVISION_CHANGED = (params.FORCE_TRIGGER || env.LAST_TRIGGERED_REVISION != env.REMOTE_REVISION) ? 'true' : 'false' + if (env.REVISION_CHANGED != 'true') { + currentBuild.result = 'NOT_BUILT' + currentBuild.description = "release 版本未变化,跳过:${env.SOURCE_BRANCH} 仍是 ${env.REMOTE_REVISION.take(12)}" + echo currentBuild.description + } + } + } + } + + stage('Resolve AGC Release Scope') { + when { + expression { return env.REVISION_CHANGED == 'true' } + } + steps { + withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_CREDENTIAL_ID, keyFileVariable: 'GENARRATIVE_GIT_SSH_KEY')]) { + script { + // 判定失败一律按「需要发布」处理,避免路径过滤阻塞客户端发布。 + def output = 'agc=changed' + try { + output = sh(script: '''#!/usr/bin/env bash + set -uo pipefail + export GIT_SSH_COMMAND="ssh -i ${GENARRATIVE_GIT_SSH_KEY:?缺少 Git SSH 凭据} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" + previous="$(cat "${REVISION_STATE_FILE}" 2>/dev/null || true)" + if [[ -z "${previous}" ]]; then + printf 'agc=changed\n' + exit 0 + fi + mkdir -p "${AGC_SCOPE_CACHE_DIR}" + if [[ ! -d "${AGC_SCOPE_CACHE_DIR}/.git" ]]; then + git -C "${AGC_SCOPE_CACHE_DIR}" init --quiet + git -C "${AGC_SCOPE_CACHE_DIR}" remote add origin "${GIT_REMOTE_URL}" 2>/dev/null || true + fi + refspec="+refs/heads/${SOURCE_BRANCH}:refs/remotes/origin/${SOURCE_BRANCH}" + if ! git -C "${AGC_SCOPE_CACHE_DIR}" fetch --quiet --depth=200 --no-tags --filter=blob:none origin "${refspec}"; then + git -C "${AGC_SCOPE_CACHE_DIR}" fetch --quiet --depth=200 --no-tags origin "${refspec}" || { printf 'agc=changed\n'; exit 0; } + fi + if ! git -C "${AGC_SCOPE_CACHE_DIR}" cat-file -e "${previous}^{commit}" 2>/dev/null; then + echo "浅取窗口内没有 ${previous},按需要发布处理" >&2 + printf 'agc=changed\n' + exit 0 + fi + changed_paths="$(git -C "${AGC_SCOPE_CACHE_DIR}" diff --name-only "${previous}" "${REMOTE_REVISION}" 2>/dev/null || true)" + agc_scope=unchanged + while IFS= read -r changed_path; do + [[ -z "${changed_path}" ]] && continue + case "${changed_path}" in + apps/ai-game-creator-shell/*|packages/*|server-rs/crates/*|plugins/agc-cocos-editor/*|plugins/agc-unity-editor/*|plugins/agc-godot-editor/*|apps/desktop-shell/src-tauri/icons/*|package.json|package-lock.json) + agc_scope=changed + ;; + esac + done <<< "${changed_paths}" + printf 'agc=%s\n' "${agc_scope}" + ''', returnStdout: true).trim() + } catch (error) { + echo "release 发布范围判定失败,按需要发布处理:${error}" + output = 'agc=changed' + } + def values = output.split('\n').collect { it.trim() }.findAll { it } + def entry = values.find { it.startsWith('agc=') } + def value = entry == null ? '' : entry.split('=')[1] + env.AGC_RELEASE_SCOPE = (value == 'unchanged') ? 'unchanged' : 'changed' + echo "release 发布范围:AGC=${env.AGC_RELEASE_SCOPE}(上一轮已触发 revision=${env.LAST_TRIGGERED_REVISION ?: '无'})" + } + } + } + } + + stage('Trigger AGC Release Builds') { + when { + expression { return env.REVISION_CHANGED == 'true' } + } + steps { + script { + def pinnedRevision = env.REMOTE_REVISION + def agcTriggered = false + if (params.FORCE_TRIGGER || env.AGC_RELEASE_SCOPE != 'unchanged') { + // Windows 与 macOS 共用一个总号;发号 Job 是唯一发号源。 + def versionRun = build job: env.AGC_GLOBAL_VERSION_JOB_NAME, + wait: true, + propagate: true, + parameters: [ + string(name: 'SOURCE_BRANCH', value: env.SOURCE_BRANCH), + string(name: 'COMMIT_HASH', value: pinnedRevision), + string(name: 'AGC_CHANNEL', value: 'release-unified'), + ] + copyArtifacts( + projectName: env.AGC_GLOBAL_VERSION_JOB_NAME, + selector: specific(versionRun.number.toString()), + filter: env.AGC_GLOBAL_VERSION_ARTIFACT, + target: '.', + flatten: true, + ) + env.AGC_GLOBAL_VERSION = readFile(env.AGC_GLOBAL_VERSION_ARTIFACT).trim() + echo "本轮 AGC release 客户端总版本号: ${env.AGC_GLOBAL_VERSION}" + def agcParameters = [ + string(name: 'SOURCE_BRANCH', value: env.SOURCE_BRANCH), + string(name: 'COMMIT_HASH', value: pinnedRevision), + string(name: 'AGC_UPDATE_CHANNEL', value: 'release'), + string(name: 'AGC_RELEASE_VERSION', value: env.AGC_GLOBAL_VERSION), + ] + build job: env.AGC_WINDOWS_BUILD_JOB_NAME, wait: false, propagate: false, parameters: agcParameters + // macOS 节点是办公机:离线期间排队的旧构建在节点回来后必须自行让位。 + def agcMacosParameters = agcParameters + [ + booleanParam(name: 'SKIP_IF_SUPERSEDED', value: true), + ] + build job: env.AGC_MACOS_BUILD_JOB_NAME, wait: false, propagate: false, parameters: agcMacosParameters + agcTriggered = true + } else { + echo "上一轮 release 调度后没有 AGC 客户端相关变更,跳过 ${env.AGC_WINDOWS_BUILD_JOB_NAME} 与 ${env.AGC_MACOS_BUILD_JOB_NAME};需要强制发布时勾选 FORCE_TRIGGER" + } + writeFile file: env.REVISION_STATE_FILE, text: pinnedRevision + def target = "${env.SOURCE_BRANCH}@${pinnedRevision.take(12)}" + currentBuild.description = agcTriggered + ? "已触发 AGC release:${env.AGC_WINDOWS_BUILD_JOB_NAME} 与 ${env.AGC_MACOS_BUILD_JOB_NAME} ${target}" + : "本轮没有 AGC 客户端相关变更,未触发 release 发布:${target}" + echo currentBuild.description + } + } + } + } + + post { + always { + echo "release 调度结束: branch=${env.SOURCE_BRANCH} revision=${env.REMOTE_REVISION ?: ''} changed=${env.REVISION_CHANGED ?: ''} agcScope=${env.AGC_RELEASE_SCOPE ?: ''}" + } + } +} diff --git a/jenkins/agc-global-version-issue-job-config.xml b/jenkins/agc-global-version-issue-job-config.xml index 858f39d5e..b55dbcac7 100644 --- a/jenkins/agc-global-version-issue-job-config.xml +++ b/jenkins/agc-global-version-issue-job-config.xml @@ -1,19 +1,22 @@ - AGC 总版本号唯一发号入口:先写 OSS agc/global-version.json,再由各渠道构建接收号发布。统一构建发一次号给所有渠道;dry-run 只预览不烧号。 + AGC 总版本号唯一发号入口:先写 OSS agc/global-version.json,再由各渠道构建接收号发布。dev / release 的统一构建各自发一次号给两个平台;dry-run 只预览不烧号。 false AGC_CHANNEL - 发号用途:unified 为统一构建(一个号同时给 dev-win 与 dev-mac),其余为单渠道热修 + 发号用途标签:unified / release-unified 为一个号同时给两个平台,其余为单渠道热修 unified dev-win dev-mac + release-unified + release-win + release-mac diff --git a/jenkins/scheduled-release-trigger-job-config.xml b/jenkins/scheduled-release-trigger-job-config.xml new file mode 100644 index 000000000..6baaa851b --- /dev/null +++ b/jenkins/scheduled-release-trigger-job-config.xml @@ -0,0 +1,55 @@ + + + + 每天 04:00 检查源码分支版本(cron 定义在 Jenkinsfile 内):revision 变化且出现客户端相关路径变更时,先经 Genarrative-Agc-Global-Version-Issue 发号,再用同一个固定 commit 与总版本号触发 AGC Windows、macOS 两个 release 分区构建;macOS 额外带 SKIP_IF_SUPERSEDED。下游 Job 一律不自带触发器。 + false + + + + + SOURCE_BRANCH + 被检查的源码分支;默认 master + master + true + + + FORCE_TRIGGER + 勾选后忽略版本与路径比较,强制发布 AGC release 渠道 + false + + + + + + + 2 + + + ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git + genarrative-local-gitea-ssh + +refs/heads/master:refs/remotes/origin/master + + + + + */master + + + false + + + + true + true + + 1 + true + + + + jenkins/Jenkinsfile.scheduled-release-trigger + false + + + false + diff --git a/scripts/check-production-ops-guardrails.mjs b/scripts/check-production-ops-guardrails.mjs index 83270f8f7..6bc31b5b6 100644 --- a/scripts/check-production-ops-guardrails.mjs +++ b/scripts/check-production-ops-guardrails.mjs @@ -54,6 +54,17 @@ const checks = [ reason: 'Copy Artifact Production 模式下,AGC 发号 Job 还必须授权手动发布管线读取总号,否则用户触发的手动发布会停在 copyArtifacts(SYSTEM 定时构建不受影响)。', }, + { + file: 'jenkins/Jenkinsfile.agc-global-version-issue', + includes: 'Genarrative-Scheduled-Release-Trigger', + reason: + 'Copy Artifact Production 模式下,AGC 发号 Job 必须授权每日 release 调度器读取总号。', + }, + { + file: 'jenkins/agc-global-version-issue-job-config.xml', + includes: 'release-unified', + reason: 'AGC 发号 Job 配置必须保留 release 调度的统一发号用途标签。', + }, { file: 'jenkins/Jenkinsfile.production-stdb-module-build', includes: 'npm run check:rustfmt', @@ -7733,6 +7744,14 @@ const scheduledRevisionTriggerJobConfig = readFileSync( 'jenkins/scheduled-revision-trigger-job-config.xml', 'utf8', ); +const scheduledReleaseTriggerContent = readFileSync( + 'jenkins/Jenkinsfile.scheduled-release-trigger', + 'utf8', +); +const scheduledReleaseTriggerJobConfig = readFileSync( + 'jenkins/scheduled-release-trigger-job-config.xml', + 'utf8', +); // 定时与版本比较统一收敛到调度管线,两个下游流水线不得再自带触发器。 for (const [file, content] of [ @@ -7883,6 +7902,69 @@ if ( '[check:production-ops] scheduled-revision-trigger Job 必须指向调度 Jenkinsfile、使用本机 Git 入口与既有 SSH 凭据,并把定时器留在 Jenkinsfile。', ); } + +for (const [snippet, reason] of [ + ["cron('0 4 * * *')", 'release 调度器必须在每天 04:00 检查远端版本'], + ['disableConcurrentBuilds()', '必须禁止 release 调度器并发触发'], + ['skipDefaultCheckout(true)', 'release 调度器不得依赖本地 SCM 工作区'], + ['git ls-remote', 'release 调度器必须直接从远端解析分支版本'], + [ + "AGC_WINDOWS_BUILD_JOB_NAME = 'Genarrative-Agc-Windows-Build'", + '必须声明 AGC Windows release 构建目标 Job', + ], + [ + "AGC_MACOS_BUILD_JOB_NAME = 'Genarrative-Agc-MacOS-Build'", + '必须声明 AGC macOS release 构建目标 Job', + ], + [ + "string(name: 'AGC_CHANNEL', value: 'release-unified')", + 'release 调度器必须先通过发号 Job 获取统一总版本号', + ], + [ + "string(name: 'AGC_UPDATE_CHANNEL', value: 'release')", + 'release 调度器必须把 Windows 与 macOS 都发到 release 分区', + ], + [ + 'build job: env.AGC_WINDOWS_BUILD_JOB_NAME, wait: false, propagate: false, parameters: agcParameters', + 'release 调度器必须触发 AGC Windows release 构建', + ], + [ + 'build job: env.AGC_MACOS_BUILD_JOB_NAME, wait: false, propagate: false, parameters: agcMacosParameters', + 'release 调度器必须触发 AGC macOS release 构建', + ], + [ + "booleanParam(name: 'SKIP_IF_SUPERSEDED', value: true)", + 'release 调度器必须让 macOS 节点恢复后跳过过期排队构建', + ], +]) { + if (!scheduledReleaseTriggerContent.includes(snippet)) { + failed = true; + console.error(`[check:production-ops] release 调度管线${reason}。`); + } +} +if (scheduledReleaseTriggerContent.includes('FULL_BUILD_JOB_NAME')) { + failed = true; + console.error( + '[check:production-ops] release 调度管线只负责 AGC release 客户端构建,不得触发 Full Build。', + ); +} +if ( + !scheduledReleaseTriggerJobConfig.includes( + 'jenkins/Jenkinsfile.scheduled-release-trigger', + ) || + !scheduledReleaseTriggerJobConfig.includes( + 'ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git', + ) || + !scheduledReleaseTriggerJobConfig.includes( + 'genarrative-local-gitea-ssh', + ) || + !scheduledReleaseTriggerJobConfig.includes('') +) { + failed = true; + console.error( + '[check:production-ops] scheduled-release-trigger Job 必须指向 release 调度 Jenkinsfile、使用本机 Git 入口与既有 SSH 凭据,并把定时器留在 Jenkinsfile。', + ); +} if ( !fullPipelineContent.includes( "string(name: 'COMMIT_HASH', value: env.SOURCE_COMMIT)",