Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
510699523b | ||
|
|
7d19d8efd2 |
+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 --checksum --delete "$src_dir/" "$dst_dir/"
|
rsync -a --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
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ description: Slark 仓库 staging、生产和 Desktop 安装包的端到端 CI/C
|
|||||||
|
|
||||||
# Slark CI/CD runbook
|
# Slark CI/CD runbook
|
||||||
|
|
||||||
驱动 Slark 从改代码到上线的端到端流程。权威流程与完整细节见仓库文档
|
驱动 Slark 从改代码到上线的端到端流程。环境身份与发布门禁见仓库文档
|
||||||
`docs/cicd-staging-production.md`(本技能命令与之一致);本文件是可直接照做的分阶段清单。
|
`docs/cicd-staging-production.md`;当前 governed staging 候选入口以仓库脚本契约和历史成功回执为准。
|
||||||
|
该文档中直接运行 `deploy-staging.sh` 的示例尚未列出当前脚本要求的不可变候选材料。
|
||||||
|
|
||||||
仓库:`qiudl/qiu-slark`(本机克隆通常在 `/Users/donglinlai/coding/slark`)。所有命令在**仓库根**执行。
|
仓库:`qiudl/qiu-slark`(本机克隆通常在 `/Users/donglinlai/coding/slark`)。所有命令在**仓库根**执行。
|
||||||
`ai-proj` 相关命令加 `GODEBUG=netdns=go` 前缀。
|
`ai-proj` 相关命令加 `GODEBUG=netdns=go` 前缀。
|
||||||
@@ -21,8 +22,8 @@ description: Slark 仓库 staging、生产和 Desktop 安装包的端到端 CI/C
|
|||||||
**每次都要人审**;命中审批闸就**等人批**,绝不绕过、绝不把这些 key 加进 auto-approve 白名单。
|
**每次都要人审**;命中审批闸就**等人批**,绝不绕过、绝不把这些 key 加进 auto-approve 白名单。
|
||||||
- staging 与生产硬隔离:staging 脚本带生产 IP 拒运行护栏、不 push origin、不反向 rsync。
|
- staging 与生产硬隔离:staging 脚本带生产 IP 拒运行护栏、不 push origin、不反向 rsync。
|
||||||
- 当前 Slark staging 权威目标是 AWS `ap-southeast-2` 实例 `i-013e5ca3fdfddc13e`
|
- 当前 Slark staging 权威目标是 AWS `ap-southeast-2` 实例 `i-013e5ca3fdfddc13e`
|
||||||
(Name=`slark-staging`,2026-08-18 核验公网 IP `15.135.112.181`)。公网 IP 自动分配,发布前必须按
|
(文档记录 Name=`slark-staging`;2026-10-23 只读核验公网 IP `15.135.112.181`)。公网 IP 自动分配,发布前必须按
|
||||||
实例 ID 重查并确认 Name/状态;不得凭历史 IP 判断目标。
|
实例 ID 从 AWS 控制面重查并确认 Name/状态/IP;不得凭历史 IP 或 SSH 可达性判断发布目标。
|
||||||
- `82.157.141.202` 是 ai-proj 预生产宿主机。其 `/opt/slark-staging` cohost 是隔离的 Slark 兼容验收栈,
|
- `82.157.141.202` 是 ai-proj 预生产宿主机。其 `/opt/slark-staging` cohost 是隔离的 Slark 兼容验收栈,
|
||||||
但不是 Slark 独立预生产;只有任务明确指定 cohost 时才能使用,不得作为普通 Slark staging 的默认目标。
|
但不是 Slark 独立预生产;只有任务明确指定 cohost 时才能使用,不得作为普通 Slark staging 的默认目标。
|
||||||
- staging 发布若影响 `packages/daemon`、`packages/runtime`、Server/daemon 公共协议或 Desktop 公共运行链,
|
- staging 发布若影响 `packages/daemon`、`packages/runtime`、Server/daemon 公共协议或 Desktop 公共运行链,
|
||||||
@@ -75,7 +76,8 @@ git branch -D <feature-branch> # 删本地(先切走)
|
|||||||
### 3.1 当前权威路径:Slark 独立 staging
|
### 3.1 当前权威路径:Slark 独立 staging
|
||||||
|
|
||||||
稳定目标是 AWS `ap-southeast-2` 实例 `i-013e5ca3fdfddc13e`(Name=`slark-staging`)。它使用自动分配
|
稳定目标是 AWS `ap-southeast-2` 实例 `i-013e5ca3fdfddc13e`(Name=`slark-staging`)。它使用自动分配
|
||||||
公网 IP,因此每次部署前先查询当前 IP;2026-08-18 核验值为 `15.135.112.181`:
|
公网 IP,因此每次部署前先查询当前 IP。`15.135.112.181` 是 2026-10-23 的只读核验值,
|
||||||
|
不是可直接用于将来发布的固定地址:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
aws ec2 describe-instances --region ap-southeast-2 \
|
aws ec2 describe-instances --region ap-southeast-2 \
|
||||||
@@ -83,20 +85,93 @@ aws ec2 describe-instances --region ap-southeast-2 \
|
|||||||
--query 'Reservations[0].Instances[0].{Name:Tags[?Key==`Name`]|[0].Value,State:State.Name,PublicIp:PublicIpAddress}' \
|
--query 'Reservations[0].Instances[0].{Name:Tags[?Key==`Name`]|[0].Value,State:State.Name,PublicIp:PublicIpAddress}' \
|
||||||
--output table
|
--output table
|
||||||
|
|
||||||
|
# 只使用上一步 AWS 返回且已核对 Name=slark-staging、State=running 的公网 IP。
|
||||||
|
export STAGING_IP='<verified-public-ip>'
|
||||||
export STAGING_SSH_KEY=~/.ssh/cloud-server-syd.pem
|
export STAGING_SSH_KEY=~/.ssh/cloud-server-syd.pem
|
||||||
export STAGING_REMOTE=ubuntu@15.135.112.181
|
export STAGING_REMOTE="ubuntu@$STAGING_IP"
|
||||||
export STAGING_REMOTE_IP=15.135.112.181
|
export STAGING_REMOTE_IP="$STAGING_IP"
|
||||||
export STAGING_BASE=http://15.135.112.181
|
export STAGING_BASE="http://$STAGING_IP"
|
||||||
|
# 使用 Slark 应用内的 project ID,不是 ai-proj 的数字项目/需求/任务 ID。
|
||||||
|
export SLARK_RELEASE_PROJECT_ID='<verified-slark-app-project-id>'
|
||||||
|
export SLARK_RELEASE_TASK_ID='REQ-...'
|
||||||
|
export SLARK_RELEASE_ID='release-staging-<40-sha>-<unique-attempt>'
|
||||||
|
# 普通发布还需从既有受信 provider 提供 mirror token;不要打印或写进日志。
|
||||||
|
|
||||||
scripts/deploy-staging.sh --migrate-dry-run
|
# 先确认候选 SHA 在 canonical GitHub 上可获取,且包含当前 main 的全部提交。
|
||||||
scripts/deploy-staging.sh
|
bash scripts/release-staging-governed.sh '<40-sha>'
|
||||||
STAGING_TOKEN=$(cat /tmp/.staging_token) scripts/staging-seed.sh
|
|
||||||
scripts/staging-verify-isolation.sh
|
scripts/staging-verify-isolation.sh
|
||||||
|
# 使用上一步发布输出的同一 release receipt;观察窗口未结束时保留原 receipt 重试。
|
||||||
|
scripts/complete-staging-verification.sh '<release-receipt-file>'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
这是历史 Slark 发布实际采用的 governed 入口:控制器从 canonical GitHub
|
||||||
|
获取不可变候选、持有本地锁,内层 `deploy-staging.sh` 执行远端构建、数据库迁移、
|
||||||
|
切换和健康检查。需要先单独演练迁移时,按仓库发布文档生成同一精确 SHA 的
|
||||||
|
`staging-candidate` manifest/archive/source,再向 `deploy-staging.sh --migrate-dry-run`
|
||||||
|
传入 `STAGING_CANDIDATE_MANIFEST`、`STAGING_CANDIDATE_ARCHIVE` 和
|
||||||
|
`STAGING_SOURCE_DIR`;当前 `deploy-staging.sh` 不接受缺少这些候选材料的直接调用。
|
||||||
|
这条演练在隔离数据库副本执行 pending up,不重启 Server;正式 governed 发布则在
|
||||||
|
live staging 数据库执行 migrate up。历史 2026-10-22 成功回执见
|
||||||
|
`acceptance/REQ-20261010-0007-rejection-execution-20261022/staging-deploy-r2.log`
|
||||||
|
与 `staging-verify-r2.log`。历史脚本里为特定 DSH UI 发布加入的配对、手动切换及回退步骤
|
||||||
|
不属于普通 Slark Server/daemon 发布命令,不能直接照搬。
|
||||||
|
|
||||||
|
候选若包含 canonical main 尚未收录的 migration,或修改发布控制面,staging admission
|
||||||
|
只允许在隔离 PDV 中验证,不会切换共享 staging。隔离验证通过后,先按 PR 门禁将改动并入
|
||||||
|
canonical main,再用新的 main 精确 SHA 重新准备候选和执行共享 staging 发布;不要把
|
||||||
|
`isolated_required` 的成功回执写成共享 staging 已发布。
|
||||||
|
|
||||||
|
若镜像 token 确实不可读,仓库另有 `deploy-staging.sh --no-mirror --reason '<具体原因>'`
|
||||||
|
审计路径。仅在已按官方 `staging-candidate.mjs` 生成并核验同一精确 SHA 的 manifest、archive、
|
||||||
|
source 和 daemon seed revision 列表,且已满足正常 CI、环境身份和 release admission 门禁时使用;
|
||||||
|
该路径不读取 Bitwarden、不切换 `staging-latest` 镜像频道。历史先例见
|
||||||
|
`docs/reports/REQ-20261012-0006-delivery-status.md`。使用后应明确记录“共享 staging 已发布,
|
||||||
|
镜像频道未更新”,不能把 no-mirror 写成镜像发布成功。
|
||||||
|
|
||||||
不得把查询到的 Name 非 `slark-staging` 的机器当作目标。四件套细节与环境隔离约束见
|
不得把查询到的 Name 非 `slark-staging` 的机器当作目标。四件套细节与环境隔离约束见
|
||||||
`docs/req-20260811-0059-staging-environment-design.md`。
|
`docs/req-20260811-0059-staging-environment-design.md`。
|
||||||
|
|
||||||
|
**AWS 凭证不可用时的只读排查(2026-10-23 记录):**先查 Slark 自己的历史发布记录,
|
||||||
|
例如 `acceptance/REQ-20260930-0015/release-staging-main-history-window.sh`;其中记录的
|
||||||
|
staging IP 是 `15.135.112.181`,Slark 应用 release project ID 是
|
||||||
|
`tpqvorREPTj2kx9poKJWH`。这些值只是定位线索,使用前须重新核对。可用已有
|
||||||
|
`cloud-server-syd.pem`、`StrictHostKeyChecking=yes` 只读 SSH 到历史 IP,并核对远端
|
||||||
|
`/sys/devices/virtual/dmi/id/board_asset_tag`、IMDSv2 `instance-id` 与
|
||||||
|
`public-ipv4`;2026-10-23 三项分别核对为 `i-013e5ca3fdfddc13e`、
|
||||||
|
`i-013e5ca3fdfddc13e`、`15.135.112.181`,当时 `slark-server` 和
|
||||||
|
`slark-daemon-staging` 均在运行。不要输出 IMDS token 或应用凭证。
|
||||||
|
|
||||||
|
只读身份核验示例(历史 IP 仅用于定位;任何一项不匹配就停止):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -i ~/.ssh/cloud-server-syd.pem -o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=yes -o ConnectTimeout=5 \
|
||||||
|
ubuntu@15.135.112.181 'bash -s' <<'STAGING_IDENTITY'
|
||||||
|
set -euo pipefail
|
||||||
|
tag="$(cat /sys/devices/virtual/dmi/id/board_asset_tag)"
|
||||||
|
token="$(curl -fsS --noproxy '*' --max-time 3 -X PUT \
|
||||||
|
-H 'X-aws-ec2-metadata-token-ttl-seconds: 60' \
|
||||||
|
http://169.254.169.254/latest/api/token)"
|
||||||
|
id="$(curl -fsS --noproxy '*' --max-time 3 \
|
||||||
|
-H "X-aws-ec2-metadata-token: $token" \
|
||||||
|
http://169.254.169.254/latest/meta-data/instance-id)"
|
||||||
|
ip="$(curl -fsS --noproxy '*' --max-time 3 \
|
||||||
|
-H "X-aws-ec2-metadata-token: $token" \
|
||||||
|
http://169.254.169.254/latest/meta-data/public-ipv4)"
|
||||||
|
test "$tag" = i-013e5ca3fdfddc13e
|
||||||
|
test "$id" = "$tag"
|
||||||
|
test "$ip" = 15.135.112.181
|
||||||
|
printf 'instance=%s public_ip=%s\n' "$id" "$ip"
|
||||||
|
STAGING_IDENTITY
|
||||||
|
```
|
||||||
|
|
||||||
|
这条 SSH/DMI/IMDS 路径只证明所连主机的实例身份,**不提供 AWS Name 标签的当次证明,
|
||||||
|
不能代替上面的发布前控制面核验**。`docs/runbooks/REQ-20260922-0005-staging-watchdog-emergency-recovery.md`
|
||||||
|
中的替代路径仅授权给那次特定事故,不能移用于普通 staging 发布。不要借用其他项目
|
||||||
|
的 AWS profile;若缺少 Slark AWS 控制面凭证,停在只读排查并记录缺口。2026-10-23
|
||||||
|
实例自带角色 `dsh-identity-staging-ec2` 调用 `DescribeInstances` 返回
|
||||||
|
`UnauthorizedOperation`,不能靠这个角色补齐 Name 核验。
|
||||||
|
|
||||||
### 3.2 非权威路径:ai-proj cohost(仅明确指定时)
|
### 3.2 非权威路径:ai-proj cohost(仅明确指定时)
|
||||||
|
|
||||||
ai-proj cohost 位于 `ubuntu@82.157.141.202:/opt/slark-staging`。全程使用隔离数据、数据库、密钥和状态目录,
|
ai-proj cohost 位于 `ubuntu@82.157.141.202:/opt/slark-staging`。全程使用隔离数据、数据库、密钥和状态目录,
|
||||||
|
|||||||
@@ -322,59 +322,6 @@ 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
|
||||||
@@ -1748,4 +1695,3 @@ 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,9 +42,6 @@ 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