Merge pull request #2798 from deepseek-harness/ci/release-check-panel

ci: stop PR gray checks from lifecycle and release publish jobs
This commit is contained in:
Chinesezjc
2026-08-20 17:36:12 +08:00
committed by GitHub
13 changed files with 333 additions and 140 deletions
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.md
2026-08-10-event-directed-pr-review-status.md: 9db9c64fc87c1701028ae825357c3cbd7fef44d1
2026-08-10-event-directed-pr-review-status.zh.md: 381a3f64a62930a584f48cfbc3571679bbcbcef7
2026-08-10-event-directed-pr-review-status.md: 3ed6038929d3c2c1e9cd82182978262ee363f5ab
2026-08-10-event-directed-pr-review-status.zh.md: 1fa8650057e53ab894c597b712720a3ee7a5c46a
@@ -12,7 +12,7 @@ A monotonic projection also cannot return an automation-owned Issue from `In rev
## Decision
The Issue lifecycle workflow treats review webhooks as commands. `pull_request.review_requested`, including a repeated request, targets `In review`. `pull_request_review.submitted` targets `In progress` only when `review.state` is `changes_requested`; the submitted event remains necessary because a reviewer can request changes without an earlier review-request event. Approved and commented submissions skip their lifecycle job before it creates a Project token, while dismissed reviews are not subscribed.
The Issue lifecycle workflow treats review webhooks as commands. `pull_request.review_requested`, including a repeated request, targets `In review`. `pull_request_review.submitted` targets `In progress` only when `review.state` is `changes_requested`; the submitted event remains necessary because a reviewer can request changes without an earlier review-request event. Approved and commented submissions run their lifecycle job but no-op (they never reach the Project token step), while dismissed reviews are not subscribed.
Ordinary subscribed pull-request events remain forward-only implementation signals: they can move `Inbox`, `Backlog`, or `Ready` to `In progress`, but they cannot move `In review` backward. Review-request commands can move any earlier active status to `In review`. Changes-requested commands can move earlier active statuses forward to `In progress` and can move `In review` back only when the latest status event for the target Project was written by the configured lifecycle actor. A human or unknown latest actor preserves the current status.
@@ -22,7 +22,7 @@ The handler resolves only exact same-repository `Fixes`, `Closes`, or `Resolves`
## Verification
[Issue-management tests](../../../../.github/issue-management/policy.test.mjs) pin the event-to-command mapping, the repeated-review-request transition after a changes-requested command, the changes-requested regression, terminal protection, and human override preservation. [Workflow tests](../../../../scripts/ci-workflow.spec.ts) pin the subscribed events, the changes-requested job condition, and the separate `ready_for_review` policy trigger.
[Issue-management tests](../../../../.github/issue-management/policy.test.mjs) pin the event-to-command mapping, the repeated-review-request transition after a changes-requested command, the changes-requested regression, terminal protection, and human override preservation. [Workflow tests](../../../../scripts/ci-workflow.spec.ts) pin the subscribed events, the job-level absence of `if` plus the step-level gate on the token/board steps (so approved/commented reviews pass without minting a token), and the separate `ready_for_review` policy trigger.
## Alternatives considered
@@ -12,7 +12,7 @@ Issue 所在 Project 中的状态记录了解决工作的下一步由谁负责
## 决策
Issue 生命周期工作流把评审 webhook 视为命令。`pull_request.review_requested`(包括重复请求)将目标状态指定为 `In review``pull_request_review.submitted` 将目标状态指定为 `In progress`,但仅在 `review.state``changes_requested` 时生效;submitted 事件仍不可省略,因为评审人即使没有先触发 review-request 事件,也可以直接提出修改要求。对于 approved 和 commented 提交,工作流会在生命周期作业创建 Project token 前跳过该作业;dismissed 评审则不在订阅范围内。
Issue 生命周期工作流把评审 webhook 视为命令。`pull_request.review_requested`(包括重复请求)将目标状态指定为 `In review``pull_request_review.submitted` 将目标状态指定为 `In progress`,但仅在 `review.state``changes_requested` 时生效;submitted 事件仍不可省略,因为评审人即使没有先触发 review-request 事件,也可以直接提出修改要求。对于 approved 和 commented 提交,生命周期作业会运行但空操作(不会走到创建 Project token 一步);dismissed 评审则不在订阅范围内。
工作流订阅的普通 PR 事件仍是只向前推进的实现信号:它们可以将 `Inbox``Backlog``Ready` 推进至 `In progress`,但不能让 `In review` 倒退。请求评审命令可将任意较早的活跃状态推进至 `In review`。请求修改命令可将较早的活跃状态推进至 `In progress`;它也可以让 `In review` 状态回退,但仅在目标 Project 的最新状态事件由配置的生命周期执行主体写入时进行。若最新状态事件的执行主体是人工用户或未知主体,则保留当前状态。
@@ -22,7 +22,7 @@ Issue 生命周期工作流把评审 webhook 视为命令。`pull_request.review
## 验证
[Issue 管理测试](../../../../.github/issue-management/policy.test.mjs)锁定事件到命令的映射、请求修改命令后重复请求评审所触发的状态转换、请求修改后的状态回退、终态保护,以及保留人工覆盖状态。[工作流测试](../../../../scripts/ci-workflow.spec.ts)锁定订阅事件、请求修改作业的条件,以及独立的 `ready_for_review` 策略触发器。
[Issue 管理测试](../../../../.github/issue-management/policy.test.mjs)锁定事件到命令的映射、请求修改命令后重复请求评审所触发的状态转换、请求修改后的状态回退、终态保护,以及保留人工覆盖状态。[工作流测试](../../../../scripts/ci-workflow.spec.ts)锁定订阅事件、job 级无 `if` 且 token/看板步骤带 step 级门控(使 approved/commented 评审以 pass 呈现且不铸 token),以及独立的 `ready_for_review` 策略触发器。
## 考虑过的替代方案
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-10-npm-release-sequences.md
2026-08-10-npm-release-sequences.md: efeda91b6a85e1316c563cc04411878122096953
2026-08-10-npm-release-sequences.zh.md: d905ac4b58691890d2aad955b87713a278dcb4f8
2026-08-10-npm-release-sequences.md: 487f2dbe717375b2adb7daf18550799544d2e147
2026-08-10-npm-release-sequences.zh.md: 051c1c9fd0e8d5330477c5e3053c55f1b51d1802
@@ -22,8 +22,8 @@ Two hard blockers sat in the way. All 217 workspace manifests set `private: true
| Sequence | Members | Version baseline | Tag | Workflow |
|---|---|---|---|---|
| dsh | Publish set: non-experimental `packages/*/*` + `apps/*`; private experimental packages join only the shared version bump | one version for the publish set, private dsh packages, and workspace root, `0.0.x` | `dsh-v<version>` | `release.yml` |
| vendored framework | the nine `vendor/*` packages | each package on its own version line | `vendor-<package>-v<version>` (one per package) | `release-vendor.yml` |
| dsh | Publish set: non-experimental `packages/*/*` + `apps/*`; private experimental packages join only the shared version bump | one version for the publish set, private dsh packages, and workspace root, `0.0.x` | `dsh-v<version>` | `release.yml` (pack) / `release-publish.yml` (publish) |
| vendored framework | the nine `vendor/*` packages | each package on its own version line | `vendor-<package>-v<version>` (one per package) | `release-vendor.yml` (pack) / `release-vendor-publish.yml` (publish) |
| native | `native/landlock-run/packages/*` | its own `0.0.x` | `landlock-run-v<version>` | `landlock-run-release.yml` |
All three publish to the `@deepseek-ai` scope on npmjs.com, and access is per sequence rather than per scope: the vendored framework and the native packages are `public`, the dsh family is `restricted` ([rationale](2026-08-13-public-vendor-and-native-sequences.md)). No publish path passes `--access`, because one flag cannot serve sequences that disagree and would override the manifest that owns the level.
@@ -105,13 +105,13 @@ The entity in this domain is a **release family**: a set of packages sharing one
The dsh family applies the repository's publication payload policy, which rejects sources and declaration maps. The vendored family keeps upstream's payload, because those manifests export `./src/*` and dropping `src` would publish an export map pointing at absent files.
### Workflow shape: pack everything at once, then publish as one set
### Workflow shape: pack on PR/push, publish from a manual dispatch workflow
The `pack` job walks the whole release set once, packing each member into one directory, writes the upload order, and uploads that directory as one artifact; the `publish` job downloads that artifact and publishes each entry in order. The release set is one unit — half the packages can never reach the registry while the other half is still building.
The `pack` job walks the whole release set once, packing each member into one directory, writes the upload order, and uploads that directory as one artifact; it lives in `release.yml` / `release-vendor.yml`. The release set is one unit — half the packages can never reach the registry while the other half is still building.
`pack` carries no credentials and runs on every pull request and master push, so a pull request proves the release set still packs. `publish` is a manual dispatch, sits behind the `npm-publish` environment for human approval, and neither builds nor rebuilds — it uploads the bytes pack produced. Pack runs are grouped per ref so concurrent pull requests do not displace each other; the publish job carries the global group, because dist-tags are shared registry state.
`pack` carries no credentials and runs on every pull request and master push, so a pull request proves the release set still packs. Publication lives in a separate `release-publish.yml` / `release-vendor-publish.yml` workflow that is `workflow_dispatch`-only (so it never appears as a PR check): it repacks the current tree and then publishes each entry in order, behind the `npm-publish` environment for human approval. Pack runs are grouped per ref so concurrent pull requests do not displace each other; the `publish` job carries the global `Release-publish` group, because dist-tags are shared registry state.
A dsh verification installs the vendored family's pack output too. The harness packages declare the vendored framework as a peer, those packages live in another sequence, and the credential-free job cannot fetch them from a private registry — so `release.yml` packs the vendored family for verification while publishing only its own set.
A dsh verification installs the vendored family's pack output too. The harness packages declare the vendored framework as a peer, those packages live in another sequence, and the credential-free job cannot fetch them from a private registry — so the dsh `pack` job packs the vendored family for verification while publishing only the dsh set. The publish workflow (`release-publish.yml`) repacks the current tree and publishes only the dsh set.
The verification also packs the Landlock entry, which `dsh-sandbox-local` declares as a plain dependency, and omits optional dependencies. The platform packages behind those optional entries need a musl toolchain and one build per architecture, so a job on one runner cannot produce them; a consumer that cannot install them must still start, which is what optional means here. The verification therefore reads a directory by its contents rather than a pack order, because a directory can hold tarballs packed only to satisfy a cross-sequence dependency.
@@ -22,8 +22,8 @@ Status: implemented
| 序列 | 成员 | 版本基线 | tag | workflow |
|---|---|---|---|---|
| dsh | 发布集:非 experimental 的 `packages/*/*` + `apps/*`;私有实验性包仅加入共享版本 bump | 发布集、私有 dsh 包与 workspace 根共用一个 `0.0.x` | `dsh-v<版本>` | `release.yml` |
| vendored framework | `vendor/*` 九个包 | 每包各自一条版本线 | `vendor-<包名>-v<版本>`(每包一个) | `release-vendor.yml` |
| dsh | 发布集:非 experimental 的 `packages/*/*` + `apps/*`;私有实验性包仅加入共享版本 bump | 发布集、私有 dsh 包与 workspace 根共用一个 `0.0.x` | `dsh-v<版本>` | `release.yml`pack/ `release-publish.yml`(发布) |
| vendored framework | `vendor/*` 九个包 | 每包各自一条版本线 | `vendor-<包名>-v<版本>`(每包一个) | `release-vendor.yml`pack/ `release-vendor-publish.yml`(发布) |
| native | `native/landlock-run/packages/*` | 自己的 `0.0.x` | `landlock-run-v<版本>` | `landlock-run-release.yml` |
三组一律发到 npmjs.com 的 `@deepseek-ai` scope,且 access 按序列而非按 scope 区分:vendored 框架与 native 包是 `public`dsh 族是 `restricted`[理由](2026-08-13-public-vendor-and-native-sequences.md))。没有任何发布路径传 `--access`——一个选项无法服务级别互不相同的序列,且会覆盖真正拥有该级别的 manifest。
@@ -105,13 +105,13 @@ registry 的两个行为决定了「怎么尝试一次发布」。写入之间
dsh 族套用仓库的发布 payload 策略(拒绝源码与声明映射)。vendored 族保留上游 payload,因为那些 manifest 导出 `./src/*`,去掉 `src` 会发出一个导出映射指向不存在文件的包。
### workflow 形状:一次性 pack 全部,再统一 publish
### workflow 形状:PR/push 上 pack,从手动 dispatch 工作流发布
`pack` job 一趟遍历整个发布集,把每个成员打进同一个目录,写出上传顺序,整个目录作为一份 artifact 上传;`publish` job 下载那一份 artifact,按顺序逐个发布。发布集是一个整体——绝不会出现一半的包已经上了 registry、另一半还在构建。
`pack` job 一趟遍历整个发布集,把每个成员打进同一个目录,写出上传顺序,整个目录作为一份 artifact 上传;它位于 `release.yml` / `release-vendor.yml`。发布集是一个整体——绝不会出现一半的包已经上了 registry、另一半还在构建。
`pack` 无凭据,在每个 pull request 和每次 master push 上跑,所以一个 pull request 就能证明发布集仍能完整打出来。`publish` 是手动 dispatch,挂在 `npm-publish` environment 后面等人工审批,且既不构建也不重建——它上传的就是 pack 产出的字节。pack 的 run 按 ref 分组,并发的 pull request 不会互相顶掉;全局分组落在 publish job 上,因为 dist-tag 是共享的 registry 状态。
`pack` 无凭据,在每个 pull request 和每次 master push 上跑,所以一个 pull request 就能证明发布集仍能完整打出来。发布则位于独立的 `release-publish.yml` / `release-vendor-publish.yml` 工作流,仅 `workflow_dispatch`(因此不会作为 PR check 出现):它重新打包当前树,再按顺序逐个发布,挂在 `npm-publish` environment 后面等人工审批。pack 的 run 按 ref 分组,并发的 pull request 不会互相顶掉;全局 `Release-publish` 分组落在 `publish` job 上,因为 dist-tag 是共享的 registry 状态。
dsh 的验证会一并安装 vendored 族的 pack 产物。harness 的包把 vendored 框架声明成 peer,而那些包属于另一条序列,无凭据的 job 无法从私有 registry 取到——所以 `release.yml` 为验证而打包 vendored 族,发布的仍只有自己那一份
dsh 的验证会一并安装 vendored 族的 pack 产物。harness 的包把 vendored 框架声明成 peer,而那些包属于另一条序列,无凭据的 job 无法从私有 registry 取到——所以 dsh 的 `pack` job 为验证而打包 vendored 族,发布的仍只有 dsh 那一份。发布工作流(`release-publish.yml`)重新打包当前树,只发布 dsh 族
验证还会打一份 Landlock entry 的 tarball——`dsh-sandbox-local` 把它声明为普通 `dependencies`——同时略去可选依赖。那些可选项背后的平台包需要 musl 工具链且每个架构各构建一次,单台 runner 产不出来;而装不到它们的消费方也必须能起,这正是「可选」在这里的含义。因此验证按目录内容读取 tarball,而不是读发布顺序:一个目录可能只装着为满足跨序列依赖而打出来的包,任何发布顺序都不描述它。
+7 -1
View File
@@ -36,7 +36,11 @@ concurrency:
jobs:
lifecycle:
name: Issue lifecycle
if: ${{ github.event_name != 'pull_request_review' || (github.event.action == 'submitted' && github.event.review.state == 'changes_requested') }}
# Runs on every pull_request_review event so the check reports success rather
# than a gray "skipped" segment. The token-creating and board-mutating steps
# are gated at step level (a skipped step does not gray the job): only a
# changes_requested review drives the Project board; approved/commented
# reviews never mint a write-capable App token.
runs-on: ubuntu-latest
steps:
- name: Check out trusted policy
@@ -46,6 +50,7 @@ jobs:
persist-credentials: false
- name: Create project token
id: app-token
if: ${{ github.event_name != 'pull_request_review' || github.event.review.state == 'changes_requested' }}
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
with:
client-id: ${{ vars.DSH_ISSUE_APP_CLIENT_ID }}
@@ -53,6 +58,7 @@ jobs:
owner: deepseek-harness
repositories: deepseek-harness
- name: Handle repository event
if: ${{ github.event_name != 'pull_request_review' || github.event.review.state == 'changes_requested' }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: node .github/issue-management/policy.mjs lifecycle
+131
View File
@@ -0,0 +1,131 @@
# Publish the dsh release sequence to npm. This workflow is manual-only
# (workflow_dispatch) and intentionally does not listen to pull_request or push:
# publication must always be an explicit, reviewed act from a dsh-v* tag, and it
# must never appear as a PR check. It repacks the current tree before publishing
# so the bytes uploaded are exactly what this dispatch produced.
name: Release publish (dsh)
on:
workflow_dispatch:
permissions:
contents: read
env:
PRIMARY_NODE_VERSION: '24'
DSH_TELEMETRY_DISABLED: '1'
jobs:
pack:
name: Pack npm tarballs
runs-on: ubuntu-24.04
steps:
# Complete history: the release scripts read tags.
- uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
- uses: pnpm/action-setup@v4
with:
dest: ${{ runner.temp }}/setup-pnpm
- uses: actions/setup-node@v6
with:
node-version: ${{ env.PRIMARY_NODE_VERSION }}
- name: Configure pnpm store path
id: pnpm-store
run: |
store_root="$HOME/.local/share/pnpm/store"
echo "PNPM_CONFIG_STORE_DIR=$store_root" >> "$GITHUB_ENV"
store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent)
echo "path=$store_path" >> "$GITHUB_OUTPUT"
- uses: actions/cache/restore@v4
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-
- name: Install (immutable)
run: pnpm install --frozen-lockfile
- name: Verify release version
env:
RELEASE_PUBLISH: 'true'
run: pnpm run release:verify --family dsh
- name: Build
run: pnpm run build:official
- name: Pack release tarballs
run: pnpm run release:pack --family dsh --out dist/npm
# The harness packages declare the vendored framework as a peer, and this
# verification must not depend on the registry already carrying matching
# versions — one pull request may bump both families before either
# publishes — so it installs that family's pack output too. Only dist/npm
# is published.
- name: Pack the vendored framework for verification
run: pnpm run release:pack --family vendor --out dist/npm-vendor
# dsh-sandbox-local declares the Landlock entry as a runtime dependency, so
# the verification needs its tarball. Its platform packages stay out: they
# are optional, and building them needs a musl toolchain per architecture.
- name: Pack the Landlock entry for verification
run: |
pnpm --dir native/landlock-run run build:ts
pnpm --dir native/landlock-run/packages/entry pack --pack-destination "$PWD/dist/npm-landlock"
- name: Verify packed install
run: pnpm run release:verify-packed-install --family dsh --from dist/npm --from dist/npm-vendor --from dist/npm-landlock
- uses: actions/upload-artifact@v4
with:
name: dsh-npm-tarballs
path: dist/npm/*
if-no-files-found: error
retention-days: 7
publish:
name: Publish to npm
needs: pack
runs-on: ubuntu-24.04
# Required reviewers and the allowed tags live on the environment; this is
# the only job in the sequence that can write to the registry.
environment: npm-publish
concurrency:
group: Release-publish
cancel-in-progress: false
permissions:
contents: read
steps:
# Checkout and install carry the release scripts only. There is no build
# step: publication uploads the bytes the pack job produced.
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: pnpm/action-setup@v4
with:
dest: ${{ runner.temp }}/setup-pnpm
- uses: actions/setup-node@v6
with:
node-version: ${{ env.PRIMARY_NODE_VERSION }}
registry-url: https://registry.npmjs.org
- name: Install (immutable, no package scripts)
run: pnpm install --frozen-lockfile --ignore-scripts
- uses: actions/download-artifact@v4
with:
name: dsh-npm-tarballs
path: dist/npm
- name: Publish tarballs
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: pnpm run release:publish --family dsh --from dist/npm
@@ -0,0 +1,114 @@
# Publish the vendored framework sequence to npm. This workflow is manual-only
# (workflow_dispatch) and intentionally does not listen to pull_request or push:
# publication must always be an explicit, reviewed act from a vendor-* tag, and
# it must never appear as a PR check. It repacks the current tree before
# publishing so the bytes uploaded are exactly what this dispatch produced.
name: Release publish (vendor)
on:
workflow_dispatch:
permissions:
contents: read
env:
PRIMARY_NODE_VERSION: '24'
DSH_TELEMETRY_DISABLED: '1'
jobs:
pack:
name: Pack npm tarballs
runs-on: ubuntu-24.04
steps:
# Complete history: the release scripts read tags.
- uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
- uses: pnpm/action-setup@v4
with:
dest: ${{ runner.temp }}/setup-pnpm
- uses: actions/setup-node@v6
with:
node-version: ${{ env.PRIMARY_NODE_VERSION }}
- name: Configure pnpm store path
id: pnpm-store
run: |
store_root="$HOME/.local/share/pnpm/store"
echo "PNPM_CONFIG_STORE_DIR=$store_root" >> "$GITHUB_ENV"
store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent)
echo "path=$store_path" >> "$GITHUB_OUTPUT"
- uses: actions/cache/restore@v4
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-
- name: Install (immutable)
run: pnpm install --frozen-lockfile
- name: Verify release version
env:
RELEASE_PUBLISH: 'true'
run: pnpm run release:verify --family vendor
# The vendored packages publish their own sources and build outputs; the
# host build produces what their manifests select.
- name: Build
run: pnpm run build:lib:host
- name: Pack release tarballs
run: pnpm run release:pack --family vendor --out dist/npm-vendor
- name: Verify packed install
run: pnpm run release:verify-packed-install --family vendor --from dist/npm-vendor
- uses: actions/upload-artifact@v4
with:
name: vendor-npm-tarballs
path: dist/npm-vendor/*
if-no-files-found: error
retention-days: 7
publish:
name: Publish to npm
needs: pack
runs-on: ubuntu-24.04
environment: npm-publish
concurrency:
group: Release-publish
cancel-in-progress: false
permissions:
contents: read
steps:
# Checkout and install carry the release scripts only; no build step.
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: pnpm/action-setup@v4
with:
dest: ${{ runner.temp }}/setup-pnpm
- uses: actions/setup-node@v6
with:
node-version: ${{ env.PRIMARY_NODE_VERSION }}
registry-url: https://registry.npmjs.org
- name: Install (immutable, no package scripts)
run: pnpm install --frozen-lockfile --ignore-scripts
- uses: actions/download-artifact@v4
with:
name: vendor-npm-tarballs
path: dist/npm-vendor
- name: Publish tarballs
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: pnpm run release:publish --family vendor --from dist/npm-vendor
+7 -54
View File
@@ -1,10 +1,11 @@
# Pack and publish the vendored framework sequence: the nine rescoped Cordis
# packages under vendor/, each on its own version line. This sequence releases
# independently of dsh and of the native packages.
# Pack the vendored framework sequence: the nine rescoped Cordis packages under
# vendor/, each on its own version line. This sequence releases independently of
# dsh and of the native packages.
#
# Pack runs without credentials on every pull request and master push.
# Publication is a manual dispatch from a vendor-* tag; a vendor release can
# carry several versions, so each package has its own tag.
# Publication is a manual workflow_dispatch of release-vendor-publish.yml from a
# vendor-* tag; a vendor release can carry several versions, so each package has
# its own tag.
name: Release (vendor)
on:
@@ -12,19 +13,12 @@ on:
push:
branches: [master]
workflow_dispatch:
inputs:
publish:
description: Publish the packed tarballs to npm. Must run from a vendor-* tag.
required: true
type: boolean
default: false
permissions:
contents: read
concurrency:
# Pack runs per ref so concurrent pull requests never displace each
# other; the publish job below serializes the shared dist-tag state.
# Pack runs per ref so concurrent pull requests never displace each other.
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
@@ -70,8 +64,6 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Verify release version
env:
RELEASE_PUBLISH: ${{ inputs.publish }}
run: pnpm run release:verify --family vendor
# The vendored packages publish their own sources and build outputs; the
@@ -91,42 +83,3 @@ jobs:
path: dist/npm-vendor/*
if-no-files-found: error
retention-days: 7
publish:
name: Publish to npm
if: inputs.publish
needs: pack
runs-on: ubuntu-24.04
environment: npm-publish
concurrency:
group: Release-publish
cancel-in-progress: false
permissions:
contents: read
steps:
# Checkout and install carry the release scripts only; no build step.
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: pnpm/action-setup@v4
with:
dest: ${{ runner.temp }}/setup-pnpm
- uses: actions/setup-node@v6
with:
node-version: ${{ env.PRIMARY_NODE_VERSION }}
registry-url: https://registry.npmjs.org
- name: Install (immutable, no package scripts)
run: pnpm install --frozen-lockfile --ignore-scripts
- uses: actions/download-artifact@v4
with:
name: vendor-npm-tarballs
path: dist/npm-vendor
- name: Publish tarballs
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: pnpm run release:publish --family vendor --from dist/npm-vendor
+6 -57
View File
@@ -1,10 +1,10 @@
# Pack and publish the dsh release sequence: every package under packages/ plus
# the apps/ entries, all on one version. The vendored framework and the native
# packages are separate sequences with their own workflows and version lines.
# Pack the dsh release sequence: every package under packages/ plus the apps/
# entries, all on one version. The vendored framework and the native packages are
# separate sequences with their own workflows and version lines.
#
# Pack runs without credentials on every pull request and master push, so a
# pull request proves the whole publish set still packs. Publication is a
# manual dispatch from a dsh-v* tag and consumes exactly the packed bytes.
# pull request proves the whole publish set still packs. Publication is a manual
# workflow_dispatch of release-publish.yml from a dsh-v* tag.
name: Release (dsh)
on:
@@ -12,19 +12,12 @@ on:
push:
branches: [master]
workflow_dispatch:
inputs:
publish:
description: Publish the packed tarballs to npm. Must run from a dsh-v* tag.
required: true
type: boolean
default: false
permissions:
contents: read
concurrency:
# Pack runs per ref so concurrent pull requests never displace each
# other; the publish job below serializes the shared dist-tag state.
# Pack runs per ref so concurrent pull requests never displace each other.
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
@@ -70,8 +63,6 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Verify release version
env:
RELEASE_PUBLISH: ${{ inputs.publish }}
run: pnpm run release:verify --family dsh
- name: Build
@@ -105,45 +96,3 @@ jobs:
path: dist/npm/*
if-no-files-found: error
retention-days: 7
publish:
name: Publish to npm
if: inputs.publish
needs: pack
runs-on: ubuntu-24.04
# Required reviewers and the allowed tags live on the environment; this is
# the only step in the sequence that can write to the registry.
environment: npm-publish
concurrency:
group: Release-publish
cancel-in-progress: false
permissions:
contents: read
steps:
# Checkout and install carry the release scripts only. There is no build
# step: publication uploads the bytes the pack job produced.
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: pnpm/action-setup@v4
with:
dest: ${{ runner.temp }}/setup-pnpm
- uses: actions/setup-node@v6
with:
node-version: ${{ env.PRIMARY_NODE_VERSION }}
registry-url: https://registry.npmjs.org
- name: Install (immutable, no package scripts)
run: pnpm install --frozen-lockfile --ignore-scripts
- uses: actions/download-artifact@v4
with:
name: dsh-npm-tarballs
path: dist/npm
- name: Publish tarballs
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: pnpm run release:publish --family dsh --from dist/npm
+47 -8
View File
@@ -417,24 +417,63 @@ describe('Python release workflows', () => {
})
describe('Issue lifecycle workflow', () => {
it('uses explicit review handoff events without rerunning when a draft becomes ready', () => {
it('runs the lifecycle job on every PR/review event but gates token and board steps', () => {
const lifecycle = loadWorkflow('.github/workflows/issue-lifecycle.yml')
const policy = loadWorkflow('.github/workflows/issue-policy.yml')
const lifecycleJob = workflowJob(lifecycle, 'lifecycle')
if (!Array.isArray(lifecycleJob.steps)) throw new TypeError('Issue lifecycle job must define steps')
// The job has no job-level `if`, so it is listed on every pull_request /
// pull_request_review event and reports success instead of a gray skip. The
// write-capable steps are gated at step level so approved/commented reviews
// never mint a Project/Issue App token nor touch the board.
expect(lifecycle.on).toHaveProperty('pull_request')
expect(lifecycle.on).toHaveProperty('pull_request_review')
expect(lifecycleJob.if).toBeUndefined()
// Keep the subscription-type gates: issue-lifecycle does not re-subscribe
// ready_for_review (issue-policy owns that) and only reacts to submitted
// review events.
const lifecyclePullRequest = workflowEvent(lifecycle, 'pull_request')
const lifecycleReview = workflowEvent(lifecycle, 'pull_request_review')
const lifecycleJob = workflowJob(lifecycle, 'lifecycle')
const policy = loadWorkflow('.github/workflows/issue-policy.yml')
const policyPullRequest = workflowEvent(policy, 'pull_request')
expect(lifecyclePullRequest.types).not.toContain('ready_for_review')
expect(lifecyclePullRequest.types).toContain('review_requested')
expect(lifecycleReview.types).toEqual(['submitted'])
expect(lifecycleJob.if).toBe(
"${{ github.event_name != 'pull_request_review' || (github.event.action == 'submitted' && github.event.review.state == 'changes_requested') }}",
)
const gated = "${{ github.event_name != 'pull_request_review' || github.event.review.state == 'changes_requested' }}"
const steps = lifecycleJob.steps.filter(isRecord)
const tokenStep = steps.find(s => s.name === 'Create project token')
const handleStep = steps.find(s => s.name === 'Handle repository event')
expect(tokenStep).toMatchObject({ if: gated })
expect(handleStep).toMatchObject({ if: gated })
// issue-policy owns PR validation; it is read-only and a real gate.
const policyPullRequest = workflowEvent(policy, 'pull_request')
expect(policyPullRequest.types).toContain('ready_for_review')
})
})
describe('npm release workflows', () => {
it('keeps publication dispatch-only and pack in the PR workflow', () => {
// pack stays in the PR/master release workflows so a PR proves the set packs.
for (const file of ['release.yml', 'release-vendor.yml']) {
const workflow = loadWorkflow(`.github/workflows/${file}`)
if (!isRecord(workflow.jobs)) throw new TypeError(`${file} must define jobs`)
expect(Object.keys(workflow.jobs).sort()).toEqual(['pack'])
}
// publication is workflow_dispatch-only (never a PR check) and keeps the
// npm-publish environment plus the shared dist-tag group.
for (const file of ['release-publish.yml', 'release-vendor-publish.yml']) {
const workflow = loadWorkflow(`.github/workflows/${file}`)
if (!isRecord(workflow.on) || !isRecord(workflow.jobs)) throw new TypeError(`${file} must define on and jobs`)
expect(Object.keys(workflow.on)).toEqual(['workflow_dispatch'])
const publish = workflow.jobs.publish
if (!isRecord(publish)) throw new TypeError(`${file} must define a publish job`)
expect(publish.environment).toBe('npm-publish')
expect(publish.concurrency).toMatchObject({ group: 'Release-publish' })
}
})
})
describe('Git hooks', () => {
it('leaves frozen Agent Note sidecars to the archive verifier', () => {
const lefthook = loadWorkflow('lefthook.yml')
@@ -26,6 +26,7 @@ const dshBuildWorkflows = [
'e2b-e2e.yml',
'e2e.yml',
'release.yml',
'release-publish.yml',
'sandbox.yml',
]