diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 5a3713a..8f7a670 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -11,9 +11,10 @@ legacy composers. Missing/stale native wheels or unresolved text fonts fail star Legacy compose functions remain for pixel-reference tools, not service recovery. References to fail-open below describe the earlier migration and must not be reintroduced into production routes. -Release: `scripts/skia_release_gate.py` runs Linux cold/pure-service and strict warm parity; tag publishing -requires `.github/workflows/renderer-release.yml` and uses its validated wheel. The configured fixture -runner/paths are mandatory, never silently skipped. Private MySekai and uncaptured symbol/stamps are +Release: tag publishing calls `.github/workflows/skia-wheels.yml` on GitHub-hosted runners and uses +the same wheel that passed ABI, capability and native codec smoke checks. Full Linux cold/pure-service +and strict warm parity remain manual acceptance via `scripts/skia_release_gate.py` or the optional +`renderer-release.yml` workflow; only that manual workflow requires a configured fixture runner/paths. Private MySekai and uncaptured symbol/stamps are user-excluded diagnostic cases, not release blockers. The private real file was explicitly authorized for migration in this task and remains untracked; preserve it and its implementation. @@ -430,8 +431,8 @@ collision between two *different* payloads (there is one payload per endpoint) read the key material. Wheels are built by `.github/workflows/skia-wheels.yml` (linux-x86_64 + macos-arm64 artifacts, not published to -an index). Docker requires exactly one matching wheel. Tag releases use the wheel produced by the full -`renderer-release.yml` validation job. Wheels are Python-version-specific: **upgrading Python means +an index). Docker requires exactly one matching wheel. Tag releases use the wheel produced and smoke-tested by the reusable +`skia-wheels.yml` workflow on GitHub-hosted runners. Wheels are Python-version-specific: **upgrading Python means rebuilding wheels first**; an absent/incompatible wheel is a build failure. **Traps that have already cost real debugging time:** diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1b01741..2b74a8f 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -6,13 +6,13 @@ on: - "v*" jobs: - renderer-validation: - uses: ./.github/workflows/renderer-release.yml + renderer-wheels: + uses: ./.github/workflows/skia-wheels.yml permissions: contents: read docker: - needs: renderer-validation + needs: renderer-wheels runs-on: ubuntu-latest permissions: contents: read @@ -22,12 +22,12 @@ jobs: - name: Checkout code uses: actions/checkout@v7 - - name: Download the wheel that passed full renderer validation + - name: Download the wheel that passed native smoke validation env: GH_TOKEN: ${{ github.token }} RELEASE_RUN_ID: ${{ github.run_id }} run: | - gh run download "$RELEASE_RUN_ID" --name validated-native-wheel --dir docker/skia-wheels + gh run download "$RELEASE_RUN_ID" --name haruki-skia-renderer-linux-x86_64 --dir docker/skia-wheels test "$(find docker/skia-wheels -maxdepth 1 -name '*.whl' | wc -l)" -eq 1 - name: Log in to GitHub Container Registry diff --git a/.github/workflows/renderer-release.yml b/.github/workflows/renderer-release.yml index e5d5d95..c3f656f 100644 --- a/.github/workflows/renderer-release.yml +++ b/.github/workflows/renderer-release.yml @@ -1,7 +1,6 @@ -name: Native Renderer Release Validation +name: Manual Full Renderer Validation on: - workflow_call: workflow_dispatch: permissions: diff --git a/.github/workflows/skia-wheels.yml b/.github/workflows/skia-wheels.yml index c8dd2ad..3ae3c24 100644 --- a/.github/workflows/skia-wheels.yml +++ b/.github/workflows/skia-wheels.yml @@ -4,6 +4,7 @@ name: Skia Renderer Wheels # for the platforms we deploy/develop on and uploads them as workflow artifacts. on: + workflow_call: push: branches: - main @@ -16,7 +17,7 @@ permissions: contents: read concurrency: - group: skia-wheels-${{ github.ref }} + group: skia-wheels-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: diff --git a/AGENTS.md b/AGENTS.md index 5a3713a..8f7a670 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,9 +11,10 @@ legacy composers. Missing/stale native wheels or unresolved text fonts fail star Legacy compose functions remain for pixel-reference tools, not service recovery. References to fail-open below describe the earlier migration and must not be reintroduced into production routes. -Release: `scripts/skia_release_gate.py` runs Linux cold/pure-service and strict warm parity; tag publishing -requires `.github/workflows/renderer-release.yml` and uses its validated wheel. The configured fixture -runner/paths are mandatory, never silently skipped. Private MySekai and uncaptured symbol/stamps are +Release: tag publishing calls `.github/workflows/skia-wheels.yml` on GitHub-hosted runners and uses +the same wheel that passed ABI, capability and native codec smoke checks. Full Linux cold/pure-service +and strict warm parity remain manual acceptance via `scripts/skia_release_gate.py` or the optional +`renderer-release.yml` workflow; only that manual workflow requires a configured fixture runner/paths. Private MySekai and uncaptured symbol/stamps are user-excluded diagnostic cases, not release blockers. The private real file was explicitly authorized for migration in this task and remains untracked; preserve it and its implementation. @@ -430,8 +431,8 @@ collision between two *different* payloads (there is one payload per endpoint) read the key material. Wheels are built by `.github/workflows/skia-wheels.yml` (linux-x86_64 + macos-arm64 artifacts, not published to -an index). Docker requires exactly one matching wheel. Tag releases use the wheel produced by the full -`renderer-release.yml` validation job. Wheels are Python-version-specific: **upgrading Python means +an index). Docker requires exactly one matching wheel. Tag releases use the wheel produced and smoke-tested by the reusable +`skia-wheels.yml` workflow on GitHub-hosted runners. Wheels are Python-version-specific: **upgrading Python means rebuilding wheels first**; an absent/incompatible wheel is a build failure. **Traps that have already cost real debugging time:** diff --git a/CLAUDE.md b/CLAUDE.md index 5a3713a..8f7a670 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,9 +11,10 @@ legacy composers. Missing/stale native wheels or unresolved text fonts fail star Legacy compose functions remain for pixel-reference tools, not service recovery. References to fail-open below describe the earlier migration and must not be reintroduced into production routes. -Release: `scripts/skia_release_gate.py` runs Linux cold/pure-service and strict warm parity; tag publishing -requires `.github/workflows/renderer-release.yml` and uses its validated wheel. The configured fixture -runner/paths are mandatory, never silently skipped. Private MySekai and uncaptured symbol/stamps are +Release: tag publishing calls `.github/workflows/skia-wheels.yml` on GitHub-hosted runners and uses +the same wheel that passed ABI, capability and native codec smoke checks. Full Linux cold/pure-service +and strict warm parity remain manual acceptance via `scripts/skia_release_gate.py` or the optional +`renderer-release.yml` workflow; only that manual workflow requires a configured fixture runner/paths. Private MySekai and uncaptured symbol/stamps are user-excluded diagnostic cases, not release blockers. The private real file was explicitly authorized for migration in this task and remains untracked; preserve it and its implementation. @@ -430,8 +431,8 @@ collision between two *different* payloads (there is one payload per endpoint) read the key material. Wheels are built by `.github/workflows/skia-wheels.yml` (linux-x86_64 + macos-arm64 artifacts, not published to -an index). Docker requires exactly one matching wheel. Tag releases use the wheel produced by the full -`renderer-release.yml` validation job. Wheels are Python-version-specific: **upgrading Python means +an index). Docker requires exactly one matching wheel. Tag releases use the wheel produced and smoke-tested by the reusable +`skia-wheels.yml` workflow on GitHub-hosted runners. Wheels are Python-version-specific: **upgrading Python means rebuilding wheels first**; an absent/incompatible wheel is a build failure. **Traps that have already cost real debugging time:** diff --git a/README.md b/README.md index 64d4c9a..f4acef2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Haruki Drawing API 是 Team Haruki 的 Project Sekai 图片生成服务。它接收 JSON 请求并输出 PNG/JPG,覆盖玩家资料、卡牌、活动、歌曲、谱面、招募、成绩和 MySekai 等页面。 -当前版本为 `3.0.0-rc2`。生产绘图必须使用 Rust + Skia 后端;缺失或过旧的原生扩展会阻止构建/启动,渲染失败不再调用 Pillow。Pillow 仅作为开发对照环境的依赖,继续消费共享 widget 树来验证像素。 +当前版本为 `3.1.0`。生产绘图必须使用 Rust + Skia 后端;缺失或过旧的原生扩展会阻止构建/启动,渲染失败不再调用 Pillow。Pillow 仅作为开发对照环境的依赖,继续消费共享 widget 树来验证像素。 ## 运行要求 @@ -46,7 +46,7 @@ Compose 默认把 `./data` 挂载到容器内 `/pjskdata/Data`,并把 `configs 公开仓库中的 `src/sekai/mysekai/drawer.py` 只是接口占位文件。生产环境必须将真实实现 bind-mount 到同一路径;不要把 `drawer.real.py` 复制进镜像或提交到仓库。 -Docker 构建前必须在 `docker/skia-wheels/` 放入且只放入一个匹配目标平台的 wheel。构建检查原生能力、实际编解码及生产依赖树,Pillow、Matplotlib、Pilmoji 均不得存在。标签发布使用完整渲染校验通过的同一个 wheel。 +Docker 构建前必须在 `docker/skia-wheels/` 放入且只放入一个匹配目标平台的 wheel。构建检查原生能力、实际编解码及生产依赖树,Pillow、Matplotlib、Pilmoji 均不得存在。标签发布使用 GitHub 托管 runner 构建并通过 ABI、能力握手和原生编解码检查的同一个 wheel。 ## 运维端点 @@ -80,7 +80,9 @@ uv run python -X gil=0 scripts/skia_release_gate.py --out-dir out/release-gate 该命令要求完整资产和 `out/parity-payloads/`,串联冷像素、禁止 Pillow 的绘图入口/完整服务、双后端热缓存检查。私有 MySekai 与尚未捕获的 symbol/stamps 按约定仅作诊断,不阻塞发布。 -标签工作流依赖 `.github/workflows/renderer-release.yml`。配置仓库变量 `RENDER_VALIDATION_RUNNER`(可信 Linux x86_64 runner 标签)、`RENDER_ASSETS_DIR`、`RENDER_PAYLOAD_DIR`、`RENDER_CONFIG_PATH`。后三项必须是 runner 上 checkout 之外的绝对路径;配置文件中的字体等路径必须适用于该 runner。缺少配置会明确失败,不能跳过校验发布。校验通过后才上传 wheel,镜像作业下载该 wheel 构建并推送;请求样本与图片不上传为工作流诊断产物。 +标签工作流复用 `.github/workflows/skia-wheels.yml`,在 GitHub 托管 runner 上构建并检查 wheel;镜像作业下载同一次工作流中通过检查的 Linux wheel,保留生产依赖无 Pillow 检查和实际编解码自检。发布不需要自建 runner 或私有素材。 + +完整素材对拍保留为手动验收:渲染或缓存逻辑变化时,在具备资产和样本的 Linux 环境运行上述命令。也可手动触发 `.github/workflows/renderer-release.yml`;只有该可选工作流需要配置 `RENDER_VALIDATION_RUNNER`、`RENDER_ASSETS_DIR`、`RENDER_PAYLOAD_DIR`、`RENDER_CONFIG_PATH`。路径必须位于 runner checkout 之外;请求样本与图片不上传为工作流诊断产物。 生产依赖安装使用 `uv sync --frozen --no-dev`,随后安装匹配的 wheel;默认开发组包含 `legacy-renderer`,因此本地对拍与测试仍可使用 Pillow。 diff --git a/docs/pillow-retirement-status.md b/docs/pillow-retirement-status.md index c95264b..ad45fa1 100644 --- a/docs/pillow-retirement-status.md +++ b/docs/pillow-retirement-status.md @@ -547,17 +547,18 @@ fontTools 仍被 TMP 轮廓与备用度量使用,已从旧 Matplotlib 间接 悄悄切换字形算法。 Docker 要求恰好一个匹配平台的 wheel,调用 `load_native_renderer()` 检查当前能力,实际执行 -codec smoke,并断言三个旧后端包不可导入。标签工作流依赖新增 `renderer-release.yml`;该作业 -构建并安装 wheel,执行 `skia_release_gate.py`,通过后上传 wheel。镜像作业下载这个已验证的 -wheel,构建成功后才推送。没有通过重新编译另一个 wheel 绕过已验证产物。 +codec smoke,并断言三个旧后端包不可导入。标签工作流复用 `skia-wheels.yml`,在 GitHub +托管 runner 构建并安装 wheel,完成 ABI、能力握手与 codec smoke 后上传。镜像作业下载 +同一次运行中已验证的 Linux wheel,构建成功后才推送。 -发布校验要求可信 Linux x86_64 runner 和 checkout 外的素材、payload、配置文件路径。 +2026-09-09 调整:完整素材对拍保留为手动验收,不再要求每次标签发布重复执行。 +仅可选的 `renderer-release.yml` 手动工作流要求可信 Linux x86_64 runner 和 checkout 外的素材、payload、配置文件路径。 仓库变量为 `RENDER_VALIDATION_RUNNER`、`RENDER_ASSETS_DIR`、`RENDER_PAYLOAD_DIR`、 `RENDER_CONFIG_PATH`;配置内字体路径应适用于该 runner。未设置这些输入会在 preflight 失败, -不会默许发布。工作流代码已接入;本任务未配置远端 runner,也未推送或部署。 +该手动验收不会静默跳过;普通标签发布不依赖这些变量或自建 runner。 `skia_release_gate.py` 只接受全新输出目录,依次运行严格冷对拍/无 Pillow 完整服务和严格双后端 -热缓存。超时、缺报告或任何阶段失败都阻止发布。严格热缓存检查拒绝缺样本、无渲染路径、缺少 +热缓存。超时、缺报告或任何阶段失败都令该手动验收失败。严格热缓存检查拒绝缺样本、无渲染路径、缺少 哈希、未知结果、重复结果及无法解释的漂移;只有 event_planner 的已知实时倒计时可在两个成功 冷渲染确实不同的前提下记录为 nondeterministic。返回 None 不能冒充时间变化。 严格冷门槛还核对实际 OpenAPI 绘图路由,防止新增路由通过“不登记 Case”逃过检查。 @@ -594,8 +595,8 @@ wheel,构建成功后才推送。没有通过重新编译另一个 wheel 绕 Pillow 恢复调用,生产依赖不含旧后端。开发用 Pillow composer 和惰性适配器仍保留供参考检查, 不构成生产渲染恢复能力。未采集的 symbol/stamps 按用户要求留待实际问题修复。 -部署交接仍需在 GitHub 配置上述可信 runner 和外部素材变量后运行标签发布;本地完成不等同于 -远端 CI 已运行。此前 stage30 压测中的 readiness 阈值触发仍是独立容量发现,未将该压测称为 +标签发布使用 GitHub 托管 runner,不再需要上述自建 runner 和素材变量;完整素材验收应在 +渲染或缓存逻辑变化时手动执行,本地完成不等同于远端 CI 已运行。此前 stage30 压测中的 readiness 阈值触发仍是独立容量发现,未将该压测称为 全通过,也未擅自调整阈值;所有图像响应成功及 Pillow 退役结果不依赖容量门槛放宽。 diff --git a/pyproject.toml b/pyproject.toml index c814de6..30cb1fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "Haruki-Drawing-API" -version = "3.0.5" +version = "3.1.0" description = "FastAPI image rendering service for Project Sekai" readme = "README.md" license = "MIT" diff --git a/uv.lock b/uv.lock index e208c92..dbfcb96 100644 --- a/uv.lock +++ b/uv.lock @@ -426,7 +426,7 @@ wheels = [ [[package]] name = "haruki-drawing-api" -version = "3.0.5" +version = "3.1.0" source = { virtual = "." } dependencies = [ { name = "aiohttp" },