diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index 480ddf86..804a229d 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -15,14 +15,17 @@ on: - "uv.lock" concurrency: - group: weather-briefing-image-${{ github.ref_type == 'branch' && 'edge' || format('release-{0}', github.ref_name) }} - cancel-in-progress: true + group: weather-briefing-image + cancel-in-progress: false env: WEATHER_BRIEFING_IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/weather-briefing jobs: weather-briefing: + if: >- + github.ref_type == 'tag' || + !contains(github.event.head_commit.message, 'Weather-Briefing-Skip-Edge-Image: true') runs-on: ${{ matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }} strategy: fail-fast: false @@ -120,7 +123,11 @@ jobs: tags=(--tag "${REGISTRY_IMAGE}:${COMMIT_TAG}") if [[ -n "${RELEASE_VERSION}" ]]; then - tags+=(--tag "${REGISTRY_IMAGE}:${RELEASE_VERSION}" --tag "${REGISTRY_IMAGE}:latest") + tags+=( + --tag "${REGISTRY_IMAGE}:${RELEASE_VERSION}" + --tag "${REGISTRY_IMAGE}:latest" + --tag "${REGISTRY_IMAGE}:edge" + ) else tags+=(--tag "${REGISTRY_IMAGE}:edge") fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 82b61397..c7a83b05 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -140,6 +140,8 @@ jobs: echo "Development version update produced no changes" exit 1 fi - git commit -m "chore: start ${NEXT_DEVELOPMENT_VERSION} development" + git commit \ + -m "chore: start ${NEXT_DEVELOPMENT_VERSION} development" \ + -m "Weather-Briefing-Skip-Edge-Image: true" git push --atomic origin HEAD:master "refs/tags/${WEATHER_BRIEFING_VERSION}" diff --git a/docs/design.md b/docs/design.md index cee47e14..6911ab95 100644 --- a/docs/design.md +++ b/docs/design.md @@ -158,4 +158,6 @@ release workflow 的手动输入是 GitHub Actions `choice`,只允许 major、 工作流先同步 `pyproject.toml`、包内版本、`uv.lock` 和 README 的稳定部署版本,生成正式版 commit 并让同名 tag 指向该 commit;再只把代码与锁文件推进到下一 patch 的 `-dev` commit。最后 atomic push `master` 与 tag,使远端不会只接收其中一部分。 +GitHub 会分别为同一次 atomic push 中的 `master` 和 tag 更新触发 push workflow。自动生成的 `-dev` commit 因此带有专用 commit trailer,使镜像流水线跳过该次 `master` 构建;tag 流水线只构建正式版 commit 一次,并让版本号、`latest` 与 `edge` 标签共同指向该镜像。后续普通 `master` 提交仍照常更新 `edge`。 + 开发版 `--version` 以包源码位置的父目录作为预期仓库根,并要求 Git `--show-toplevel` 返回同一目录后才附加 commit SHA 和 dirty 状态。因此从其他 Git 仓库启动 CLI,或把普通安装放在其他仓库的虚拟环境中,都不会误报外部仓库信息。 diff --git a/docs/notes.md b/docs/notes.md index d0d92354..d2b976e0 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -4,6 +4,25 @@ ## 审查时应保留的显式边界 +### 镜像构建与发布串行边界 + +release tag 工作流构建的镜像会复用同一套镜像构建流程产出 `edge`。为了避免在 master 与 +release tag 两类触发并发时互相覆盖可变 tag(`edge`),`.github/workflows/image.yml` 统一使用单一 +`weather-briefing-image` 并发组,并且不主动取消进行中的同组任务,这样 tag 构建与 master 边构建会按序 +完成,最终 `edge` 只会被最后完成(而不是并发竞争)的一次更新覆盖。 + +`weather_briefing/__init__.py` 与 `pyproject.toml` 的版本更新逻辑在 release 流程中引入了 commit trailer +`Weather-Briefing-Skip-Edge-Image: true` 的提交,用于让“先打 release tag,再立即切到 `-dev` +并推送新提交”时跳过一轮与其冲突的 master edge 构建。这样可以减少一次镜像构建,并且确保最终 `edge` +与本次 release tag 对应的制品一致。 + +该机制的边界: + +- 只在 release 工作流的 commit message 中显式设置该 trailer 的提交才会跳过 edge 构建。 +- `edge` 继续作为“最近一次可运行制品”的游标标签;`latest` 与 `vX.Y.Z` 标签依然各自表示完整发布语义。 +- 若未来需要同时对 `edge` 与别的分支/场景并发发布或允许多路覆盖策略发生变化,需要同步更新 + `.github/workflows/image.yml` 的并发策略与这段决策说明。 + ### 配置与启动边界 DeepSeek 是唯一保留旧环境变量别名的 LLM provider:`DEEPSEEK_MODEL` 后备到 `LLM_MODEL`,`DEEPSEEK_BASE_URL` 后备到 any-llm 使用的 `DEEPSEEK_API_BASE`。这是已投入部署的输入兼容,不是厂商映射;新增 provider 应直接使用 any-llm 的 provider ID 和环境变量,不得把这一分支扩展成通用 provider 配置注册表。 diff --git a/docs/requirements.md b/docs/requirements.md index c28fa6da..bac00415 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -60,4 +60,4 @@ ## 运行环境 -目标为自有服务器。应用支持 Python 3.11–3.14,以当前最新稳定 Python 作为首选开发和测试版本,并在 CI 中覆盖全部受支持版本;项目元数据不设置未经验证的未来 Python 版本上限。项目使用 uv 原生 `uv_build` 构建后端并提交 `uv.lock`,不引入 PDM 工具链。Distroless Debian 13 镜像使用其系统 Python。应用以内置调度器常驻运行,SQLite 位于外部持久目录或卷中。项目维护单一、非 root、由 `uv.lock` 锁定依赖的 OCI 镜像,不使用 Docker Compose;GitHub Actions 仅用于 CI 和独立镜像发布,不预设生产运行 secrets。每次 `X.Y.Z` Git tag 构建更新同名 OCI 标签、`latest` 和 commit SHA 标签;`master` 构建更新 `edge` 和 commit SHA 标签,不修改 `latest` 或版本标签。发布工作流不接收自由格式版本号,由用户单选 major、minor 或 patch:major 递增并把 minor、patch 归零,minor 递增并把 patch 归零,patch 在正式版上递增、在 `-dev` 版上发布其已声明的目标版本。工作流先创建正式版提交和同名 tag,再创建下一个 patch 的 `-dev` 提交,并将 master 与 tag 原子推送;README 部署示例保留最新正式版。开发版本只在包代码位于 weather-briefing 自身 Git worktree 时通过 `--version` 附加短 commit SHA,有未提交或未跟踪改动时同时标记 dirty;从其他 Git 仓库运行、正式版或无法读取自身 Git 状态时只显示内置版本。 +目标为自有服务器。应用支持 Python 3.11–3.14,以当前最新稳定 Python 作为首选开发和测试版本,并在 CI 中覆盖全部受支持版本;项目元数据不设置未经验证的未来 Python 版本上限。项目使用 uv 原生 `uv_build` 构建后端并提交 `uv.lock`,不引入 PDM 工具链。Distroless Debian 13 镜像使用其系统 Python。应用以内置调度器常驻运行,SQLite 位于外部持久目录或卷中。项目维护单一、非 root、由 `uv.lock` 锁定依赖的 OCI 镜像,不使用 Docker Compose;GitHub Actions 仅用于 CI 和独立镜像发布,不预设生产运行 secrets。每次 `X.Y.Z` Git tag 只构建正式版 commit 一次,并让同名 OCI 标签、`latest`、`edge` 和 commit SHA 标签共同指向该镜像;自动生成的下一开发版 `master` commit 跳过重复的 edge 构建,后续普通 `master` 构建继续更新 `edge` 和 commit SHA 标签而不修改 `latest` 或版本标签。发布工作流不接收自由格式版本号,由用户单选 major、minor 或 patch:major 递增并把 minor、patch 归零,minor 递增并把 patch 归零,patch 在正式版上递增、在 `-dev` 版上发布其已声明的目标版本。工作流先创建正式版提交和同名 tag,再创建下一个 patch 的 `-dev` 提交,并将 master 与 tag 原子推送;README 部署示例保留最新正式版。开发版本只在包代码位于 weather-briefing 自身 Git worktree 时通过 `--version` 附加短 commit SHA,有未提交或未跟踪改动时同时标记 dirty;从其他 Git 仓库运行、正式版或无法读取自身 Git 状态时只显示内置版本。