---
phase: 01-p0
plan: 02
type: execute
wave: 2
depends_on:
  - 01-01
files_modified:
  - internal/task/polling_handler.go
  - pkg/queue/handler.go
autonomous: true
requirements:
  - CRITICAL-03
must_haves:
  truths:
    - "PollingHandler struct 有 asynqClient *asynq.Client 字段"
    - "NewPollingHandler 构造函数接受 asynqClient 参数"
    - "triggerFirstRealnameActivation 使用 asynqClient.EnqueueContext 入队，不再 RPush"
    - "_ = task 这行被删除"
    - "registerPollingHandlers 传入 h.asynqClient 给 NewPollingHandler"
  artifacts:
    - path: internal/task/polling_handler.go
      provides: "PollingHandler 注入 asynq.Client，triggerFirstRealnameActivation 正确入队"
      contains: "asynqClient.EnqueueContext"
    - path: pkg/queue/handler.go
      provides: "registerPollingHandlers 传入 asynqClient"
      contains: "h.asynqClient"
  key_links:
    - from: pkg/queue/handler.go
      to: internal/task/polling_handler.go
      via: "NewPollingHandler(h.db, h.redis, h.asynqClient, ...)"
      pattern: "NewPollingHandler"
    - from: internal/task/polling_handler.go
      to: asynq.Client
      via: "h.asynqClient.EnqueueContext(ctx, task)"
      pattern: "asynqClient\\.EnqueueContext"
---

<objective>
修复实名激活任务断链（CRITICAL-03）：为 PollingHandler 注入 asynq.Client，删除 RPush 降级方案，改用正式 Asynq 入队。

Purpose: 首次实名后自动激活"囤货套餐"的完整链路当前完全断裂——task 被 `_ = task` 丢弃，实际走 Redis RPush 降级，但无消费者消费这个列表。修复后套餐激活任务正常进入 Asynq 队列被消费。

Output:
- polling_handler.go: 新增 asynqClient 字段，triggerFirstRealnameActivation 改为 EnqueueContext
- pkg/queue/handler.go: registerPollingHandlers 传入 h.asynqClient
</objective>

<execution_context>
@$HOME/.config/opencode/get-shit-done/workflows/execute-plan.md
@$HOME/.config/opencode/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/phases/01-p0/01-01-SUMMARY.md
@.planning/phases/01-p0/01-CONTEXT.md

# 修复规格书（方案 A-3，第 165-299 行）
@.sisyphus/plans/修正业务-完整方案.md

<interfaces>
<!-- pkg/queue/handler.go Handler struct 已有 asynqClient 字段 -->
```go
// pkg/queue/handler.go Handler struct（已存在）
type Handler struct {
    db          *gorm.DB
    redis       *redis.Client
    asynqClient *asynq.Client   // ← 已有此字段，直接传给 NewPollingHandler
    // ...
}

// registerPollingHandlers 当前调用（需修改）
pollingHandler := task.NewPollingHandler(
    h.db,
    h.redis,
    // h.asynqClient ← 缺少此行
    h.gatewayClient,
    h.workerResult.Services.UsageService,
    h.logger,
)
```

<!-- polling_handler.go 当前错误代码（triggerFirstRealnameActivation 第 1100 行） -->
```go
// 当前降级代码（需删除 RPush 部分，直接用 EnqueueContext）
task := asynq.NewTask(constants.TaskTypePackageFirstActivation, payloadBytes, ...)

activationKey := constants.RedisPollingManualQueueKey(constants.TaskTypePackageFirstActivation)
h.redis.RPush(ctx, activationKey, string(payloadBytes))  // ← 删掉

_ = task  // ← 删掉
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: PollingHandler 注入 asynqClient（CRITICAL-03）</name>
  <files>internal/task/polling_handler.go, pkg/queue/handler.go</files>
  <action>
**步骤 1：修改 `internal/task/polling_handler.go` struct 定义**

在 PollingHandler struct（第 31-42 行附近）新增字段：
```go
type PollingHandler struct {
    db                    *gorm.DB
    redis                 *redis.Client
    asynqClient           *asynq.Client    // 新增
    gatewayClient         *gateway.Client
    // ... 其余字段不变
}
```

**步骤 2：修改 NewPollingHandler 构造函数签名（第 44 行附近）**

新增 `asynqClient *asynq.Client` 参数（位置：redis 之后，gatewayClient 之前）：
```go
func NewPollingHandler(
    db *gorm.DB,
    redis *redis.Client,
    asynqClient *asynq.Client,   // 新增
    gatewayClient *gateway.Client,
    usageService *packagepkg.UsageService,
    logger *zap.Logger,
) *PollingHandler {
    return &PollingHandler{
        db:            db,
        redis:         redis,
        asynqClient:   asynqClient, // 新增
        gatewayClient: gatewayClient,
        // ... 其余字段不变
    }
}
```

**步骤 3：修改 triggerFirstRealnameActivation（第 1100 行附近）**

删除 RPush 降级代码，改为 EnqueueContext：
```go
// 删除这两行：
// activationKey := constants.RedisPollingManualQueueKey(constants.TaskTypePackageFirstActivation)
// h.redis.RPush(ctx, activationKey, string(payloadBytes))
// _ = task

// 替换为：
if _, err := h.asynqClient.EnqueueContext(ctx, task); err != nil {
    h.logger.Warn("提交首次实名激活任务失败",
        zap.Uint("package_usage_id", pkg.ID),
        zap.Error(err))
    continue
}
```

注意：`task` 变量已在上方声明，不需要重新创建。

**步骤 4：修改 `pkg/queue/handler.go:registerPollingHandlers`（第 151 行附近）**

在 NewPollingHandler 调用中补充 `h.asynqClient` 参数：
```go
pollingHandler := task.NewPollingHandler(
    h.db,
    h.redis,
    h.asynqClient, // 新增，Handler 结构体已有此字段
    h.gatewayClient,
    h.workerResult.Services.UsageService,
    h.logger,
)
```

修改完成后执行 `go build ./...`，确认编译通过，提交独立 commit（per D-05）。
  </action>
  <verify>
    <automated>go build ./... && rg "_ = task" internal/task/polling_handler.go && echo "FAIL: _ = task 仍存在" || echo "PASS: _ = task 已删除"</automated>
  </verify>
  <done>
    - PollingHandler struct 含 asynqClient 字段
    - NewPollingHandler 接受 asynqClient 参数
    - triggerFirstRealnameActivation 使用 asynqClient.EnqueueContext，不再 RPush
    - _ = task 这行已被删除
    - pkg/queue/handler.go 传入 h.asynqClient
    - go build ./... 编译通过
  </done>
</task>

</tasks>

<verification>
整体验收：

```bash
# 1. 编译验证
go build ./...

# 2. 确认降级代码已删除
rg "_ = task" internal/task/polling_handler.go
rg "RPush.*TaskTypePackageFirstActivation\|RedisPollingManualQueueKey" internal/task/polling_handler.go

# 3. 确认正确入队代码存在
rg "asynqClient.EnqueueContext" internal/task/polling_handler.go

# 4. 确认 registerPollingHandlers 传了 asynqClient
rg "NewPollingHandler" pkg/queue/handler.go
```

人工验收（参见修正业务-完整方案.md A-3 验收清单）：
- Worker 日志：首次实名触发时应出现实名激活任务入队日志
- DBHub：`SELECT id, status, pending_realname_activation, activated_at FROM tb_package_usage WHERE pending_realname_activation=true ORDER BY id DESC LIMIT 20;`
</verification>

<success_criteria>
1. `go build ./...` 编译通过
2. PollingHandler 有 asynqClient 字段且被正确初始化
3. triggerFirstRealnameActivation 使用 EnqueueContext，不再 RPush
4. `_ = task` 行不存在
5. 独立 commit 已提交
</success_criteria>

<output>
完成后创建 `.planning/phases/01-p0/01-02-SUMMARY.md`，记录：
- 修改文件及改动说明
- 关键代码对比（修改前后）
- 编译验证结果
- CRITICAL-03 完成状态
</output>
