# Invite Code Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** 在现有 OTP 注册链路中引入邀请码能力，支持用户自动生成专属邀请码、注册时可选填邀请码并记录邀请关系与使用次数。

**Architecture:** 采用数据库中心实现：通过 Alembic 新增 `invite_codes` 表、扩展 `profiles` 字段，并在 `auth.users` 的现有 trigger 函数中完成邀请码校验与记账，保证注册与邀请关系写入尽量原子。应用层只负责透传 `invite_code` 到 Supabase `raw_user_meta_data`。

**Tech Stack:** FastAPI, SQLAlchemy, Alembic, Supabase Auth, PostgreSQL PL/pgSQL, Pytest

---

### Task 1: 更新注册请求 Schema（TDD）

**Files:**
- Modify: `backend/src/v1/auth/schemas.py`
- Modify: `backend/tests/integration/test_auth_routes.py`

**Step 1: Write the failing test**

在 `test_signup_start_returns_pending_response` 基础上新增断言路径：请求体带 `invite_code` 时返回仍为 202，且未触发 422。

**Step 2: Run test to verify it fails**

Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k signup_start_returns_pending_response -v`
Expected: FAIL（`invite_code` 为额外字段或校验不通过）

**Step 3: Write minimal implementation**

在 `VerificationCreateRequest` 增加可选字段：

```python
invite_code: str | None = Field(default=None, min_length=8, max_length=8)
```

**Step 4: Run test to verify it passes**

Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k signup_start_returns_pending_response -v`
Expected: PASS

**Step 5: Commit**

```bash
git add backend/src/v1/auth/schemas.py backend/tests/integration/test_auth_routes.py
git commit -m "feat: accept invite code in signup request"
```

### Task 2: 透传 invite_code 到 Supabase metadata（TDD）

**Files:**
- Modify: `backend/src/v1/auth/gateway.py`
- Modify: `backend/tests/unit/v1/auth/test_auth_service.py`

**Step 1: Write the failing test**

在 `test_supabase_signup_passes_username_in_metadata` 增加 `invite_code` 并断言：

```python
assert captured_payload["data"] == {
    "username": "demo",
    "invite_code": "A1B2C3D4",
}
```

**Step 2: Run test to verify it fails**

Run: `cd backend && uv run pytest tests/unit/v1/auth/test_auth_service.py -k metadata -v`
Expected: FAIL（metadata 未包含 `invite_code`）

**Step 3: Write minimal implementation**

在 `create_verification` 中构建 metadata：

```python
metadata = {"username": request.username}
if request.invite_code:
    metadata["invite_code"] = request.invite_code
payload = {
    "email": request.email,
    "password": request.password,
    "data": metadata,
}
```

**Step 4: Run test to verify it passes**

Run: `cd backend && uv run pytest tests/unit/v1/auth/test_auth_service.py -k metadata -v`
Expected: PASS

**Step 5: Commit**

```bash
git add backend/src/v1/auth/gateway.py backend/tests/unit/v1/auth/test_auth_service.py
git commit -m "feat: pass invite code through signup metadata"
```

### Task 3: 新增 invite_codes 表与 profiles.referred_by（迁移先行）

**Files:**
- Create: `backend/alembic/versions/20260227_0006_invite_codes_and_profile_referral.py`
- Modify: `backend/src/models/profile.py`
- Create: `backend/src/models/invite_code.py`
- Modify: `backend/src/models/__init__.py`

**Step 1: Write the failing test**

在 `backend/tests/unit/database/test_profile_models.py` 新增 `referred_by` 读写测试；新增 `backend/tests/unit/database/test_invite_code_models.py` 验证 `InviteCode` 基本创建与约束字段。

**Step 2: Run test to verify it fails**

Run: `cd backend && uv run pytest tests/unit/database/test_profile_models.py tests/unit/database/test_invite_code_models.py -v`
Expected: FAIL（字段/模型不存在）

**Step 3: Write minimal implementation**

- Alembic 创建 `invite_codes`：
  - `code` 唯一索引
  - `owner_id` 外键到 `profiles.id`（可空）
  - `status`、`used_count`、`max_uses` check 约束
  - `max_uses` 默认 `NULL`（无限制）
  - `expires_at` 默认 `NULL`（无限制）
  - `reward_config` JSONB 默认 `{}`
  - 启用 RLS（按项目默认 deny-all）
- **注意**：本期不开放 invite_codes 表直接读取，用户邀请码通过 profile 聚合接口返回（后续实现）

- Alembic 给 `profiles` 增加 `referred_by` + 索引 + 外键
- ORM 同步 `Profile.referred_by` 与 `InviteCode` 模型

**Step 4: Run test to verify it passes**

Run: `cd backend && uv run pytest tests/unit/database/test_profile_models.py tests/unit/database/test_invite_code_models.py -v`
Expected: PASS

**Step 5: Commit**

```bash
git add backend/alembic/versions/20260227_0006_invite_codes_and_profile_referral.py backend/src/models/profile.py backend/src/models/invite_code.py backend/src/models/__init__.py backend/tests/unit/database/test_profile_models.py backend/tests/unit/database/test_invite_code_models.py
git commit -m "feat: add invite code schema and profile referral fields"
```

### Task 4: 扩展注册 trigger 生成邀请码并消费邀请（TDD）

**Files:**
- Modify: `backend/alembic/versions/20260227_0006_invite_codes_and_profile_referral.py`
- Modify: `backend/tests/integration/test_auth_routes.py`

**Step 1: Write the failing test**

新增集成测试（建议通过测试替身/fixture 验证行为）：
- 注册不带邀请码时，profile 创建后存在 owner 邀请码
- 注册带有效邀请码时，`referred_by` 生效且 `used_count + 1`

**Step 2: Run test to verify it fails**

Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k invite -v`
Expected: FAIL（触发器逻辑尚未实现）

**Step 3: Write minimal implementation**

在迁移 SQL 中：
- 新增 helper function：生成 8 位随机码（排除易混淆字符 0/O/1/I/L，冲突重试）
- 重建 `public.create_profile_for_new_user()`：
  1. 插入 `profiles`
  2. 创建该用户专属 `invite_codes`（`owner_id = NEW.id`）
  3. 读取 `NEW.raw_user_meta_data ->> 'invite_code'`
  4. 校验邀请码状态/过期/次数
  5. 若有效：更新 `profiles.referred_by`，并 `used_count = used_count + 1`

**Step 4: Run test to verify it passes**

Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k invite -v`
Expected: PASS

**Step 5: Commit**

```bash
git add backend/alembic/versions/20260227_0006_invite_codes_and_profile_referral.py backend/tests/integration/test_auth_routes.py
git commit -m "feat: extend signup trigger for invite code generation and usage"
```

### Task 5: 覆盖邀请码边界场景（TDD）

**Files:**
- Modify: `backend/tests/integration/test_auth_routes.py`
- Optional Modify: `backend/tests/e2e/test_auth_flow.py`

**Step 1: Write the failing test**

新增场景测试：
- 邀请码不存在
- 邀请码 disabled
- 邀请码 expires_at 已过期
- 邀请码达到 `max_uses`

断言：注册仍成功（202/200 链路正常），仅邀请关系不建立。

**Step 2: Run test to verify it fails**

Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k "invite and (expired or disabled or max_uses or invalid)" -v`
Expected: FAIL

**Step 3: Write minimal implementation**

修正 trigger 判断顺序和条件，确保“邀请无效不影响注册”原则。

**Step 4: Run test to verify it passes**

Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k invite -v`
Expected: PASS

**Step 5: Commit**

```bash
git add backend/tests/integration/test_auth_routes.py backend/alembic/versions/20260227_0006_invite_codes_and_profile_referral.py
git commit -m "test: cover invite code edge cases in signup flow"
```

### Task 6: 文档同步与运行手册更新

**Files:**
- Modify: `docs/runtime/runtime-route.md`
- Modify: `docs/runtime/runtime-runbook.md`

**Step 1: Write the failing test**

无自动化测试；改为文档一致性检查清单（手工）：
- 注册接口 request 字段包含 `invite_code`
- 说明邀请码消费时机与“无效码不阻断注册”

**Step 2: Run check to verify missing docs**

Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k signup_start -v`
Expected: PASS（作为行为基线），文档尚未同步

**Step 3: Write minimal implementation**

- 更新 `POST /auth/verifications` 请求字段
- 新增邀请码行为说明
- 在 runbook 变更日志添加本次改动记录

**Step 4: Run check after docs update**

Run: `cd backend && uv run pytest tests/integration/test_auth_routes.py -k signup_start -v`
Expected: PASS（行为与文档一致）

**Step 5: Commit**

```bash
git add docs/runtime/runtime-route.md docs/runtime/runtime-runbook.md
git commit -m "docs: document invite code behavior in signup flow"
```

### Task 7: 全量验证与风险审查（L2）

**Files:**
- Verify only

**Step 1: Run lint/type checks**

Run:
- `cd backend && uv run ruff check src tests`
- `cd backend && uv run basedpyright src`

Expected: 全部通过

**Step 2: Run test suites**

Run:
- `cd backend && uv run pytest tests/unit -v`
- `cd backend && uv run pytest tests/integration -v`
- `cd backend && uv run pytest tests/e2e/test_auth_flow.py -v`

Expected: 通过

**Step 3: Run mandatory review gates for L2**

- `refactor-cleaner` agent：确认无死代码/重复代码
- `code-reviewer` agent：检查 DB trigger、安全边界、可维护性

Expected: CRITICAL/HIGH 为 0

**Step 4: Security-specific sanity checks**

检查项：
- 未硬编码密钥
- SQL 逻辑无注入风险（trigger 中仅参数/列操作）
- 邀请码校验失败不泄露内部细节

**Step 5: Commit verification evidence (if needed in docs/PR notes)**

```bash
git add <updated verification notes if any>
git commit -m "chore: record invite code verification results"
```

---

## 交付验收标准

1. 新用户注册后必有 1 条专属邀请码。
2. 注册时传入有效邀请码会建立 `profiles.referred_by` 并增加 `used_count`。
3. 无效邀请码不会阻断注册成功。
4. 支持运营码（`owner_id IS NULL`）与后续奖励扩展（`reward_config`）。
5. 文档已同步，测试与检查通过。

## 备注

- 本需求触发 L2（数据库迁移 + trigger + 多文件大改），必须走双审查 gate。
- 不在本期实现运营后台批量发码 API；仅完成数据层与注册链路支撑。
