# Changelog — xiangqin

所有重要变化记在这。格式基于 [Keep a Changelog](https://keepachangelog.com/)，版本号遵循 [SemVer](https://semver.org/)。

## [Unreleased]

## [0.6.0] - 2026-04-23

### Added — 实名认证闭环（PRD-001）

- **`xq verify-id <姓名> <身份证号>`** 实名二要素核验 CLI（FEAT-030）
- **`POST /auth/verify-id`** API endpoint + Bearer 认证（FEAT-031'）
- **`realname.py`** 核心模块（FEAT-031）：
  - 接阿里云 Cloudauth `Id2MetaVerify` API（复用 sms.aliyun 签名）
  - BizCode 翻译：1 通过 / 2 不一致 / 3 无此号 / 5 姓名错
  - 白名单测试号 `XIANGQIN_TEST_CERT_IDS` 走 mock（不扣钱）
- **DB 迁移**（FEAT-033）：`users` 表加 `verified_at` / `cert_id_hash` / `legal_name`
  + `UNIQUE(cert_id_hash)` 防同证多注册；`hash_cert_id()` 独立 salt
- **`mail.send` 前置实名闸门**（FEAT-034）：未实名 → `MailError(not_verified)` 403
- **`query.py` + DSL `verified` 字段**（决策点 #4/#5）：
  - 结果集每条带 `verified: bool` 标签
  - 用户可写 `AND verified=true/false` 自由过滤
- **限流**（FEAT-035）：24h 内失败 >= 3 次 → `too_many_attempts` 429

### Changed

- CI `deploy-staging.yml` E2E 冒烟 `envelope buy` 加 retry 3 次（阿里云沙箱偶发 flaky）

## [0.5.4] - 2026-04-23

### Changed
- **skill 仓迁移**：`acong-tech/skill-xiangqin` → `acong-tech-skills/xiangqin`（issue #007）。
  - 新 org `acong-tech-skills` 专用托管 skill 仓集群，命名去掉 `skill-` 前缀
  - `release.yml` 的 `sync-skills-sh` job clone URL 对应更新
  - 老地址 GitHub 自动 301 redirect 至少 1 年，skills.sh 侧旧用户无感
- **CI E2E 测试号白名单自动注入**：`deploy-staging.yml` 每次部署把 `XIANGQIN_TEST_PHONES` + `XIANGQIN_TEST_OTP` 写入 alpha `/opt/xiangqin/.env`（prod 不注入，白名单机制 prod 默认关）

### Added
- `auth.py` 测试号白名单分支（`is_test_phone()` + `_test_otp()`）—— 白名单号不调 SMS provider，直接用固定 OTP。E2E 测试可全程序化不依赖 SMS
- `test/test_e2e_onboarding.py` 完整闭环套件：注册 → profile → query → dm/send → inbox → reply
- `brain/blueprints/testing-strategy.md` 三层金字塔（L1 单测 mock / L2 沙箱 / L3 真冒烟）

## [0.5.3] - 2026-04-23

### Fixed
- **db 迁移顺序 bug**：0.5.2 SCHEMA 里 `CREATE INDEX idx_reports_review_status ON reports(review_status, ...)` 对老库（没 review_status 列）跑在 ALTER 之前 → epsilon 起服 `sqlite3.OperationalError: no such column: review_status` 502。
  - 修：把这条 index 移出 SCHEMA，放 `_migrate_reports_review()` 里在 ALTER 之后再建。同样保留 `idx_reports_review_status` 行为。

## [0.5.2] - 2026-04-23

### Added
- **`xq admin` CLI（替代 HTML 后台，老板决策：给 Claude 用更快）**：
  - `xq admin user {info,ban,unban,list}` 封号 / 冻结 / 解封 / 按状态筛用户
  - `xq admin review {pending,pass,reject,confirm}` 举报审核（confirm 可 --ban-target 顺手封号）
  - `xq admin stats` 业务指标（用户数 / 付款量 / 举报数）
  - 权限：env `XIANGQIN_ADMIN_USER_IDS` 白名单；非 admin 返 403
  - API: `/admin/*` 系列 endpoint
  - 核心：`src/xiangqin/admin.py`（240 行）

- **FEAT-025 封号**：users 表加 `status / status_reason / status_updated_at`；banned 清 sessions；`mail.send` 前置 `assert_user_active` 拦截 banned/frozen
- **FEAT-028/029 举报审核**：reports 表加 `review_status / reviewed_by / reviewed_at`；pending → passed/rejected/confirmed 流转 + 幂等（already_reviewed 拒重审）
- 21 个 admin 单测覆盖权限 / 封号 / session 清 / 审核流转 / stats
- db migration：`_migrate_users_status` + `_migrate_reports_review`（幂等）

### Changed
- `mail.send` 首先检查 sender status（banned/frozen 直接拒 + 统一错误码透传）

## [0.5.1] - 2026-04-23

### Added
- **FEAT-013 envelope-buy-real（支付宝真付）**：
  - `xq envelope buy --count N`（默认真付；`--mock` 仍保留为测试）
  - `xq envelope order <order_id>` 查订单状态（CLI 轮询用）
  - 服务端：`POST /envelope/buy` 创 pending 订单 → 调 `alipay.trade.precreate` 拿二维码 URL
  - CLI：`qrcode` 库渲染 ASCII 二维码，用户支付宝扫
  - 支付宝异步回调 `POST /envelope/webhook/alipay`：RSA2 验签 + 订单更新 + 余额充值
  - 幂等：同订单多次回调只充值一次
  - webhook 返 `PlainTextResponse("success")` 才让支付宝停止重试
  - 失败订单标 `status=failed` + 抛 `payment_provider_error`
- `src/xiangqin/alipay.py` 新模块（沿用 M3 时代旧 `payment/alipay.py` 核心，适配新 envelope 表）
- 7 个 mail 单测覆盖真付链路 + 幂等 + 订单查询
- `cryptography` + `qrcode` 依赖加回（M4 砍后首次使用）

### Changed (BREAKING)
- `buy_envelopes(user_id, count, mock)` 默认 `mock=False`（旧 `mock=True`）。调用方如果要 mock 必须显式传
- CLI `xq envelope buy` 默认真付，老的 `--mock` 仍可用但不再必填

### Fixed
- `.github/workflows/release.yml` + `deploy-staging.yml`：rsync `--exclude '.env'` + `--exclude 'logs'` 保护运行时资产，远端 mkdir `logs`/`data` 防 systemd `ReadWritePaths` 挂

## [0.5.0] - 2026-04-23

### Added
- **FEAT-008 avatar + FEAT-009 album（照片上传）**：
  - `xq profile avatar upload/show` + `xq profile album add/list/remove`
  - `POST /photo/avatar` / `POST /photo/album` / `GET /photo/album` / `DELETE /photo/{id}` / `GET /photo/avatar/{target_user_id}`
  - OSS bucket `agentaily-xiangqin-photos-prod`（`users/<uid>/<purpose>/<ulid>.<ext>`）
  - 签名 URL 1h 有效；query 结果每条 hit 返回 `avatar_url`
  - 限制：jpeg/png/webp，≤ 5MB
  - 审核：`_moderate()` stub（下阶段接阿里云绿网，上线前必做）
  - db 新表 `user_photos` + profiles.avatar_photo_id 列
- **brain/features/ 分区**（27 个 FEAT 跨 8 domain，3 个示范完整填，其他骨架）
- **brain/ audit**：objectives O1/O2 + plan M1-M4 归档（产品方向 pivot）；新 O3 商业 / O4 生产就绪 / O5 可靠性；M5 M6 M7 M8 milestone；scenarios index 刷到信封方向；wiki/log append 今夜大事件
- **brain/issues/#024 #025**：skill-repo-README 主仓单源化 + db migration 测试规范
- `test/test_db.py`：schema-from-scratch + 幂等 2 种模式覆盖

### Changed
- systemd unit 加 `XIANGQIN_OSS_BUCKET` / `XIANGQIN_OSS_ENDPOINT` + `EnvironmentFile=-/opt/xiangqin/.env`（敏感 AK/SK 走外部文件不进 git）
- query 命令 docstring 去掉 stale "付费用户带 🔥 并置顶"（M4 已砍曝光）
- vault.json 补 alpha ECS resource + OSS photos bucket resource
- vault who-uses 跨仓修复：默认 workspace 从 ~/Documents 改 ~/ + 加 VAULT_WORKSPACE env（vault repo commit `01ff71b`）

### Chaos Engineering
- M6 full migration drill workflow（第一次实测暴露 aliyun RunInstances 超时 → issue #008 跟踪修）
- brain/blueprints/chaos-engineering.md 整套设计入库
- docs/reliability.md 对外透明展示

## [0.4.5] - 2026-04-23

> 注：**v0.4.4 tag 已 push 但 release.yml test job 因 ruff format check 失败中断**（没有任何产物发到 PyPI / ClawHub / skills.sh）。同内容以 0.4.5 重发，v0.4.4 tag 留作事故档案。

### Added
- **性能监控 M5**（issue #006 最小 viable）：
  - FastAPI middleware：每个请求写 JSON 到 `/opt/xiangqin/logs/access.jsonl`（method/path/status/latency_ms）
  - `scripts/aggregate_metrics.py`：每日 24h 窗口聚合 p50/p95/p99/error_rate → shields.io 徽章 JSON
  - `.github/workflows/chaos-metrics-daily.yml`：每日 ssh epsilon 拉 access.jsonl → aggregate → force-push 到 `metrics-data` 孤儿分支（不污染 main）
  - README 徽章：`api-p95` / `error-rate` 从 `metrics-data` branch raw URL 读
- **Chaos engineering M4 migration drill**（issue #005 完结 milestone）：
  - `.github/workflows/chaos-migration-drill.yml`：每月 1 号 + dispatch，aliyun CLI 开按量 ECS → provision + deploy → smoke → cleanup
  - 复用 alpha 的 VPC/VSwitch/SG/Image，1C1G ecs.t6-c1m1.large，成本 ≤ ¥1/次
  - `scripts/provision.sh` + `scripts/deploy.sh` 支持任意 IP 参数（[0-9]*.*[0-9]* case）

### Fixed
- **systemd sandbox**：`ReadWritePaths=/opt/xiangqin/data /opt/xiangqin/logs`（原只允许写 data/，middleware 写 logs/ 被拒导致 access.jsonl 从未生成）

## [0.4.3] - 2026-04-23

### Security / Privacy
- **SKILL.md 去内部实践细节**：删除"和 vault 的关系"整段 + "自治单体实验"段 + 服务端 IP（`epsilon 112.124.27.213`）暴露
- **SKILL.md 通用化**：不再 Claude Code 专属措辞（`Claude:` → `agent:`），声明适配任何支持 SKILL.md 协议的 agent runtime
- **去私有仓链接**：SKILL.md 和 skill 仓 README 移除 `github.com/yarnovo/xiangqin` 私仓链接

### Added
- **SKILL.md 前置段**：补齐系统要求（Python 3.12+）、3 种安装方式（uv / pipx / venv+pip）、验证步骤（`xq --version` / `xq health`）、SOCKS 代理说明

### Changed
- **skill 仓 README 改写**：说明这是通用 agent skill（不挑 runtime）+ 使用步骤 + MIT-0 协议

### Chaos Engineering 运维演练
- **M1 backup freshness**（`.github/workflows/chaos-backup-freshness.yml`）：每小时 ssh epsilon 查 OSS 最新备份 mtime，>26h fail
- **M2 backup integrity**（`.github/workflows/chaos-backup-integrity.yml`）：每日 epsilon 本地拉最新备份 gunzip + sqlite integrity_check + 核心表存在
- **M3 restore drill**（`.github/workflows/chaos-restore-drill.yml`）：每周 GHA runner（独立环境）从 OSS 拉备份 restore 演练，模拟"epsilon 整机 loss 异地恢复"
- 新 secret：`ALIYUN_OSS_AK_ID` / `ALIYUN_OSS_AK_SECRET`
- README 加 `backup-fresh` 徽章（GHA status badge）
- 方案见 issue #005

### Fixed
- `scripts/restore.sh`：查 `exposure_orders` 表（M4 已砍）的语句改成查 `messages`（M4 表）+ fallback 兼容老备份

## [0.4.2] - 2026-04-23

### Changed (infra)
- **workflow 重构**：`deploy-production.yml` 废弃 → 合并到 `release.yml` 的 `deploy-production` job，`needs: [publish-pypi]` chain 触发。打 tag 一键全链路上线（PyPI + skills.sh + ClawHub + epsilon），不再点按钮。
- **release.yml 加 `publish-clawhub` job**：GHA runner 装 clawhub CLI + 配 `CLAWHUB_TOKEN` secret → `clawhub publish`
- **test gate**：所有 publish/deploy job 前置 `test` job（pytest + ruff），绿了才跑。`deploy-staging.yml` 同理
- **跳号 0.4.1**：ClawHub 抢先发了 0.4.1 作 display name metadata patch，主仓从 0.4.0 直接跳 0.4.2 对齐三端
- SOP 文档同步更新（release-flow / deploy / publish / hotfix / decision ADR 补丁段）

## [0.4.0] - 2026-04-23

### Known Limitations
- **真实支付未接入**：`xq envelope buy --mock` 是唯一购买路径，**不真扣钱**；mock=1 直接增加余量
- **付费闭环留给 v0.5.0**：支付宝 / 微信支付接入会作为独立 deliverable
- 每日 3 封免费信封（UTC 0 点重置）对大多数用户足够用

### Added
- 2026-04-23 **xq CLI 版本检查 + 新版本提示**（issue #002，`src/xiangqin/version_check.py`）
  - 每次 `xq <cmd>` 检查 PyPI，新版本在 **stderr** 打印 `[xq] 新版本 X.Y.Z 可用（当前 A.B.C）...`
  - 缓存 `~/.xiangqin/version-cache.json` 24h TTL，后台子进程异步刷，不阻塞命令
  - `XIANGQIN_NO_UPDATE_CHECK=1` 关闭；CI 环境自动关
  - SKILL.md 新增 TRIGGER：agent 识别字样后主动询问用户升级

### Changed (BREAKING)
- 2026-04-23 **epsilon 生产 PHONE_SALT 轮换**（issue #003）
  - 原生产用默认值 `dev-salt-not-for-prod`（安全漏洞，salt 形同公开）
  - 替换为独立随机 salt（`openssl rand -hex 32`，写 systemd drop-in `override.conf`）
  - 副作用：老用户（老板自己测试的 4 个账号）phone_hash 全失配，需重新注册
  - alpha staging 在 provision 时已自动生成独立 salt（per-host）

### Added
- 2026-04-22 项目骨架（new-team skill 产出）
- 2026-04-22 立项 ADR：付费置顶商业模式 + 自治单体实验
- 2026-04-22 objectives O1 (0.1 闭环) / O2 (自治假设验证) 定义
- 2026-04-22 plan M1..M4 四个里程碑拆分
- 2026-04-22 vault 分配 epsilon (112.124.27.213) 给 xiangqin，gamma 从 matchmaker 释放
- 2026-04-22 **M1 通关**：epsilon 上 FastAPI + uvicorn + nginx + systemd，全链路 `xq health` 绿
- 2026-04-22 **M2 通关**：注册 / verify / profile / query / 买曝光完整闭环
  - 6 张 sqlite 表（users / profiles / exposure_orders / exposure_usage / sessions / auth_requests）+ WAL + 索引
  - 受限 WHERE DSL（字段 + 操作符 + 值三重白名单，禁 OR/JOIN/子查询）
  - query `ORDER BY exposure_remaining>0 DESC`，被命中原子扣 1
  - mock 买曝光（paid=1 + 事务充值）
  - `xq` CLI 完整：register / verify / logout / me / profile / query / expose
  - 66 单测全绿，含手机号永不落明文 red-line
  - 三用户 end-to-end demo 实证（A 查到 B🔥置顶 + C 普通；B 余额 100→99）
- 2026-04-22 **M3 通关**：sqlite → OSS 每日备份 + 恢复演练
  - OSS 桶 `agentaily-backup-xiangqin-prod` 创建（vault resource 入库）
  - aliyun cli `xiangqin` profile 装在 epsilon（AK/SK 从 vault 取）
  - 首次备份 `daily/xiangqin-20260422.db.gz` 成功
  - `/etc/cron.d/xiangqin-backup` 02:00 daily（明日 02:00 自动跑）
  - `scripts/restore.sh` 恢复演练通过（integrity_check=ok，3 users + 1 order 对得上）

### Security
- 2026-04-22 **SG 端口收窄**：epsilon 阿里云安全组关闭老 openclaw 遗留端口（7000 / 9876 / 22000-22999 / 内网 5432），只保留 22 / 80 / 443
- 2026-04-22 **服务降权**：systemd unit `User=root` → `User=xiangqin`（system user，nologin shell），加 ProtectSystem/ProtectHome/NoNewPrivileges/PrivateTmp 沙箱
- 2026-04-22 uv 管理的 Python 装到 `/opt/uv-python`（原 `/root/.local/share/uv/python` 非 xiangqin 用户可读路径），provision.sh + deploy.sh 已适配

### Changed
- 2026-04-22 **M4 方向调整**：从"1 对 1 真人访谈"改为"skill 发布 + 视频宣传 + agent 自助提示付费"（详见 [decisions/2026-04-22-2230-m4-switch-to-skill-distribution.md](./decisions/2026-04-22-2230-m4-switch-to-skill-distribution.md)）
- 2026-04-22 **pypi 包命名统一**：pyproject `name` 从 `xiangqin` 改为 `acong-tech-xiangqin`（对齐 shipyard 命名空间约定；CLI 二进制 `xq` 不变）

### Publish
- 2026-04-22 **M4 发布骨架就绪**：
  - `scripts/publish-pypi.sh` dry-run / --live / --canary，从 vault 取 PyPI token（自治单体，不依赖 shipyard）
  - `scripts/publish-skill.sh` 包装 `skill-publish-cli publish`
  - pypi build dry-run 通过（22KB wheel + 80KB sdist）
  - README.md 对外用户版（装 / 上手 / 心智模型）
  - 视频台词 scenarios/promo-video-script.md（2:30-3:00 8 分镜）
- 2026-04-22 **canary 实发通过**：pypi `acong-tech-xiangqin==0.1.0.dev202604220423` + ClawHub `xiangqin-canary`
  → `pip install --pre` + `xq health` 打生产返回 ok；`xq register` 走通
- 2026-04-23 **M4 触达架构：虚拟信封私信**（ADR `brain/decisions/2026-04-23-m4-mail-model-touchreach.md`）
  - 信箱 + 通知分离：信箱注册即开，通知（agent gateway push）可选；信件 30 天 TTL
  - 每天 3 封免费信封（UTC 0 点重置），超额 ¥1/封；投递失败（对方拉黑 / 注销）不扣
  - 信件 5 维状态：投递 / 通知 / 阅读 / 回应 / 处置
  - 通知去重：B 有未读 + <12h 不推；≥12h 兜底再提醒
  - 匿名代理：双方互不知对方 `agent_gateway_url` + `hooks_token`
  - 举报永久留档；拉黑单向拦截后续发信
  - profile 加字段：`height` / `education` / `agent_gateway_url` / `agent_hooks_token` / `notify_on_new_mail`
  - DSL 白名单扩展支持 `height` / `education`
  - CLI 新增：`xq dm` / `xq inbox` / `xq envelope` / `xq block` 命令族
  - 新表 `messages` / `envelope_usage` / `envelope_balance` / `envelope_orders` / `reports` / `blocks` / `notify_state`
- 2026-04-23 **brain/ 对内资产统一**：`decisions/ objectives/ plan/ scenarios/ sop/ wiki/` 根目录 git mv 到 `brain/`
- 2026-04-23 **docs/internal/ 拆分**：数据库 schema / 架构详情只本地 `mkdocs serve` 可见，CI 打包时剔除
- 2026-04-23 **CLAUDE.md 项目地图**：Claude 新会话 cold-start 入口；vault 项目同步建

### Removed
- 2026-04-23 **曝光系统整体下线**（ADR `brain/decisions/2026-04-23-drop-exposure-system.md`）
  - 删 `src/xiangqin/exposure.py`（211 行）
  - 删 `src/xiangqin/payment/` 整个目录（base / mock / alipay / wechat）
  - `app.py` 删 `/exposure/*` endpoints + alipay webhook
  - `cli.py` 删 `xq expose buy/balance/history` 命令
  - `query.py` 排序简化为 `ORDER BY p.updated_at DESC`（去付费置顶）
  - DB 加 `_drop_exposure_tables` 幂等迁移（生产 epsilon 部署时 DROP `exposure_orders` / `exposure_usage`）
  - 砍依赖 `cryptography`（原 alipay 验签）+ `qrcode`（原 ASCII 二维码）
  - 废掉的 M4 信封真付走支付宝待 v0.2 重新接入支付层；vault `alipay-kongxuanpin` 凭证资产保留

## [0.3.0] — 2026-04-23 零等待接入真 SMS / 真支付宝

### Added
- **短信真跑通**：`XIANGQIN_SMS_PROVIDER=aliyun`
  - 复用 vault `aliyun.power-app`（sign="杭州阿空智能科技" / template=SMS_317151039）
  - 无需新申请，零等待上线
- **支付宝真扣款**：`XIANGQIN_PAYMENT_PROVIDER=alipay`
  - 复用 vault `alipay.enterprise.kongxuanpin`（企业应用"阿空选品助手"）
  - `alipay.trade.precreate` 真调，prod 返回可扫二维码（实测）
  - webhook 路由 `/exposure/webhook/alipay` 验签 + 幂等充值
  - RSA2 签名踩坑 2 处：sign_type 参与签名 / charset 放 URL query；都在代码注释留底
- **CLI 渲染 ASCII 二维码**：`xq expose buy` 直接在 terminal 画二维码，用户手机支付宝 app 扫
- **sqlite schema 扩展**：exposure_orders 加 `provider / biz_order_id / status` 三列（老行迁移默认 `status=paid`）
- **新依赖**：`cryptography>=43`（RSA2 签名）+ `qrcode>=8`（ASCII 渲染）

### Changed
- 版本 0.2.0 → 0.3.0
- epsilon systemd unit 加 3 个 Environment：`XIANGQIN_SMS_PROVIDER` / `XIANGQIN_PAYMENT_PROVIDER` / `XIANGQIN_BASE_URL`
- SOP `sms-onboarding.md` + `payment-onboarding.md` 加"零等待复用"段（v2）

### Fixed
- sqlite schema migration 顺序 bug：`CREATE INDEX ... WHERE status='pending'` 老库没 status 列会炸，改为 `_migrate_orders_columns` 里先加列后建索引
- epsilon 上 `uv sync` 连 pypi.org 慢 → 用 `UV_INDEX_URL=阿里云 pypi mirror`，几秒完成

## [0.2.0] — 2026-04-22 夜（已被 0.3.0 覆盖）

### Added
- **SMS provider 抽象层** (`src/xiangqin/sms/`)
  - `mock`（默认，code 恒 `123456`）
  - `aliyun`（真调阿里云短信服务，RPC 签名自实现避开巨型 SDK）
  - 切换走 `XIANGQIN_SMS_PROVIDER` 环境变量
- **Payment provider 抽象层** (`src/xiangqin/payment/`)
  - `mock`（默认，立即 paid）
  - `wechat` / `alipay` 骨架（0.2 正式期对接，`__init__` 直接 raise NotImplementedError 避免误用）
  - 切换走 `XIANGQIN_PAYMENT_PROVIDER` 环境变量
- **OrderOut 扩展**：返回 `provider` / `status` / `qr_code_content`（便于支付跳转）
- **xq expose buy** CLI 适配：真支付路径输出二维码内容
- **SOP 文件齐套**：
  - `sop/services/deploy.md` - 发布新代码流程
  - `sop/services/publish.md` - PyPI + ClawHub 发版流程
  - `sop/services/restore.md` - OSS 备份恢复流程
  - `sop/operations/sms-onboarding.md` - 阿里云短信签名 / 模板申请指南
  - `sop/operations/payment-onboarding.md` - 微信 / 支付宝商户号申请指南
  - `docs/operator-manual.md` - 日常运维手册（一站式排查）

### Changed
- `pyproject.toml` version 0.1.0 → 0.2.0
- `src/xiangqin/__init__.py` __version__ 0.1.0 → 0.2.0
- epsilon 生产 version 已升级（`curl /health` 返回 0.2.0）

### Fixed / 保留兼容
- 0.2.0 API 完全向下兼容 0.1.0 客户端（新增字段全为 Optional；mock provider 默认行为不变）

### Canary
- pypi: `acong-tech-xiangqin==0.2.0.dev202604220439`
- ClawHub: `xiangqin-canary` commit `90a98f0`
