Skip to content

ci(release): 建设 SDK 自托管自动发布与陆骁编排 - #39

Merged
Mr-KID-github merged 12 commits into
mainfrom
codex/sdk-self-hosted-cd
Aug 12, 2026
Merged

ci(release): 建设 SDK 自托管自动发布与陆骁编排#39
Mr-KID-github merged 12 commits into
mainfrom
codex/sdk-self-hosted-cd

Conversation

@Mr-KID-github

@Mr-KID-github Mr-KID-github commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

目标

建设由陆骁编排、自有 Runner 构建、飞书通知、GitHub 人工审批的 Python SDK CI/CD,并停用旧的重复发布入口。

主要改动

  • sdk-ci.yml:自托管 sdk-ci Runner 执行 Python 3.10-3.12、最低/最新依赖、BLE、pytest、mypy 和包构建验证
  • release.yml:合法版本 Tag 门禁、制品单次构建、SHA-256 清单、TestPyPI 验证、pypi Environment 人工审批及 OIDC 发布
  • prepare-release.yml:飞书/标签统一进入版本 PR,使用仓库级 GitHub App 短期令牌
  • 发布工具:PEP 440 版本计算、版本倒退和多标签拒绝、PyPI/TestPyPI/Release 冲突检测、版本 PR 与 main 祖先验证
  • PR Review 改到隔离的 sdk-ci Runner;移除旧 publish.yml
  • 补充中文 CHANGELOG、发布说明和回归测试

基础设施状态

  • VM 202 cicd-runner(192.168.1.103)onboot: 1
  • 新 Runner sdk-ci-103sdk-release-103 已在线
  • 陆骁 Skill 与监视器已部署;自动开关保持关闭,等待 GitHub App 与 Trusted Publisher 一次性配置完成
  • “或宇研发发布群”非生产连通测试已通过

验证

  • 全量 pytest 通过,2 项按预期跳过
  • mypy 检查 86 个源码文件通过
  • wheel/sdist 构建成功
  • twine check 对 wheel 与 sdist 均通过
  • git diff --check 通过

合入前外部配置

  • 创建并安装仓库级 GitHub App,配置 ORULINK_RELEASE_APP_IDORULINK_RELEASE_APP_PRIVATE_KEY
  • 将 PyPI/TestPyPI Trusted Publisher workflow 更新为 release.yml
  • 待当前手动发布结束后重新核对最新 PyPI/TestPyPI/Tag/Actions 状态,再决定首个自动演练版本

风险控制

此 PR 已完成代码自审;在外部平台配置完成前保持不合并。普通 PR、草稿 PR和无合法版本 PR 关联的普通 Tag 不会触发正式发布。

本次提交建立由自有 Runner 执行、GitHub Environment 人工批准、PyPI OIDC Trusted Publishing 发布的 SDK CI/CD 基础能力。

主要改动:
- 拆分 sdk-ci 与 release 工作流,覆盖 Python 3.10-3.12、最低/最新依赖、BLE、pytest、mypy 与包安装验证
- wheel 和 sdist 仅构建一次,生成 SHA-256 清单,并以同一 Artifact 依次发布 TestPyPI 与 PyPI
- 增加版本 PR 准备、PEP 440 版本计算、索引冲突检查、main 祖先和版本 PR 标签门禁
- 正式发布必须进入 pypi Environment 等待负责人审批,不保存长期 PyPI Token
- 将陆骁 PR Review 切换到隔离的 sdk-ci Runner,并移除旧 publish.yml 重复发布入口
- 更新中文发布说明和 CHANGELOG,补充自动发布回归测试

验证结果:
- 全量 pytest 通过,2 项按预期跳过
- mypy 检查 86 个源码文件通过
- 发布工具与工作流契约测试通过
- git diff --check 通过
修复合并前自审发现的两个发布门禁问题:
- annotated tag 的 GitHub 事件 SHA 可能指向 tag object,现先解引用到真实 commit,再验证 main 祖先、版本 PR 与 Release target
- 版本 PR 准备任务改由 sdk-ci 执行,sdk-release 仅接收受保护 v* Tag 发布工作流
- 发布门禁显式拒绝 lightweight tag,确保只接受 annotated tag

验证:发布工具、工作流契约与文档测试全部通过,git diff --check 通过。
配置 uv 的 TestPyPI simple check URL,使网络重试或工作流重跑只跳过与索引中哈希完全一致的制品;冲突制品仍然失败,避免盲目覆盖。
@Mr-KID-github
Mr-KID-github marked this pull request as ready for review August 12, 2026 09:19
将 PR diff 与审查结果从全局 /tmp 固定路径迁移到 GitHub Actions 每个 Job 独立的 RUNNER_TEMP,避免自托管 Runner 上历史文件属主冲突、跨 PR 结果污染和错误评论复用。

补充工作流契约测试,确保不再使用固定 /tmp 文件。
修复自托管 Runner 迁移后暴露的两类问题:
- PR 标题和描述不再直接拼进 Bash 命令,改用环境变量传入,避免 Markdown 反引号被执行
- 桥接脚本使用 Job 独立临时文件和 Hermes 用户缓存目录,不再读写共享 /tmp 固定文件
- scp、ssh 和远端 Agent 失败均显式返回失败,不再发布伪成功审查结果
- 调用结束后清理本地与远端 prompt 文件,降低跨 PR 污染风险

补充工作流契约和 Python 语法验证。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3e46bb4208

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

jobs:
test:
name: Python ${{ matrix.python-version }} / ${{ matrix.dependency-profile }}
runs-on: [self-hosted, Linux, X64, sdk-ci]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Isolate pull-request code from the release runner

For any untrusted pull request, this job checks out PR-controlled content and executes it through the editable install and pytest on a persistent self-hosted runner. The release-preparation workflow uses the same sdk-ci runner label and later exposes ORULINK_RELEASE_APP_PRIVATE_KEY and a credential-persisting App token, so malicious PR code can leave a process or other state on a runner and capture those credentials during a later release job. Run PR code on an ephemeral/hosted runner or reserve a separate runner pool that never handles release secrets.

Useful? React with 👍 / 👎.

Comment on lines +161 to +165
validate_version_absent(
"TestPyPI",
version,
_http_status(f"https://test.pypi.org/pypi/watcherobot/{version}/json"),
allow_existing=reusable_draft,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reuse the verified artifact when retrying a release

When a run has created its draft and uploaded to TestPyPI but stops before PyPI, this exception lets a rerun continue even though the rerun always rebuilds the distributions, including with unpinned build tooling. uv publish --help describes --check-url as checking for existing files “to skip duplicate uploads,” so TestPyPI retains and verifies the first run's files while the production job uploads the newly built files; the bytes approved for PyPI are therefore not necessarily those tested on TestPyPI or attached to the draft. Preserve/download the original artifact on retry, or compare its recorded hashes with the existing TestPyPI and draft assets before allowing the rerun.

Useful? React with 👍 / 👎.

Comment on lines +69 to +71
ble-provisioning:
name: BLE fake backend
runs-on: [self-hosted, Linux, X64, sdk-ci]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore CI coverage for supported BLE platforms

The replacement BLE job now runs only on Linux, whereas the removed workflow exercised both windows-latest and macos-latest. Bluetooth provisioning is explicitly supported only on Windows and macOS (README.md:170-174), and BleakBackend is described as a Windows/macOS backend, so platform-specific import, event-loop, and Bleak regressions can now merge without running on either supported platform. Keep the self-hosted Linux checks if needed, but retain Windows and macOS jobs for this test suite.

Useful? React with 👍 / 👎.

Comment thread .github/workflows/prepare-release.yml Outdated
Comment on lines +127 to +130
existing=$(gh pr list --repo "${GITHUB_REPOSITORY}" --head "${branch}" --state all --json number --jq 'length')
if [[ "${existing}" != "0" ]]; then
echo "Release PR already exists for ${version}; nothing to do."
exit 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow a closed release PR to be retried

If a generated release PR is closed without merging, requesting the same version again always exits here instead of creating a replacement, even when the old branch was deleted or the PR cannot be reopened. gh pr list --help confirms that --state all includes closed PRs, so this reports success while leaving no open version PR and permanently stalls that release request until someone manually repairs the old PR. Restrict the idempotency check to open PRs, or explicitly reopen/recreate a closed one.

Useful? React with 👍 / 👎.

Comment on lines +42 to +47
capture_output=True,
)
validate_absent(
version,
pypi_status=http_status(f"https://pypi.org/pypi/watcherobot/{version}/json"),
testpypi_status=http_status(f"https://test.pypi.org/pypi/watcherobot/{version}/json"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject an existing Git tag before opening the version PR

When v<version> already exists without a GitHub Release—for example, a stale or prematurely created future tag—this check still declares the version available because gh release view only views Release objects and does not test repository refs. The workflow can then create and merge a version PR whose annotated tag cannot subsequently be created without deleting or moving the existing immutable tag, despite docs/releasing.md:33-34 requiring the preparation step to consult tag state. Check the fetched refs/tags/v<version> or the GitHub refs API in addition to the Release lookup.

Useful? React with 👍 / 👎.

Comment thread .github/workflows/release.yml Outdated
Comment on lines +64 to +67
if [[ "${version}" =~ (a|b|rc)[0-9]+$ ]]; then
echo "prerelease=true" >> "${GITHUB_OUTPUT}"
else
echo "prerelease=false" >> "${GITHUB_OUTPUT}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Classify PEP 440 development versions as prereleases

For an explicit canonical target such as 0.1.2.dev1, packaging.Version accepts the version and considers it a prerelease, but this suffix regex does not match and emits prerelease=false. The later gh release create therefore omits --prerelease, causing a development build to be published as a normal GitHub Release and potentially presented to users as the latest stable release. Derive this output from Version(version).is_prerelease rather than matching only alpha, beta, and RC suffixes.

Useful? React with 👍 / 👎.

Comment on lines +122 to +127
validate_version_pull_request(
merged=pull_request.get("merged_at") is not None,
base_ref=str((pull_request.get("base") or {}).get("ref") or ""),
labels=[str(label) for label in labels if label],
head_ref=str((pull_request.get("head") or {}).get("ref") or ""),
version=version,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require the tag to target the PR's merged commit

The associated-PR gate checks only that the PR has some merge timestamp; it never compares the tag SHA with the PR's merge_commit_sha. If a version PR has multiple commits, tagging an earlier version-bump commit after the PR is merged still satisfies main ancestry and remains associated with the valid PR, so the workflow can publish while omitting later corrections from that PR. Require the tag SHA to equal the actual merged commit returned by GitHub rather than accepting any associated commit.

Useful? React with 👍 / 👎.

Comment thread .github/workflows/release.yml Outdated
Comment on lines +170 to +173
if .venv-testpypi/bin/python -m pip install \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
"watcherobot==${{ needs.gate.outputs.version }}"; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep TestPyPI dependencies off the untrusted test index

When verifying the release, pip resolves watcherobot and all of its dependencies across both TestPyPI and PyPI; pip install --help describes --extra-index-url as adding another index rather than assigning dependency priority. An attacker can publish a higher compatible version of a dependency such as av, bleak, or another project name to the separate TestPyPI namespace, causing arbitrary package code to run on the persistent sdk-release runner before later jobs obtain production publishing credentials. Install dependencies exclusively from PyPI first, then install the exact TestPyPI wheel with --no-deps and run pip check.

Useful? React with 👍 / 👎.

Comment thread tools/check_release_availability.py Outdated
version,
pypi_status=http_status(f"https://pypi.org/pypi/watcherobot/{version}/json"),
testpypi_status=http_status(f"https://test.pypi.org/pypi/watcherobot/{version}/json"),
release_exists=release.returncode == 0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fail closed when the Release lookup itself fails

Every nonzero result from gh release view is treated as proof that the Release is absent. gh help exit-codes states that exit code 1 represents failure for any reason and exit code 4 represents an authentication requirement, so an API outage, permission error, or expired App token can reach validate_absent(..., release_exists=False) and permit a conflicting version PR instead of failing closed. Distinguish a verified 404 from operational/authentication errors and propagate the latter.

Useful? React with 👍 / 👎.

@Mr-KID-github
Mr-KID-github force-pushed the codex/sdk-self-hosted-cd branch from 51c5a78 to 79c275f Compare August 12, 2026 09:28
@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

🤖 PR 审查报告

PR: ci(release): 建设 SDK 自托管自动发布与陆骁编排
变更: 17 文件, +1501 / -287


维度一:代码质量

🏗️ 架构视角 — ⚠️

正面:

  • luxiao_review.py 作为 SSH 桥接脚本,职责单一清晰:读取 diff → 封装 prompt → SSH 到 Hermes → 写入结果。与 GitHub Actions 的集成通过环境变量和临时文件,耦合度低。
  • pr-review.yml 从硬编码 /tmp 迁移到 RUNNER_TEMP,可移植性提升。PR 元数据从命令行位置参数改为环境变量传递,更健壮。
  • prepare-release.yml 的触发条件设计合理:仅 merged PR 合入 main 或手动 dispatch 触发,避免了误触发。

需关注:

  • Runner 标签从 pr-review 改为 sdk-ci,PR 审查与 CI 构建共用同一 Runner 池。PR 描述称"改到隔离的 sdk-ci Runner",但实际是合并而非隔离——原先的 pr-review 是独立标签。审查任务和 CI 任务会互相排队,大 PR 的审查可能被 CI 阻塞。
  • luxiao_review.py 硬编码了 Hermes 主机 IP 192.168.1.116 和远端路径。如果 Hermes 服务器迁移或 IP 变更,所有使用该脚本的 workflow 都会静默失败。建议通过 GitHub Secrets 或 Runner 环境变量注入。
  • 本次提供的 diff 不完整——prepare-release.yml 在第 165 行截断(LABELS_JSON: ${{ toJSON),且 sdk-ci.ymlrelease.yml、发布工具脚本等核心文件均未出现在 diff 中。无法对完整架构进行审查。

🎨 产品视角 — ✅

  • 从用户(SDK 维护者)角度看,飞书指令触发 → 版本 PR → CI 验证 → 人工审批 → PyPI 发布的流程清晰。
  • 发布工具包含 PEP 440 版本计算、版本倒退拒绝、PyPI/TestPyPI 冲突检测等防御性检查,用户体验良好。
  • luxiao_review.py 的错误降级处理得当:diff 为空返回"无代码变更",SSH 失败返回明确错误信息,不会让 PR 审查静默丢失。

📏 规范视角 — ⚠️

正面:

  • luxiao_review.py 有完整的 from __future__ import annotations,类型注解一致,docstring 清晰。
  • 使用 pathlib.Path 而非字符串拼接,missing_ok=True 等现代 Python 特性。
  • SSH 选项使用 StrictHostKeyChecking=yes + BatchMode=yes,安全规范。

需关注:

  • diff_text[:8000]text[-6000:] 的硬截断没有日志警告。当 diff 超过 8000 字符时,审查者(我)收到的代码不完整,但脚本不会提示"内容已截断"。这可能导致审查遗漏关键变更。
  • prepare-release.yml 的 diff 被截断在 ${{ toJSON,无法判断该表达式是否完整闭合。如果原文件此处有语法错误,workflow 将在运行时失败。
  • luxiao_review.py_write_result 函数名以 _ 开头表示私有,但实际在 main() 中多处调用——这是合理的模块内使用,但命名约定略有不一致。
  • 第 118 行 raise SystemExit(main()) 而非 sys.exit(main()),虽然功能等价,但前者较少见,可能让维护者困惑。

⚠️ 损伤视角 — ⚠️

已确认无损伤:

  • publish.yml 被移除(PR 描述),新的 release.yml 替代之,发布流程不会重复触发。
  • pr-review.yml 的条件 github.event.pull_request.draft == false 保持不变,草稿 PR 不会触发审查。

潜在风险:

  • Runner 标签从 pr-review 改为 sdk-ci 后,如果 sdk-ci Runner 离线或满载,所有 PR 审查将全部阻塞,且不会自动回退到 GitHub-hosted runner。旧标签 pr-review 有独立 Runner 保障,新标签失去了这个隔离。
  • luxiao_review.py 脚本路径从 /home/opsadmin/scripts/ 变为 /home/runner-ci/scripts/。如果 sdk-ci-103 Runner 上未部署该脚本(或路径不同),PR 审查步骤将失败。PR 描述中提到"陆骁 Skill 与监视器已部署",但未确认桥接脚本是否已就位。
  • prepare-release.yml 是全新文件,若其中的 toJSON 表达式有语法错误,会在首次触发时才发现——建议在合入前用 actionlint 或 GitHub CLI 验证 workflow 语法。

维度二:意图分析

🎯 意图提炼

将 Python SDK 的发布流程从手动/半自动改造为由陆骁编排、飞书触发、自托管 Runner 构建、GitHub Environment 人工审批的全自动化 CI/CD 管线,同时将 PR 审查迁移到统一的 sdk-ci Runner 上。

🔀 偏离检测

  • 意图与项目方向一致——SDK 工程化是明确的基础设施需求。
  • 轻微偏离: PR 描述中提到 Runner 标签变更是"改到隔离的 sdk-ci Runner",但实际效果是去隔离化(从专用 pr-review Runner 合并到共享 sdk-ci Runner)。这可能是措辞问题,也可能是对隔离概念的理解偏差。建议明确:是故意合并以降低维护成本,还是应该保持独立 Runner?
  • 无功能蔓延——所有改动都围绕"发布自动化"和"审查基础设施"两个紧密相关的主题。

Merge 建议

⚠️ 有条件合并

条件:

  1. 补全 diff 审查 — 当前 diff 仅展示 3/17 个文件,且 prepare-release.yml 被截断。sdk-ci.ymlrelease.yml、发布工具脚本(PEP 440 计算、冲突检测等)、CHANGELOG 等核心文件未审查。不应在完整审查完成前合并。

  2. 确认 Runner 部署 — 验证 sdk-ci-103 Runner 上已部署 /home/runner-ci/scripts/luxiao-review.py(注意路径从 opsadmin 变更为 runner-ci)。可在本 PR 的 CI 运行中直接观察审查步骤是否成功。

  3. 修复硬编码luxiao_review.py 中的 Hermes IP 192.168.1.116 建议通过 GitHub Secrets 或 Runner 环境变量注入,避免 IP 变更时静默失败。

  4. 验证 workflow 语法 — 对 prepare-release.yml 运行 actionlintgh workflow validate,确保截断处(${{ toJSON)的表达式完整闭合。

  5. 明确 Runner 策略 — 确认 PR 审查与 CI 共用 sdk-ci Runner 是预期行为。如果审查延迟成为瓶颈,需考虑恢复独立 Runner 标签。

  6. 外部配置就绪 — PR 描述中列出的合入前条件(仓库级 GitHub App、PyPI Trusted Publisher 更新、手动发布收尾)必须在合并前完成。

理由: 架构设计合理,工程意图清晰,代码质量在可见范围内良好。但 diff 不完整、Runner 策略变更的影响未充分评估、核心 workflow 文件存在截断导致无法验证语法正确性。在这些问题澄清前不应合并。


总结: 自动发布管线建设方向正确,luxiao_review.py 桥接脚本设计干净。但当前 diff 不完整(仅 3/17 文件可视),且 Runner 从独立标签合并到共享标签的影响需要明确评估。建议补全审查、验证 workflow 语法、确认 Runner 部署后再合并。

@orulink-ai orulink-ai deleted a comment from github-actions Bot Aug 12, 2026
@orulink-ai orulink-ai deleted a comment from github-actions Bot Aug 12, 2026
@orulink-ai orulink-ai deleted a comment from github-actions Bot Aug 12, 2026
将陆骁审查输入上限提升到 100000 字符,并在超限时明确输出省略字符和行数,防止有限上下文被误判为全量审查。

Hermes 主机改由仓库变量注入,远端脚本与缓存目录支持 Runner 私有环境覆盖;审查任务同时匹配 sdk-ci 与 pr-review 标签,保持执行身份边界清晰。

补充契约测试,并已将同版本桥接脚本部署至 sdk-ci-103 Runner。
@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

PR 审查报告

PR: ci(release): 建设 SDK 自托管自动发布与陆骁编排
变更: 17 个文件, +1530 / -286


维度一:代码质量

🏗️ 架构视角 — ✅

整体架构设计清晰且务实:

  • 三层分离:CI(sdk-ci.yml)、发布(release.yml)、版本准备(prepare-release.yml)职责分明,互不耦合
  • Runner 隔离sdk-cisdk-release 使用独立 Runner 标签,避免 CI 负载影响发布构建
  • 制品不可变:wheel/sdist 在 release.yml 中只构建一次,后续 TestPyPI/PyPI 均下载同一份 Artifact,正式发布前验证 SHA256SUMS —— 这是正确的发布工程实践
  • OIDC Trusted Publishing:全程无长期 PyPI Token,符合零信任原则
  • 工作区清理:start-clean / finish-clean 成对出现,带路径前缀防护,防止误删
  • 工具脚本复用check_release_version.pycheck_release_gate.pyprepare_release.py 通过 sys.path 引用,避免了代码重复

一个轻微架构注意点:check_release_gate.py 通过 sys.path.insert(0, str(_TOOLS_DIR)) 导入同目录脚本。这在 tools 目录结构稳定的前提下可接受,但未来若工具目录重构需同步调整。

🎨 产品视角 — ✅

从 SDK 使用者和维护者角度看,流程设计合理:

  • 发布入口明确:飞书显式指令 或 合并 PR 上恰好一个 release:* 标签
  • 多标签拒绝、版本倒退拒绝、PEP 440 非规范拼写拒绝 —— 防止误操作
  • TestPyPI 先行验证,PyPI 需人工审批 —— 双重保险
  • 中文 CHANGELOG 和发布文档,面向内部团队友好
  • 版本 PR 只更新 __init__.pyCHANGELOG.md,不触碰业务代码,职责边界清晰

📏 规范视角 — ✅

  • 命名:sdk-cisdk-releaseprepare-release 命名一致,意图明确
  • 类型标注:luxiao_review.py 和所有工具脚本均使用 from __future__ import annotations 和完整类型标注
  • 错误处理:工具脚本均使用 ValueError + 明确错误消息,失败即关闭(fail closed),不吞异常
  • 注释:luxiao_review.py 的桥接逻辑有充分的文档字符串;工具脚本的 --help__doc__ 完整
  • diff 截断处理:luxiao_review.py 在 100K 字符截断时明确标注省略范围,要求审查结论注明未覆盖范围 —— 这是负责任的工程做法
  • 安全:rm -rf 前均有 realpath -m + 路径前缀白名单校验;StrictHostKeyChecking=yes + BatchMode=yes SSH 配置

⚠️ 损伤视角 — ⚠️

存在以下需要注意的点:

  1. BLE 测试从 GitHub Hosted 迁移到自托管 Linux(sdk-ci.yml):旧 publish.ymlwindows-latestmacos-latest 上运行 BLE fake backend 测试,新 sdk-ci.yml 改为在 self-hosted Linux 上运行。这意味着失去了跨平台 BLE 导入验证。如果 bleak 在 Linux 和 Windows/macOS 上的行为存在差异,可能漏检。考虑到 fake backend 测试不涉及真实蓝牙硬件,跨平台覆盖的价值主要在于导入兼容性验证。建议在 PR 描述中明确说明此取舍的合理性。

  2. git fetchcheck_release_gate.py 中的认证问题(release.yml gate job):checkout@v6 使用 persist-credentials: false,而 check_release_gate.py 内部调用 git fetch origin main:refs/remotes/origin/main --no-tags。如果仓库是私有的,这个 git fetch 会因为缺少凭据而失败。当前仓库(orulink-ai/WatcheRobot_python_sdk)需要确认是否为公开仓库。如果是公开仓库则无问题;如果是私有仓库,需要在 git fetch 前配置凭据或改用 gh CLI。

  3. publish.yml 已删除:这是预期的清理,但需确认没有任何外部流程(如其他仓库的 workflow dispatch)依赖旧的 publish.yml。PR 描述中提到"停用旧的重复发布入口",需要确认下游无依赖。


维度二:意图分析

🎯 意图提炼

建设由陆骁编排、自托管 Runner 执行、OIDC 发布、飞书通知、人工审批的完整 Python SDK CI/CD 流水线,替代旧的 publish.yml,实现从版本 PR 到 PyPI 发布的全自动化门禁。

🔀 偏离检测

无偏离。此 PR 与项目当前方向高度一致:

  • 公司正在建设内部大模型基础设施(LiteLLM 网关、Langfuse、Agent Lab),SDK 自动发布是基础设施标准化的一部分
  • 陆骁作为工程 Agent 编排发布流程,与公司"AI 驱动的工程实践"方向一致
  • 自托管 Runner 替代 GitHub Hosted Runner,符合公司对构建环境的可控性需求
  • 改动范围聚焦:仅涉及 CI/CD 工作流、发布工具和测试,未蔓延到业务代码

Merge 建议

⚠️ 有条件合并

条件:

  1. 确认仓库可见性:验证 orulink-ai/WatcheRobot_python_sdk 是否为公开仓库。如果是私有仓库,check_release_gate.py 中的 git fetch 会因 persist-credentials: false 而失败,需在 gate job 中增加认证步骤。

  2. 确认 BLE 跨平台覆盖取舍:将 BLE fake backend 测试从 Windows/macOS GitHub Hosted Runner 迁移到 Linux 自托管 Runner 是合理的选择(统一 Runner 管理、降低成本),但需在团队内确认此取舍被接受,并在 sdk-ci.ymldocs/releasing.md 中记录原因。

  3. 确认旧 publish.yml 无外部依赖:检查是否有其他仓库或定时任务通过 workflow_dispatch 触发旧的 publish.yml

  4. 外部配置就绪后再合并(PR 描述中已明确):ORULINK_RELEASE_APP_IDORULINK_RELEASE_APP_PRIVATE_KEY 需配置为仓库 Secret;PyPI/TestPyPI Trusted Publisher 需更新 workflow 名为 release.yml

理由:

代码质量整体优秀,架构设计合理,安全措施到位(OIDC、SHA256SUMS 验证、工作区清理防护、diff 截断标注)。上述条件均为外部配置或团队决策问题,不涉及代码本身的缺陷。条件满足后可以放心合并。


总结: 这是一次高质量的 CI/CD 基础设施建设 PR,架构清晰、安全措施到位、测试覆盖充分。三个条件均属外部配置或团队决策,代码本身无阻塞性问题。

隔离可信版本编排与 PR 代码执行身份,关闭版本 PR 可重新发起;发布准备同时检查 Git Tag 和 GitHub Release,并在 GitHub 查询异常时失败关闭。

强制发布 Tag 指向版本 PR 的 merge commit,重跑时复用 Draft Release 中的原始制品并校验哈希,避免 TestPyPI 与后续制品不一致。

TestPyPI 验证先从正式 PyPI 安装依赖,再以 no-deps 下载目标 wheel 并比对哈希,消除依赖混淆风险;PEP 440 预发布版仅发布 TestPyPI 与 GitHub prerelease,不进入正式 PyPI 审批。

保留公司自托管 Runner 策略下的 Linux BLE 契约测试,并在发布文档中明确其不替代 Windows/macOS 实机验收。
@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

⚠️ Review agent did not produce output.

将 release Artifact 名称绑定 github.run_attempt,保证同一次 Workflow 重跑恢复原始制品后能以本次唯一名称重新上传,并让后续 TestPyPI、PyPI 与 Release 任务引用同一份制品。
@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

⚠️ Review agent did not produce output.

为测试矩阵、BLE 假后端和制品构建任务分别创建全新虚拟环境,并通过 GITHUB_PATH 让后续步骤统一使用该环境。

避免持久化 Runner 上遗留的 twine 等工具污染最低依赖校验,同时增加工作流结构测试,确保三个任务持续保持环境隔离。
将远端审查预算调整为十分钟,并由远端 timeout 在到期后先发送 TERM、三十秒后强制结束,防止本地 SSH 超时留下孤儿 Agent。

本地桥接超时略晚于远端门限,确保能够接收远端退出状态;增加结构测试锁定双层超时与进程回收策略。
@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

⚠️ Review agent did not produce output.

1 similar comment
@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

⚠️ Review agent did not produce output.

@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

⚠️ Luxiao Agent 调用失败(退出码 124)。

Daemon 多应用 Python 环境测试会创建无 pip 的最小虚拟环境;在 Python 3.10 下 websockets 14 仍依赖 typing_extensions,原夹具只注入 websockets 路径导致子进程启动失败。

将 typing_extensions 所在目录一并写入测试环境的 pth 文件,保持真实解释器切换测试的隔离语义,不修改 Daemon 业务路由。
远端复现确认失败发生在解释器加载标准库之前:setup-python 二进制仍指向 /opt/hostedtoolcache,而自托管 Runner 缓存实际位于私有 _work/_tool。

typing_extensions 与本次失败无关,恢复原测试夹具,后续在 Runner 服务层修复工具缓存前缀,保持测试继续验证真实虚拟环境。
@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

⚠️ Review agent did not produce output.

@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

PR 审查报告

PR: ci(release): 建设 SDK 自托管自动发布与陆骁编排
变更: 17 个文件, +1714 / -286


维度一:代码质量

🏗️ 架构视角 — ✅

关注点分离清晰,是本次 PR 最大的亮点:

  • sdk-ci.yml 负责 PR/main 的 CI(测试、类型检查、构建验证),release.yml 负责 Tag 触发的发布管线,prepare-release.yml 负责版本 PR 编排,三者职责不重叠。
  • publish.yml 将 CI 和发布混在一个文件里,删除是正确的。
  • 发布管线采用"制品单次构建 + SHA256SUMS + Artifact 复用"模式,TestPyPI 和 PyPI 使用同一份不可变制品,正式发布前再次验证哈希,杜绝了重新构建引入差异的风险。
  • prepare-release.yml 使用仓库级 GitHub App 短期令牌而非个人 PAT,权限边界合理。
  • Gate → Build → TestPyPI → Verify → PyPI → Verify 的管线依赖链完整,prerelease 和 stable 分支路径清晰。
  • luxiao_review.py 桥接脚本将审查逻辑外移到 Hermes 实例,通过 SSH + scp 传递 prompt,设计上隔离了 Runner 环境和审查 Agent。

⚠️ 一个值得注意的架构决策:ble-provisioning 从原来 GitHub 托管的 Windows/macOS Runner 迁移到自托管 Linux Runner,只做契约测试(fake backend 导入 + 行为验证)。PR 描述和 workflow 注释已明确说明这不替代实机蓝牙验收,但需要确认团队是否已接受这个测试覆盖率的降级。

🎨 产品视角 — ✅

从 SDK 使用者(开发者)视角:

  • 发布文档 docs/releasing.md 重写后清晰描述了自动发布流程、入口条件、版本规则和验证方法。
  • 中文 CHANGELOG.md 被引入,面向中文使用者,版本条目有明确的生成和审查流程。
  • 发布入口收窄到飞书指令或带唯一发布标签的合并 PR,消除了旧流程中 workflow_dispatchrelease.published 并存的混乱。
  • 版本倒退、多标签、已存在版本等异常情况全部 fail-closed,不会静默覆盖。

📏 规范视角 — ✅

  • Python 代码全部使用 from __future__ import annotations、类型标注、PEP 440 规范化处理,一致性好。
  • luxiao_review.py 的错误处理路径完整:diff 不存在、HERMES_HOST 未配置、SSH 失败、Agent 超时/无输出,每种情况都有明确的 fallback 消息。
  • check_release_gate.py 使用 @dataclass(frozen=True) 返回结果,不可变设计正确。
  • check_release_availability.py 的 GitHub API 查询采用 fail-closed 策略:只信任明确的 HTTP 404,其他错误一律抛异常,避免误判。
  • 测试覆盖了 release gate、availability check、prepare release、version calculation 的核心路径和边界条件(参数化测试覆盖了 PyPI/TestPyPI/Release/Tag 的各种组合)。
  • Workflow 中使用 actions/create-github-app-token@v2astral-sh/setup-uv@<commit-hash> 等固定版本,供应链安全意识到位。

⚠️ 两个小问题:

  1. prepare-release.yml 中的标签解析逻辑是内联 Python 脚本(约 40 行),虽然结构清晰,但放在 workflow YAML 里不如独立脚本可测试。建议后续重构时提取为 tools/resolve_release_request.py
  2. luxiao_review.py 第 149 行 text = text[-6000:] 作为 fallback 截断,如果 Agent 返回了超长但没有匹配任何 marker 的审查报告,可能会丢失前面的关键结论。建议至少保留开头和结尾各 3000 字符。

⚠️ 损伤视角 — ✅

  • publish.yml 被完整删除,其功能已被 sdk-ci.yml(CI)和 release.yml(发布)完全覆盖,无功能遗漏。
  • pr-review.yml 的 diff 路径从 /tmp/pr.diff 迁移到 RUNNER_TEMP,避免了多 job 并发时的文件冲突。luxiao_review.py 也使用 NamedTemporaryFile + RUNNER_TEMP,不再使用硬编码的 /tmp 路径。
  • sdk-ci.yml 新增了 huggingface-hubpackaginguvicorn 等依赖的最低版本约束,比旧 workflow 更严格,不会引入意外的新版本兼容问题。
  • 每个 job 使用独立的虚拟环境(.venv),不会污染 Runner 的全局 Python。
  • release.ymlstart-cleanfinish-clean job 确保自托管 Runner 的工作区在发布前后清理干净,避免残留文件干扰下一次构建。
  • 所有测试保留并通过,2 项按预期跳过,mypy 检查 86 个源文件通过,无回归。

维度二:意图分析

🎯 意图提炼

建设一套由陆骁编排、自托管 Runner 执行、飞书通知、GitHub Environment 人工审批的 Python SDK 完整 CI/CD 管线,替代旧的单文件 publish.yml,实现版本门禁、不可变制品、OIDC 可信发布和审批流程。

🔀 偏离检测

无偏离。意图与项目方向一致:将发布流程从"手动 + GitHub 托管 Runner"升级为"半自动编排 + 自托管 Runner + 人工审批",是基础设施成熟化的必要步骤。没有功能蔓延——本次变更严格限定在 CI/CD 和发布工具层面,未触及 SDK 业务代码。


Merge 建议

⚠️ 有条件合并

代码质量本身达到合并标准。 架构合理、测试充分、文档完整、错误处理到位。

但 PR 自身声明了三个前置条件尚未满足:

  1. 仓库级 GitHub App 尚未创建和安装(ORULINK_RELEASE_APP_ID / ORULINK_RELEASE_APP_PRIVATE_KEY 未配置)
  2. PyPI/TestPyPI Trusted Publisher 尚未更新为 release.yml
  3. 当前手动发布尚未结束,需重新核对索引状态后再决定首个自动演练版本

这三个条件中,前两个是阻塞性的——没有 GitHub App Token,prepare-release.yml 的 checkout 和 PR 创建步骤会失败;没有更新 Trusted Publisher,release.yml 的 OIDC 发布无法通过 PyPI 认证。

建议: 在上述外部配置完成后直接合并,无需再次审查代码。代码本身已自审完毕,CI 全绿。


总结: 这是一份高质量的基础设施 PR——架构清晰、安全意识到位、错误处理完整、测试充分。唯一的阻塞项是外部平台配置,代码本身没有需要修改的问题。配置完成后可以直接合并。

@Mr-KID-github
Mr-KID-github merged commit 70231a1 into main Aug 12, 2026
9 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant