news 2026/9/6 21:39:08

caveman 公共包发布流水线:从注解标签到 npm/PyPI 可信发布的完整机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
caveman 公共包发布流水线:从注解标签到 npm/PyPI 可信发布的完整机制

caveman 公共包发布流水线:从注解标签到 npm/PyPI 可信发布的完整机制

【免费下载链接】caveman🪨 why use many token when few token do trick — Claude Code skill that cuts 65% of tokens by talking like caveman项目地址: https://gitcode.com/GitHub_Trending/caveman1/caveman

本文基于 docs/PACKAGE_RELEASES.md 展开,讲解 caveman 如何通过.github/workflows/release-packages.yml工作流,把@caveman-ai/sdk@caveman-ai/agent@caveman-ai/create-agent与 PyPIcaveman-sdk四个公共包安全发布到注册表。读完后你将理解:caveman 的发布标签规范、"构建与发布隔离"的 OIDC 安全模型、标签与包元数据的版本一致性校验、npm tarball 与 Python wheel/sdist 的构建及冒烟验证流程,以及发布后的验证与回滚纪律。

发布范围与构件矩阵

caveman 在 2026-08-11 以 Bootstrap 方式发布了四个经过审查的构件,并在干净环境中匿名验证通过:

发布渠道包名初始版本
npm@caveman-ai/sdk1.0.0
npm@caveman-ai/agent0.1.0
npm@caveman-ai/create-agent0.1.0
PyPIcaveman-sdk1.0.0

这些版本与当前仓库中的包元数据完全一致:packages/sdk/typescript/package.json中为@caveman-ai/sdk@1.0.0packages/agent/package.json@caveman-ai/agent@0.1.0packages/create-caveman-agent/package.json0.1.0,而 packages/sdk/python/pyproject.toml 中项目名为caveman-sdk、版本1.0.0(Python 侧的运行时导入名是caveman_cloud,包名与导入名不同,这是发布时需要特别留意的点)。

验证结论包括:全新安装与运行时 import 均通过,注册表下载内容与受审构件的 SHA-256 哈希一致,且发布构件中不包含任何注册表凭证。

需要说明的是,从工作流源码看,.github/workflows/release-packages.yml 的触发标签模式实际还包含第五种pi-v*,对应 packages/pi-extension(npm 名@caveman-ai/pi,当前版本0.1.0,仓库中也存在pi-v0.1.0标签)。文档描述的"四个构件"是 Bootstrap 阶段的受审发布面,工作流本身对 pi 扩展包的发布通道已预留。

工作流架构:构建与发布严格隔离

整个工作流的安全设计核心是一条原则:构建任务没有 OIDC 权限,只有隔离的发布任务可以铸造注册表令牌。具体体现在工作流顶层与三个 job 的划分:

# .github/workflows/release-packages.yml permissions: contents: read # 工作流默认只有只读内容权限 concurrency: group: release-package-${{ github.ref }} cancel-in-progress: false # 同标签的发布任务不允许互相抢占 jobs: build: # 只负责解析包、构建、测试、打包 publish-npm: # environment: npm,permissions: id-token: write publish-pypi: # environment: pypi,permissions: id-token: write
  • buildjob(30 分钟超时)完成所有重活:校验标签、解析包、构建、测试、审计、打包,最后把 tarball 或 wheel/sdist 上传为一次性 artifact(保留期仅 1 天,retention-days: 1)。
  • publish-npm/publish-pypijob(各 10 分钟超时)只依赖build的输出,下载 artifact 后调用注册表发布接口。它们绑定受保护的 GitHub 环境(npm/pypi),并在environmenturl字段中直接写明对应包的注册表页面,方便在 Actions 界面跳转核对。
  • 只有这两个发布 job 声明了id-token: write,即只有它们能通过 OIDC 向 npm/PyPI 换取发布令牌;buildjob 即使被恶意代码劫持,也无法触达注册表凭证。
  • cancel-in-progress: false保证同一个标签一旦开始发布就不会被后续 push 取消,避免半截发布。

标签门禁:注解、签名验证与 main 祖先关系

标签是发布的第一道闸门。文档规定:标签必须是注解标签(annotated)、必须通过 GitHub 签名验证、且必须指向main。工作流中有一个专门的步骤把这三点都硬性校验(release-packages.yml):

set -euo pipefail tag_ref="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$GITHUB_REF_NAME" --jq .object.sha)" tag_type="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$GITHUB_REF_NAME" --jq .object.type)" [[ "$tag_type" == "tag" ]] || { echo "release tag must be annotated" >&2; exit 1; } verified="$(gh api "repos/$GITHUB_REPOSITORY/git/tags/$tag_ref" --jq .verification.verified)" [[ "$verified" == "true" ]] || { echo "release tag signature is not GitHub-verified" >&2; exit 1; } target="$(gh api "repos/$GITHUB_REPOSITORY/git/tags/$tag_ref" --jq .object.sha)" git cat-file -e "$target^{commit}" git merge-base --is-ancestor "$target" origin/main

这段脚本依次检查:

  1. 标签对象类型为tag(注解标签),而非轻量的commit指针;
  2. GitHub 对该标签签名的验证结果为true
  3. 标签指向的对象确实是一个 commit;
  4. 该 commit 是origin/main的祖先(git merge-base --is-ancestor),即发布内容必须来自main分支,杜绝从任意旁支标签直接发布。

标签命名遵循"包前缀 + 版本号"的约定,触发模式为sdk-ts-v*sdk-python-v*agent-v*create-agent-v*(以及上文提到的pi-v*)。

版本一致性校验:标签必须等于包元数据

文档明确"Workflow rejects a tag whose version differs from package metadata(工作流拒绝版本与包元数据不一致的标签)"。工作流的Resolve package and require tag/version parity步骤(release-packages.yml)实现了这一点:

case "$GITHUB_REF_NAME" in sdk-ts-v*) package="packages/sdk/typescript" ecosystem="npm" version="${GITHUB_REF_NAME#sdk-ts-v}" actual="$(node -p "require('./$package/package.json').version")" ;; sdk-python-v*) package="packages/sdk/python" ecosystem="pypi" version="${GITHUB_REF_NAME#sdk-python-v}" actual="$(python -c 'import pathlib,tomllib; print(tomllib.loads(pathlib.Path("packages/sdk/python/pyproject.toml").read_text())["project"]["version"])')" ;; agent-v*) # → packages/agent create-agent-v*)# → packages/create-caveman-agent pi-v*) # → packages/pi-extension esac [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]] || { echo "release version is not semver: $version" >&2; exit 1; } [[ "$actual" == "$version" ]] || { echo "tag version $version does not match package version $actual" >&2; exit 1; }

这里有三层防护:

  • 标签到包的映射:按前缀把标签解析到具体的包目录,并判定发布生态(npmpypi);
  • Semver 语法校验:版本必须匹配标准 SemVer 正则(允许+build/-pre后缀),防止agent-v1.0这类非法版本进入流水线;
  • 精确相等比对:从package.json(npm 包用node -p读取)或pyproject.toml(用tomllib解析project.version)中读出实际版本,与标签版本做字符串相等比较,任何漂移(比如忘了 bump 版本号就打标签)都会直接exit 1

当前标签与构件的对应关系(文档表格,已用仓库实际元数据核对):

标签构件当前版本
sdk-ts-v1.0.0npm@caveman-ai/sdk1.0.0
sdk-python-v1.0.0PyPIcaveman-sdk1.0.0
agent-v0.1.0npm@caveman-ai/agent0.1.0
create-agent-v0.1.0npm@caveman-ai/create-agent0.1.0

npm 构建流水线:安装、审计、测试与按包定制的冒烟验证

npm 生态的构建步骤(Build and test npm package,release-packages.yml)在对应包的 working directory 中执行:

npm ci --ignore-scripts --no-audit --no-fund npm audit --package-lock-only --omit=dev --audit-level=low npm audit --package-lock-only --audit-level=high npm test npm pack --pack-destination "$RUNNER_TEMP/package-dist" mapfile -t tarballs < <(find "$RUNNER_TEMP/package-dist" -maxdepth 1 -type f -name '*.tgz' -print) [[ "${#tarballs[@]}" == 1 ]] || { echo "expected exactly one npm tarball" >&2; exit 1; }

关键设计点:

  • 可移植的提交锁文件:使用npm ci --ignore-scripts基于已提交的 lockfile 安装,保证"锁文件里写什么就装什么",且禁用 install 脚本以防安装期代码执行;
  • 双层安全审计:对运行时依赖图做low级别审计(任何运行时漏洞即失败),再对完整依赖图做high级别审计。这与 packages/agent/README.md 中描述的安全边界一致——发布 CI 拒绝任何运行时 advisory 以及完整图中的 high/critical advisory;
  • 全量测试npm test运行该包的完整测试套件;
  • 恰好一个 tarballnpm pack产物必须唯一,防止意外打包了多余文件。

随后工作流把 tarball 装进一个临时干净项目($RUNNER_TEMP/npm-artifact-smoke),按包类型执行不同的"真机冒烟":

case "${{ steps.package.outputs.package }}" in packages/sdk/typescript) (cd "$smoke" && node --input-type=module -e 'const m=await import("@caveman-ai/sdk"); if(typeof m.Cave!=="function") process.exit(1)') ;; packages/agent) (cd "$smoke" && node --input-type=module -e 'const m=await import("@caveman-ai/agent"); if(typeof m.agent!=="function") process.exit(1)') "$smoke/node_modules/.bin/caveman-agent" --version ;; packages/pi-extension) # 发布的 tarball 必须真正携带 package.json `pi.extensions` 所命名的打包扩展 test -s "$smoke/node_modules/@caveman-ai/pi/dist/index.mjs" ;; packages/create-caveman-agent) generated="$smoke/generated-agent" "$smoke/node_modules/.bin/create-caveman-agent" "$generated" --provider openai --no-install test -f "$generated/src/agent.ts" test -f "$generated/src/run.ts" node -e '... 断言生成项目依赖 "@caveman-ai/agent": "^0.1.0" ...' "$generated/package.json" node -e '... 断言 run 脚本为 "node --experimental-strip-types src/run.ts" ...' "$generated/package.json" grep -F 'entryPath: "src/agent.ts"' "$generated/src/run.ts" grep -F 'rootDir' "$generated/src/run.ts" ;; esac

这些冒烟测试验证的是"用户拿到 tarball 之后真正会发生什么":

  • @caveman-ai/sdk:干净项目 import 后必须能拿到Cave构造函数。对照 packages/sdk/typescript/src/index.ts 的导出,Cave确实是 SDK 的核心入口类(CaveOptions支持apiKeybaseURLagentretentiontimeoutMssignal等选项),断言的是真实 API 面而非占位导出;
  • @caveman-ai/agent:除agent()导出外,还实际执行caveman-agent --version,验证bin可执行文件在 tarball 中可用;
  • @caveman-ai/pi:检查dist/index.mjs非空,确保打包扩展真的随 tarball 分发——工作流注释直言"这正是关键",因为package.jsonpi.extensions字段指向的构建产物若缺失,插件市场安装后会是空壳;
  • @caveman-ai/create-agent:实际运行 initializer 生成一个项目,然后逐项断言生成的src/agent.tssrc/run.ts存在、package.json依赖锁定在^0.1.0、run 脚本为node --experimental-strip-types src/run.tsrun.ts内含entryPath: "src/agent.ts"。这是对"脚手架产物契约"的端到端验证。

Python 构建流水线:wheel 与 sdist 双工件

Python 包发布"wheel plus sdist",构建步骤(release-packages.yml):

python -m pip install --disable-pip-version-check build==1.3.0 pytest==9.0.3 python -m pytest python -m build --sdist --wheel --outdir "$RUNNER_TEMP/package-dist" # 断言恰好 1 个 wheel + 1 个 sdist # wheel 冒烟: python -m venv "$RUNNER_TEMP/wheel-smoke" "$RUNNER_TEMP/wheel-smoke/bin/python" -m pip install --no-deps "${wheels[0]}" "$RUNNER_TEMP/wheel-smoke/bin/python" -c 'import caveman_cloud,importlib.metadata; assert importlib.metadata.version("caveman-sdk") == "${{ steps.package.outputs.version }}"' # sdist 冒烟: python -m venv "$RUNNER_TEMP/sdist-smoke" "$RUNNER_TEMP/sdist-smoke/bin/python" -m pip install setuptools==80.9.0 "$RUNNER_TEMP/sdist-smoke/bin/python" -m pip install --no-deps --no-build-isolation "${sdists[0]}" "$RUNNER_TEMP/sdist-smoke/bin/python" -c 'import caveman_cloud,importlib.metadata; assert importlib.metadata.version("caveman-sdk") == "${{ steps.package.outputs.version }}"'

两个独立的 venv 分别验证 wheel 和 sdist 的安装路径:wheel 直装,sdist 用固定版本 setuptools 且--no-build-isolation源码构建。两者都要满足"能import caveman_cloudimportlib.metadata读到的发行版名/版本与标签一致"。注意这里再次体现了 PyPI 发行名(caveman-sdk)与 Python 导入名(caveman_cloud)的分离——对应源码目录 packages/sdk/python/caveman_cloud,测试位于 packages/sdk/python/tests。

OIDC 可信发布:发布任务的令牌从哪来

两个发布 job 都不带长期凭证。publish-npm(release-packages.yml)的流程:

permissions: contents: read id-token: write steps: - uses: actions/download-artifact@... # 拉取 build 产出的 tarball - uses: actions/setup-node@... with: node-version: "24.16.0" registry-url: https://registry.npmjs.org - name: Publish with npm trusted publishing run: | set -euo pipefail npm install --global npm@11.5.1 npm publish ./dist/*.tgz --access public

publish-pypi则使用pypa/gh-action-pypi-publish@v1.14.0动作(release-packages.yml)。发布时 GitHub Actions 用id-token: write权限向注册表出示 OIDC 身份(owner / repo / workflow / environment / tag),注册表侧配置的"可信发布者"校验该身份后自动铸造一次性令牌——这正是文档所说"only isolated publish jobs can mint registry tokens"的实现方式:构建机上的任何秘密都无法被用来发布,因为 build job 根本拿不到 OIDC 权限。

文档同时给出了可信发布者的精确身份字段(注册表身份字段大小写敏感),这是未来切到受信发布者发布时的核对清单:

  • npm:确认 founder 拥有@caveman-aiscope;为每个包配置可信发布者:ownerJuliusBrussee、repositorycaveman、workflowrelease-packages.yml、environmentnpm、actionnpm publish,随后禁用长期发布令牌;
  • PyPI:为项目caveman-sdk配置可信发布者:ownerJuliusBrussee、repositorycaveman、workflowrelease-packages.yml、environmentpypi;Python 导入名保持caveman_cloud不变。

从源码结构看,当前工作流中两个发布 job 已绑定npm/pypi两个 GitHub 环境(含指向注册表页面的url字段),环境侧的受保护规则(限制部署到main上的签名发布标签、要求 founder 审批)是文档"剩余工作"中需要补齐的配套设置。

后续切换受信发布的四步计划

文档列出了把未来发布迁移到 trusted publishers 的完整步骤,值得作为发布工程参考:

  1. tools/publish-public.sh --apply镜像受审源码,人工检查 diff 后再 commit 并 push 到公共仓库JuliusBrussee/caveman。脚本本身从不commit 或 push——推送动作必须由人完成。需要说明:当前仓库中未见tools/publish-public.sh,该脚本属于文档描述的外部发布仓库侧工具,本仓库作为受审源码镜像不承载它;
  2. 在公共仓库创建受保护的 GitHub 环境npmpypi:限制部署到main上的签名发布标签,并要求 founder 审批;
  3. 在 npm 侧确认@caveman-aiscope 归属,按上文身份字段配置各包的可信发布者,并禁用长期发布令牌;
  4. 在 PyPI 侧为caveman-sdk配置可信发布者。

发布后验证:证据先行,绝不覆盖

文档对"发布成功"的定义非常严格——不要在注册表端点从干净环境解析回本项目之前,切换任何公共安装命令。每个发布版本都要证明五件事:确切版本号、包归属(owner/repository)、全新安装可用、import 可用、initializer 输出正确。对 Agent SDK,具体做法是在生成项目中运行零 provider 调用的caveman-agent doctor,再执行一次带凭证的陌生人(stranger)响应。这与 packages/agent/README.md 中的说明吻合:npx caveman-agent doctor是"零 provider 调用"的就绪检查,Engine/CLI/gateway 缺失只给 WARN 且退出码 0(observe-only 仍可运行),而 Node 版本、沙箱隔离、配置或锁漂移等问题才会使其失败。provider 费用明确属于发布工作流之外。

失败处理路径同样是强制性的:

  • 冒烟失败时,对受影响的 npm 版本执行deprecate,或yankPyPI 发布;
  • 移除公共安装命令;
  • 用新版本前向修复(fix forward);
  • 保留失败的构件与工作流日志作为证据;
  • 绝不覆盖已发布的版本

相关文件索引

  • docs/PACKAGE_RELEASES.md:本文的主体文档,记录注册表状态、剩余设置、标签规范与发布后证明要求
  • .github/workflows/release-packages.yml:发布工作流完整实现(标签门禁、版本一致性、npm/Python 构建与冒烟、OIDC 发布)
  • packages/sdk/typescript/package.json:@caveman-ai/sdk@1.0.0元数据
  • packages/sdk/python/pyproject.toml:caveman-sdk@1.0.0元数据(导入名caveman_cloud
  • packages/agent/package.json:@caveman-ai/agent@0.1.0元数据
  • packages/create-caveman-agent/package.json:@caveman-ai/create-agent@0.1.0元数据与bin定义
  • packages/pi-extension/package.json:@caveman-ai/pi@0.1.0(工作流已覆盖的第五个发布通道)
  • packages/agent/README.md:caveman-agent doctor的就绪检查语义,与发布后证明步骤对应

【免费下载链接】caveman🪨 why use many token when few token do trick — Claude Code skill that cuts 65% of tokens by talking like caveman项目地址: https://gitcode.com/GitHub_Trending/caveman1/caveman

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 21:34:59

结构专业BIM落地实践:从软件选型到族文件与AI应用

简介&#xff1a;这是一份围绕BIM技术在建筑结构设计中的应用所撰写的论文参考资料&#xff0c;面向土木建筑、结构设计相关专业的学生与从业者&#xff0c;适用于课程论文写作、技术调研或对BIM应用现状的快速了解。文档从BIM技术概述入手&#xff0c;系统梳理了模型在构件信息…

作者头像 李华
网站建设 2026/9/6 21:28:02

Windows 安装 pgvector 0.8.6 完整流程:从编译到向量检索只需 3 步

Windows 安装 pgvector 0.8.6 完整流程&#xff1a;从编译到向量检索只需 3 步 【免费下载链接】pgvector Open-source vector similarity search for Postgres 项目地址: https://gitcode.com/GitHub_Trending/pg/pgvector pgvector 是一个开源的向量相似度搜索扩展&am…

作者头像 李华
网站建设 2026/9/6 21:27:36

猫抓:如何在浏览器中嗅探并下载网页媒体资源指南

猫抓&#xff1a;如何在浏览器中嗅探并下载网页媒体资源指南 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 猫抓&#xff08;cat-catch&#xff0…

作者头像 李华
网站建设 2026/9/6 21:25:45

Buzz 离线语音转录指南:本地 Whisper 把录音转成文字与字幕

Buzz 离线语音转录指南&#xff1a;本地 Whisper 把录音转成文字与字幕 【免费下载链接】buzz Buzz transcribes and translates audio offline on your personal computer. Powered by OpenAIs Whisper. 项目地址: https://gitcode.com/GitHub_Trending/buz/buzz 处理采…

作者头像 李华