mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-11 04:00:38 +00:00
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:
+2
-2
@@ -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,而不是读发布顺序:一个目录可能只装着为满足跨序列依赖而打出来的包,任何发布顺序都不描述它。
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user