From b8fd57ad430c1591b290c1a8c9d59be56425e3d3 Mon Sep 17 00:00:00 2001 From: winewill Date: Wed, 4 Nov 2026 11:52:59 +0800 Subject: [PATCH] fix(desktop): clarify publication retry limits and route evidence --- apps/desktop/publishing/ci/README.i18n.yaml | 4 ++-- apps/desktop/publishing/ci/README.md | 2 +- apps/desktop/publishing/ci/README.zh.md | 2 +- apps/desktop/scripts/desktop-publish-route.ts | 3 +-- apps/desktop/tests/desktop-publish-route.spec.ts | 10 ++++++++++ 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/desktop/publishing/ci/README.i18n.yaml b/apps/desktop/publishing/ci/README.i18n.yaml index 70c5d0e62b..4d7f5f635e 100644 --- a/apps/desktop/publishing/ci/README.i18n.yaml +++ b/apps/desktop/publishing/ci/README.i18n.yaml @@ -27,8 +27,8 @@ en: 3f58698708671202 zh: be0ba215a67168d6 /desktop-ci-publication/recovery-and-records: - en: 0d87b7a9c073993c - zh: 9f135e8c1a4b7d8e + en: 974b76843f83972e + zh: 73ba720adeeec32a /desktop-ci-publication/restore-a-channel: en: d82e2ccf70cce397 zh: d93dcb77cc4b463e diff --git a/apps/desktop/publishing/ci/README.md b/apps/desktop/publishing/ci/README.md index eb21407702..32b73af8df 100644 --- a/apps/desktop/publishing/ci/README.md +++ b/apps/desktop/publishing/ci/README.md @@ -86,7 +86,7 @@ Production COS publication enables website installer upload and cache refresh by Set the repository variable `DOWNLOAD_PROD_EO_ZONE_ID` to the production EdgeOne zone. The production COS credential pair also needs `teo:CreatePurgeTask` and `teo:DescribePurgeTasks`. Website uploads serialize by bucket/target in Actions and by bucket/object on the original machine. Cache observation runs separately. Version publication, website upload and cache refresh each use the production approval Environment; later jobs and reruns may require another approval. GitHub concurrency retains at most one pending job per group, so a newer pending job can cancel an older pending job; inspect canceled jobs before rerunning them. -Website state lives in `upload-records/website--/state.json`, beside the retained releases root. macOS and Windows use the same record format on separate local disks. Website URLs and download filenames follow the [fixed-download rules](../README.md#fixed-downloads). Upload retries reconcile both bytes and `Content-Disposition`; identical bytes with a missing or different header receive one upload and readback. A completed journal with different remote bytes stops recovery and requires a newly approved publication. Completion evidence survives failed metadata repairs. Each new upload intent clears the previous purge record, so repaired metadata requires a new cache refresh; inspect EdgeOne history for the previous task ID and diagnostics. Unknown writes are read before any retry; CDN header forwarding requires real-environment validation. Rerun only the purge job to inspect its saved task. With website upload enabled, website jobs skip only an explicitly selected test deployment; missing preparation outputs must pass local preflight instead of silently skipping. Restore upstream outputs by rerunning preparation before retrying publication. A manually rerun purge may submit once after confirming the previous task failed. A lost submission response queries matching history instead of resubmitting. An unknown result with a task ID means submission was confirmed; without an ID it may never have been submitted. Inspect EdgeOne history before manual recovery, and a fresh publication run is one recovery option. Operators may instead use the documented [fixed-download purge command](../README.md#fixed-downloads); Windows operators use the existing DPAPI credential launcher described there. This submits only a fixed-URL purge: it does not observe completion or update this Actions run’s journal. Inspect the returned task in EdgeOne before considering recovery complete. +Website state lives in `upload-records/website--/state.json`, beside the retained releases root. macOS and Windows use the same record format on separate local disks. Website URLs and download filenames follow the [fixed-download rules](../README.md#fixed-downloads). Upload retries reconcile both bytes and `Content-Disposition`; identical bytes with a missing or different header receive one upload and readback. A completed journal with different remote bytes stops recovery and requires a newly approved publication. Completion evidence survives failed metadata repairs. Each new upload intent clears the previous purge record, so repaired metadata requires a new cache refresh; inspect EdgeOne history for the previous task ID and diagnostics. Unknown writes are read before any retry; CDN header forwarding requires real-environment validation. Rerun only the purge job to inspect its saved task when the original routing and preparation outputs remain available. With website upload enabled, website jobs skip only an explicitly selected test deployment. Missing preparation parameters fail local preflight; missing runner routing can prevent scheduling before preflight. Stop partial retries when outputs are missing; the workflow does not reconstruct them. Before restarting the publication workflow, inspect current remote state and obtain approval for its upload scope; this is not cache-only recovery. Do not rerun packaging to recover publication outputs. A manually rerun purge may submit once after confirming the previous task failed. A lost submission response queries matching history instead of resubmitting. An unknown result with a task ID means submission was confirmed; without an ID it may never have been submitted. Inspect EdgeOne history before manual recovery, and a fresh publication run is one recovery option. Operators may instead use the documented [fixed-download purge command](../README.md#fixed-downloads); Windows operators use the existing DPAPI credential launcher described there. This submits only a fixed-URL purge: it does not observe completion or update this Actions run’s journal. Inspect the returned task in EdgeOne before considering recovery complete. Purge observation waits up to five minutes at ten-second intervals, with one additional query after an unknown result or observation deadline. Confirmed failed, timeout or canceled tasks fail the job. An unresolved result exits successfully with an Actions warning and a prominent summary; its durable state remains unknown, not completed. Diagnostic artifact archival is best effort; local records remain the recovery source. Simulated-provider tests cover these paths; release operators must verify production COS/EdgeOne permissions, job-id filtering, time-range queries, task type and timestamp formats, cross-attempt Actions recovery, and actual CDN behavior before relying on recovery in production. diff --git a/apps/desktop/publishing/ci/README.zh.md b/apps/desktop/publishing/ci/README.zh.md index 91e2ac717d..44d9796e10 100644 --- a/apps/desktop/publishing/ci/README.zh.md +++ b/apps/desktop/publishing/ci/README.zh.md @@ -86,7 +86,7 @@ Environment 审批限制此工作流,不限制同一机器账户运行的任 将仓库变量 `DOWNLOAD_PROD_EO_ZONE_ID` 配置为生产 EdgeOne 站点。生产 COS 凭据还需要 `teo:CreatePurgeTask` 和 `teo:DescribePurgeTasks` 权限。官网上传在 Actions 中按 bucket/平台互斥,在原机器上按 bucket/对象互斥;缓存观察单独运行。版本发布、官网上传和缓存刷新分别使用生产审批 Environment,后续 job 和重跑可能需要再次审批。GitHub 每个并发组最多保留一个排队 job,新排队 job 可能取消旧排队 job;重跑前应检查取消记录。 -官网状态保存在 releases 根目录同级的 `upload-records/website-<发布 run ID>-/state.json`。macOS 和 Windows 在各自本地磁盘使用相同记录格式。官网 URL 和保存文件名遵循[固定下载规则](../README.zh.md#fixed-downloads)。上传重跑同时核对字节和 `Content-Disposition`;字节相同但响应头缺失或错误时,上传一次并读回验证。记录已完成但远端字节改变时停止恢复,需要重新审批发布。完成标记在元数据修复失败后仍保留。每次新的上传意图清除旧刷新记录,因此修复元数据后需重新刷新缓存;上一次任务 ID 和诊断需查 EdgeOne 历史。未知写入先读取再决定重试;CDN 响应头透传仍需实测。仅重跑刷新 job 即可查询已保存任务。启用官网上传时,官网 job 仅跳过明确选择的 test 环境;准备阶段输出缺失时仍须通过本地预检,不再静默跳过。重跑准备阶段恢复上游输出后再重试发布;人工重跑确认旧任务失败后可提交一次。提交响应丢失时查询匹配的历史记录,不重新提交。unknown 带任务 ID 表示提交已确认;没有 ID 则可能从未提交。人工恢复前应检查 EdgeOne 历史,重新发起发布运行是恢复方式之一。操作者也可使用已有的[固定下载刷新命令](../README.zh.md#fixed-downloads);Windows 操作者使用该处说明的 DPAPI 凭据启动器。该命令只提交固定 URL 刷新,不观察完成状态,也不更新本次 Actions 运行的 journal。确认恢复完成前应在 EdgeOne 检查返回的任务。 +官网状态保存在 releases 根目录同级的 `upload-records/website-<发布 run ID>-/state.json`。macOS 和 Windows 在各自本地磁盘使用相同记录格式。官网 URL 和保存文件名遵循[固定下载规则](../README.zh.md#fixed-downloads)。上传重跑同时核对字节和 `Content-Disposition`;字节相同但响应头缺失或错误时,上传一次并读回验证。记录已完成但远端字节改变时停止恢复,需要重新审批发布。完成标记在元数据修复失败后仍保留。每次新的上传意图清除旧刷新记录,因此修复元数据后需重新刷新缓存;上一次任务 ID 和诊断需查 EdgeOne 历史。未知写入先读取再决定重试;CDN 响应头透传仍需实测。原机器路由和准备阶段输出完整时,仅重跑刷新 job 即可查询已保存任务。启用官网上传时,官网 job 仅跳过明确选择的 test 环境。准备参数缺失时本地预检失败;runner 路由缺失可能导致无法调度,尚未进入预检。输出缺失时停止局部重试,workflow 不重建这些输出。重新启动发布 workflow 前,先检查当前远端状态并确认上传范围的审批;这不属于仅缓存恢复。不要为恢复发布输出重跑打包。人工重跑确认旧任务失败后可提交一次。提交响应丢失时查询匹配的历史记录,不重新提交。unknown 带任务 ID 表示提交已确认;没有 ID 则可能从未提交。人工恢复前应检查 EdgeOne 历史,重新发起发布运行是恢复方式之一。操作者也可使用已有的[固定下载刷新命令](../README.zh.md#fixed-downloads);Windows 操作者使用该处说明的 DPAPI 凭据启动器。该命令只提交固定 URL 刷新,不观察完成状态,也不更新本次 Actions 运行的 journal。确认恢复完成前应在 EdgeOne 检查返回的任务。 刷新观察每十秒查询一次,最长五分钟;结果未知或观察超时后额外查询一次。明确的 failed、timeout、canceled 结果使 job 失败。仍无法确认时成功退出,并输出 Actions warning 和醒目摘要;持久化状态保持 unknown,不标为完成。诊断 artifact 尽力归档,本地记录仍是恢复依据。模拟服务测试覆盖这些路径;发布操作者必须在生产依赖恢复能力前验证 COS/EdgeOne 权限、job-id 筛选、时间范围查询、任务类型和时间戳格式、Actions 跨 attempt 恢复及实际 CDN 行为。 diff --git a/apps/desktop/scripts/desktop-publish-route.ts b/apps/desktop/scripts/desktop-publish-route.ts index a58c8c3cf4..cf28e12107 100644 --- a/apps/desktop/scripts/desktop-publish-route.ts +++ b/apps/desktop/scripts/desktop-publish-route.ts @@ -6,8 +6,7 @@ import { desktopPublishQualificationError } from './desktop-publish-qualificatio const positive = z.number().int().positive() const runSchema = z.object({ id: positive, run_attempt: positive, head_sha: z.string().regex(/^[a-f0-9]{40}$/u), - // The package job establishes eligibility; archival failure does not disqualify its build. - path: z.string(), conclusion: z.string(), event: z.literal('workflow_dispatch'), + path: z.string(), event: z.literal('workflow_dispatch'), repository: z.object({ full_name: z.string() }), }) const requestSchema = z.object({ source: z.object({ diff --git a/apps/desktop/tests/desktop-publish-route.spec.ts b/apps/desktop/tests/desktop-publish-route.spec.ts index f01d03251b..c2deaf0695 100644 --- a/apps/desktop/tests/desktop-publish-route.spec.ts +++ b/apps/desktop/tests/desktop-publish-route.spec.ts @@ -96,3 +96,13 @@ it('accepts a successful package when the attempt failed during archival', () => expect(resolveDesktopPublishRoute('owner/repo', f.run, { source: f.source }, f.jobs, f.routes)) .toMatchObject({ runId: '42', attempt: '2' }) }) + +it.each([null, undefined])('routes from successful package evidence without a run conclusion (%s)', (conclusion) => { + const f = fixture() + const run = { ...f.run, conclusion } + expect(resolveDesktopPublishRoute('owner/repo', run, { source: f.source }, f.jobs, f.routes)) + .toMatchObject({ runId: '42', attempt: '2', runnerLabel: 'dsh-mini' }) + f.jobs[0]!.conclusion = 'failure' + expect(() => resolveDesktopPublishRoute('owner/repo', run, { source: f.source }, f.jobs, f.routes)) + .toThrow('single successful packaging runner') +})