# AGENTS.md

## Non-negotiables


1. 写代码不要猜。先读当前实现、接口、配置和测试，再改。
2. 不写流水账。文档、注释和总结都要解释边界、决策和可执行动作。
3. 前端或客户端改动不要添加无意义描述文案。界面文字必须服务真实操作或状态。
4. 不把房间核心状态扩散到多个服务。Room Cell 仍然是房间状态 owner。
5. 不把连接态塞进 `room-service`。客户端长连接和消息投递属于腾讯云 IM。
6. 代码永远保持高密度注释
7. 写代码的时候碰到为提交修改不用关心，只去做自己的
8. 注意关心代码历史兼容问题

## Architecture Boundaries

- `gateway-service` 只做 HTTP 入口、鉴权、协议转换、腾讯云 IM UserSig 签发和内部 gRPC 编排。
- `room-service` 负责 Room Cell、房间状态、麦位、房间 presence、本地榜、snapshot、command log、outbox、Redis lease，以及腾讯云 IM 群组/房间系统消息桥接。
- 腾讯云 IM 负责客户端长连接、房间群消息、公屏、单聊、离线/漫游和全球接入；
- `wallet-service` 负责账务语义。`SendGift` 必须先扣费成功，再落房间表现。
- `activity-service` 通过 room outbox 事件消费，不侵入 Room Cell 主链路。
- `activity-service/internal/service/message` 是 App 系统/活动消息 inbox 的唯一业务 owner；活动发通知必须走 `CreateSystemNotice`、`CreateActivityNotice` 或 `CreateNoticeFanout`，后台和其他服务不能直接写 `user_inbox_messages` / `message_fanout_jobs`。
- `notice-service` 负责从各 owner service 的 outbox/事件源读取事实并投递私有通知、腾讯云 IM 单聊、后续 push/inbox；钱包、活动和房间不能直接耦合私有通知投递实现。
- `user-service` 负责用户主数据，不应该成为房间高频流程的同步中心。
- `cron-service` 只负责后台调度、任务运行记录和通过内部 gRPC 触发 owner service 批处理；不能直接拥有房间、账务、用户或活动业务状态。
- `statistics-service` 只负责消费 owner service 已发布到 RocketMQ 的业务事实并写本服务聚合表；它不是业务事实 owner，不能直连 room/wallet/user/game 等 owner 数据库补数或查询明细。
- `server/admin` 是后台管理服务独立 module，负责管理员、RBAC、菜单、审计和后台 HTTP API；访问 app 后端只能通过 `hyapp.local/api` 生成的 protobuf client 或明确的集成接口。
- `api` 是独立 Go module，module path 为 `hyapp.local/api`，只放 protobuf contract 和生成代码，不放服务实现或业务工具包。
- `wallet_outbox`、`room_outbox`、`user_outbox` 和 `game_outbox` 只能由各自 owner service 直接读取并发布到 RocketMQ；其他服务不能配置 owner 数据库 DSN 或直连 owner 数据库扫描 outbox。
- 下游服务消费 owner outbox 必须使用自己的 RocketMQ consumer group 和本服务幂等表；消费位点属于消息系统和本地幂等事实，不能共享 owner service 的 outbox 状态字段。
- 数据大屏查询入口在 `server/admin`，由 admin 调 `statistics-service` 内部查询 API；`gateway-service` 是 App HTTP 入口，除非有明确 App 产品需求，否则不要把后台统计大屏暴露到 gateway。

## Statistics Rules

- 硬性规则：统计查询不能查全表，不能回查 owner service 明细表，不能在 HTTP handler 临时扫业务流水；所有统计展示一律查询 `statistics-service` 的聚合表。
- `statistics-service` 的事实输入只来自 RocketMQ：`WalletRechargeRecorded`、`GameOrderSettled`、`RoomGiftSent`、`RoomUserJoined`、`UserRegistered` 等事件必须先由 owner service outbox 持久化，再由 owner service 发布。
- 充值口径以 `WalletRechargeRecorded` 为准；渠道分桶只能使用事件里的 `recharge_type`，当前缺渠道时归入币商充值，不能从支付表或用户资料反推。
- 游戏口径以 `GameOrderSettled` 为准；`debit/bet` 计投注流水，`credit/payout` 计返奖，`refund/reverse` 计退款，利润按 `投注 - 返奖 - 退款` 计算。
- 活跃口径以事实事件去重用户：进房来自 `RoomUserJoined`，游戏活跃来自 `GameOrderSettled`，统计服务用本地唯一键收敛日活，不能依赖 Redis presence。
- 留存口径以 `UserRegistered` cohort 和 `stat_user_day_activity` 活跃表计算；注册、活跃、充值、游戏和礼物事件都按 UTC `stat_day` 聚合。
- ARPU 直接由充值聚合除以活跃用户；ARPPU/UP 值直接由充值聚合除以付费用户。产品若要调整 UP 值定义，必须先改明确字段名和边界测试。
- 国家维度只能来自事件字段，例如用户注册 region、房间 `visible_region_id`、充值目标区域或游戏事件区域；不能用服务所在地、IP 或本地时区推断。

## Persistence Rules

- `room-service` 生产路径使用 MySQL repository 和 Redis directory。
- `memory` repository/directory 只用于单元测试或明确的本地隔离测试。
- MySQL 是房间恢复的持久来源：`rooms`、`room_snapshots`、`room_command_log`、`room_outbox`。
- `wallet-service` 的 `wallet_outbox` 是账务事实的持久来源；它负责补偿发布到 RocketMQ，下游不得把钱包库当作事件查询库。
- `statistics-service` 的 MySQL 聚合表是数据大屏唯一查询来源：`statistics_event_consumption` 记录幂等事实，`stat_app_day_country`、`stat_game_day_country`、`stat_user_day_activity`、`stat_user_registration`、`stat_recharge_day_payers` 和 `stat_lucky_gift_day_payers` 保存统计读模型。
- Redis 只保存当前路由/lease 等热状态，不能作为唯一恢复来源。
- Redis lease 更新必须保持原子性，不能改回非原子的 GET + SET 接管逻辑。

## Time Rules

- 全局业务时间统一使用 UTC，不使用服务器所在地、容器本地时区、客户端时区或 IP 推断时区作为结算、统计、刷新和幂等周期的切日来源。
- 跨服务契约、HTTP 响应、数据库业务事实和 outbox payload 中的时间统一使用 Unix epoch milliseconds，字段名保持 `*_ms`。
- 统计、账务和列表时间范围统一使用 `[start_ms, end_ms)`：包含开始毫秒，不包含结束毫秒，避免相邻区间重复计算边界记录。
- 每日任务、自然日刷新和 `task_day` 统一按 UTC 日期计算；不要重新引入 `task_timezone`、`time.Local` 或 `loc=Local`。
- MySQL DSN 必须使用 `loc=UTC`；Docker/Compose 服务必须保持 `TZ=UTC`，MySQL 必须保持 `--default-time-zone=+00:00`。
- 如果产品需要用户本地时间展示，只能在展示层或资料/风控元数据中处理；要把它用于业务切日，必须先形成明确产品规则、契约字段和边界测试。

## Protocol Rules

- 外部 HTTP JSON 入口在 `gateway-service`。
- `gateway-service` 的 `/api/v1` 业务响应必须使用 `{code,message,request_id,data}` envelope；healthcheck 不套业务响应格式。
- `request_id` 是后端生成的链路追踪字段，必须透传到内部 gRPC `RequestMeta.request_id`；不要把它当幂等键使用。
- 外部实时消息入口在腾讯云 IM SDK；本仓库只提供 `/api/v1/im/usersig` 签发登录票据。
- 内部服务通信统一 gRPC + protobuf，Go 代码通过 `hyapp.local/api/proto/...` 引用生成包。
- 当前处于开发阶段，不保留历史协议；Proto/API/DB 可以按当前事实直接调整。
- 修改 `api/proto` 后必须运行 `make proto`，并提交生成后的 `.pb.go` / `_grpc.pb.go`。
- 不要在服务之间直接 import 对方 `internal` 包。跨服务只走 protobuf client 或明确的接口抽象。
- `server/admin` 不能 import `hyapp/services/*` 或 `hyapp/pkg/*`；后台专属基础设施放在 `server/admin/internal/platform`。
- 系统/活动消息协议只使用后端 inbox 的 `message_type=system|activity`、`title`、`summary`、`body`、`action_type`、`action_param`、`metadata_json` 等字段；后台全服通知必须创建 fanout job，不能在 admin HTTP handler 中同步遍历用户发消息。

## Port Rules

本项目本地端口使用 `13xxx`：

- `13000`: gateway HTTP
- `13001`: room gRPC
- `13004`: wallet gRPC
- `13005`: user gRPC
- `13006`: activity gRPC
- `13007`: cron gRPC
- `13008`: game gRPC
- `13009`: notice gRPC
- `13010`: statistics query HTTP
- `23306`: local Docker MySQL host port
- `13379`: local Docker Redis host port

不要重新引入 `808x` 作为默认端口。

## Coding Rules

- 默认使用 Go 标准库和仓库已有小包；不要为了轻量逻辑引入重框架。
- 手写代码保持高密度有效注释，所有业务逻辑、状态流转、权限判断、支付链路、异步流程都要解释工程语义和失败分支，不复述语法。
- 新增配置必须同时考虑本地 `config.yaml`、Docker `config.docker.yaml`、以及线上配置示例。
- 所有逻辑功能配置默认开启；密钥、地址、三方账号等必须由运行环境提供的敏感配置只能使用占位符或环境配置，不能写入真实值，也不能伪造可用凭证。
- 新增服务依赖必须在 `docker-compose.yml` 中体现本地可运行形态。
- 数据库表结构变更必须同步更新对应 owner service 的 `deploy/mysql/initdb`。
- 不要把线上密钥、云数据库密码、Redis 密码写进真实配置。只能写占位示例。
- 保持 `gofmt`，不要手调 Go import 顺序。

## Test Expectations

改动后至少运行：

```bash
make test
```

涉及 protobuf 时运行：

```bash
make proto
go test ./...
```

涉及 Docker、端口、服务配置或基础设施时运行：

```bash
docker compose config
docker compose build
```

涉及启动链路时实际拉起：

```bash
docker compose up -d
docker compose ps
docker compose down
```

## Safe Change Checklist

- 先确认改的是服务内部逻辑、跨服务契约、配置、还是部署编排。
- 跨服务字段优先改 protobuf，不要在 HTTP handler 或腾讯云 IM 自定义消息 payload 里偷偷扩字段。
- 房间状态变更必须经过 Room Cell 命令链路。
- 需要恢复的关键动作必须写 command log 或 snapshot，不能只改内存。
- 房间外副作用优先走 outbox，除非是明确的同步主链路依赖。
- 新增统计、账务、任务刷新或时间范围逻辑时，必须补 UTC 边界测试，至少覆盖开始边界、结束前一毫秒和结束边界。
- 文档只写当前事实和明确约束，不记录“做了什么”的流水账。
