@@ -0,0 +1,72 @@
## Context
见 `proposal.md` 。`scripts/context-health.sh` 以集合相等检查主 Specs、`requirement-evidence.json` 和 `entry-capability-requirement-matrix.json` ;调查确认主 Specs 为 89 条、现有证据为 53 条,缺失 36 条,且没有过期 evidence。矩阵也缺少同一 36 条 Requirement 链接。
## Goals / Non-Goals
**Goals: **
- 仅以当前可达源码、迁移/脚本、配置和已有归档验证记录补齐 36 条的静态可追溯证据。
- 令两个 JSON 文件分别满足脚本的 Requirement 集合双向覆盖;保持 HTTP 路由集和 Asynq 注册入口集不变。
- 每条证据在 `entries` 、`handler_consumer_job` 、`application_service_query` 、`domain_state_amount` 、`store_migration_config` 和 `verification` 中只引用实际存在的事实。
**Non-Goals: **
- 不修改 `openspec/specs/**` 、业务代码、迁移、测试、运行配置、路由或 Worker 注册。
- 不以历史 Change 的完成状态、推测的调用链或无法重现的生产结果替代当前可达证据。
- 不添加、删除或重分类既有 HTTP/async 矩阵入口。
## Decisions
### 1. 固定缺失集合,按 capability 分组补行
以下是唯一可新增的 evidence Requirement 键;实现阶段先由同一解析规则重算,若集合变化则停止并重新调查:
| capability | 缺失 Requirement |
| --- | --- |
| `agent-funds-commission` | 代理在线充值可用支付方式按支付配置判定 |
| `agent-open-api` | 代理开放接口查询不触发可靠卡观测 |
| `external-integration` | 富友主扫统一下单与订单查询;高频轮询外部交互日志保留边界;外部交互日志归档与留存受控执行;外部交互日志逐日物理留存 |
| `operations-audit` | 轮询审计事实保留边界;审计归档与日留存受控执行;审计在线数据逐日物理留存;日留存独立运行日志;审计调查留存边界连续 |
| `package-lifecycle` | 资产套餐层级投影;套餐历史整组筛选与分页;套餐历史稳定排序;物理缺失与不可展示关系区分;套餐历史读取范围与兼容边界 |
| `package-queue-activation` | 当前主套餐过期后自动激活下一个; 孤儿待生效套餐必须公平恢复; Asynq 激活结果必须准确且可重试 |
| `polling-load-control` | 轮询全局并发与背压;正常无变化观测的低写入处理;业务变化与轮询异常仍可追溯 |
| `polling-operations` | 停复机遵循实际生效实名策略 |
| `qicheng-migration-package-lifecycle-overrides` | 迁移配置支持逐卡套餐生命周期覆盖;覆盖必须完整且可验证;未覆盖资产维持严格冲突阻断 |
| `qicheng-migration-package-usage` | 迁移套餐使用记录保存计时条款快照;迁移套餐使用 SQL 满足快照完整性约束 |
| `refund-approval` | 零金额退款审批通过;零金额退款不产生资金回款;非法退款金额仍被拒绝 |
| `shop-bulk-import` | 导入输入必须在生成 SQL 前完成全量预检;生成的 SQL 必须在写入前校验目标库状态;批量导入 SQL 必须创建完整的店铺初始事实;导入执行必须由维护者审核后以单事务手工确认;导入结果必须可审核和重跑定位 |
### 2. 各类证据的直接来源
| Requirement 分组 | 入口/任务与调用链事实 | 状态、存储、迁移或验证事实 |
| --- | --- | --- |
| 代理充值与富友(前两组中相关 2 条) | `internal/handler/admin` 与 `internal/routes` 的代理充值入口;`internal/application/agentrecharge/online_creation.go` ; `internal/infrastructure/payment/fuiou_scan.go` ; `pkg/fuiou/scan.go` | `model.AgentRechargeRecord.PaymentMethod/PaymentChannel` 、支付配置完整性、`integrationlog.Repository` ;验证输出只取当前源码可命中的 `PreCreate` 、`CommonQuery` 、`trans_stat` 映射或既有归档 Change 的可复核命令。 |
| 开放接口本地查询( 1 条) | 四个 GET 入口:`/api/open/v1/cards/traffic` 、`/cards/status` 、`/cards/realname-status` 、`/devices/traffic` ; `internal/handler/openapi/handler.go` → `internal/service/agent_open_api/service.go` | 对四个读取方法做静态可达性检查:不得引用观测分发/系列调度;只列实际查询的 Store/模型。 |
| 轮询低写入、轮询审计与外部交互( 3 条) | 五个已注册 polling Task; `internal/task/polling_base.go` 和各 `polling_*_handler.go` ; `internal/infrastructure/cardobservation/series_runner.go` | `internal/application/cardobservation/{apply,network,traffic}.go` 的变化审计/Outbox 事实,及 `internal/task/polling_integration_log.go` 的失败终结事实;正常无变化仅在当前代码存在明确跳过分支时记录,不以 Requirement 文本反推。 |
| 审计/Integration 归档、清理和调查( 7 条) | 已注册 `constants.TaskTypeAuditDailyArchive` 、`TaskTypeIntegrationDailyArchive` 、`TaskTypeAuditDailyRetention` ; `cmd/worker/main.go` 的注册/总开关;`internal/task/{integration_archive,audit_monthly_retention}.go` | `internal/application/auditarchive/{service,integration,retention}.go` ; `internal/infrastructure/audit/retention.go` ; `pkg/constants/audit_archive.go` ; `cmd/worker/main.go` 的 `NewRetentionLogger` ; `internal/query/{audit,retention}` 的在线边界。 |
| 套餐历史层级( 5 条) | `GET /api/admin/assets/{identifier}/packages` 与 `GET /api/c/v1/asset/package-history` ;后台 Service、H5 Handler 均调用 `internal/query/asset/package_history.go` | `internal/model/package.go` 的 `master_usage_id` ,历史 DTO 的 children/关系状态字段; Query 的整组筛选、排序、物理存在性核对和分页函数。 |
| 套餐队列激活( 3 条) | `internal/polling/package_activation_handler.go` 的定时检查、孤儿恢复、任务投递和 `HandlePackageQueueActivation` ;已注册 `constants.TaskTypePackageQueueActivation` | `tb_package_usage` 的状态条件更新、`master_usage_id` 、优先级排序、Asynq `MaxRetry(3)` 与 Redis 锁冲突返回错误。 |
| 轮询总并发与背压( 1 条) | 五个 polling Handler 均调用 `PollingBase.acquireConcurrency` ;调度器 `processOneShard` 背压跳过 | `internal/task/polling_base.go` 的双计数 Lua、释放/重入队;`pkg/config/config.go` 的范围校验;`pkg/constants/{redis,polling}.go` 。 |
| 停复机实名策略( 1 条) | 自动、设备、手动停复机路径经 `internal/service/iot_card/stop_resume_service.go` ;策略管理路由保持现有入口 | `isRealnameOK` 的行业卡、已实名、独立卡和设备策略分支;`model.IotCard/Device.RealnamePolicy` 与现有套餐/流量判断。 |
| 奇成套餐迁移( 5 条) | 离线迁移入口和配置加载:`scripts/migration/migrate_runtime.py` 、`lib/mapping_loader.py` 、`lib/sql_builder.py` | `package_lifecycle_overrides` 、`tbl_card_life` 、`multiple_active_packages` ; Step2 INSERT 的 `expiry_base_snapshot` 、`calendar_type_snapshot` 、`duration_months_snapshot` 、`duration_days_snapshot` 。 |
| 零金额退款( 3 条) | `POST /api/admin/refunds/{id}/approve` 与企业微信审批共用的退款决策路径;`internal/service/refund/{approval_decision,service}.go` | `validateApprovedRefundAmount` 拒绝负数;事务更新退款/订单;`refundWalletPayment` 在 `amount == 0` 直接返回,不写钱包回款。 |
| 店铺批量导入( 5 条) | 离线 CLI `scripts/migration/import_shops.py` → `lib/shop_import.py` → `lib/shop_sql.py` | CSV/配置预检、SQL 事务起始守卫、店铺/账号/角色/双钱包/审计 SQL、bcrypt 哈希、同批次错误/结果/摘要产物;运行说明明确不连接目标 PostgreSQL。 |
每个 `verification` 对象保留一条可直接执行的本地只读命令及其确定性源码文字命中;没有稳定文字命中的调用链,使用可重跑的只读脚本/命令输出。不得杜撰隔离库、生产库、外部渠道或已删除临时 smoke 的结果。
### 3. 两个 JSON 的最小同步策略
1. `requirement-evidence.json` :仅追加上述 36 个唯一键各一行;不改动现有 53 行,不新增过期键。
2. `entry-capability-requirement-matrix.json` :仅把上述 36 个键链接到现有、已注册的 HTTP 或 async 行。HTTP 条目保持与 Specs 提取路由的集合相同; async 条目保持与 Worker 注册常量集合相同。
3. 对无 HTTP/async 入口的两类离线流程追加 `cli` 行,入口分别为 `scripts/migration/migrate_runtime.py` 与 `scripts/migration/import_shops.py` ;现有健康脚本只比较 `http` 与 `async` 的入口集合,`cli` 行仅承担这五条离线 Requirement 的可追溯链接,不改变该比较。不得把离线流程伪挂到 HTTP 或 Asynq 入口。
4. 写入前用集合差验证恰为 36; 写入后验证 evidence 键集和 matrix 链接集均等于主 Specs 键集,并确认 HTTP/async 入口集合未漂移。
### 4. 证据选择优先级
当前可达实现与可复跑只读输出优先;`migrations/` 、脚本和显式配置是存储/离线事实;已归档 Change 只用于发现来源和复核历史验证边界,不能单独作为当前实现证据。无证明项保留为缺口并停止,不以相近功能、相同命名或 Spec 场景推断。
## Risks / Trade-offs
- `polling-load-control` 和低写入类 Requirement 跨五个 Worker Handler; 只引用共享基类无法证明每个入口均接入, 必须在最终写入前逐 Handler 核对获取、释放和令牌不足重入队。
- 离线迁移与店铺导入没有 HTTP/async 注册入口。将其错误塞入现有入口会使矩阵语义失真;是否允许 `cli` 类型是唯一可能需要确认的矩阵契约缺口。
- 历史 Change 中有未完成验证任务。它们不能写成已验证事实;证据只能引用仍在仓库中的源码、脚本、迁移、配置和可重跑只读检查。
- JSON 人工编辑容易造成重复键或遗漏链接;实现阶段必须在写入前后解析并做三集合精确比较,再运行 `./scripts/context-health.sh` 。