---
id: M2
name: 核心流程闭环
slug: core-flow
status: done
deadline: 2026-05-05
completed: 2026-04-22
dod: 本地 + epsilon 两端都能 demo 完整用户旅程：`xq register` → `xq profile set` → `xq query` → `xq expose buy --count 100` (mock) → 下一个用户 `xq query` 能看到我置顶
---

# M2 — 核心流程闭环 ✅

## 通关 DoD（2026-04-22 全部达成）

真实可演示的用户旅程：
1. ✅ 用户 A `xq register 13800001111` → 验证 → 资料填完（m/28/hangzhou）
2. ✅ 用户 B `xq register 13900002222` → 资料填完（f/27/hangzhou/登山+做饭） + `xq expose buy --count 100 --mock` 买 100 次
3. ✅ 用户 C 对照组（f/29/hangzhou/瑜伽，不买曝光）
4. ✅ A `xq query 'gender=f AND city=hangzhou'` → **B 🔥 置顶 + C 普通排序**
5. ✅ B `xq expose balance` → 100 → **99**（扣 1 精确）
6. ✅ B `xq expose history` → 订单可查

## 任务清单

- [x] sqlite schema（users / profiles / exposure_orders / exposure_usage / sessions / auth_requests 共 6 表 + WAL + index）
- [x] 注册 / 验证码 / session（mock code=123456，手机号永不落明文 red-line test）
- [x] profile CRUD（gender f/m/nb + age 18-99 + city ≤30 + tags ≤20 + bio ≤500）
- [x] 受限 WHERE DSL + 23 单测（禁 OR / JOIN / 子查询 / 引号 / 分号）
- [x] query 后端：`ORDER BY exposure_remaining>0 DESC, updated_at DESC`，被命中原子扣 1
- [x] mock 买曝光 / balance / history 接口 + 事务充值
- [x] `xq` CLI 完整覆盖：register / verify / logout / me / profile / query / expose
- [x] 66 单测全绿（DSL / auth / profile / query / exposure）
- [x] 部署到 epsilon + 三用户 end-to-end demo 实证

## 实装发现

- 起步踩坑：`def get_conn(path=DEFAULT_DB_PATH)` 的默认参数在 def 时捕获，导致测试跨文件污染。修法 ✅ 改成运行时查模块常量（`_resolve_path`）
- ULID 同毫秒内不单调，`ORDER BY id DESC` tiebreak 不稳；改用 `rowid DESC`
- httpx 默认继承系统 SOCKS proxy env；依赖加 `httpx[socks]` 才过
- 两用户 demo 用 `XIANGQIN_CONFIG_DIR` 隔离 session（每用户单独配置目录）

## 服务的目标

[O1-closed-loop](../objectives/O1-closed-loop.md) KR1.2 + KR1.3

## 服务的目标

[O1-closed-loop](../objectives/O1-closed-loop.md) KR1.2 + KR1.3
