Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
998b22e905 | ||
|
|
826bba8485 | ||
|
|
f5c5bd3f40 | ||
|
|
4e6ae9036a | ||
|
|
3b38deb078 |
@@ -401,6 +401,19 @@
|
|||||||
],
|
],
|
||||||
"strict": false
|
"strict": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "slark-cicd-plugin",
|
||||||
|
"source": "./skills-dev/slark-cicd-plugin",
|
||||||
|
"description": "Slark 仓库 staging、生产与 Desktop 安装包的端到端 CI/CD 发布技能。",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"category": "devops",
|
||||||
|
"keywords": [
|
||||||
|
"devops",
|
||||||
|
"deployment",
|
||||||
|
"operations"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "req-audit-plugin",
|
"name": "req-audit-plugin",
|
||||||
"source": "./skills-req/req-audit-plugin",
|
"source": "./skills-req/req-audit-plugin",
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ def load_config():
|
|||||||
def get_category_and_keywords(plugin_name):
|
def get_category_and_keywords(plugin_name):
|
||||||
if any(x in plugin_name for x in ['dev-', 'coding', 'frontend']):
|
if any(x in plugin_name for x in ['dev-', 'coding', 'frontend']):
|
||||||
return "development", ["development", "coding", "workflow"]
|
return "development", ["development", "coding", "workflow"]
|
||||||
elif any(x in plugin_name for x in ['ops-', 'deploy', 'server']):
|
elif any(x in plugin_name for x in ['ops-', 'deploy', 'server', 'slark-cicd']):
|
||||||
return "devops", ["devops", "deployment", "operations"]
|
return "devops", ["devops", "deployment", "operations"]
|
||||||
elif any(x in plugin_name for x in ['ai-proj', 'req']):
|
elif any(x in plugin_name for x in ['ai-proj', 'req']):
|
||||||
return "productivity", ["project-management", "tasks", "requirements"]
|
return "productivity", ["project-management", "tasks", "requirements"]
|
||||||
|
|||||||
+1
-1
@@ -365,7 +365,7 @@ install_plugin() {
|
|||||||
else
|
else
|
||||||
mkdir -p "$dst_dir"
|
mkdir -p "$dst_dir"
|
||||||
# rsync resolved source (handles nested skills/ structures)
|
# rsync resolved source (handles nested skills/ structures)
|
||||||
rsync -a --delete "$src_dir/" "$dst_dir/"
|
rsync -a --checksum --delete "$src_dir/" "$dst_dir/"
|
||||||
state_set "$install_name" "$version" "$effective_install_type" "$source_digest"
|
state_set "$install_name" "$version" "$effective_install_type" "$source_digest"
|
||||||
ok "$install_name → skill (v$version)"
|
ok "$install_name → skill (v$version)"
|
||||||
INSTALL_ACTION=true
|
INSTALL_ACTION=true
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "slark-cicd-plugin",
|
||||||
|
"description": "Slark 仓库 staging、生产与 Desktop 安装包的端到端 CI/CD 发布技能。",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "slark-cicd",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
---
|
||||||
|
name: slark-cicd
|
||||||
|
description: Slark 仓库 staging、生产和 Desktop 安装包的端到端 CI/CD 可执行 runbook。覆盖本地预推快检、PR 与内网 ci/internal-gate 门禁、合并 origin/main、生产前强制 staging 验收、审批式生产发布,以及在 m5max 构建签名、公证并上传 macOS/Windows Desktop 包到 OSS。当用户要在 slark(qiudl/qiu-slark)里发布/上线、部署到生产或 staging、发布 Desktop 安装包、盯 CI/合并 PR、复现或验证运行时行为、或排查发布链/推送路由问题时使用。
|
||||||
|
---
|
||||||
|
|
||||||
|
# Slark CI/CD runbook
|
||||||
|
|
||||||
|
驱动 Slark 从改代码到上线的端到端流程。权威流程与完整细节见仓库文档
|
||||||
|
`docs/cicd-staging-production.md`(本技能命令与之一致);本文件是可直接照做的分阶段清单。
|
||||||
|
|
||||||
|
仓库:`qiudl/qiu-slark`(本机克隆通常在 `/Users/donglinlai/coding/slark`)。所有命令在**仓库根**执行。
|
||||||
|
`ai-proj` 相关命令加 `GODEBUG=netdns=go` 前缀。
|
||||||
|
|
||||||
|
## 铁律(先记死)
|
||||||
|
|
||||||
|
- GitHub `origin/main` 是唯一代码权威、合并入口与生产发布来源;`internal` 只用于 CI/镜像,永不发布。
|
||||||
|
- 生产发布只接受**最新 `origin/main` 的精确 40 位 SHA**;`HEAD != origin/main` 会被 `deploy.sh` 拒绝。
|
||||||
|
- 生产发布必须在**普通 clone** 中执行,且 `.git` 必须是目录;`release-prod.sh` / `deploy.sh` 会拒绝
|
||||||
|
`.git` 为指针文件的 git worktree。不要污染现有 checkout:主目录脏或被占用时,新建干净的临时 clone。
|
||||||
|
- **生产发布前必须先发布并验收 staging,不得跳过**:同一最新 `origin/main` 精确 SHA 必须先完成迁移演练、
|
||||||
|
staging 正式部署、隔离检查和需求功能验收。把证据报告给用户后,必须再次取得明确的生产确认;开始 staging
|
||||||
|
前获得的生产授权不能替代这次确认。未通过、未完成或用户未确认时停止,不得执行生产发布命令。
|
||||||
|
- 审批式部署,永不自动:亲手敲的 `sudo`/`rm -rf`/`dd`/`curl|sh`/包安装是 `destructive`(NEVER_AUTO),
|
||||||
|
**每次都要人审**;命中审批闸就**等人批**,绝不绕过、绝不把这些 key 加进 auto-approve 白名单。
|
||||||
|
- staging 与生产硬隔离:staging 脚本带生产 IP 拒运行护栏、不 push origin、不反向 rsync。
|
||||||
|
- 当前 Slark staging 权威目标是 AWS `ap-southeast-2` 实例 `i-013e5ca3fdfddc13e`
|
||||||
|
(Name=`slark-staging`,2026-08-18 核验公网 IP `15.135.112.181`)。公网 IP 自动分配,发布前必须按
|
||||||
|
实例 ID 重查并确认 Name/状态;不得凭历史 IP 判断目标。
|
||||||
|
- `82.157.141.202` 是 ai-proj 预生产宿主机。其 `/opt/slark-staging` cohost 是隔离的 Slark 兼容验收栈,
|
||||||
|
但不是 Slark 独立预生产;只有任务明确指定 cohost 时才能使用,不得作为普通 Slark staging 的默认目标。
|
||||||
|
- staging 发布若影响 `packages/daemon`、`packages/runtime`、Server/daemon 公共协议或 Desktop 公共运行链,
|
||||||
|
必须把 `server-staging` 与 `daemon-staging` 更新到同一精确 SHA;两者版本不一致不得宣告 staging 成功。
|
||||||
|
- 只修 PR 范围内的 CI 失败;不为过检去改 CI/workflow 或做无关改动。
|
||||||
|
|
||||||
|
## 选择链路
|
||||||
|
|
||||||
|
- 「发布/上线/部署生产」→ 生产发布链:阶段 1→2→3(同 SHA staging 验收)→ 人工确认 →4→5。
|
||||||
|
- 「Desktop/桌面版打包、发布、上传 OSS」→ 阶段 1→2 后走 `docs/desktop-oss-release.md` 的 m5max 双平台链路。
|
||||||
|
- 「盯 CI/合并 PR」→ 阶段 1→2(用 babysit 心态循环到 green + mergeable + 评论收口)。
|
||||||
|
- 「复现/验证运行时行为、别碰生产」→ staging 预验链(阶段 3,可独立随时跑)。
|
||||||
|
|
||||||
|
## 阶段 1:PR 与内网 gate(`ci/internal-gate`)
|
||||||
|
|
||||||
|
真实门禁是内网 CI(GitHub Actions 已停用)。检查与自查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gh pr checks <PR> # 应见 ci/internal-gate
|
||||||
|
gh pr view <PR> --json state,mergeable,mergeStateStatus,isDraft,reviewDecision
|
||||||
|
```
|
||||||
|
|
||||||
|
- 基础门(全 PR):release-contract / member-gate / authz-matrix / controlled-content-exits /
|
||||||
|
onconflict-predicate / migration-idempotency / wsl-path-translation / typecheck / test(全量)。
|
||||||
|
- 条件外部门(按改动路径):改 server DB/迁移/schema → `postgresql-security`;改 web → `web-e2e`;
|
||||||
|
android / daemon+desktop 各有门。
|
||||||
|
- 本地复跑基础门可跑部分:`pnpm -r typecheck && pnpm test`。
|
||||||
|
|
||||||
|
红了:只修本 PR 范围内的失败。疑似无关的合并阻塞→先 merge 最新 `main`(可能别的 PR 已修)。
|
||||||
|
`BLOCKED: <label> requires <VAR>` = runner 环境未配外部门命令,属运维问题,不是 PR 代码问题。
|
||||||
|
|
||||||
|
## 阶段 2:合并到 `origin/main`
|
||||||
|
|
||||||
|
合并前确认:`mergeable=MERGEABLE`、`mergeStateStatus=CLEAN`、非 draft、`ci/internal-gate` 绿、评论收口。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gh pr merge <PR> --merge # 仓库风格:merge commit
|
||||||
|
```
|
||||||
|
|
||||||
|
若本机 `main` 被其他 worktree 占用导致 `--delete-branch` 本地 checkout 失败(属本地副作用):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gh pr view <PR> --json state,mergedAt,mergeCommit # 确认 GitHub 侧 MERGED
|
||||||
|
git push origin --delete <feature-branch> # 删远端
|
||||||
|
git branch -D <feature-branch> # 删本地(先切走)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 阶段 3:staging 真机预验(生产前强制,也可独立运行)
|
||||||
|
|
||||||
|
生产候选必须使用最新 `origin/main` 的同一精确 SHA。先执行迁移演练,再正式部署;随后完成隔离检查和本次需求的
|
||||||
|
功能验收。验收不能只看通用 health:凡需求涉及真实集成(如飞书 OAuth),staging 缺少对应 provider/凭据时必须
|
||||||
|
明确标为未完成并停止生产链,除非用户在看到该限制后明确接受。完成后报告目标实例、SHA、Server/Daemon revision、
|
||||||
|
迁移演练、健康、隔离和功能结果,并等待用户再次确认是否进入生产。
|
||||||
|
|
||||||
|
### 3.1 当前权威路径:Slark 独立 staging
|
||||||
|
|
||||||
|
稳定目标是 AWS `ap-southeast-2` 实例 `i-013e5ca3fdfddc13e`(Name=`slark-staging`)。它使用自动分配
|
||||||
|
公网 IP,因此每次部署前先查询当前 IP;2026-08-18 核验值为 `15.135.112.181`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
aws ec2 describe-instances --region ap-southeast-2 \
|
||||||
|
--instance-ids i-013e5ca3fdfddc13e \
|
||||||
|
--query 'Reservations[0].Instances[0].{Name:Tags[?Key==`Name`]|[0].Value,State:State.Name,PublicIp:PublicIpAddress}' \
|
||||||
|
--output table
|
||||||
|
|
||||||
|
export STAGING_SSH_KEY=~/.ssh/cloud-server-syd.pem
|
||||||
|
export STAGING_REMOTE=ubuntu@15.135.112.181
|
||||||
|
export STAGING_REMOTE_IP=15.135.112.181
|
||||||
|
export STAGING_BASE=http://15.135.112.181
|
||||||
|
|
||||||
|
scripts/deploy-staging.sh --migrate-dry-run
|
||||||
|
scripts/deploy-staging.sh
|
||||||
|
STAGING_TOKEN=$(cat /tmp/.staging_token) scripts/staging-seed.sh
|
||||||
|
scripts/staging-verify-isolation.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
不得把查询到的 Name 非 `slark-staging` 的机器当作目标。四件套细节与环境隔离约束见
|
||||||
|
`docs/req-20260811-0059-staging-environment-design.md`。
|
||||||
|
|
||||||
|
### 3.2 非权威路径:ai-proj cohost(仅明确指定时)
|
||||||
|
|
||||||
|
ai-proj cohost 位于 `ubuntu@82.157.141.202:/opt/slark-staging`。全程使用隔离数据、数据库、密钥和状态目录,
|
||||||
|
但宿主机身份仍是 ai-proj staging;普通“Slark 预生产”不得走这里。仅在任务明确要求 cohost 兼容验收时,
|
||||||
|
先在**干净的独立 worktree**检出待验精确 SHA,再执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
STAGING_SSH_KEY=~/.ssh/ai_proj_stg.pem \
|
||||||
|
STAGING_KNOWN_HOSTS_FILE=~/.ssh/known_hosts \
|
||||||
|
STAGING_REMOTE=ubuntu@82.157.141.202 \
|
||||||
|
STAGING_REMOTE_IP=82.157.141.202 \
|
||||||
|
scripts/deploy-staging-cohost.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
如果改动影响 daemon/runtime/公共协议,还需用 Compose `daemon` profile 更新 `daemon-staging`,并核对
|
||||||
|
Server/daemon revision 同为待验 SHA。
|
||||||
|
|
||||||
|
### 3.3 环境选择检查
|
||||||
|
|
||||||
|
普通 Slark staging 默认走 3.1;只有用户或需求明确说“ai-proj cohost”才走 3.2。任何验收都必须记录:
|
||||||
|
环境稳定身份、当次 IP、待验 40 位 SHA、Server/daemon revision/status、Server health 和目标功能结果。
|
||||||
|
|
||||||
|
routed-remote 要 `network=enabled` 必须同时满足:① agent `workspace_access=write` +
|
||||||
|
`repository_access=write`;② per-agent 工作区是可写 git worktree。细节见
|
||||||
|
`docs/req-20260811-0059-staging-environment-design.md`。staging 刻意不碰 `deploy.sh` 的生产闸门。
|
||||||
|
|
||||||
|
## 阶段 4:生产发布(审批式)
|
||||||
|
|
||||||
|
**前置门禁**:阶段 3 已对同一 SHA 完成 staging 部署和验收,证据已报告,且用户在报告之后明确确认生产发布。
|
||||||
|
缺任一项即停止;不得把 PR 合并授权、较早的生产授权或单纯的“继续”视为验收后确认。
|
||||||
|
|
||||||
|
**唯一姿势**:从干净的普通 clone 发布最新 `origin/main`(`HEAD == origin/main` 精确 SHA)。staging 可用
|
||||||
|
独立 worktree,但生产脚本为保证同步与发布边界,会拒绝 worktree;不要在用户已有脏 checkout 中清理或发布。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 若当前仓库不是普通 clone,先在安全父目录创建一次性干净 clone:
|
||||||
|
git clone https://github.com/qiudl/qiu-slark.git <clean-production-clone>
|
||||||
|
cd <clean-production-clone>
|
||||||
|
|
||||||
|
git fetch origin main
|
||||||
|
test -d .git # 必须通过;worktree 的 .git 是文件
|
||||||
|
git status --porcelain # 必须干净
|
||||||
|
git checkout --detach "$(git rev-parse origin/main)" # 在产品分支上时先 detach 到该 SHA
|
||||||
|
|
||||||
|
pnpm release:prod # = release-prod.sh → deploy.sh --backend --no-install --no-mirror
|
||||||
|
# 备选:scripts/deploy.sh --pull --backend
|
||||||
|
```
|
||||||
|
|
||||||
|
- `deploy.sh` 用 `check-production-release-sha.sh` 锁 `HEAD == origin/main` SHA,不符即拒。
|
||||||
|
- `--backend` 才跑迁移:owner 连接(DDL 权)与运行时 `slark_app`(DML-only)分离;幂等、失败即非零。
|
||||||
|
- 命中审批闸(发布/sudo)→ **等人批**;不要绕。脚本内部 SSH 载荷子进程的 sudo 不触发闸。
|
||||||
|
- 普通工作 clone 发布完切回原分支:`git checkout <branch>`;一次性发布 clone 可保留作审计,清理时用可恢复方式。
|
||||||
|
|
||||||
|
### HANDOFF_BLOCKED 恢复入口
|
||||||
|
|
||||||
|
发布准备遇到 `HANDOFF_BLOCKED`、handoff manifest=`failed_closed` 或命令状态不确定时,停止发布切换;禁止靠重跑
|
||||||
|
发布脚本、直接改表或默认 replay 来“解卡”。先把 manifest、命令、execution、lease/outbox 和审计证据做只读盘点,
|
||||||
|
再让用户明确授权每条记录的 reconciliation action/outcome 及是否允许继续发布。恢复细节见
|
||||||
|
[references/production-handoff-recovery.md](references/production-handoff-recovery.md);仅遇到该类事故时读取。
|
||||||
|
|
||||||
|
`COMMAND_AUTHORITY_CHANGED` 是有效并发护栏,不是可忽略错误:重新读取当前 authority/transport/execution generation,
|
||||||
|
确认期间没有新活动,再在原授权范围内幂等续办。若发现新工作、活跃 lease/outbox 或无法解释的副作用,立即停止并
|
||||||
|
重新请求决策。
|
||||||
|
|
||||||
|
## 阶段 5:发布后核查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/verify-deploy.sh # 健康(前端/api/health=200)、bundle、server/daemon SHA、迁移集、federation、凭据
|
||||||
|
```
|
||||||
|
|
||||||
|
预期 `✓ deploy verification passed: server=<sha> daemon=<sha>` 且 `<sha> == origin/main`,舰队 `online ≥ 发布前`。
|
||||||
|
完成前还要独立复核 governed release 账本为 `succeeded/complete`、四组件实际 SHA 均为目标 SHA、租约已释放、
|
||||||
|
新 handoff manifest=`verified`;若本次处理过旧 manifest,还要核对其全部 case 已达授权终态且 replay 数为 0。
|
||||||
|
|
||||||
|
默认生产包装器使用 `--no-mirror`:构建 daemon candidate binary 用于运行态验证不等于上传镜像资产。镜像落后告警
|
||||||
|
在该模式下是预期的非阻断结果;不得未经单独授权补传镜像。交付说明要明确国内安装器可能回退 upstream,避免把
|
||||||
|
本地构建、生产 daemon 更新和镜像上传混为一谈。
|
||||||
|
|
||||||
|
## Desktop m5max 双平台发布
|
||||||
|
|
||||||
|
Desktop 安装包不走 Server 生产部署脚本。权威流程见仓库 `docs/desktop-oss-release.md`,已验证基线为
|
||||||
|
REQ-20260819-0005 / Desktop 0.3.1:
|
||||||
|
|
||||||
|
- 版本 PR 通过 `ci/internal-gate` 并合并后,m5max 从最新 `origin/main` 精确 SHA 的干净普通 clone 构建。
|
||||||
|
- m5max 原生构建、Developer ID 签名并公证/staple macOS DMG/ZIP;交叉构建 Windows x64 NSIS EXE。
|
||||||
|
- Darwin 不能执行 Windows 原生 `smoke:windows`。只有发布负责人明确接手 Windows 原生验收时才能继续交叉打包,交付证据必须标注该边界。
|
||||||
|
- 资产不回传开发机;由 m5max 直接上传 `xiaoqu-public-file/slark/desktop/releases/v<version>/`,manifest 最后更新。
|
||||||
|
- `xiaoqu-public-file` 的 canonical 凭证源是发布控制机上的 `/Users/donglinlai/coding/param.rxt`(必须为 `0600`);AK/SK 分别位于 XML 标签 `ossAccessKeyId` / `ossAccessKeySecret`。不得改猜 Bitwarden、Keychain、通用 `credentials.env` 或 m5max 本地配置;只有该文件缺失、权限不符或标签解析为空时才停止并报告。
|
||||||
|
- m5max 不持久化 OSS 凭证。发布控制机从 `param.rxt` 提取 AK/SK 后,只用 `printf '%s\n%s\n' ... | ssh m5max ...` 经 SSH stdin 传两行;远端用 `read -r` 接收并仅为当次 shell 导出 `OSS_ACCESS_KEY_ID` / `OSS_ACCESS_KEY_SECRET`,随后运行上传脚本。禁止 `scp` 凭证文件、写远端临时文件或把值打印到日志。
|
||||||
|
- m5max 非交互 SSH 必须显式补 `PATH=/opt/homebrew/bin:/usr/local/bin:$HOME/.local/bin:$PATH`;已验证 `ossutil` 位于 `/Users/johnq/.local/bin/ossutil`。执行前检查 Node、三个安装包、精确 main SHA 和干净工作树,避免因交互 shell 环境差异重新猜路径。
|
||||||
|
- m5max 默认出口若走 `utun`,只为当次 `ossutil` 使用 `--bind-address "$(ipconfig getifaddr en0)"`,不得修改整机 Tailscale/默认路由。
|
||||||
|
- 完成前核对三份资产 SHA256/大小、macOS 签名与公证、公开 URL/manifest,并回写 ai-proj 证据。
|
||||||
|
|
||||||
|
## 推送路由自查(换机器/新克隆必做)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git remote -v # origin 收发都应是 https://github.com/qiudl/qiu-slark.git
|
||||||
|
git config --get-regexp '^url\.' # 有输出 = pushInsteadOf 改写,push 可能被静默重定向到内网
|
||||||
|
scripts/git-release-governance.sh check-local
|
||||||
|
scripts/git-release-governance.sh status
|
||||||
|
```
|
||||||
|
|
||||||
|
## 故障分流
|
||||||
|
|
||||||
|
| 现象 | 处置 |
|
||||||
|
| --- | --- |
|
||||||
|
| `release-prod` 拒绝 HEAD≠origin/main | `git fetch origin main` 后 detach 到 origin/main SHA,工作树须干净 |
|
||||||
|
| `release-prod` 拒绝 git worktree / `.git` 不是目录 | 改用干净的普通 clone,锁定最新 `origin/main`;不要在现有脏 checkout 中强行切换 |
|
||||||
|
| `ci/internal-gate` red | 看 gate 分组日志找 FAIL 项;`pnpm -r typecheck && pnpm test` 本地复现修复 |
|
||||||
|
| `BLOCKED: <label> requires <VAR>` | runner 未配外部门命令,报运维配 `SLARK_CI_*_CMD`,非 PR 问题 |
|
||||||
|
| 合并阻塞但与 PR 无关 | merge 最新 `main` 再看 |
|
||||||
|
| push 成功但 GitHub 没有 | 查 `git config --get-regexp '^url\.'`,移除 pushInsteadOf 或显式指定 GitHub URL |
|
||||||
|
| staging routed-remote 恒断网 | 查两道放网杠杆(workspace_access=write + 可写 git worktree) |
|
||||||
|
| cohost Server 已更新但 daemon 仍是旧 SHA | 用 `--profile daemon up -d daemon-staging` 更新 daemon;核对两容器 revision 后才算完成 |
|
||||||
|
| cohost daemon 启动即退出/fatal | 看 `docker logs slark_daemon_staging`;修复凭据/环境或版本契约,禁止用旧 daemon 冒充验收通过 |
|
||||||
|
| verify 失败 SHA 不符 | 部署到了旧码,重发 |
|
||||||
|
| `HANDOFF_BLOCKED` / manifest=`failed_closed` | 停止切换并读取 handoff recovery 参考;只读盘点后取得逐项 action/outcome 授权,禁止默认 replay |
|
||||||
|
| reconciliation 返回 `COMMAND_AUTHORITY_CHANGED` | 查当前 generation 与新活动;无新活动才在原授权内幂等续办,有变化则停止重新决策 |
|
||||||
|
| `--no-mirror` 后提示镜像落后 | 预期非阻断;报告 upstream fallback,未经单独授权不上传镜像 |
|
||||||
|
| 生产授权已给但 staging 尚未验收 | 先完成同 SHA staging 全量验收,报告证据并重新等用户确认;不得沿用较早授权 |
|
||||||
|
| staging 缺少需求依赖的真实集成配置 | 将对应验收标为未完成并停止生产链;补齐配置后重验,或让用户基于明确限制作决定 |
|
||||||
|
|
||||||
|
## ai-proj 流程(强制)
|
||||||
|
|
||||||
|
任何改产品/代码行为的工作,动手前先在 ai-proj 立项 + 建 Task + 关联,实现/测试/交付回写;
|
||||||
|
commit/PR 引用 `REQ-...`。纯文档/工具/只读排查除外。详见仓库 `AGENTS.md` / `CLAUDE.md`。
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# Production handoff recovery
|
||||||
|
|
||||||
|
仅在生产发布被 coding-session handoff 阻塞、manifest 进入 `failed_closed`,或存在 `needs_review` / 不确定命令时读取。
|
||||||
|
目标是保留 fail-closed 语义,在明确授权下终结歧义状态,并证明没有误重放命令。
|
||||||
|
|
||||||
|
## 先冻结发布,不先解锁
|
||||||
|
|
||||||
|
- 停止发布切换和自动重试;记录 release ID、候选 SHA、manifest ID、阻塞发生阶段与原始错误。
|
||||||
|
- 不把“继续”“再试一次”解释为 replay、force-end 或生产发布授权。向用户展示证据后,确认语句应绑定 manifest、
|
||||||
|
条目数量或 command ID、目标 action/outcome、是否禁止 replay,以及要继续发布的精确 SHA。
|
||||||
|
- 不直接更新 handoff、command、execution 表来改变状态。优先走已部署版本的内部治理 API;只有 API 无法恢复且
|
||||||
|
已获得对应操作授权时,才可调用该版本现有 domain repository,并保留 lifecycle event 与 audit log。
|
||||||
|
|
||||||
|
## 只读证据盘点
|
||||||
|
|
||||||
|
对 manifest 中每条 item 核对并保存摘要:
|
||||||
|
|
||||||
|
- command ID、session/project/computer ID、command status、execution state;
|
||||||
|
- manifest 记录的 authority term、transport generation、execution generation,以及当前实际值;
|
||||||
|
- command/execution lease 是否过期,outbox 是否仍有 active/pending 记录;
|
||||||
|
- session 中是否还有其他活跃命令或新事件;
|
||||||
|
- daemon acknowledgement、result/receipt、审计事件是否能证明执行或未执行。
|
||||||
|
|
||||||
|
若 lease/outbox 仍活跃、存在新命令、证据相互矛盾,或无法排除外部副作用,停止恢复并请求新的人工判断。
|
||||||
|
|
||||||
|
## Action 与 outcome 必须匹配证据
|
||||||
|
|
||||||
|
- `no_action / not_executed`:只有权威证据能证明命令从未执行,且治理接口允许该组合时使用。
|
||||||
|
- `terminate / terminated`:用于显式受控终结仍处于 uncertain/active 语义的命令;先通过 domain 操作取消 command、
|
||||||
|
force-end execution、完成 session 并写审计,再完成 reconciliation,不产生 replay。
|
||||||
|
- `mark_failed / failed`:证据确认失败、且不能安全恢复为成功或未执行时使用。
|
||||||
|
- `replay / replayed_success`:仅在用户明确授权 replay 且 exactly-once/幂等边界得到证明时使用。发布解卡绝不能默认 replay。
|
||||||
|
|
||||||
|
不要为了迎合预先选择的 outcome 忽略现场证据;如果授权与可证明事实不兼容,停止并把差异报告给用户。
|
||||||
|
|
||||||
|
## 受治理恢复顺序
|
||||||
|
|
||||||
|
1. 使用精确 manifest/item 建立或读取 reconciliation case。
|
||||||
|
2. 取得具备 action、evidence digest、operator 和有效期约束的审批。
|
||||||
|
3. 记录 decision;执行 domain 终结或治理 apply;随后用权威运行态证据 confirm terminal outcome。
|
||||||
|
4. 若 apply 返回 `COMMAND_AUTHORITY_CHANGED`,不要覆盖或绕过。重新读取当前三类 generation 和新活动:
|
||||||
|
- generation 只因刚完成的受控终结而变化,且无新命令、lease/outbox 时,可在同一授权 action 下幂等续办;
|
||||||
|
- generation 对应新工作或原因不明时,停止并重新审批。
|
||||||
|
5. 每个 item 都 terminal 后,确认 manifest 自动或受治理地进入 `reconciled`,再启动新的发布尝试。
|
||||||
|
|
||||||
|
凭据失败发生在事务前时应视为零变更并记录。不要反复猜测 owner 凭据;不得打印连接串或密钥。若必须使用运行时
|
||||||
|
`slark_app` 连接,只能通过已部署 domain repository、正确的 per-transaction project/user RLS context 和审计路径操作。
|
||||||
|
|
||||||
|
## 零重放与发布恢复验收
|
||||||
|
|
||||||
|
恢复完成至少证明:
|
||||||
|
|
||||||
|
- `terminalCount == totalCount`,每条 decision action 与 terminal outcome 等于用户授权;
|
||||||
|
- 禁止 replay 的场景下,decision action=`replay` 数和 execution replay attempt 增量均为 0;
|
||||||
|
- `terminate/terminated` 场景下,command=`cancelled`、execution=`force_ended`,并存在对应 lifecycle/audit 证据;
|
||||||
|
- 旧 manifest=`reconciled`,没有 active handoff reservation 残留。
|
||||||
|
|
||||||
|
继续生产发布时使用新 governed release / handoff manifest,不复用 failed-closed manifest。重新检查候选仍是最新
|
||||||
|
`origin/main`、同 SHA staging eligibility receipt 仍有效、工作树干净;任何一项变化都回到 staging 与人工确认门禁。
|
||||||
|
|
||||||
|
发布成功后再独立执行一次 `scripts/verify-deploy.sh`,并只读核对:release=`succeeded/complete`、target/actual Web、
|
||||||
|
Server、daemon、artifact SHA 全相等、lease 已释放、finished 已记录、新 manifest=`verified`、舰队在线数不低于基线。
|
||||||
|
将旧 manifest reconciliation 与新发布证据一起回写 ai-proj。
|
||||||
|
|
||||||
|
## `--no-mirror` 的解释边界
|
||||||
|
|
||||||
|
- `pnpm release:prod` 默认走 `deploy.sh --backend --no-install --no-mirror`;不上传 daemon 镜像资产。
|
||||||
|
- 发布过程仍可能在隔离 candidate 目录构建 daemon binary,用于同 SHA 验证或部署运行态 daemon;这不是镜像上传。
|
||||||
|
- mirror freshness 告警表示镜像源仍旧,国内安装器可能回退到较慢 upstream。它不阻断当前生产运行态发布。
|
||||||
|
- 补传 mirror 是单独的外部写操作,需要单独授权和凭据;不能为了消除告警在发布收尾时顺手执行。
|
||||||
@@ -322,6 +322,59 @@ npm run lint
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## ⚠️ 既有契约与读模型迁移门禁(必须)
|
||||||
|
|
||||||
|
> **惨痛教训**:新写链路把真实数据写入新表,旧 API 仍读原表,前端虽然没改却从此看不到新数据。“后端已成功执行”不等于“用户能看到执行结果”。
|
||||||
|
|
||||||
|
开发计划涉及新表、新账本、新写路径、替换存储模型或接口版本时,必须先完成以下门禁。未通过时,不得把“新建平行表/新建平行 API”写成默认方案。
|
||||||
|
|
||||||
|
### 1. 先证明旧模型不能复用
|
||||||
|
|
||||||
|
- [ ] 已检查现有表、字段、索引、状态机和扩展字段(如 metadata/JSON)。
|
||||||
|
- [ ] 已说明为什么增加字段、子表或现有类型无法满足需求。
|
||||||
|
- [ ] 新表承载的是真正独立的实体或生命周期,而不是为同一业务实体建立第二个彼此脱节的世界。
|
||||||
|
- [ ] 已区分“内部不可变证据/事件账本”和“产品对外读模型”;新建内部账本不代表必须更换原有产品接口。
|
||||||
|
|
||||||
|
### 2. 真实调用方查证
|
||||||
|
|
||||||
|
必须从对外接口向所有消费方反向追踪,不能只审查新后端写路径:
|
||||||
|
|
||||||
|
- [ ] 列出现有 API 路径、请求参数、响应结构和身份/分页语义。
|
||||||
|
- [ ] 使用 `rg` 查找 Web、iOS、Android、定时任务、报表和第三方调用方。
|
||||||
|
- [ ] 若仓库存在 `.codex-fe-ref/`,必须在其中查证配套前端的真实调用。
|
||||||
|
- [ ] 开发计划的变更文件清单必须覆盖“写入 → 投影/查询 → API → 页面”完整链路。
|
||||||
|
|
||||||
|
### 3. 稳定契约默认不变
|
||||||
|
|
||||||
|
- 既有用户界面已消费的 API,默认保留路径和响应契约;除非用户明确批准破坏性迁移,不得通过新建版本接口把兼容成本转嫁给前端。
|
||||||
|
- 同一业务概念只保留一个产品级读模型。内部账本可以拆分,但必须投影回既有读模型,或由原 API 无感聚合。
|
||||||
|
- 日志、告警、信号或推测数据不得在查询降级时冒充业务事实(例如把 warning 当成交易记录)。
|
||||||
|
|
||||||
|
### 4. 不可避免新写模型时的强制迁移计划
|
||||||
|
|
||||||
|
若确有独立生命周期、不可变审计或 1:N/N:N 证据需求,可以增加新表,但开发计划必须同时包含:
|
||||||
|
|
||||||
|
1. **写入兼容**:双写,或从新账本向既有读模型做事务性/幂等投影。
|
||||||
|
2. **历史回填**:有界、可恢复、可重跑,且不猜测无法证明的归属。
|
||||||
|
3. **去重身份**:定义跨旧/新数据源的稳定业务键,并用数据库唯一约束或等价强保证防重。
|
||||||
|
4. **读取切换**:定义何时以新证据为准、旧数据如何兼容,以及结果数量/金额/状态对账门槛。
|
||||||
|
5. **回滚策略**:回滚新写入时仍能读取已产生的真实业务事实,不删除或隐藏已成功数据。
|
||||||
|
6. **可观测性**:监控新写入成功但旧读模型缺失、投影延迟、去重冲突和回填差异。
|
||||||
|
|
||||||
|
### 5. 用户可见性是必须验收项
|
||||||
|
|
||||||
|
测试不得只证明“新写路径成功”。至少要有一个真实业务记录贯穿测试,同时证明:
|
||||||
|
|
||||||
|
- [ ] 新写路径产生了正确事实。
|
||||||
|
- [ ] 原有对外 API 可以立即或在约定 SLA 内返回该记录。
|
||||||
|
- [ ] 每个真实消费页面/客户端都能正确解码并展示。
|
||||||
|
- [ ] 时间、方向、状态、数量、金额与唯一标识在数据库、API 和 UI 之间一致。
|
||||||
|
- [ ] 部分失败、重试、重复投递、迟到数据和回滚后仍不丢数、不重复、不冒充。
|
||||||
|
|
||||||
|
开发计划必须把上述内容落成明确的变更文件、任务、测试用例和发布门禁,不得只在“风险”章节留一句提醒。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 开发计划文档模板
|
## 开发计划文档模板
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
@@ -1695,3 +1748,4 @@ migrate -path migrations -database "postgres://..." up
|
|||||||
|------|------|----------|--------|
|
|------|------|----------|--------|
|
||||||
| V1.0 | 2026-01-26 | 初始版本,创建 req-dev 技能 | Claude + qiudl |
|
| V1.0 | 2026-01-26 | 初始版本,创建 req-dev 技能 | Claude + qiudl |
|
||||||
| V1.1 | 2026-01-29 | 添加 iOS 端开发规范(SwiftUI + MVVM 架构) | Claude Opus 4.5 |
|
| V1.1 | 2026-01-29 | 添加 iOS 端开发规范(SwiftUI + MVVM 架构) | Claude Opus 4.5 |
|
||||||
|
| V1.2 | 2026-08-26 | 新增既有契约与读模型迁移强制门禁,防止新写链路与原 API/前端脱节 | Codex |
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ description: Installer fixture version two.
|
|||||||
---
|
---
|
||||||
version two
|
version two
|
||||||
EOF
|
EOF
|
||||||
|
# Reproduce rsync's quick-check edge case: changed content with identical size
|
||||||
|
# and mtime must still replace the recorded, unmodified installation.
|
||||||
|
touch -r "$TEST_HOME/.agents/skills/example/SKILL.md" "$FIXTURE_REPO/skills-dev/example-plugin/skills/SKILL.md"
|
||||||
HOME="$TEST_HOME" "$FIXTURE_REPO/install-skills.sh" >/dev/null
|
HOME="$TEST_HOME" "$FIXTURE_REPO/install-skills.sh" >/dev/null
|
||||||
grep -q 'version two' "$TEST_HOME/.agents/skills/example/SKILL.md"
|
grep -q 'version two' "$TEST_HOME/.agents/skills/example/SKILL.md"
|
||||||
grep -q '"version": "2.0.0"' "$TEST_HOME/.agents/.ai-proj-helper-installed-skills.json"
|
grep -q '"version": "2.0.0"' "$TEST_HOME/.agents/.ai-proj-helper-installed-skills.json"
|
||||||
|
|||||||
Reference in New Issue
Block a user