Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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.0.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.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "slark-cicd",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
---
|
||||||
|
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。
|
||||||
|
- 审批式部署,永不自动:亲手敲的 `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→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 真机预验(可选,随时)
|
||||||
|
|
||||||
|
### 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:生产发布(审批式)
|
||||||
|
|
||||||
|
**唯一姿势**:从干净的普通 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 可保留作审计,清理时用可恢复方式。
|
||||||
|
|
||||||
|
## 阶段 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 ≥ 发布前`。
|
||||||
|
镜像落后告警(默认 `--no-mirror`)非阻断,需补推按告警的幂等命令(需 Bitwarden session);镜像仅单向 fast-forward。
|
||||||
|
|
||||||
|
## 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 不符 | 部署到了旧码,重发 |
|
||||||
|
|
||||||
|
## ai-proj 流程(强制)
|
||||||
|
|
||||||
|
任何改产品/代码行为的工作,动手前先在 ai-proj 立项 + 建 Task + 关联,实现/测试/交付回写;
|
||||||
|
commit/PR 引用 `REQ-...`。纯文档/工具/只读排查除外。详见仓库 `AGENTS.md` / `CLAUDE.md`。
|
||||||
@@ -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