Files
junhong_cmp_fiber/docs/verification/context-reset/requirement-evidence.json
break 5ed6b39deb
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
feat(收口): 补齐 8 月迭代缺口并同步 Spec 与证据链
- 新增六对成对迁移 000232–000237:H5 弹窗类型、退款结算标识与申请人备注、优先轮询事实字段与两个新终态、通道阈值命中留痕、手机号最近解绑人、提现资格校验留痕
- 退款:原因必填与申请人备注、来源支付与渠道流水冻结、线下处理流水号补录审计、按订单查询可选退款方式、企微审批材料补齐且新增字段缺失映射即明确失败
- 优先轮询:人工关闭、有效期到期独立周期任务、失败与过期人工重触发、事实字段与异常重试查询、资产解析端点只读投影
- 通道阈值:命中事实同事务留痕与命中记录查询;员工账单:列表筛选与详情投影;商户池:列表投影与统计周期语义;H5:弹窗类型与类别排序
- 手机号:有效关联数量与最近解绑人、短信验证码失败次数限制;导出:佣金明细十五列与报表序号列
- 时间筛选:三处新增筛选纳入统一严格解析契约,员工账单产生时间参数改名
- 同步 12 份主 Spec 需求、两端点与异步任务证据链,门禁 context-health 与 OpenSpec 校验通过
2026-09-18 15:34:29 +08:00

5657 lines
323 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[
{
"capability": "agent-funds-commission",
"requirement": "代理钱包与提现状态门禁",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"/api/admin/shops/{shop_id}/withdrawal-requests"
],
"handler_consumer_job": [
"internal/routes/commission.go"
],
"application_service_query": [
"internal/application/wallet/debit.go"
],
"domain_state_amount": [
"internal/domain/wallet/wallet.go",
"pkg/constants/iot.go"
],
"store_migration_config": [
"internal/store/postgres/agent_wallet_store.go",
"internal/store/postgres/commission_withdrawal_request_store.go"
],
"verification": {
"command": "代码中检索 当前钱包状态不允许资金操作",
"literal_output": [
"internal/domain/wallet/wallet.go:122:return appErrors.New(appErrors.CodeInvalidStatus, \"当前钱包状态不允许资金操作\")"
],
"exit_status": 0
}
},
{
"capability": "agent-funds-commission",
"requirement": "佣金异常状态可见",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"/api/admin/commission-records/{id}/resolve"
],
"handler_consumer_job": [
"internal/routes/commission.go"
],
"application_service_query": [
"internal/service/commission_calculation"
],
"domain_state_amount": [
"pkg/constants/iot.go"
],
"store_migration_config": [
"internal/store/postgres/commission_record_store.go"
],
"verification": {
"command": "代码中检索 CommissionStatusPendingReview",
"literal_output": [
"internal/service/commission_calculation/service.go:90:if order.CommissionStatus == model.CommissionStatusCompleted || order.CommissionStatus == model.CommissionStatusPendingReview {"
],
"exit_status": 0
}
},
{
"capability": "agent-funds-commission",
"requirement": "代理在线充值本地支付状态",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"/api/admin/agent-recharges/{id}/payment-status"
],
"handler_consumer_job": [
"internal/handler/admin/agent_recharge.go"
],
"application_service_query": [
"internal/query/agentrecharge"
],
"domain_state_amount": [
"pkg/constants/payment.go"
],
"store_migration_config": [
"internal/store/postgres/payment_store.go"
],
"verification": {
"command": "代码中检索 PaymentRecordStatusPending",
"literal_output": [
"pkg/constants/payment.go:4:// PaymentRecordStatusPending 表示支付单待支付。"
],
"exit_status": 0
}
},
{
"capability": "agent-funds-commission",
"requirement": "充值边界",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"/api/admin/agent-recharges",
"/api/c/v1/wallet/recharge"
],
"handler_consumer_job": [
"internal/handler/admin/agent_recharge.go",
"internal/handler/app/client_wallet.go"
],
"application_service_query": [
"internal/application/agentrecharge",
"internal/service/recharge"
],
"domain_state_amount": [
"pkg/constants/wallet.go"
],
"store_migration_config": [
"internal/store/postgres/agent_recharge_store.go",
"internal/store/postgres/asset_recharge_store.go"
],
"verification": {
"command": "代码中检索 AgentRechargeMinAmount = 1, AgentOnlineRechargeMinAmount = 10000, AgentRechargeMaxAmount = 100000000, AssetRechargeMinAmount = 100, AssetRechargeMaxAmount = 10000000",
"literal_output": [
"pkg/constants/wallet.go:83:AgentRechargeMinAmount = 1 // 线下最小充值金额1分",
"pkg/constants/wallet.go:84:AgentOnlineRechargeMinAmount = 10000 // 在线扫码最小充值金额100元",
"pkg/constants/wallet.go:85:AgentRechargeMaxAmount = 100000000 // 最大充值金额1000000元",
"pkg/constants/wallet.go:118:AssetRechargeMinAmount = 100 // 最小充值金额1元",
"pkg/constants/wallet.go:119:AssetRechargeMaxAmount = 10000000 // 最大充值金额100000元"
],
"exit_status": 0
}
},
{
"capability": "agent-open-api",
"requirement": "开放接口认证",
"spec": "openspec/specs/agent-open-api/spec.md",
"entries": [
"/api/open/v1/"
],
"handler_consumer_job": [
"internal/routes/open.go",
"internal/handler/openapi"
],
"application_service_query": [
"internal/service/agent_open_api"
],
"domain_state_amount": [
"pkg/middleware"
],
"store_migration_config": [
"internal/store/postgres/account_store.go"
],
"verification": {
"command": "代码中检索 X-Agent-Sign",
"literal_output": [
"internal/routes/open.go:15:1. 每个请求必须携带 HeaderX-Agent-Account、X-Agent-Password、X-Agent-Timestamp、X-Agent-Nonce、X-Agent-Sign。"
],
"exit_status": 0
}
},
{
"capability": "agent-open-api",
"requirement": "开放接口数据范围",
"spec": "openspec/specs/agent-open-api/spec.md",
"entries": [
"/api/open/v1/"
],
"handler_consumer_job": [
"internal/handler/openapi"
],
"application_service_query": [
"internal/service/agent_open_api"
],
"domain_state_amount": [
"pkg/middleware"
],
"store_migration_config": [
"internal/store/postgres/shop_store.go"
],
"verification": {
"command": "代码中检索 shop",
"literal_output": [
"internal/service/agent_open_api/service.go:20:shopCommissionSvc \"github.com/break/junhong_cmp_fiber/internal/service/shop_commission\""
],
"exit_status": 0
}
},
{
"capability": "asset-device",
"requirement": "资产业务状态",
"spec": "openspec/specs/asset-device/spec.md",
"entries": [
"/api/admin/assets",
"/api/admin/iot-cards"
],
"handler_consumer_job": [
"internal/handler/admin/asset.go"
],
"application_service_query": [
"internal/service/asset",
"internal/service/iot_card"
],
"domain_state_amount": [
"pkg/constants/iot.go"
],
"store_migration_config": [
"internal/store/postgres/iot_card_store.go"
],
"verification": {
"command": "代码中检索 IotCardStatusInStock",
"literal_output": [
"internal/service/iot_card/service.go:577:\t\tif isPlatform && card.Status != constants.IotCardStatusInStock {"
],
"exit_status": 0
}
},
{
"capability": "asset-device",
"requirement": "设备多卡关系",
"spec": "openspec/specs/asset-device/spec.md",
"entries": [
"/api/admin/devices"
],
"handler_consumer_job": [
"internal/handler/admin/device.go"
],
"application_service_query": [
"internal/service/device"
],
"domain_state_amount": [
"pkg/constants/iot.go"
],
"store_migration_config": [
"internal/store/postgres/device_sim_binding_store.go"
],
"verification": {
"command": "代码中检索 BindStatusBound",
"literal_output": [
"internal/service/device/binding_audit.go:136:query := db.WithContext(ctx).Where(\"device_id = ? AND bind_status = ?\", deviceID, constants.BindStatusBound).Order(\"slot_position ASC\")"
],
"exit_status": 0
}
},
{
"capability": "asset-device",
"requirement": "ICCID 按原始长度精确唯一",
"spec": "openspec/specs/asset-device/spec.md",
"entries": [
"/api/admin/iot-cards/import"
],
"handler_consumer_job": [
"internal/handler/admin/iot_card.go"
],
"application_service_query": [
"internal/service/iot_card"
],
"domain_state_amount": [
"internal/model/iot_card.go"
],
"store_migration_config": [
"internal/store/postgres/iot_card_store.go",
"migrations/000178_make_iot_card_iccid_exact_unique.up.sql"
],
"verification": {
"command": "rg -n --sort path 'CREATE UNIQUE INDEX idx_iot_card_iccid_(19|20)' migrations/000178_make_iot_card_iccid_exact_unique.up.sql",
"literal_output": [
"59:CREATE UNIQUE INDEX idx_iot_card_iccid_19",
"63:CREATE UNIQUE INDEX idx_iot_card_iccid_20"
],
"exit_status": 0
}
},
{
"capability": "export-task",
"requirement": "导出任务终态",
"spec": "openspec/specs/export-task/spec.md",
"entries": [
"/api/admin/export-tasks",
"constants.TaskTypeExportDispatch"
],
"handler_consumer_job": [
"internal/handler/admin/export_task.go",
"internal/task/export_dispatch.go"
],
"application_service_query": [
"internal/service/export_task"
],
"domain_state_amount": [
"pkg/constants/constants.go"
],
"store_migration_config": [
"internal/store/postgres/export_task_store.go"
],
"verification": {
"command": "代码中检索 ExportTaskStatusCompleted",
"literal_output": [
"internal/task/export_dispatch.go:77:if exportTask.Status == constants.ExportTaskStatusCompleted || exportTask.Status == constants.ExportTaskStatusFailed || exportTask.Status == constants.ExportTaskStatusCancelled {"
],
"exit_status": 0
}
},
{
"capability": "external-integration",
"requirement": "企微审批状态",
"spec": "openspec/specs/external-integration/spec.md",
"entries": [
"/api/admin/wecom/",
"constants.TaskTypeWeComApprovalSync"
],
"handler_consumer_job": [
"internal/handler/admin/wecom.go"
],
"application_service_query": [
"internal/application/approval"
],
"domain_state_amount": [
"pkg/constants/approval.go"
],
"store_migration_config": [
"internal/infrastructure/wecom/application_repository.go"
],
"verification": {
"command": "代码中检索 ApprovalStatusSubmissionUnknown",
"literal_output": [
"pkg/constants/approval.go:81:// ApprovalStatusSubmissionUnknown 表示审批提交请求已发出但结果未知。"
],
"exit_status": 0
}
},
{
"capability": "external-integration",
"requirement": "企微回调与补偿",
"spec": "openspec/specs/external-integration/spec.md",
"entries": [
"/api/callback/wecom",
"constants.TaskTypeWeComApprovalRecovery"
],
"handler_consumer_job": [
"internal/routes/wecom_callback.go"
],
"application_service_query": [
"internal/infrastructure/wecom/callback_service.go"
],
"domain_state_amount": [
"internal/domain/approval/instance.go"
],
"store_migration_config": [
"internal/infrastructure/wecom/approval_context_repository.go"
],
"verification": {
"command": "代码中检索 ApplyDecision",
"literal_output": [
"internal/domain/approval/instance.go:95:// ApplyDecision 校验标准决策状态迁移并冻结首次到达该终态的决策快照。"
],
"exit_status": 0
}
},
{
"capability": "external-integration",
"requirement": "外部失败边界",
"spec": "openspec/specs/external-integration/spec.md",
"entries": [
"/api/admin/wecom/",
"/api/admin/iot-cards"
],
"handler_consumer_job": [
"internal/handler/admin"
],
"application_service_query": [
"internal/gateway/client.go",
"internal/infrastructure/wecom"
],
"domain_state_amount": [
"pkg/errors/codes.go"
],
"store_migration_config": [
"internal/infrastructure/integrationlog/repository.go"
],
"verification": {
"command": "代码中检索 CodeGatewayTimeout",
"literal_output": [
"internal/gateway/client.go:217:return nil, true, errors.Wrap(errors.CodeGatewayTimeout, err, \"Gateway 请求超时\")"
],
"exit_status": 0
}
},
{
"capability": "external-integration",
"requirement": "外部调用重试边界",
"spec": "openspec/specs/external-integration/spec.md",
"entries": [
"constants.TaskTypeWeComApprovalRecovery"
],
"handler_consumer_job": [
"cmd/worker/main.go"
],
"application_service_query": [
"internal/gateway/client.go",
"internal/infrastructure/wecom/approval_submission_client.go"
],
"domain_state_amount": [
"pkg/constants/wecom.go"
],
"store_migration_config": [
"internal/infrastructure/wecom/approval_context_repository.go"
],
"verification": {
"command": "代码中检索 SafeToRetry",
"literal_output": [
"internal/infrastructure/wecom/approval_submission_client.go:41:SafeToRetry bool"
],
"exit_status": 0
}
},
{
"capability": "external-integration",
"requirement": "富友调用超时兼容行为",
"spec": "openspec/specs/external-integration/spec.md",
"entries": [
"POST /api/c/v1/orders/{id}/pay",
"POST /api/c/v1/wallet/recharge",
"POST /api/admin/agent-recharges"
],
"handler_consumer_job": [
"internal/handler/callback/payment.go"
],
"application_service_query": [
"pkg/fuiou/client.go"
],
"domain_state_amount": [
"pkg/fuiou/types.go"
],
"store_migration_config": [
"internal/store/postgres/payment_store.go"
],
"verification": {
"command": "代码中检索 http.Post",
"literal_output": [
"pkg/fuiou/client.go:164:httpResp, err := http.Post(reqURL, \"application/x-www-form-urlencoded\", strings.NewReader(body))"
],
"exit_status": 0
}
},
{
"capability": "external-integration",
"requirement": "运营商回调幂等",
"spec": "openspec/specs/external-integration/spec.md",
"entries": [
"/api/callback/carriers/ctcc/realname",
"/api/callback/carriers/cmcc/realname",
"/api/callback/carriers/cucc/realname",
"/api/callback/carriers/cucc/realname/remove"
],
"handler_consumer_job": [
"internal/handler/callback"
],
"application_service_query": [
"internal/infrastructure/carriercallback"
],
"domain_state_amount": [
"pkg/constants/integration_log.go"
],
"store_migration_config": [
"internal/infrastructure/integrationlog/repository.go"
],
"verification": {
"command": "代码中检索 ctccIdempotencyKey, cmccIdempotencyKey, cuccRealnameIdempotencyKey, cuccRemovalIdempotencyKey",
"literal_output": [
"internal/handler/callback/ctcc_realname.go:87:idempotencyKey := ctccIdempotencyKey(body, translated)",
"internal/handler/callback/cmcc_realname.go:65:idempotencyKey := cmccIdempotencyKey(body, translated)",
"internal/handler/callback/cucc_realname.go:66:key := cuccRealnameIdempotencyKey(body, translated, translateErr == nil)",
"internal/handler/callback/cucc_realname_removal.go:62:key := cuccRemovalIdempotencyKey(body, translated, translateErr == nil)"
],
"exit_status": 0
}
},
{
"capability": "identity-access",
"requirement": "令牌生命周期",
"spec": "openspec/specs/identity-access/spec.md",
"entries": [
"/api/admin/auth",
"/api/c/v1/auth"
],
"handler_consumer_job": [
"internal/handler/auth",
"internal/handler/app/client_auth.go"
],
"application_service_query": [
"internal/service/auth",
"internal/service/client_auth"
],
"domain_state_amount": [
"pkg/constants"
],
"store_migration_config": [
"internal/store/postgres/account_store.go"
],
"verification": {
"command": "代码中检索 Refresh",
"literal_output": [
"internal/handler/auth/handler.go:67:// 尝试从请求体获取 refresh_token可选"
],
"exit_status": 0
}
},
{
"capability": "identity-access",
"requirement": "数据范围拒绝",
"spec": "openspec/specs/identity-access/spec.md",
"entries": [
"/api/admin/"
],
"handler_consumer_job": [
"internal/handler/admin"
],
"application_service_query": [
"internal/service/permission"
],
"domain_state_amount": [
"pkg/middleware"
],
"store_migration_config": [
"internal/store/postgres/permission_store.go"
],
"verification": {
"command": "代码中检索 CodeForbidden",
"literal_output": [
"internal/handler/admin/asset.go:162:return errors.New(errors.CodeForbidden)"
],
"exit_status": 0
}
},
{
"capability": "notification-delivery",
"requirement": "通知接收人隔离",
"spec": "openspec/specs/notification-delivery/spec.md",
"entries": [
"/api/admin/notifications",
"/api/c/v1/notifications"
],
"handler_consumer_job": [
"internal/handler/admin/notification.go",
"internal/handler/app/client_notification.go"
],
"application_service_query": [
"internal/query/notification"
],
"domain_state_amount": [
"pkg/constants/notification.go"
],
"store_migration_config": [
"internal/model/notification.go"
],
"verification": {
"command": "代码中检索 recipient_id",
"literal_output": [
"internal/query/notification/query.go:37:Where(\"recipient_kind = ? AND recipient_id = ? AND is_read = ? AND (expires_at IS NULL OR expires_at > ?)\","
],
"exit_status": 0
}
},
{
"capability": "notification-delivery",
"requirement": "通知已读幂等",
"spec": "openspec/specs/notification-delivery/spec.md",
"entries": [
"/api/admin/notifications/{id}/read",
"/api/c/v1/notifications/{id}/read"
],
"handler_consumer_job": [
"internal/handler/admin/notification.go",
"internal/handler/app/client_notification.go"
],
"application_service_query": [
"internal/application/notification/read.go"
],
"domain_state_amount": [
"pkg/constants/audit.go"
],
"store_migration_config": [
"internal/model/notification.go"
],
"verification": {
"command": "代码中检索 is_read = ?",
"literal_output": [
"internal/application/notification/read.go:95:Where(\"id = ? AND is_read = ?\", notificationID, false).Take(&notification)"
],
"exit_status": 0
}
},
{
"capability": "operations-audit",
"requirement": "审计时间线",
"spec": "openspec/specs/operations-audit/spec.md",
"entries": [
"/api/admin/audit/"
],
"handler_consumer_job": [
"internal/handler/admin/audit.go"
],
"application_service_query": [
"internal/query/audit"
],
"domain_state_amount": [
"pkg/constants/audit.go"
],
"store_migration_config": [
"internal/infrastructure/audit"
],
"verification": {
"command": "代码中检索 CorrelationID",
"literal_output": [
"internal/handler/admin/audit.go:42:RequestID: request.RequestID, CorrelationID: request.CorrelationID,"
],
"exit_status": 0
}
},
{
"capability": "order-payment-wallet",
"requirement": "金额单位",
"spec": "openspec/specs/order-payment-wallet/spec.md",
"entries": [
"/api/c/v1/wallet",
"/api/admin/agent-recharges"
],
"handler_consumer_job": [
"internal/handler/app/client_wallet.go"
],
"application_service_query": [
"internal/application/wallet"
],
"domain_state_amount": [
"pkg/constants/wallet.go"
],
"store_migration_config": [
"internal/store/postgres/agent_wallet_transaction_store.go"
],
"verification": {
"command": "代码中检索 Amount",
"literal_output": [
"internal/handler/app/client_wallet.go:230:Amount: tx.Amount,"
],
"exit_status": 0
}
},
{
"capability": "order-payment-wallet",
"requirement": "支付回调幂等",
"spec": "openspec/specs/order-payment-wallet/spec.md",
"entries": [
"/api/callback/alipay",
"/api/callback/wechat-pay",
"/api/callback/fuiou-pay"
],
"handler_consumer_job": [
"internal/handler/callback/payment.go"
],
"application_service_query": [
"internal/service/client_order"
],
"domain_state_amount": [
"pkg/constants/payment.go"
],
"store_migration_config": [
"internal/store/postgres/payment_store.go"
],
"verification": {
"command": "代码中检索 IdempotencyKey",
"literal_output": [
"internal/handler/callback/payment.go:234:idempotencyKey := callback.TransactionID"
],
"exit_status": 0
}
},
{
"capability": "order-payment-wallet",
"requirement": "钱包并发",
"spec": "openspec/specs/order-payment-wallet/spec.md",
"entries": [
"/api/c/v1/wallet/recharge",
"/api/c/v1/orders/{id}/pay"
],
"handler_consumer_job": [
"internal/handler/app/client_wallet.go"
],
"application_service_query": [
"internal/application/wallet"
],
"domain_state_amount": [
"internal/domain/wallet/wallet.go"
],
"store_migration_config": [
"internal/store/postgres/asset_wallet_store.go"
],
"verification": {
"command": "代码中检索 Version",
"literal_output": [
"internal/handler/app/client_wallet.go:683:Version: 0,"
],
"exit_status": 0
}
},
{
"capability": "order-refund-exchange",
"requirement": "订单、退款与换货状态门禁",
"spec": "openspec/specs/order-refund-exchange/spec.md",
"entries": [
"/api/admin/orders",
"/api/admin/refunds",
"/api/admin/exchanges"
],
"handler_consumer_job": [
"internal/handler/admin"
],
"application_service_query": [
"internal/service/order",
"internal/service/refund",
"internal/service/exchange"
],
"domain_state_amount": [
"pkg/constants/constants.go",
"internal/model/refund.go"
],
"store_migration_config": [
"internal/store/postgres/order_store.go",
"internal/store/postgres/refund_store.go",
"internal/store/postgres/exchange_order_store.go"
],
"verification": {
"command": "代码中检索 ExchangeStatusPendingInfo",
"literal_output": [
"internal/service/exchange/service.go:99:Status: constants.ExchangeStatusPendingInfo,"
],
"exit_status": 0
}
},
{
"capability": "package-lifecycle",
"requirement": "套餐状态流转",
"spec": "openspec/specs/package-lifecycle/spec.md",
"entries": [
"/api/admin/packages",
"constants.TaskTypePackageFirstActivation"
],
"handler_consumer_job": [
"internal/handler/admin/package.go",
"internal/polling/package_activation_handler.go"
],
"application_service_query": [
"internal/service/package",
"internal/domain/package"
],
"domain_state_amount": [
"pkg/constants/iot.go"
],
"store_migration_config": [
"internal/store/postgres/package_usage_store.go"
],
"verification": {
"command": "代码中检索 PackageUsageStatusActive",
"literal_output": [
"internal/polling/package_activation_handler.go:211:constants.PackageUsageStatusActive,"
],
"exit_status": 0
}
},
{
"capability": "package-lifecycle",
"requirement": "批量操作可追踪",
"spec": "openspec/specs/package-lifecycle/spec.md",
"entries": [
"/api/admin/asset-package-batch-orders"
],
"handler_consumer_job": [
"internal/handler/admin"
],
"application_service_query": [
"internal/service/asset_package_batch_order"
],
"domain_state_amount": [
"pkg/constants/constants.go"
],
"store_migration_config": [
"internal/store/postgres/asset_package_batch_order_task_store.go"
],
"verification": {
"command": "代码中检索 Task",
"literal_output": [
"internal/handler/admin/asset.go:62:return client.EnqueueTask("
],
"exit_status": 0
}
},
{
"capability": "personal-customer",
"requirement": "个人客户身份",
"spec": "openspec/specs/personal-customer/spec.md",
"entries": [
"/api/c/v1/auth"
],
"handler_consumer_job": [
"internal/handler/app/client_auth.go"
],
"application_service_query": [
"internal/service/client_auth"
],
"domain_state_amount": [
"pkg/constants"
],
"store_migration_config": [
"internal/store/postgres/personal_customer_store.go"
],
"verification": {
"command": "代码中检索 RefreshToken",
"literal_output": [
"pkg/constants/auth.go:14:// DefaultRefreshTokenTTL 刷新令牌默认有效期7 天)"
],
"exit_status": 0
}
},
{
"capability": "personal-customer",
"requirement": "资产归属查询",
"spec": "openspec/specs/personal-customer/spec.md",
"entries": [
"/api/c/v1/asset",
"/api/c/v1/device"
],
"handler_consumer_job": [
"internal/handler/app/client_asset.go",
"internal/handler/app/client_device.go"
],
"application_service_query": [
"internal/service/personal_customer"
],
"domain_state_amount": [
"pkg/constants/iot.go"
],
"store_migration_config": [
"internal/store/postgres/personal_customer_device_store.go",
"internal/store/postgres/personal_customer_iccid_store.go"
],
"verification": {
"command": "代码中检索 CustomerID",
"literal_output": [
"internal/handler/app/client_asset.go:104:CustomerID uint"
],
"exit_status": 0
}
},
{
"capability": "polling-operations",
"requirement": "异步任务可观察",
"spec": "openspec/specs/polling-operations/spec.md",
"entries": [
"/api/admin/polling-stats",
"/api/admin/data-cleanup"
],
"handler_consumer_job": [
"internal/handler/admin"
],
"application_service_query": [
"internal/service/polling"
],
"domain_state_amount": [
"pkg/constants/polling.go"
],
"store_migration_config": [
"internal/store/postgres/polling_manual_trigger_store.go"
],
"verification": {
"command": "代码中检索 StatusProcessing",
"literal_output": [
"internal/handler/admin/polling_manual_trigger.go:298:case constants.PollingManualTriggerStatusProcessing:"
],
"exit_status": 0
}
},
{
"capability": "polling-operations",
"requirement": "手工轮询任务状态",
"spec": "openspec/specs/polling-operations/spec.md",
"entries": [
"/api/admin/polling-manual-trigger",
"constants.TaskTypePollingRealname",
"constants.TaskTypePollingCarddata",
"constants.TaskTypePollingPackage"
],
"handler_consumer_job": [
"internal/handler/admin"
],
"application_service_query": [
"internal/service/polling/manual_trigger_service.go"
],
"domain_state_amount": [
"pkg/constants/polling.go"
],
"store_migration_config": [
"internal/store/postgres/polling_manual_trigger_store.go"
],
"verification": {
"command": "代码中检索 PollingManualTriggerStatusCancelled",
"literal_output": [
"internal/handler/admin/polling_manual_trigger.go:302:case constants.PollingManualTriggerStatusCancelled:"
],
"exit_status": 0
}
},
{
"capability": "system-operations",
"requirement": "健康检查响应语义",
"spec": "openspec/specs/system-operations/spec.md",
"entries": [
"/health",
"/ready"
],
"handler_consumer_job": [
"internal/routes/health.go"
],
"application_service_query": [
"internal/routes/health.go"
],
"domain_state_amount": [
"internal/routes/health.go"
],
"store_migration_config": [
"pkg/response/response.go"
],
"verification": {
"command": "代码中检索 \"status\": \"healthy\", \"status\": \"ready\"",
"literal_output": [
"internal/routes/health.go:18:\"status\": \"healthy\",",
"internal/routes/health.go:31:\"status\": \"ready\","
],
"exit_status": 0
}
},
{
"capability": "system-operations",
"requirement": "受控系统配置更新",
"spec": "openspec/specs/system-operations/spec.md",
"entries": [
"/api/admin/system-configs/{key}"
],
"handler_consumer_job": [
"internal/handler/admin/system_config.go"
],
"application_service_query": [
"internal/application/systemconfig/update.go"
],
"domain_state_amount": [
"internal/infrastructure/systemconfig/registry.go"
],
"store_migration_config": [
"internal/model/system_config.go"
],
"verification": {
"command": "代码中检索 pg_advisory_xact_lock",
"literal_output": [
"internal/application/systemconfig/update.go:108:if err := tx.Exec(\"SELECT pg_advisory_xact_lock(hashtext(?))\", key).Error; err != nil {"
],
"exit_status": 0
}
},
{
"capability": "system-operations",
"requirement": "上下文健康检查证据链完整",
"spec": "openspec/specs/system-operations/spec.md",
"entries": [
"scripts/context-health.sh"
],
"handler_consumer_job": [
"scripts/context-health.sh"
],
"application_service_query": [
"docs/verification/context-reset/requirement-evidence.json"
],
"domain_state_amount": [
"docs/verification/context-reset/entry-capability-requirement-matrix.json"
],
"store_migration_config": [
"openspec/specs"
],
"verification": {
"command": "./scripts/context-health.sh",
"literal_output": [
"Context 健康检查通过"
],
"exit_status": 0
}
},
{
"capability": "merchant-payment-routing",
"requirement": "商户与微信授权配置管理",
"spec": "openspec/specs/merchant-payment-routing/spec.md",
"entries": [
"GET /api/admin/payment-merchants",
"POST /api/admin/payment-merchants",
"GET /api/admin/payment-merchants/{id}",
"PUT /api/admin/payment-merchants/{id}",
"DELETE /api/admin/payment-merchants/{id}",
"GET /api/admin/wechat-authorizations",
"PUT /api/admin/wechat-authorizations/current"
],
"handler_consumer_job": [
"internal/handler/admin/payment_merchant.go"
],
"application_service_query": [
"internal/application/merchantpayment/management.go"
],
"domain_state_amount": [
"internal/model/payment_merchant.go"
],
"store_migration_config": [
"migrations/000210_add_payment_merchant_pools.up.sql"
],
"verification": {
"command": "代码中检索 requirePaymentMerchantAccess 与 无权限访问支付商户配置",
"literal_output": [
"internal/routes/payment_merchant.go:16:func requirePaymentMerchantAccess(handler fiber.Handler) fiber.Handler {",
"internal/routes/payment_merchant.go:20:return errors.New(errors.CodeForbidden, \"无权限访问支付商户配置\")"
],
"exit_status": 0
}
},
{
"capability": "merchant-payment-routing",
"requirement": "商户池唯一性与轮询配置",
"spec": "openspec/specs/merchant-payment-routing/spec.md",
"entries": [
"GET /api/admin/payment-merchant-pools",
"POST /api/admin/payment-merchant-pools",
"GET /api/admin/payment-merchant-pools/{id}",
"PUT /api/admin/payment-merchant-pools/{id}",
"POST /api/admin/payment-merchant-pools/{id}/enable",
"POST /api/admin/payment-merchant-pools/{id}/disable"
],
"handler_consumer_job": [
"internal/handler/admin/payment_merchant.go"
],
"application_service_query": [
"internal/application/merchantpayment/management.go"
],
"domain_state_amount": [
"internal/application/merchantpayment/routing.go"
],
"store_migration_config": [
"migrations/000210_add_payment_merchant_pools.up.sql"
],
"verification": {
"command": "代码中检索 暂无可用商户",
"literal_output": [
"internal/application/merchantpayment/routing.go:327:\tif len(merchants) == 1 {",
"internal/application/merchantpayment/routing.go:328:\t\treturn routingOutcome{Merchant: merchants[0]}, nil",
"internal/application/merchantpayment/routing.go:342:\t\treturn routingOutcome{}, errors.New(errors.CodeNoPaymentConfig, \"当前周期暂无可用商户\")",
"internal/application/merchantpayment/routing.go:344:\treturn routingOutcome{Merchant: merchants[0], OpenNewRound: true}, nil",
"internal/application/merchantpayment/routing.go:365:\tif err := advanceRoutingEpoch(ctx, tx, pool); err != nil {",
"internal/application/merchantpayment/management.go:426:\t\trestartTimedSlot := !creating && strings.TrimSpace(req.Strategy) == model.PaymentMerchantStrategyTime && timedSlotChanged(&pool, &req, previousMemberIDs)",
"internal/application/merchantpayment/management.go:435:\t\t\tpool.TimePeriodStartedAt = &savedAt"
],
"exit_status": 0
}
},
{
"capability": "merchant-payment-routing",
"requirement": "新支付商户快照与历史兼容",
"spec": "openspec/specs/merchant-payment-routing/spec.md",
"entries": [
"POST /api/c/v1/orders/{id}/pay",
"POST /api/c/v1/wallet/recharge",
"POST /api/admin/agent-recharges"
],
"handler_consumer_job": [
"internal/service/client_order/service.go",
"internal/service/recharge_order/service.go",
"internal/application/agentrecharge/online_creation.go"
],
"application_service_query": [
"internal/application/merchantpayment/routing.go"
],
"domain_state_amount": [
"internal/model/payment.go"
],
"store_migration_config": [
"migrations/000210_add_payment_merchant_pools.up.sql"
],
"verification": {
"command": "代码中检索 SelectForNewPaymentWithTx 与 FreezeRoute",
"literal_output": [
"internal/service/client_order/service.go:388:route, err := s.merchantRuntime.SelectForNewPaymentWithTx(ctx, tx, paymentMethod, time.Now())",
"internal/service/client_order/service.go:400:merchantpayment.FreezeRoute(payment, route)"
],
"exit_status": 0
}
},
{
"capability": "merchant-payment-routing",
"requirement": "商户池列表投影",
"spec": "openspec/specs/merchant-payment-routing/spec.md",
"entries": [
"GET /api/admin/payment-merchant-pools",
"GET /api/admin/payment-merchant-pools/{id}"
],
"handler_consumer_job": [
"internal/handler/admin/payment_merchant.go商户池列表与详情"
],
"application_service_query": [
"internal/application/merchantpayment/management.go:ManagementService.ListPools/poolView按页批量装配投影",
"internal/application/merchantpayment/routing.go:loadPoolProjections只读计算当前命中成员"
],
"domain_state_amount": [
"internal/model/dto/payment_merchant_dto.go:EnabledMemberCount/MemberCount/CurrentMerchantID/CurrentMerchantName/UpdatedAt"
],
"store_migration_config": [
"internal/application/merchantpayment/routing.go:selectRouteMember与支付创建共用同一选择规则只读复用"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/application/merchantpayment/routing.go\", \"只读:不创建支付单、不推进统计世代、不计入成功累计\"), (\"internal/application/merchantpayment/routing.go\", \"func loadPoolProjections(\"), (\"internal/application/merchantpayment/routing.go\", \"projection.CurrentMerchant = outcome.Merchant\"), (\"internal/application/merchantpayment/management.go\", \"projections, err := loadPoolProjections(ctx, s.db, pools, time.Now())\"), (\"internal/model/dto/payment_merchant_dto.go\", \"EnabledMemberCount int\"), (\"internal/model/dto/payment_merchant_dto.go\", \"当前命中商户ID商户池停用或无可用成员时为空\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: merchant-payment-routing::商户池列表投影\")'",
"literal_output": [
"OK: merchant-payment-routing::商户池列表投影"
],
"exit_status": 0
}
},
{
"capability": "agent-series-package-options",
"requirement": "查询系列授权套餐候选项",
"spec": "openspec/specs/agent-series-package-options/spec.md",
"entries": [
"/api/admin/shop-series-grants/package-options"
],
"handler_consumer_job": [
"internal/handler/admin/shop_series_grant.go"
],
"application_service_query": [
"internal/service/shop_series_grant"
],
"domain_state_amount": [
"internal/model/dto/package_dto.go"
],
"store_migration_config": [
"internal/store/postgres/shop_package_allocation_store.go"
],
"verification": {
"command": "代码中检索 ListPackageOptions",
"literal_output": [
"internal/service/shop_series_grant/service.go:656:func (s *Service) ListPackageOptions"
],
"exit_status": 0
}
},
{
"capability": "notification-delivery",
"requirement": "主钱包低余额跨阈值提醒",
"spec": "openspec/specs/notification-delivery/spec.md",
"entries": [
"constants.OutboxEventTypeAgentMainWalletDebited",
"constants.OutboxEventTypeAdminDirectNotification"
],
"handler_consumer_job": [
"internal/infrastructure/wallet/debit_event.go"
],
"application_service_query": [
"internal/application/wallet/debit.go",
"internal/application/wallet/reservation.go"
],
"domain_state_amount": [
"pkg/constants/wallet.go"
],
"store_migration_config": [
"internal/infrastructure/messaging/outbox/repository.go"
],
"verification": {
"command": "代码中检索 AgentMainWalletLowBalanceThreshold",
"literal_output": [
"internal/infrastructure/wallet/debit_event.go:47:if event.BalanceBefore < constants.AgentMainWalletLowBalanceThreshold || event.BalanceAfter >= constants.AgentMainWalletLowBalanceThreshold {"
],
"exit_status": 0
}
},
{
"capability": "package-lifecycle",
"requirement": "授权页面禁止重复选择套餐",
"spec": "openspec/specs/package-lifecycle/spec.md",
"entries": [
"/api/admin/shop-series-grants/package-options"
],
"handler_consumer_job": [
"internal/handler/admin/shop_series_grant.go"
],
"application_service_query": [
"internal/service/shop_series_grant"
],
"domain_state_amount": [
"internal/model/dto/shop_series_grant_dto.go"
],
"store_migration_config": [
"internal/store/postgres/shop_package_allocation_store.go"
],
"verification": {
"command": "代码中检索 authorized",
"literal_output": [
"internal/model/dto/shop_series_grant_dto.go:105:Authorized bool `json:\"authorized\" description:\"目标店铺是否已启用授权\"`"
],
"exit_status": 0
}
},
{
"capability": "polling-operations",
"requirement": "轮询并发计数可观测",
"spec": "openspec/specs/polling-operations/spec.md",
"entries": [
"GET /api/admin/polling-concurrency",
"POST /api/admin/polling-concurrency/reset"
],
"handler_consumer_job": [
"internal/handler/admin/polling_concurrency.go"
],
"application_service_query": [
"internal/service/polling/concurrency_service.go"
],
"domain_state_amount": [
"pkg/constants/redis.go",
"internal/task/polling_base.go"
],
"store_migration_config": [
"internal/store/postgres/polling_concurrency_config_store.go"
],
"verification": {
"command": "rg -n --sort path 'current < 0|Available < 0|ResetConcurrency' internal/service/polling/concurrency_service.go",
"literal_output": [
"73:\t\tif current < 0 {",
"79:\t\tif status.Available < 0 {",
"111:\tif current < 0 {",
"117:\tif status.Available < 0 {",
"174:// ResetConcurrency 重置并发计数(用于信号量修复)",
"175:func (s *ConcurrencyService) ResetConcurrency(ctx context.Context, taskType string) error {"
],
"exit_status": 0
}
},
{
"capability": "polling-operations",
"requirement": "轮询并发配置覆盖实际任务",
"spec": "openspec/specs/polling-operations/spec.md",
"entries": [
"GET /api/admin/polling-concurrency",
"PUT /api/admin/polling-concurrency/{task_type}"
],
"handler_consumer_job": [
"internal/handler/admin/polling_concurrency.go",
"pkg/queue/handler.go"
],
"application_service_query": [
"internal/service/polling/concurrency_service.go"
],
"domain_state_amount": [
"pkg/constants/polling.go"
],
"store_migration_config": [
"internal/store/postgres/polling_concurrency_config_store.go",
"migrations/000206_fix_polling_concurrency_task_configs.up.sql"
],
"verification": {
"command": "rg -n --sort path 'TaskTypePolling(Realname|Carddata|Package|Protect|CardStatus)' pkg/queue/handler.go",
"literal_output": [
"297:\th.mux.HandleFunc(constants.TaskTypePollingRealname, realnameHandler.Handle)",
"298:\th.mux.HandleFunc(constants.TaskTypePollingCarddata, carddataHandler.Handle)",
"299:\th.mux.HandleFunc(constants.TaskTypePollingPackage, packageHandler.Handle)",
"300:\th.mux.HandleFunc(constants.TaskTypePollingProtect, protectHandler.Handle)",
"301:\th.mux.HandleFunc(constants.TaskTypePollingCardStatus, cardStatusHandler.Handle)"
],
"exit_status": 0
}
},
{
"capability": "polling-operations",
"requirement": "轮询任务类型名称可读",
"spec": "openspec/specs/polling-operations/spec.md",
"entries": [
"GET /api/admin/polling-concurrency",
"GET /api/admin/polling-concurrency/{task_type}"
],
"handler_consumer_job": [
"internal/handler/admin/polling_concurrency.go"
],
"application_service_query": [
"internal/service/polling/concurrency_service.go"
],
"domain_state_amount": [
"internal/model/dto/polling_concurrency_dto.go"
],
"store_migration_config": [
"internal/store/postgres/polling_concurrency_config_store.go"
],
"verification": {
"command": "rg -n --sort path -A 1 'case \"card_status\":' internal/service/polling/concurrency_service.go",
"literal_output": [
"288:\tcase \"card_status\":",
"289-\t\treturn \"卡状态检查\""
],
"exit_status": 0
}
},
{
"capability": "agent-funds-commission",
"requirement": "历史待审批线下代理充值可主动接入企业微信审批",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"/api/admin/agent-recharges/{id}/trigger-approval"
],
"handler_consumer_job": [
"internal/routes/agent_recharge.go",
"internal/handler/admin/agent_recharge.go"
],
"application_service_query": [
"internal/application/agentrecharge/offline_creation.go",
"internal/service/agent_recharge/service.go"
],
"domain_state_amount": [],
"store_migration_config": [
"internal/model/agent_wallet.go",
"migrations/000190_add_agent_recharge_approval_instance.up.sql",
"migrations/000170_create_approval_core.up.sql"
],
"verification": {
"command": "rg -n \"TriggerHistorical|trigger-approval\" internal/application internal/handler/admin internal/routes",
"literal_output": [
"internal/application/agentrecharge/offline_creation.go:51:func (s *OfflineCreationService) TriggerHistorical(ctx context.Context, recordID uint) (*CreateOfflineResult, error) {",
"internal/routes/agent_recharge.go:64: Register(group, doc, groupPath, \"POST\", \"/:id/trigger-approval\", handler.TriggerApproval, RouteSpec{"
],
"exit_status": 0
}
},
{
"capability": "order-refund-exchange",
"requirement": "历史待审批退款可主动接入企业微信审批",
"spec": "openspec/specs/order-refund-exchange/spec.md",
"entries": [
"/api/admin/refunds/{id}/trigger-approval"
],
"handler_consumer_job": [
"internal/routes/refund.go",
"internal/handler/admin/refund.go"
],
"application_service_query": [
"internal/application/refundapproval/creation.go",
"internal/service/refund/service.go"
],
"domain_state_amount": [],
"store_migration_config": [
"internal/model/refund.go",
"migrations/000191_add_refund_approval_instance.up.sql",
"migrations/000170_create_approval_core.up.sql"
],
"verification": {
"command": "rg -n --sort path \"TriggerHistorical|trigger-approval\" internal/application internal/handler/admin internal/routes",
"literal_output": [
"internal/application/agentrecharge/offline_creation.go:55:// TriggerHistorical 为历史待审批线下代充值补发一次企业微信审批。",
"internal/application/agentrecharge/offline_creation.go:56:func (s *OfflineCreationService) TriggerHistorical(ctx context.Context, recordID uint) (*CreateOfflineResult, error) {",
"internal/application/refundapproval/creation.go:98:// TriggerHistorical 为历史待审批退款补发一次企业微信审批。",
"internal/application/refundapproval/creation.go:103:func (s *CreationService) TriggerHistorical(ctx context.Context, refundID uint, material RefundMaterial) (*CreateResult, error) {",
"internal/handler/admin/agent_recharge.go:233:// POST /api/admin/agent-recharges/:id/trigger-approval",
"internal/handler/admin/refund.go:73:// POST /api/admin/refunds/:id/trigger-approval",
"internal/routes/agent_recharge.go:73:\tRegister(group, doc, groupPath, \"POST\", \"/:id/trigger-approval\", handler.TriggerApproval, RouteSpec{",
"internal/routes/refund.go:65:\tRegister(refund, doc, groupPath, \"POST\", \"/:id/trigger-approval\", handler.TriggerApproval, RouteSpec{"
],
"exit_status": 0
}
},
{
"capability": "agent-funds-commission",
"requirement": "佣金待计算状态可恢复",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"constants.TaskTypeCommission"
],
"handler_consumer_job": [
"pkg/queue/handler.go",
"cmd/worker/main.go"
],
"application_service_query": [
"pkg/queue/handler.go",
"cmd/worker/main.go"
],
"domain_state_amount": [],
"store_migration_config": [],
"verification": {
"command": "openspec validate --specs",
"literal_output": [
"Totals: 16 passed, 0 failed (16 items)"
],
"exit_status": 0
}
},
{
"capability": "agent-funds-commission",
"requirement": "退款佣金回扣可靠完成",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"constants.TaskTypeCommission"
],
"handler_consumer_job": [
"internal/infrastructure/commissiondelivery",
"pkg/queue/handler.go"
],
"application_service_query": [
"internal/infrastructure/commissiondelivery",
"pkg/queue/handler.go"
],
"domain_state_amount": [],
"store_migration_config": [],
"verification": {
"command": "openspec validate --specs",
"literal_output": [
"Totals: 16 passed, 0 failed (16 items)"
],
"exit_status": 0
}
},
{
"capability": "agent-funds-commission",
"requirement": "退款后处理可补偿",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"constants.TaskTypeCommission"
],
"handler_consumer_job": [
"internal/service/refund",
"pkg/queue/handler.go"
],
"application_service_query": [
"internal/service/refund",
"pkg/queue/handler.go"
],
"domain_state_amount": [],
"store_migration_config": [],
"verification": {
"command": "openspec validate --specs",
"literal_output": [
"Totals: 16 passed, 0 failed (16 items)"
],
"exit_status": 0
}
},
{
"capability": "agent-funds-commission",
"requirement": "代理商资金概况按店铺 ID 检索",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"/api/admin/shops/fund-summary"
],
"handler_consumer_job": [
"internal/routes/commission.go",
"internal/service/shop_commission"
],
"application_service_query": [
"internal/routes/commission.go",
"internal/service/shop_commission"
],
"domain_state_amount": [],
"store_migration_config": [],
"verification": {
"command": "openspec validate --specs",
"literal_output": [
"Totals: 16 passed, 0 failed (16 items)"
],
"exit_status": 0
}
},
{
"capability": "asset-device",
"requirement": "临期颜色等级仅用于展示",
"spec": "openspec/specs/asset-device/spec.md",
"entries": [
"/api/admin/expiring-assets",
"constants.TaskTypePackageExpiryReminder"
],
"handler_consumer_job": [
"internal/routes/asset.go",
"pkg/queue/handler.go"
],
"application_service_query": [
"internal/routes/asset.go",
"pkg/queue/handler.go"
],
"domain_state_amount": [],
"store_migration_config": [],
"verification": {
"command": "openspec validate --specs",
"literal_output": [
"Totals: 16 passed, 0 failed (16 items)"
],
"exit_status": 0
}
},
{
"capability": "asset-device",
"requirement": "卡业务观测可靠事件标识",
"spec": "openspec/specs/asset-device/spec.md",
"entries": [
"constants.TaskTypeCardObservationSeries"
],
"handler_consumer_job": [
"pkg/queue/handler.go",
"internal/application/cardobservation"
],
"application_service_query": [
"pkg/queue/handler.go",
"internal/application/cardobservation"
],
"domain_state_amount": [],
"store_migration_config": [],
"verification": {
"command": "openspec validate --specs",
"literal_output": [
"Totals: 16 passed, 0 failed (16 items)"
],
"exit_status": 0
}
},
{
"capability": "asset-device",
"requirement": "资产与卡详情的优先轮询状态投影",
"spec": "openspec/specs/asset-device/spec.md",
"entries": [
"GET /api/admin/assets/resolve/{identifier}"
],
"handler_consumer_job": [
"internal/handler/admin/asset.go:AssetHandler.Resolve卡与设备两个分支共用解析端点"
],
"application_service_query": [
"internal/query/asset/priority_polling.go:ExchangeTraceQuery.ResolvePriorityPolling只读投影与数据范围"
],
"domain_state_amount": [
"internal/model/dto/asset_dto.go:AssetPriorityPollingProjection是否在优先轮询中、最近触发场景、最近结果、最近时间、失败原因"
],
"store_migration_config": [
"internal/store/postgres/polling_priority_item_store.go:ProjectPriorityPolling按卡集合与店铺范围只读投影"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/handler/admin/asset.go\", \"ResolvePriorityPolling(c.UserContext(), result.AssetType, result.AssetID)\"), (\"internal/handler/admin/asset.go\", \"result.PriorityPolling = &dto.AssetPriorityPollingProjection{}\"), (\"internal/query/asset/priority_polling.go\", \"func (q *ExchangeTraceQuery) ResolvePriorityPolling(\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) ProjectPriorityPolling(\"), (\"internal/model/dto/asset_dto.go\", \"InPriorityPolling bool\"), (\"internal/model/dto/asset_dto.go\", \"读取不创建、不合并、不推进、不出队任何优先项\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: asset-device::资产与卡详情的优先轮询状态投影\")'",
"literal_output": [
"OK: asset-device::资产与卡详情的优先轮询状态投影"
],
"exit_status": 0
}
},
{
"capability": "notification-delivery",
"requirement": "套餐临期每日站内提醒",
"spec": "openspec/specs/notification-delivery/spec.md",
"entries": [
"constants.TaskTypePackageExpiryReminder"
],
"handler_consumer_job": [
"pkg/queue/handler.go",
"internal/service/notification"
],
"application_service_query": [
"pkg/queue/handler.go",
"internal/service/notification"
],
"domain_state_amount": [],
"store_migration_config": [],
"verification": {
"command": "openspec validate --specs",
"literal_output": [
"Totals: 16 passed, 0 failed (16 items)"
],
"exit_status": 0
}
},
{
"capability": "order-commission-delivery",
"requirement": "佣金计算重复投递幂等",
"spec": "openspec/specs/order-commission-delivery/spec.md",
"entries": [
"constants.TaskTypeCommission"
],
"handler_consumer_job": [
"pkg/queue/handler.go",
"internal/infrastructure/commissiondelivery"
],
"application_service_query": [
"pkg/queue/handler.go",
"internal/infrastructure/commissiondelivery"
],
"domain_state_amount": [],
"store_migration_config": [],
"verification": {
"command": "openspec validate --specs",
"literal_output": [
"Totals: 16 passed, 0 failed (16 items)"
],
"exit_status": 0
}
},
{
"capability": "order-commission-delivery",
"requirement": "已支付订单佣金计算可靠投递",
"spec": "openspec/specs/order-commission-delivery/spec.md",
"entries": [
"constants.TaskTypeCommission"
],
"handler_consumer_job": [
"pkg/queue/handler.go",
"internal/infrastructure/commissiondelivery"
],
"application_service_query": [
"pkg/queue/handler.go",
"internal/infrastructure/commissiondelivery"
],
"domain_state_amount": [],
"store_migration_config": [],
"verification": {
"command": "openspec validate --specs",
"literal_output": [
"Totals: 16 passed, 0 failed (16 items)"
],
"exit_status": 0
}
},
{
"capability": "order-commission-delivery",
"requirement": "待计算订单可补偿",
"spec": "openspec/specs/order-commission-delivery/spec.md",
"entries": [
"constants.TaskTypeCommission"
],
"handler_consumer_job": [
"pkg/queue/handler.go",
"internal/infrastructure/commissiondelivery"
],
"application_service_query": [
"pkg/queue/handler.go",
"internal/infrastructure/commissiondelivery"
],
"domain_state_amount": [],
"store_migration_config": [],
"verification": {
"command": "openspec validate --specs",
"literal_output": [
"Totals: 16 passed, 0 failed (16 items)"
],
"exit_status": 0
}
},
{
"capability": "order-refund-exchange",
"requirement": "代理退款查询按所属店铺隔离",
"spec": "openspec/specs/order-refund-exchange/spec.md",
"entries": [
"/api/admin/refunds",
"/api/admin/refunds/{id}"
],
"handler_consumer_job": [
"internal/routes/refund.go",
"internal/store/postgres/refund_store.go"
],
"application_service_query": [
"internal/routes/refund.go",
"internal/store/postgres/refund_store.go"
],
"domain_state_amount": [],
"store_migration_config": [],
"verification": {
"command": "openspec validate --specs",
"literal_output": [
"Totals: 16 passed, 0 failed (16 items)"
],
"exit_status": 0
}
},
{
"capability": "package-lifecycle",
"requirement": "套餐使用记录价格快照",
"spec": "openspec/specs/package-lifecycle/spec.md",
"entries": [
"/api/admin/orders"
],
"handler_consumer_job": [
"internal/routes/order.go",
"internal/service/order"
],
"application_service_query": [
"internal/routes/order.go",
"internal/service/order"
],
"domain_state_amount": [],
"store_migration_config": [],
"verification": {
"command": "openspec validate --specs",
"literal_output": [
"Totals: 16 passed, 0 failed (16 items)"
],
"exit_status": 0
}
},
{
"capability": "agent-funds-commission",
"requirement": "代理在线充值可用支付方式按允许范围与可用商户池判定",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"GET /api/admin/agent-recharges/payment-methods",
"GET /api/admin/agent-self-recharge-payment-methods",
"POST /api/admin/agent-recharges"
],
"handler_consumer_job": [
"internal/handler/admin/agent_recharge.go:AgentRechargeHandler.PaymentMethods"
],
"application_service_query": [
"internal/application/agentrecharge/online_creation.go:OnlineCreationService.AvailablePaymentMethods",
"internal/application/agentrecharge/online_payment_policy.go:OnlinePaymentMethodPolicy.AllowedMethods"
],
"domain_state_amount": [
"internal/model/agent_wallet.go:AgentRechargeRecord.PaymentMethod/PaymentChannel"
],
"store_migration_config": [
"internal/application/agentrecharge/online_creation.go:adapter.Available"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/application/agentrecharge/online_creation.go\", \"allowed, err := s.policy.AllowedMethods(ctx)\"), (\"internal/application/agentrecharge/online_creation.go\", \"adapter.Available(config)\"), (\"internal/application/agentrecharge/online_payment_policy.go\", \"func (p *OnlinePaymentMethodPolicy) AllowedMethods(\"), (\"internal/application/agentrecharge/online_payment_policy.go\", \"func (p *OnlinePaymentMethodPolicy) IsAllowed(\"), (\"internal/handler/admin/agent_recharge.go\", \"func (h *AgentRechargeHandler) SelfRechargePaymentMethods(\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: agent-funds-commission::代理在线充值可用支付方式按允许范围与可用商户池判定\")'",
"literal_output": [
"OK: agent-funds-commission::代理在线充值可用支付方式按允许范围与可用商户池判定"
],
"exit_status": 0
}
},
{
"capability": "agent-open-api",
"requirement": "代理开放接口查询不触发可靠卡观测",
"spec": "openspec/specs/agent-open-api/spec.md",
"entries": [
"GET /api/open/v1/cards/traffic",
"GET /api/open/v1/cards/status",
"GET /api/open/v1/cards/realname-status",
"GET /api/open/v1/devices/traffic"
],
"handler_consumer_job": [
"internal/handler/openapi/handler.go:GetCardTraffic -> service.GetCardTraffic",
"internal/handler/openapi/handler.go:GetCardStatus -> service.GetCardStatus",
"internal/handler/openapi/handler.go:GetRealnameStatus -> service.GetRealnameStatus",
"internal/handler/openapi/handler.go:GetDeviceTraffic -> service.GetDeviceTraffic"
],
"application_service_query": [
"internal/service/agent_open_api/service.go:GetCardTraffic",
"internal/service/agent_open_api/service.go:GetCardStatus",
"internal/service/agent_open_api/service.go:GetRealnameStatus",
"internal/service/agent_open_api/service.go:GetDeviceTraffic"
],
"domain_state_amount": [
"仅静态证明上述四个 GET handler 直接调用对应查询服务方法;不对全闭包的观测可达性作断言"
],
"store_migration_config": [
"internal/service/agent_open_api/service.go:packageUsageStore/deviceSimBindingStore/deviceStore"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/handler/openapi/handler.go'\"'\"', '\"'\"'func (h *Handler) GetCardTraffic'\"'\"'), ('\"'\"'internal/handler/openapi/handler.go'\"'\"', '\"'\"'h.service.GetCardTraffic(c.UserContext(), &req)'\"'\"'), ('\"'\"'internal/service/agent_open_api/service.go'\"'\"', '\"'\"'func (s *Service) GetCardTraffic'\"'\"'), ('\"'\"'internal/handler/openapi/handler.go'\"'\"', '\"'\"'func (h *Handler) GetCardStatus'\"'\"'), ('\"'\"'internal/handler/openapi/handler.go'\"'\"', '\"'\"'h.service.GetCardStatus(c.UserContext(), &req)'\"'\"'), ('\"'\"'internal/service/agent_open_api/service.go'\"'\"', '\"'\"'func (s *Service) GetCardStatus'\"'\"'), ('\"'\"'internal/handler/openapi/handler.go'\"'\"', '\"'\"'func (h *Handler) GetRealnameStatus'\"'\"'), ('\"'\"'internal/handler/openapi/handler.go'\"'\"', '\"'\"'h.service.GetRealnameStatus(c.UserContext(), &req)'\"'\"'), ('\"'\"'internal/service/agent_open_api/service.go'\"'\"', '\"'\"'func (s *Service) GetRealnameStatus'\"'\"'), ('\"'\"'internal/handler/openapi/handler.go'\"'\"', '\"'\"'func (h *Handler) GetDeviceTraffic'\"'\"'), ('\"'\"'internal/handler/openapi/handler.go'\"'\"', '\"'\"'h.service.GetDeviceTraffic(c.UserContext(), &req)'\"'\"'), ('\"'\"'internal/service/agent_open_api/service.go'\"'\"', '\"'\"'func (s *Service) GetDeviceTraffic'\"'\"')]; ordered=[('\"'\"'internal/handler/openapi/handler.go'\"'\"', ['\"'\"'func (h *Handler) GetCardTraffic'\"'\"', '\"'\"'h.service.GetCardTraffic(c.UserContext(), &req)'\"'\"']), ('\"'\"'internal/handler/openapi/handler.go'\"'\"', ['\"'\"'func (h *Handler) GetCardStatus'\"'\"', '\"'\"'h.service.GetCardStatus(c.UserContext(), &req)'\"'\"']), ('\"'\"'internal/handler/openapi/handler.go'\"'\"', ['\"'\"'func (h *Handler) GetRealnameStatus'\"'\"', '\"'\"'h.service.GetRealnameStatus(c.UserContext(), &req)'\"'\"']), ('\"'\"'internal/handler/openapi/handler.go'\"'\"', ['\"'\"'func (h *Handler) GetDeviceTraffic'\"'\"', '\"'\"'h.service.GetDeviceTraffic(c.UserContext(), &req)'\"'\"'])]; absent=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; unordered=[]; exec(\"for p,ts in ordered:\\n text=Path(p).read_text(); pos=-1; ok=True\\n for t in ts:\\n pos=text.find(t,pos+1)\\n if pos<0: ok=False; break\\n if not ok: unordered.append(f'\"'\"'{p}:{ts}'\"'\"')\"); present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in absent if t in Path(p).read_text()]; assert not missing and not unordered and not present, {'\"'\"'missing'\"'\"':missing,'\"'\"'unordered'\"'\"':unordered,'\"'\"'present'\"'\"':present}; print('\"'\"'OK: agent-open-api::代理开放接口查询不触发可靠卡观测'\"'\"')'",
"literal_output": [
"OK: agent-open-api::代理开放接口查询不触发可靠卡观测"
],
"exit_status": 0
}
},
{
"capability": "external-integration",
"requirement": "富友主扫统一下单与订单查询",
"spec": "openspec/specs/external-integration/spec.md",
"entries": [
"POST /api/admin/agent-recharges"
],
"handler_consumer_job": [
"internal/infrastructure/payment/fuiou_scan.go:FuiouScanAdapter.CreatePaymentURL/Query"
],
"application_service_query": [
"pkg/fuiou/scan.go:Client.PreCreate/CommonQuery"
],
"domain_state_amount": [
"pkg/fuiou/types.go:CommonQueryResponse.TransStat"
],
"store_migration_config": [
"internal/infrastructure/integrationlog"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/infrastructure/payment/fuiou_scan.go'\"'\"', '\"'\"'client.PreCreate('\"'\"'), ('\"'\"'internal/infrastructure/payment/fuiou_scan.go'\"'\"', '\"'\"'client.CommonQuery('\"'\"'), ('\"'\"'pkg/fuiou/scan.go'\"'\"', '\"'\"'func (c *Client) PreCreate'\"'\"'), ('\"'\"'pkg/fuiou/scan.go'\"'\"', '\"'\"'func (c *Client) CommonQuery'\"'\"'), ('\"'\"'pkg/fuiou/types.go'\"'\"', '\"'\"'xml:\"trans_stat\"'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: external-integration::富友主扫统一下单与订单查询'\"'\"')'",
"literal_output": [
"OK: external-integration::富友主扫统一下单与订单查询"
],
"exit_status": 0
}
},
{
"capability": "external-integration",
"requirement": "高频轮询外部交互日志保留边界",
"spec": "openspec/specs/external-integration/spec.md",
"entries": [
"constants.TaskTypePollingCarddata",
"constants.TaskTypePollingCardStatus",
"constants.TaskTypePollingRealname"
],
"handler_consumer_job": [
"internal/task/polling_integration_log.go:newGatewayAttempt/recordGatewayAttempt"
],
"application_service_query": [
"internal/application/cardobservation/traffic.go:ApplyTrafficObservation"
],
"domain_state_amount": [
"internal/infrastructure/integrationlog.Repository:Attempt.StateChanged"
],
"store_migration_config": [
"internal/query/retention/retention.go:Info.OnlineFrom/ArchivedBefore"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/task/polling_integration_log.go'\"'\"', '\"'\"'成功无变化的轮询不落库'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'recordGatewayAttempt'\"'\"'), ('\"'\"'internal/query/retention/retention.go'\"'\"', '\"'\"'ArchivedBefore'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: external-integration::高频轮询外部交互日志保留边界'\"'\"')'",
"literal_output": [
"OK: external-integration::高频轮询外部交互日志保留边界"
],
"exit_status": 0
}
},
{
"capability": "external-integration",
"requirement": "外部交互日志归档与留存受控执行",
"spec": "openspec/specs/external-integration/spec.md",
"entries": [
"constants.TaskTypeIntegrationDailyArchive",
"constants.TaskTypeAuditDailyRetention"
],
"handler_consumer_job": [
"internal/task/integration_archive.go:IntegrationArchiveHandler.HandleDaily",
"internal/task/audit_monthly_retention.go:AuditRetentionHandler.Handle"
],
"application_service_query": [
"internal/application/auditarchive/integration.go:ArchivePreviousIntegrationDay",
"internal/application/auditarchive/retention.go:RetainPendingDays"
],
"domain_state_amount": [
"internal/application/auditarchive/retention.go:RetentionBlockedError"
],
"store_migration_config": [
"pkg/constants/audit_archive.go:IntegrationArchiveSource"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/task/integration_archive.go'\"'\"', '\"'\"'func (h *IntegrationArchiveHandler) HandleDaily'\"'\"'), ('\"'\"'internal/task/audit_monthly_retention.go'\"'\"', '\"'\"'cleanupEnabled'\"'\"'), ('\"'\"'internal/application/auditarchive/retention.go'\"'\"', '\"'\"'RetentionBlockedError'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: external-integration::外部交互日志归档与留存受控执行'\"'\"')'",
"literal_output": [
"OK: external-integration::外部交互日志归档与留存受控执行"
],
"exit_status": 0
}
},
{
"capability": "external-integration",
"requirement": "外部交互日志逐日物理留存",
"spec": "openspec/specs/external-integration/spec.md",
"entries": [
"constants.TaskTypeIntegrationDailyArchive",
"constants.TaskTypeAuditDailyRetention"
],
"handler_consumer_job": [
"internal/task/integration_archive.go:IntegrationArchiveHandler.HandleDaily",
"internal/task/audit_monthly_retention.go:AuditRetentionHandler.Handle"
],
"application_service_query": [
"internal/application/auditarchive/integration.go:ArchivePreviousIntegrationDay",
"internal/application/auditarchive/retention.go:RetainPendingDays"
],
"domain_state_amount": [
"internal/application/auditarchive/retention.go:RetentionResult.IntegrationCount"
],
"store_migration_config": [
"internal/query/retention/retention.go:SourceIntegration/tb_integration_log"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/application/auditarchive/integration.go'\"'\"', '\"'\"'ArchivePreviousIntegrationDay'\"'\"'), ('\"'\"'internal/application/auditarchive/retention.go'\"'\"', '\"'\"'retainIntegrationDate'\"'\"'), ('\"'\"'internal/query/retention/retention.go'\"'\"', '\"'\"'tb_integration_log'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: external-integration::外部交互日志逐日物理留存'\"'\"')'",
"literal_output": [
"OK: external-integration::外部交互日志逐日物理留存"
],
"exit_status": 0
}
},
{
"capability": "operations-audit",
"requirement": "轮询审计事实保留边界",
"spec": "openspec/specs/operations-audit/spec.md",
"entries": [
"constants.TaskTypePollingCarddata",
"constants.TaskTypePollingCardStatus",
"constants.TaskTypePollingRealname"
],
"handler_consumer_job": [
"internal/task/polling_carddata_handler.go:Handle",
"internal/task/polling_cardstatus_handler.go:Handle",
"internal/task/polling_realname_handler.go:Handle"
],
"application_service_query": [
"internal/application/cardobservation/apply.go:ApplyCardObservation",
"internal/application/cardobservation/network.go:ApplyNetworkObservation",
"internal/application/cardobservation/traffic.go:ApplyTrafficObservation"
],
"domain_state_amount": [
"internal/application/cardobservation:workerObservationAudited"
],
"store_migration_config": [
"internal/query/audit/events.go:EventPage.Retention"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/application/cardobservation/network.go'\"'\"', '\"'\"'workerObservationAudited(ctx) && stateChanged'\"'\"'), ('\"'\"'internal/application/cardobservation/traffic.go'\"'\"', '\"'\"'workerObservationAudited(ctx) && stateChanged'\"'\"'), ('\"'\"'internal/query/audit/events.go'\"'\"', '\"'\"'在线查询留存边界'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: operations-audit::轮询审计事实保留边界'\"'\"')'",
"literal_output": [
"OK: operations-audit::轮询审计事实保留边界"
],
"exit_status": 0
}
},
{
"capability": "operations-audit",
"requirement": "审计归档与日留存受控执行",
"spec": "openspec/specs/operations-audit/spec.md",
"entries": [
"constants.TaskTypeAuditDailyArchive",
"constants.TaskTypeAuditDailyRetention"
],
"handler_consumer_job": [
"cmd/worker/main.go:registerAsynqScheduleTasks",
"internal/task/audit_monthly_retention.go:AuditRetentionHandler.Handle"
],
"application_service_query": [
"internal/application/auditarchive/service.go:ArchivePreviousDay",
"internal/application/auditarchive/retention.go:RetainPendingDays"
],
"domain_state_amount": [
"internal/application/auditarchive/retention.go:RetentionBlockedError"
],
"store_migration_config": [
"pkg/constants/audit_archive.go:TaskTypeAuditDailyArchive/TaskTypeAuditDailyRetention"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'cmd/worker/main.go'\"'\"', '\"'\"'constants.TaskTypeAuditDailyArchive'\"'\"'), ('\"'\"'cmd/worker/main.go'\"'\"', '\"'\"'constants.TaskTypeAuditDailyRetention'\"'\"'), ('\"'\"'internal/task/audit_monthly_retention.go'\"'\"', '\"'\"'cleanupEnabled'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: operations-audit::审计归档与日留存受控执行'\"'\"')'",
"literal_output": [
"OK: operations-audit::审计归档与日留存受控执行"
],
"exit_status": 0
}
},
{
"capability": "operations-audit",
"requirement": "审计在线数据逐日物理留存",
"spec": "openspec/specs/operations-audit/spec.md",
"entries": [
"constants.TaskTypeAuditDailyRetention"
],
"handler_consumer_job": [
"internal/task/audit_monthly_retention.go:AuditRetentionHandler.Handle"
],
"application_service_query": [
"internal/application/auditarchive/retention.go:RetainPendingDays/retainDate"
],
"domain_state_amount": [
"internal/application/auditarchive/retention.go:RetentionResult.EventCount/ResourceCount"
],
"store_migration_config": [
"internal/query/retention/retention.go:tb_audit_event/LogArchiveRun"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/application/auditarchive/retention.go'\"'\"', '\"'\"'RetainPendingDays'\"'\"'), ('\"'\"'internal/application/auditarchive/retention.go'\"'\"', '\"'\"'retainAuditDate'\"'\"'), ('\"'\"'internal/query/retention/retention.go'\"'\"', '\"'\"'tb_audit_event'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: operations-audit::审计在线数据逐日物理留存'\"'\"')'",
"literal_output": [
"OK: operations-audit::审计在线数据逐日物理留存"
],
"exit_status": 0
}
},
{
"capability": "operations-audit",
"requirement": "日留存独立运行日志",
"spec": "openspec/specs/operations-audit/spec.md",
"entries": [
"constants.TaskTypeAuditDailyRetention"
],
"handler_consumer_job": [
"cmd/worker/main.go:NewRetentionLogger",
"internal/task/audit_monthly_retention.go:AuditRetentionHandler.Handle"
],
"application_service_query": [
"internal/application/auditarchive/retention.go:RetainPendingDays"
],
"domain_state_amount": [
"internal/application/auditarchive/retention.go:RetentionResult.Duration"
],
"store_migration_config": [
"cmd/worker/main.go:cfg.Logging.RetentionLog"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'cmd/worker/main.go'\"'\"', '\"'\"'logger.NewRetentionLogger'\"'\"'), ('\"'\"'cmd/worker/main.go'\"'\"', '\"'\"'cfg.Logging.RetentionLog'\"'\"'), ('\"'\"'internal/task/audit_monthly_retention.go'\"'\"', '\"'\"'日志日留存处理完成'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: operations-audit::日留存独立运行日志'\"'\"')'",
"literal_output": [
"OK: operations-audit::日留存独立运行日志"
],
"exit_status": 0
}
},
{
"capability": "operations-audit",
"requirement": "审计调查留存边界连续",
"spec": "openspec/specs/operations-audit/spec.md",
"entries": [
"GET /api/admin/audit/events",
"GET /api/admin/audit/resources/{resource_type}/{resource_id}/timeline"
],
"handler_consumer_job": [
"internal/handler/admin/audit.go",
"internal/query/audit/timeline.go:LinkTimeline"
],
"application_service_query": [
"internal/query/audit/events.go:List",
"internal/query/audit/timeline.go:LinkTimeline"
],
"domain_state_amount": [
"internal/query/retention/retention.go:NormalizeRange"
],
"store_migration_config": [
"internal/query/retention/retention.go:sourceBoundary"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/query/audit/timeline.go'\"'\"', '\"'\"'Audit与Integration共同在线留存边界'\"'\"'), ('\"'\"'internal/query/retention/retention.go'\"'\"', '\"'\"'func NormalizeRange'\"'\"'), ('\"'\"'internal/query/retention/retention.go'\"'\"', '\"'\"'for _, row := range rows'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: operations-audit::审计调查留存边界连续'\"'\"')'",
"literal_output": [
"OK: operations-audit::审计调查留存边界连续"
],
"exit_status": 0
}
},
{
"capability": "package-lifecycle",
"requirement": "资产套餐层级投影",
"spec": "openspec/specs/package-lifecycle/spec.md",
"entries": [
"GET /api/admin/assets/{identifier}/packages",
"GET /api/c/v1/asset/package-history"
],
"handler_consumer_job": [
"internal/handler/admin/asset.go:AssetHandler.Packages -> asset.Service.GetPackages",
"internal/handler/app/client_asset.go:ClientAssetHandler.GetPackageHistory -> assetquery.NewPackageHistoryQuery.List"
],
"application_service_query": [
"internal/service/asset/service.go:Service.GetPackages -> assetquery.NewPackageHistoryQuery.List",
"internal/query/asset/package_history.go:PackageHistoryQuery.List"
],
"domain_state_amount": [
"internal/query/asset/package_history.go:PackageHistoryNode.Children/RelationshipStatus",
"internal/model/package.go:PackageUsage.MasterUsageID"
],
"store_migration_config": [
"internal/query/asset/package_history.go:baseUsageQuery"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/handler/admin/asset.go'\"'\"', '\"'\"'func (h *AssetHandler) Packages'\"'\"'), ('\"'\"'internal/handler/admin/asset.go'\"'\"', '\"'\"'h.assetService.GetPackages(c.UserContext(), asset.AssetType, asset.AssetID, page, pageSize, status, callerAccountType)'\"'\"'), ('\"'\"'internal/service/asset/service.go'\"'\"', '\"'\"'func (s *Service) GetPackages'\"'\"'), ('\"'\"'internal/service/asset/service.go'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(s.db).List'\"'\"'), ('\"'\"'internal/handler/app/client_asset.go'\"'\"', '\"'\"'func (h *ClientAssetHandler) GetPackageHistory'\"'\"'), ('\"'\"'internal/handler/app/client_asset.go'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(h.db).List'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'func (q *PackageHistoryQuery) List'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'Children []*PackageHistoryNode'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'RelationshipStatus'\"'\"'), ('\"'\"'internal/model/package.go'\"'\"', '\"'\"'MasterUsageID'\"'\"')]; ordered=[('\"'\"'internal/handler/admin/asset.go'\"'\"', ['\"'\"'func (h *AssetHandler) Packages'\"'\"', '\"'\"'h.assetService.GetPackages(c.UserContext(), asset.AssetType, asset.AssetID, page, pageSize, status, callerAccountType)'\"'\"']), ('\"'\"'internal/service/asset/service.go'\"'\"', ['\"'\"'func (s *Service) GetPackages'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(s.db).List'\"'\"']), ('\"'\"'internal/handler/app/client_asset.go'\"'\"', ['\"'\"'func (h *ClientAssetHandler) GetPackageHistory'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(h.db).List'\"'\"'])]; absent=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; unordered=[]; exec(\"for p,ts in ordered:\\n text=Path(p).read_text(); pos=-1; ok=True\\n for t in ts:\\n pos=text.find(t,pos+1)\\n if pos<0: ok=False; break\\n if not ok: unordered.append(f'\"'\"'{p}:{ts}'\"'\"')\"); present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in absent if t in Path(p).read_text()]; assert not missing and not unordered and not present, {'\"'\"'missing'\"'\"':missing,'\"'\"'unordered'\"'\"':unordered,'\"'\"'present'\"'\"':present}; print('\"'\"'OK: package-lifecycle::资产套餐层级投影'\"'\"')'",
"literal_output": [
"OK: package-lifecycle::资产套餐层级投影"
],
"exit_status": 0
}
},
{
"capability": "package-lifecycle",
"requirement": "套餐历史整组筛选与分页",
"spec": "openspec/specs/package-lifecycle/spec.md",
"entries": [
"GET /api/admin/assets/{identifier}/packages",
"GET /api/c/v1/asset/package-history"
],
"handler_consumer_job": [
"internal/handler/admin/asset.go:AssetHandler.Packages -> asset.Service.GetPackages",
"internal/handler/app/client_asset.go:ClientAssetHandler.GetPackageHistory -> assetquery.NewPackageHistoryQuery.List"
],
"application_service_query": [
"internal/service/asset/service.go:Service.GetPackages -> assetquery.NewPackageHistoryQuery.List",
"internal/query/asset/package_history.go:PackageHistoryQuery.List"
],
"domain_state_amount": [
"internal/query/asset/package_history.go:filterPackageHistoryGroups/paginatePackageHistoryGroups"
],
"store_migration_config": [
"internal/query/asset/package_history.go:baseUsageQuery"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/handler/admin/asset.go'\"'\"', '\"'\"'func (h *AssetHandler) Packages'\"'\"'), ('\"'\"'internal/handler/admin/asset.go'\"'\"', '\"'\"'h.assetService.GetPackages(c.UserContext(), asset.AssetType, asset.AssetID, page, pageSize, status, callerAccountType)'\"'\"'), ('\"'\"'internal/service/asset/service.go'\"'\"', '\"'\"'func (s *Service) GetPackages'\"'\"'), ('\"'\"'internal/service/asset/service.go'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(s.db).List'\"'\"'), ('\"'\"'internal/handler/app/client_asset.go'\"'\"', '\"'\"'func (h *ClientAssetHandler) GetPackageHistory'\"'\"'), ('\"'\"'internal/handler/app/client_asset.go'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(h.db).List'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'func (q *PackageHistoryQuery) List'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'filterPackageHistoryGroups'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'paginatePackageHistoryGroups'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'PageSize'\"'\"')]; ordered=[('\"'\"'internal/handler/admin/asset.go'\"'\"', ['\"'\"'func (h *AssetHandler) Packages'\"'\"', '\"'\"'h.assetService.GetPackages(c.UserContext(), asset.AssetType, asset.AssetID, page, pageSize, status, callerAccountType)'\"'\"']), ('\"'\"'internal/service/asset/service.go'\"'\"', ['\"'\"'func (s *Service) GetPackages'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(s.db).List'\"'\"']), ('\"'\"'internal/handler/app/client_asset.go'\"'\"', ['\"'\"'func (h *ClientAssetHandler) GetPackageHistory'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(h.db).List'\"'\"'])]; absent=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; unordered=[]; exec(\"for p,ts in ordered:\\n text=Path(p).read_text(); pos=-1; ok=True\\n for t in ts:\\n pos=text.find(t,pos+1)\\n if pos<0: ok=False; break\\n if not ok: unordered.append(f'\"'\"'{p}:{ts}'\"'\"')\"); present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in absent if t in Path(p).read_text()]; assert not missing and not unordered and not present, {'\"'\"'missing'\"'\"':missing,'\"'\"'unordered'\"'\"':unordered,'\"'\"'present'\"'\"':present}; print('\"'\"'OK: package-lifecycle::套餐历史整组筛选与分页'\"'\"')'",
"literal_output": [
"OK: package-lifecycle::套餐历史整组筛选与分页"
],
"exit_status": 0
}
},
{
"capability": "package-lifecycle",
"requirement": "套餐历史稳定排序",
"spec": "openspec/specs/package-lifecycle/spec.md",
"entries": [
"GET /api/admin/assets/{identifier}/packages",
"GET /api/c/v1/asset/package-history"
],
"handler_consumer_job": [
"internal/handler/admin/asset.go:AssetHandler.Packages -> asset.Service.GetPackages",
"internal/handler/app/client_asset.go:ClientAssetHandler.GetPackageHistory -> assetquery.NewPackageHistoryQuery.List"
],
"application_service_query": [
"internal/service/asset/service.go:Service.GetPackages -> assetquery.NewPackageHistoryQuery.List",
"internal/query/asset/package_history.go:PackageHistoryQuery.List"
],
"domain_state_amount": [
"internal/query/asset/package_history.go:sortPackageHistoryGroups/packageHistoryChildLess"
],
"store_migration_config": [
"internal/query/asset/package_history.go:baseUsageQuery"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/handler/admin/asset.go'\"'\"', '\"'\"'func (h *AssetHandler) Packages'\"'\"'), ('\"'\"'internal/handler/admin/asset.go'\"'\"', '\"'\"'h.assetService.GetPackages(c.UserContext(), asset.AssetType, asset.AssetID, page, pageSize, status, callerAccountType)'\"'\"'), ('\"'\"'internal/service/asset/service.go'\"'\"', '\"'\"'func (s *Service) GetPackages'\"'\"'), ('\"'\"'internal/service/asset/service.go'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(s.db).List'\"'\"'), ('\"'\"'internal/handler/app/client_asset.go'\"'\"', '\"'\"'func (h *ClientAssetHandler) GetPackageHistory'\"'\"'), ('\"'\"'internal/handler/app/client_asset.go'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(h.db).List'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'func (q *PackageHistoryQuery) List'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'sort.Slice(item.Children'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'sort.Slice(items'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'left.ID < right.ID'\"'\"')]; ordered=[('\"'\"'internal/handler/admin/asset.go'\"'\"', ['\"'\"'func (h *AssetHandler) Packages'\"'\"', '\"'\"'h.assetService.GetPackages(c.UserContext(), asset.AssetType, asset.AssetID, page, pageSize, status, callerAccountType)'\"'\"']), ('\"'\"'internal/service/asset/service.go'\"'\"', ['\"'\"'func (s *Service) GetPackages'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(s.db).List'\"'\"']), ('\"'\"'internal/handler/app/client_asset.go'\"'\"', ['\"'\"'func (h *ClientAssetHandler) GetPackageHistory'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(h.db).List'\"'\"'])]; absent=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; unordered=[]; exec(\"for p,ts in ordered:\\n text=Path(p).read_text(); pos=-1; ok=True\\n for t in ts:\\n pos=text.find(t,pos+1)\\n if pos<0: ok=False; break\\n if not ok: unordered.append(f'\"'\"'{p}:{ts}'\"'\"')\"); present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in absent if t in Path(p).read_text()]; assert not missing and not unordered and not present, {'\"'\"'missing'\"'\"':missing,'\"'\"'unordered'\"'\"':unordered,'\"'\"'present'\"'\"':present}; print('\"'\"'OK: package-lifecycle::套餐历史稳定排序'\"'\"')'",
"literal_output": [
"OK: package-lifecycle::套餐历史稳定排序"
],
"exit_status": 0
}
},
{
"capability": "package-lifecycle",
"requirement": "物理缺失与不可展示关系区分",
"spec": "openspec/specs/package-lifecycle/spec.md",
"entries": [
"GET /api/admin/assets/{identifier}/packages",
"GET /api/c/v1/asset/package-history"
],
"handler_consumer_job": [
"internal/handler/admin/asset.go:AssetHandler.Packages -> asset.Service.GetPackages",
"internal/handler/app/client_asset.go:ClientAssetHandler.GetPackageHistory -> assetquery.NewPackageHistoryQuery.List"
],
"application_service_query": [
"internal/service/asset/service.go:Service.GetPackages -> assetquery.NewPackageHistoryQuery.List",
"internal/query/asset/package_history.go:PackageHistoryQuery.List"
],
"domain_state_amount": [
"internal/query/asset/package_history.go:inRange/presentMasters/relationshipStatusMasterMissing"
],
"store_migration_config": [
"internal/query/asset/package_history.go:baseUsageQuery"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/handler/admin/asset.go'\"'\"', '\"'\"'func (h *AssetHandler) Packages'\"'\"'), ('\"'\"'internal/handler/admin/asset.go'\"'\"', '\"'\"'h.assetService.GetPackages(c.UserContext(), asset.AssetType, asset.AssetID, page, pageSize, status, callerAccountType)'\"'\"'), ('\"'\"'internal/service/asset/service.go'\"'\"', '\"'\"'func (s *Service) GetPackages'\"'\"'), ('\"'\"'internal/service/asset/service.go'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(s.db).List'\"'\"'), ('\"'\"'internal/handler/app/client_asset.go'\"'\"', '\"'\"'func (h *ClientAssetHandler) GetPackageHistory'\"'\"'), ('\"'\"'internal/handler/app/client_asset.go'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(h.db).List'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'func (q *PackageHistoryQuery) List'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'if inRange'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'presentMasters'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'relationshipStatusMasterMissing'\"'\"')]; ordered=[('\"'\"'internal/handler/admin/asset.go'\"'\"', ['\"'\"'func (h *AssetHandler) Packages'\"'\"', '\"'\"'h.assetService.GetPackages(c.UserContext(), asset.AssetType, asset.AssetID, page, pageSize, status, callerAccountType)'\"'\"']), ('\"'\"'internal/service/asset/service.go'\"'\"', ['\"'\"'func (s *Service) GetPackages'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(s.db).List'\"'\"']), ('\"'\"'internal/handler/app/client_asset.go'\"'\"', ['\"'\"'func (h *ClientAssetHandler) GetPackageHistory'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(h.db).List'\"'\"'])]; absent=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; unordered=[]; exec(\"for p,ts in ordered:\\n text=Path(p).read_text(); pos=-1; ok=True\\n for t in ts:\\n pos=text.find(t,pos+1)\\n if pos<0: ok=False; break\\n if not ok: unordered.append(f'\"'\"'{p}:{ts}'\"'\"')\"); present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in absent if t in Path(p).read_text()]; assert not missing and not unordered and not present, {'\"'\"'missing'\"'\"':missing,'\"'\"'unordered'\"'\"':unordered,'\"'\"'present'\"'\"':present}; print('\"'\"'OK: package-lifecycle::物理缺失与不可展示关系区分'\"'\"')'",
"literal_output": [
"OK: package-lifecycle::物理缺失与不可展示关系区分"
],
"exit_status": 0
}
},
{
"capability": "package-lifecycle",
"requirement": "套餐历史读取范围与兼容边界",
"spec": "openspec/specs/package-lifecycle/spec.md",
"entries": [
"GET /api/admin/assets/{identifier}/packages",
"GET /api/c/v1/asset/package-history"
],
"handler_consumer_job": [
"internal/handler/admin/asset.go:AssetHandler.Packages -> asset.Service.GetPackages",
"internal/handler/app/client_asset.go:ClientAssetHandler.GetPackageHistory -> assetquery.NewPackageHistoryQuery.List"
],
"application_service_query": [
"internal/service/asset/service.go:Service.GetPackages -> assetquery.NewPackageHistoryQuery.List",
"internal/query/asset/package_history.go:PackageHistoryQuery.List"
],
"domain_state_amount": [
"internal/query/asset/package_history.go:PackageHistoryInput.AssetType/Generation/baseUsageQuery"
],
"store_migration_config": [
"internal/query/asset/package_history.go:baseUsageQuery"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/handler/admin/asset.go'\"'\"', '\"'\"'func (h *AssetHandler) Packages'\"'\"'), ('\"'\"'internal/handler/admin/asset.go'\"'\"', '\"'\"'h.assetService.GetPackages(c.UserContext(), asset.AssetType, asset.AssetID, page, pageSize, status, callerAccountType)'\"'\"'), ('\"'\"'internal/service/asset/service.go'\"'\"', '\"'\"'func (s *Service) GetPackages'\"'\"'), ('\"'\"'internal/service/asset/service.go'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(s.db).List'\"'\"'), ('\"'\"'internal/handler/app/client_asset.go'\"'\"', '\"'\"'func (h *ClientAssetHandler) GetPackageHistory'\"'\"'), ('\"'\"'internal/handler/app/client_asset.go'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(h.db).List'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'func (q *PackageHistoryQuery) List'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'AssetType'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'Generation'\"'\"'), ('\"'\"'internal/query/asset/package_history.go'\"'\"', '\"'\"'baseUsageQuery'\"'\"')]; ordered=[('\"'\"'internal/handler/admin/asset.go'\"'\"', ['\"'\"'func (h *AssetHandler) Packages'\"'\"', '\"'\"'h.assetService.GetPackages(c.UserContext(), asset.AssetType, asset.AssetID, page, pageSize, status, callerAccountType)'\"'\"']), ('\"'\"'internal/service/asset/service.go'\"'\"', ['\"'\"'func (s *Service) GetPackages'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(s.db).List'\"'\"']), ('\"'\"'internal/handler/app/client_asset.go'\"'\"', ['\"'\"'func (h *ClientAssetHandler) GetPackageHistory'\"'\"', '\"'\"'assetquery.NewPackageHistoryQuery(h.db).List'\"'\"'])]; absent=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; unordered=[]; exec(\"for p,ts in ordered:\\n text=Path(p).read_text(); pos=-1; ok=True\\n for t in ts:\\n pos=text.find(t,pos+1)\\n if pos<0: ok=False; break\\n if not ok: unordered.append(f'\"'\"'{p}:{ts}'\"'\"')\"); present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in absent if t in Path(p).read_text()]; assert not missing and not unordered and not present, {'\"'\"'missing'\"'\"':missing,'\"'\"'unordered'\"'\"':unordered,'\"'\"'present'\"'\"':present}; print('\"'\"'OK: package-lifecycle::套餐历史读取范围与兼容边界'\"'\"')'",
"literal_output": [
"OK: package-lifecycle::套餐历史读取范围与兼容边界"
],
"exit_status": 0
}
},
{
"capability": "package-queue-activation",
"requirement": "当前主套餐过期后自动激活下一个",
"spec": "openspec/specs/package-queue-activation/spec.md",
"entries": [
"constants.TaskTypePackageQueueActivation"
],
"handler_consumer_job": [
"internal/polling/package_activation_handler.go:PackageActivationHandler.Handle",
"pkg/queue/handler.go:HandlePackageQueueActivation"
],
"application_service_query": [
"internal/polling/package_activation_handler.go:activateNextPackage"
],
"domain_state_amount": [
"internal/model/package.go:PackageUsage.Priority/MasterUsageID"
],
"store_migration_config": [
"internal/polling/package_activation_handler.go:tb_package_usage"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/polling/package_activation_handler.go'\"'\"', '\"'\"'expires_at <= ?'\"'\"'), ('\"'\"'internal/polling/package_activation_handler.go'\"'\"', '\"'\"'enqueueActivationTask'\"'\"'), ('\"'\"'pkg/queue/handler.go'\"'\"', '\"'\"'TaskTypePackageQueueActivation'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: package-queue-activation::当前主套餐过期后自动激活下一个'\"'\"')'",
"literal_output": [
"OK: package-queue-activation::当前主套餐过期后自动激活下一个"
],
"exit_status": 0
}
},
{
"capability": "package-queue-activation",
"requirement": "孤儿待生效套餐必须公平恢复",
"spec": "openspec/specs/package-queue-activation/spec.md",
"entries": [
"constants.TaskTypePackageQueueActivation"
],
"handler_consumer_job": [
"internal/polling/package_activation_handler.go:findAndActivateOrphanPackages"
],
"application_service_query": [
"internal/polling/package_activation_handler.go:enqueueActivationTask"
],
"domain_state_amount": [
"internal/polling/package_activation_handler.go:ROW_NUMBER/priority/created_at/id"
],
"store_migration_config": [
"internal/polling/package_activation_handler.go:tb_package_usage"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/polling/package_activation_handler.go'\"'\"', '\"'\"'func (h *PackageActivationHandler) findAndActivateOrphanPackages'\"'\"'), ('\"'\"'internal/polling/package_activation_handler.go'\"'\"', '\"'\"'ROW_NUMBER() OVER'\"'\"'), ('\"'\"'internal/polling/package_activation_handler.go'\"'\"', '\"'\"'ORDER BY candidate.priority ASC, candidate.created_at ASC, candidate.id ASC'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: package-queue-activation::孤儿待生效套餐必须公平恢复'\"'\"')'",
"literal_output": [
"OK: package-queue-activation::孤儿待生效套餐必须公平恢复"
],
"exit_status": 0
}
},
{
"capability": "package-queue-activation",
"requirement": "Asynq 激活结果必须准确且可重试",
"spec": "openspec/specs/package-queue-activation/spec.md",
"entries": [
"constants.TaskTypePackageQueueActivation"
],
"handler_consumer_job": [
"pkg/queue/handler.go:registerPackageActivationHandlers -> TaskTypePackageQueueActivation",
"internal/polling/package_activation_handler.go:PackageActivationHandler.HandlePackageQueueActivation"
],
"application_service_query": [
"internal/polling/package_activation_handler.go:enqueueActivationTask",
"internal/service/package/activation_service.go:ActivationService.ActivateSpecificPackage"
],
"domain_state_amount": [
"internal/service/package/activation_service.go:SetNX conflict -> CodePackageActivationConflict",
"internal/polling/package_activation_handler.go:business failure returns err"
],
"store_migration_config": [
"internal/service/package/activation_service.go:RedisPackageActivationLockKey",
"internal/polling/package_activation_handler.go:tb_package_usage"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'pkg/queue/handler.go'\"'\"', '\"'\"'h.mux.HandleFunc(constants.TaskTypePackageQueueActivation, packageActivationHandler.HandlePackageQueueActivation)'\"'\"'), ('\"'\"'internal/polling/package_activation_handler.go'\"'\"', '\"'\"'func (h *PackageActivationHandler) enqueueActivationTask'\"'\"'), ('\"'\"'internal/polling/package_activation_handler.go'\"'\"', '\"'\"'asynq.MaxRetry(3)'\"'\"'), ('\"'\"'internal/polling/package_activation_handler.go'\"'\"', '\"'\"'return err'\"'\"'), ('\"'\"'internal/service/package/activation_service.go'\"'\"', '\"'\"'return errors.New(errors.CodePackageActivationConflict)'\"'\"')]; ordered=[('\"'\"'internal/polling/package_activation_handler.go'\"'\"', ['\"'\"'if err := h.activationService.ActivateSpecificPackage'\"'\"', '\"'\"'return err'\"'\"'])]; absent=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; unordered=[]; exec(\"for p,ts in ordered:\\n text=Path(p).read_text(); pos=-1; ok=True\\n for t in ts:\\n pos=text.find(t,pos+1)\\n if pos<0: ok=False; break\\n if not ok: unordered.append(f'\"'\"'{p}:{ts}'\"'\"')\"); present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in absent if t in Path(p).read_text()]; assert not missing and not unordered and not present, {'\"'\"'missing'\"'\"':missing,'\"'\"'unordered'\"'\"':unordered,'\"'\"'present'\"'\"':present}; print('\"'\"'OK: package-queue-activation::Asynq 激活结果必须准确且可重试'\"'\"')'",
"literal_output": [
"OK: package-queue-activation::Asynq 激活结果必须准确且可重试"
],
"exit_status": 0
}
},
{
"capability": "polling-load-control",
"requirement": "轮询全局并发与背压",
"spec": "openspec/specs/polling-load-control/spec.md",
"entries": [
"constants.TaskTypePollingCarddata",
"constants.TaskTypePollingCardStatus",
"constants.TaskTypePollingPackage",
"constants.TaskTypePollingProtect",
"constants.TaskTypePollingRealname"
],
"handler_consumer_job": [
"internal/task/polling_carddata_handler.go:PollingCarddataHandler.Handle",
"internal/task/polling_cardstatus_handler.go:PollingCardStatusHandler.Handle",
"internal/task/polling_package_handler.go:PollingPackageHandler.Handle",
"internal/task/polling_protect_handler.go:PollingProtectHandler.Handle",
"internal/task/polling_realname_handler.go:PollingRealnameHandler.Handle"
],
"application_service_query": [
"internal/task/polling_base.go:PollingBase.acquireConcurrency/requeueCard/releaseConcurrency"
],
"domain_state_amount": [
"internal/task/polling_base.go:acquireConcurrencyScript"
],
"store_migration_config": [
"pkg/constants/redis.go",
"pkg/constants/polling.go"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingCarddata)'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingCarddata)'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingCarddata)'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingCardStatus)'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingCardStatus)'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingCardStatus)'\"'\"'), ('\"'\"'internal/task/polling_package_handler.go'\"'\"', '\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingPackage)'\"'\"'), ('\"'\"'internal/task/polling_package_handler.go'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingPackage)'\"'\"'), ('\"'\"'internal/task/polling_package_handler.go'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingPackage)'\"'\"'), ('\"'\"'internal/task/polling_protect_handler.go'\"'\"', '\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingProtect)'\"'\"'), ('\"'\"'internal/task/polling_protect_handler.go'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)'\"'\"'), ('\"'\"'internal/task/polling_protect_handler.go'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingProtect)'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingRealname)'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingRealname)'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingRealname)'\"'\"'), ('\"'\"'internal/task/polling_base.go'\"'\"', '\"'\"'var acquireConcurrencyScript'\"'\"')]; ordered=[('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', ['\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingCarddata)'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingCarddata)'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingCarddata)'\"'\"']), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', ['\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingCardStatus)'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingCardStatus)'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingCardStatus)'\"'\"']), ('\"'\"'internal/task/polling_package_handler.go'\"'\"', ['\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingPackage)'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingPackage)'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingPackage)'\"'\"']), ('\"'\"'internal/task/polling_protect_handler.go'\"'\"', ['\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingProtect)'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingProtect)'\"'\"']), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', ['\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingRealname)'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingRealname)'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingRealname)'\"'\"'])]; absent=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; unordered=[]; exec(\"for p,ts in ordered:\\n text=Path(p).read_text(); pos=-1; ok=True\\n for t in ts:\\n pos=text.find(t,pos+1)\\n if pos<0: ok=False; break\\n if not ok: unordered.append(f'\"'\"'{p}:{ts}'\"'\"')\"); present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in absent if t in Path(p).read_text()]; assert not missing and not unordered and not present, {'\"'\"'missing'\"'\"':missing,'\"'\"'unordered'\"'\"':unordered,'\"'\"'present'\"'\"':present}; print('\"'\"'OK: polling-load-control::轮询全局并发与背压'\"'\"')'",
"literal_output": [
"OK: polling-load-control::轮询全局并发与背压"
],
"exit_status": 0
}
},
{
"capability": "polling-load-control",
"requirement": "正常无变化观测的低写入处理",
"spec": "openspec/specs/polling-load-control/spec.md",
"entries": [
"constants.TaskTypePollingCarddata",
"constants.TaskTypePollingCardStatus",
"constants.TaskTypePollingRealname"
],
"handler_consumer_job": [
"internal/task/polling_carddata_handler.go:PollingCarddataHandler.Handle",
"internal/task/polling_cardstatus_handler.go:PollingCardStatusHandler.Handle",
"internal/task/polling_realname_handler.go:PollingRealnameHandler.Handle",
"internal/task/polling_integration_log.go:recordGatewayAttempt"
],
"application_service_query": [
"internal/application/cardobservation/traffic.go:ApplyTrafficObservation",
"internal/application/cardobservation/network.go:ApplyNetworkObservation",
"internal/application/cardobservation/apply.go:ApplyCardObservation"
],
"domain_state_amount": [
"internal/task/polling_integration_log.go:attempt created in memory then persisted only on decided outcome"
],
"store_migration_config": [
"internal/infrastructure/integrationlog.Repository"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingCarddata)'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingCarddata)'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingCarddata)'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingCardStatus)'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingCardStatus)'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingCardStatus)'\"'\"'), ('\"'\"'internal/task/polling_package_handler.go'\"'\"', '\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingPackage)'\"'\"'), ('\"'\"'internal/task/polling_package_handler.go'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingPackage)'\"'\"'), ('\"'\"'internal/task/polling_package_handler.go'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingPackage)'\"'\"'), ('\"'\"'internal/task/polling_protect_handler.go'\"'\"', '\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingProtect)'\"'\"'), ('\"'\"'internal/task/polling_protect_handler.go'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)'\"'\"'), ('\"'\"'internal/task/polling_protect_handler.go'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingProtect)'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingRealname)'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingRealname)'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingRealname)'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'attempt := newGatewayAttempt'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'h.observation.ApplyTrafficObservation'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'changed := decision.IncrementMB > 0 || decision.CrossMonth'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'if changed {'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'IntegrationResultSuccess, true'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'attempt := newGatewayAttempt'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'h.observation.ApplyNetworkObservation'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'changed := decision.StatusChanged || decision.StopReasonChanged || decision.StopPolling'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'if changed {'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'IntegrationResultSuccess, true'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'attempt := newGatewayAttempt'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'h.observation.ApplyCardObservation'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'changed := decision.StatusChanged'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'if changed {'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'IntegrationResultSuccess, true'\"'\"')]; ordered=[('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', ['\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingCarddata)'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingCarddata)'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingCarddata)'\"'\"']), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', ['\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingCardStatus)'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingCardStatus)'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingCardStatus)'\"'\"']), ('\"'\"'internal/task/polling_package_handler.go'\"'\"', ['\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingPackage)'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingPackage)'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingPackage)'\"'\"']), ('\"'\"'internal/task/polling_protect_handler.go'\"'\"', ['\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingProtect)'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingProtect)'\"'\"']), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', ['\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingRealname)'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingRealname)'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingRealname)'\"'\"']), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', ['\"'\"'attempt := newGatewayAttempt'\"'\"', '\"'\"'h.observation.ApplyTrafficObservation'\"'\"', '\"'\"'changed := decision.IncrementMB > 0 || decision.CrossMonth'\"'\"', '\"'\"'if changed {'\"'\"', '\"'\"'IntegrationResultSuccess, true'\"'\"']), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', ['\"'\"'attempt := newGatewayAttempt'\"'\"', '\"'\"'h.observation.ApplyNetworkObservation'\"'\"', '\"'\"'changed := decision.StatusChanged || decision.StopReasonChanged || decision.StopPolling'\"'\"', '\"'\"'if changed {'\"'\"', '\"'\"'IntegrationResultSuccess, true'\"'\"']), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', ['\"'\"'attempt := newGatewayAttempt'\"'\"', '\"'\"'h.observation.ApplyCardObservation'\"'\"', '\"'\"'changed := decision.StatusChanged'\"'\"', '\"'\"'if changed {'\"'\"', '\"'\"'IntegrationResultSuccess, true'\"'\"'])]; absent=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; unordered=[]; exec(\"for p,ts in ordered:\\n text=Path(p).read_text(); pos=-1; ok=True\\n for t in ts:\\n pos=text.find(t,pos+1)\\n if pos<0: ok=False; break\\n if not ok: unordered.append(f'\"'\"'{p}:{ts}'\"'\"')\"); present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in absent if t in Path(p).read_text()]; assert not missing and not unordered and not present, {'\"'\"'missing'\"'\"':missing,'\"'\"'unordered'\"'\"':unordered,'\"'\"'present'\"'\"':present}; print('\"'\"'OK: polling-load-control::正常无变化观测的低写入处理'\"'\"')'",
"literal_output": [
"OK: polling-load-control::正常无变化观测的低写入处理"
],
"exit_status": 0
}
},
{
"capability": "polling-load-control",
"requirement": "业务变化与轮询异常仍可追溯",
"spec": "openspec/specs/polling-load-control/spec.md",
"entries": [
"constants.TaskTypePollingCarddata",
"constants.TaskTypePollingCardStatus",
"constants.TaskTypePollingRealname"
],
"handler_consumer_job": [
"internal/task/polling_carddata_handler.go:PollingCarddataHandler.Handle",
"internal/task/polling_cardstatus_handler.go:PollingCardStatusHandler.Handle",
"internal/task/polling_realname_handler.go:PollingRealnameHandler.Handle",
"internal/task/polling_integration_log.go:recordGatewayAttempt"
],
"application_service_query": [
"internal/application/cardobservation/traffic.go:ApplyTrafficObservation",
"internal/application/cardobservation/network.go:ApplyNetworkObservation",
"internal/application/cardobservation/apply.go:ApplyCardObservation"
],
"domain_state_amount": [
"internal/task polling gateway failure/result paths",
"internal/application/cardobservation traffic/network Outbox and worker audit paths"
],
"store_migration_config": [
"internal/infrastructure/integrationlog.Repository"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingCarddata)'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingCarddata)'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingCarddata)'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingCardStatus)'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingCardStatus)'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingCardStatus)'\"'\"'), ('\"'\"'internal/task/polling_package_handler.go'\"'\"', '\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingPackage)'\"'\"'), ('\"'\"'internal/task/polling_package_handler.go'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingPackage)'\"'\"'), ('\"'\"'internal/task/polling_package_handler.go'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingPackage)'\"'\"'), ('\"'\"'internal/task/polling_protect_handler.go'\"'\"', '\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingProtect)'\"'\"'), ('\"'\"'internal/task/polling_protect_handler.go'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)'\"'\"'), ('\"'\"'internal/task/polling_protect_handler.go'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingProtect)'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingRealname)'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingRealname)'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingRealname)'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'IntegrationResultFailed'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'应用流量观测失败'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'IntegrationResultUnknown'\"'\"'), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', '\"'\"'return h.failAndRequeue'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'IntegrationResultFailed'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'应用网络状态观测失败'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'IntegrationResultUnknown'\"'\"'), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', '\"'\"'return h.failAndRequeue'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'IntegrationResultFailed'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'应用实名观测失败'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'IntegrationResultUnknown'\"'\"'), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', '\"'\"'return h.failAndRequeue'\"'\"')]; ordered=[('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', ['\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingCarddata)'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingCarddata)'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingCarddata)'\"'\"']), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', ['\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingCardStatus)'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingCardStatus)'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingCardStatus)'\"'\"']), ('\"'\"'internal/task/polling_package_handler.go'\"'\"', ['\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingPackage)'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingPackage)'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingPackage)'\"'\"']), ('\"'\"'internal/task/polling_protect_handler.go'\"'\"', ['\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingProtect)'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingProtect)'\"'\"']), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', ['\"'\"'!h.base.acquireConcurrency(ctx, constants.TaskTypePollingRealname)'\"'\"', '\"'\"'return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingRealname)'\"'\"', '\"'\"'defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingRealname)'\"'\"']), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', ['\"'\"'IntegrationResultFailed'\"'\"', '\"'\"'return h.failAndRequeue'\"'\"']), ('\"'\"'internal/task/polling_carddata_handler.go'\"'\"', ['\"'\"'IntegrationResultUnknown'\"'\"', '\"'\"'应用流量观测失败'\"'\"', '\"'\"'return h.failAndRequeue'\"'\"']), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', ['\"'\"'IntegrationResultFailed'\"'\"', '\"'\"'return h.failAndRequeue'\"'\"']), ('\"'\"'internal/task/polling_cardstatus_handler.go'\"'\"', ['\"'\"'IntegrationResultUnknown'\"'\"', '\"'\"'应用网络状态观测失败'\"'\"', '\"'\"'return h.failAndRequeue'\"'\"']), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', ['\"'\"'IntegrationResultFailed'\"'\"', '\"'\"'return h.failAndRequeue'\"'\"']), ('\"'\"'internal/task/polling_realname_handler.go'\"'\"', ['\"'\"'IntegrationResultUnknown'\"'\"', '\"'\"'应用实名观测失败'\"'\"', '\"'\"'return h.failAndRequeue'\"'\"'])]; absent=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; unordered=[]; exec(\"for p,ts in ordered:\\n text=Path(p).read_text(); pos=-1; ok=True\\n for t in ts:\\n pos=text.find(t,pos+1)\\n if pos<0: ok=False; break\\n if not ok: unordered.append(f'\"'\"'{p}:{ts}'\"'\"')\"); present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in absent if t in Path(p).read_text()]; assert not missing and not unordered and not present, {'\"'\"'missing'\"'\"':missing,'\"'\"'unordered'\"'\"':unordered,'\"'\"'present'\"'\"':present}; print('\"'\"'OK: polling-load-control::业务变化与轮询异常仍可追溯'\"'\"')'",
"literal_output": [
"OK: polling-load-control::业务变化与轮询异常仍可追溯"
],
"exit_status": 0
}
},
{
"capability": "polling-operations",
"requirement": "停复机遵循实际生效实名策略",
"spec": "openspec/specs/polling-operations/spec.md",
"entries": [
"constants.TaskTypePollingPackage"
],
"handler_consumer_job": [
"internal/task/polling_package_handler.go:PollingPackageHandler.Handle",
"pkg/queue/handler.go:registerPollingHandlers"
],
"application_service_query": [
"internal/task/polling_package_handler.go:PollingPackageHandler.Handle -> StopResumeServiceInterface.EvaluateAndAct",
"internal/service/iot_card/stop_resume_service.go:StopResumeService.EvaluateAndAct/isRealnameOK"
],
"domain_state_amount": [
"internal/model/iot_card.go:IotCard.RealnamePolicy",
"internal/model/device.go:Device.RealnamePolicy"
],
"store_migration_config": [
"internal/task/polling_package_handler.go:IotCardStore.GetByID"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: polling-operations::停复机遵循实际生效实名策略\")'",
"literal_output": [
"OK: polling-operations::停复机遵循实际生效实名策略"
],
"exit_status": 0
}
},
{
"capability": "qicheng-migration-package-lifecycle-overrides",
"requirement": "迁移配置支持逐卡套餐生命周期覆盖",
"spec": "openspec/specs/qicheng-migration-package-lifecycle-overrides/spec.md",
"entries": [
"scripts/migration/migrate_runtime.py"
],
"handler_consumer_job": [
"scripts/migration/migrate_runtime.py:main"
],
"application_service_query": [
"scripts/migration/lib/mapping_loader.py:Mapping.lookup_package_lifecycle_override",
"scripts/migration/lib/sql_builder.py:_apply_package_lifecycle_override"
],
"domain_state_amount": [
"scripts/migration/lib/mapping_loader.py:PackageLifecycleOverride"
],
"store_migration_config": [
"scripts/migration/config/mapping.yaml",
"scripts/migration/lib/legacy_query.py:tbl_card_life"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'scripts/migration/lib/mapping_loader.py'\"'\"', '\"'\"'package_lifecycle_overrides'\"'\"'), ('\"'\"'scripts/migration/lib/sql_builder.py'\"'\"', '\"'\"'_apply_package_lifecycle_override'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: qicheng-migration-package-lifecycle-overrides::迁移配置支持逐卡套餐生命周期覆盖'\"'\"')'",
"literal_output": [
"OK: qicheng-migration-package-lifecycle-overrides::迁移配置支持逐卡套餐生命周期覆盖"
],
"exit_status": 0
}
},
{
"capability": "qicheng-migration-package-lifecycle-overrides",
"requirement": "覆盖必须完整且可验证",
"spec": "openspec/specs/qicheng-migration-package-lifecycle-overrides/spec.md",
"entries": [
"scripts/migration/migrate_runtime.py"
],
"handler_consumer_job": [
"scripts/migration/migrate_runtime.py:main"
],
"application_service_query": [
"scripts/migration/lib/sql_builder.py:_apply_package_lifecycle_override"
],
"domain_state_amount": [
"scripts/migration/lib/sql_builder.py:selected != migratable"
],
"store_migration_config": [
"scripts/migration/lib/mapping_loader.py:_load_package_lifecycle_overrides"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'scripts/migration/lib/sql_builder.py'\"'\"', '\"'\"'套餐生命周期覆盖必须完整裁决原本可迁移记录'\"'\"'), ('\"'\"'scripts/migration/lib/mapping_loader.py'\"'\"', '\"'\"'生命周期 ID 不可重复或跨状态重复'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: qicheng-migration-package-lifecycle-overrides::覆盖必须完整且可验证'\"'\"')'",
"literal_output": [
"OK: qicheng-migration-package-lifecycle-overrides::覆盖必须完整且可验证"
],
"exit_status": 0
}
},
{
"capability": "qicheng-migration-package-lifecycle-overrides",
"requirement": "未覆盖资产维持严格冲突阻断",
"spec": "openspec/specs/qicheng-migration-package-lifecycle-overrides/spec.md",
"entries": [
"scripts/migration/migrate_runtime.py"
],
"handler_consumer_job": [
"scripts/migration/migrate_runtime.py:main"
],
"application_service_query": [
"scripts/migration/lib/sql_builder.py:_resolve_runtime_packages"
],
"domain_state_amount": [
"scripts/migration/lib/sql_builder.py:multiple_active_packages/blocked"
],
"store_migration_config": [
"scripts/migration/lib/legacy_query.py:tbl_card_life"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'scripts/migration/lib/sql_builder.py'\"'\"', '\"'\"'multiple_active_packages'\"'\"'), ('\"'\"'scripts/migration/lib/sql_builder.py'\"'\"', '\"'\"'同一资产存在多个当前生效正式套餐,无法唯一裁决'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: qicheng-migration-package-lifecycle-overrides::未覆盖资产维持严格冲突阻断'\"'\"')'",
"literal_output": [
"OK: qicheng-migration-package-lifecycle-overrides::未覆盖资产维持严格冲突阻断"
],
"exit_status": 0
}
},
{
"capability": "qicheng-migration-package-usage",
"requirement": "迁移套餐使用记录保存计时条款快照",
"spec": "openspec/specs/qicheng-migration-package-usage/spec.md",
"entries": [
"scripts/migration/migrate_runtime.py"
],
"handler_consumer_job": [
"scripts/migration/migrate_runtime.py:main"
],
"application_service_query": [
"scripts/migration/lib/sql_builder.py:_write_package_usages"
],
"domain_state_amount": [
"scripts/migration/lib/sql_builder.py:expiry_base_snapshot/calendar_type_snapshot/duration snapshots"
],
"store_migration_config": [
"migrations/000163_add_package_usage_terms_snapshots.up.sql"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'scripts/migration/lib/sql_builder.py'\"'\"', '\"'\"'expiry_base_snapshot, calendar_type_snapshot, duration_months_snapshot, duration_days_snapshot'\"'\"'), ('\"'\"'scripts/migration/lib/sql_builder.py'\"'\"', '\"'\"'INSERT INTO tb_package_usage'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: qicheng-migration-package-usage::迁移套餐使用记录保存计时条款快照'\"'\"')'",
"literal_output": [
"OK: qicheng-migration-package-usage::迁移套餐使用记录保存计时条款快照"
],
"exit_status": 0
}
},
{
"capability": "qicheng-migration-package-usage",
"requirement": "迁移套餐使用 SQL 满足快照完整性约束",
"spec": "openspec/specs/qicheng-migration-package-usage/spec.md",
"entries": [
"scripts/migration/migrate_runtime.py"
],
"handler_consumer_job": [
"scripts/migration/migrate_runtime.py:main"
],
"application_service_query": [
"scripts/migration/lib/sql_builder.py:_write_package_usages"
],
"domain_state_amount": [
"migrations/000163_add_package_usage_terms_snapshots.up.sql:validate_package_usage_terms_snapshot"
],
"store_migration_config": [
"migrations/000163_add_package_usage_terms_snapshots.up.sql:trg_validate_package_usage_terms_snapshot"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'migrations/000163_add_package_usage_terms_snapshots.up.sql'\"'\"', '\"'\"'CREATE TRIGGER trg_validate_package_usage_terms_snapshot'\"'\"'), ('\"'\"'migrations/000163_add_package_usage_terms_snapshots.up.sql'\"'\"', '\"'\"'套餐使用记录计时快照不完整或无效'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: qicheng-migration-package-usage::迁移套餐使用 SQL 满足快照完整性约束'\"'\"')'",
"literal_output": [
"OK: qicheng-migration-package-usage::迁移套餐使用 SQL 满足快照完整性约束"
],
"exit_status": 0
}
},
{
"capability": "refund-approval",
"requirement": "零金额退款审批通过",
"spec": "openspec/specs/refund-approval/spec.md",
"entries": [
"POST /api/admin/refunds/{id}/approve"
],
"handler_consumer_job": [
"internal/handler/admin/refund.go:RefundHandler.ApproveBodyParser 后直接传 Service该方法体无 Validate/ValidateStruct 调用)",
"cmd/worker/main.go:ApprovalBusinessTypeRefund -> refund.Service.Handle"
],
"application_service_query": [
"internal/service/refund/service.go:Service.Approve/validateApprovedRefundAmount",
"internal/service/refund/approval_decision.go:Service.applyApprovedDecision"
],
"domain_state_amount": [
"Service 和企业微信受控审批决策路径可传递/处理 approvedAmount == 0公开 DTO 字段仍标记 omitempty,min=1"
],
"store_migration_config": [
"internal/model/refund.go:RefundRequest",
"migrations/archive/000093_create_refund_request.up.sql"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/model/dto/refund_dto.go'\"'\"', '\"'\"'validate:\"omitempty,min=1\"'\"'\"'), ('\"'\"'internal/handler/admin/refund.go'\"'\"', '\"'\"'func (h *RefundHandler) Approve'\"'\"'), ('\"'\"'internal/handler/admin/refund.go'\"'\"', '\"'\"'if err := c.BodyParser(&req); err != nil'\"'\"'), ('\"'\"'internal/handler/admin/refund.go'\"'\"', '\"'\"'h.service.Approve(c.UserContext(), uint(id), &req)'\"'\"'), ('\"'\"'internal/service/refund/service.go'\"'\"', '\"'\"'if approvedAmount < 0'\"'\"'), ('\"'\"'internal/service/refund/approval_decision.go'\"'\"', '\"'\"'approvedAmount := refund.RequestedRefundAmount'\"'\"')]; ordered=[('\"'\"'internal/handler/admin/refund.go'\"'\"', ['\"'\"'func (h *RefundHandler) Approve'\"'\"', '\"'\"'if err := c.BodyParser(&req); err != nil'\"'\"', '\"'\"'h.service.Approve(c.UserContext(), uint(id), &req)'\"'\"'])]; absent=[('\"'\"'internal/handler/admin/refund.go'\"'\"', '\"'\"'ValidateStruct'\"'\"'), ('\"'\"'internal/handler/admin/refund.go'\"'\"', '\"'\"'.Validate('\"'\"')]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; unordered=[]; exec(\"for p,ts in ordered:\\n text=Path(p).read_text(); pos=-1; ok=True\\n for t in ts:\\n pos=text.find(t,pos+1)\\n if pos<0: ok=False; break\\n if not ok: unordered.append(f'\"'\"'{p}:{ts}'\"'\"')\"); present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in absent if t in Path(p).read_text()]; assert not missing and not unordered and not present, {'\"'\"'missing'\"'\"':missing,'\"'\"'unordered'\"'\"':unordered,'\"'\"'present'\"'\"':present}; print('\"'\"'OK: refund-approval::零金额退款审批通过'\"'\"')'",
"literal_output": [
"OK: refund-approval::零金额退款审批通过"
],
"exit_status": 0
}
},
{
"capability": "refund-approval",
"requirement": "零金额退款不产生资金回款",
"spec": "openspec/specs/refund-approval/spec.md",
"entries": [
"POST /api/admin/refunds/{id}/approve"
],
"handler_consumer_job": [
"internal/handler/admin/refund.go:RefundHandler.ApproveBodyParser 后直接传 Service该方法体无 Validate/ValidateStruct 调用)",
"cmd/worker/main.go:ApprovalBusinessTypeRefund -> refund.Service.Handle"
],
"application_service_query": [
"internal/service/refund/service.go:Service.Approve/validateApprovedRefundAmount",
"internal/service/refund/approval_decision.go:Service.applyApprovedDecision"
],
"domain_state_amount": [
"Service 和企业微信受控审批决策路径可传递/处理 approvedAmount == 0公开 DTO 字段仍标记 omitempty,min=1"
],
"store_migration_config": [
"internal/model/refund.go:RefundRequest",
"migrations/archive/000093_create_refund_request.up.sql"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/model/dto/refund_dto.go'\"'\"', '\"'\"'validate:\"omitempty,min=1\"'\"'\"'), ('\"'\"'internal/handler/admin/refund.go'\"'\"', '\"'\"'func (h *RefundHandler) Approve'\"'\"'), ('\"'\"'internal/handler/admin/refund.go'\"'\"', '\"'\"'if err := c.BodyParser(&req); err != nil'\"'\"'), ('\"'\"'internal/handler/admin/refund.go'\"'\"', '\"'\"'h.service.Approve(c.UserContext(), uint(id), &req)'\"'\"'), ('\"'\"'internal/service/refund/service.go'\"'\"', '\"'\"'if approvedAmount < 0'\"'\"'), ('\"'\"'internal/service/refund/approval_decision.go'\"'\"', '\"'\"'approvedAmount := refund.RequestedRefundAmount'\"'\"'), ('\"'\"'internal/service/refund/service.go'\"'\"', '\"'\"'if amount == 0 || order.PaymentMethod != model.PaymentMethodWallet'\"'\"')]; ordered=[('\"'\"'internal/handler/admin/refund.go'\"'\"', ['\"'\"'func (h *RefundHandler) Approve'\"'\"', '\"'\"'if err := c.BodyParser(&req); err != nil'\"'\"', '\"'\"'h.service.Approve(c.UserContext(), uint(id), &req)'\"'\"'])]; absent=[('\"'\"'internal/handler/admin/refund.go'\"'\"', '\"'\"'ValidateStruct'\"'\"'), ('\"'\"'internal/handler/admin/refund.go'\"'\"', '\"'\"'.Validate('\"'\"')]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; unordered=[]; exec(\"for p,ts in ordered:\\n text=Path(p).read_text(); pos=-1; ok=True\\n for t in ts:\\n pos=text.find(t,pos+1)\\n if pos<0: ok=False; break\\n if not ok: unordered.append(f'\"'\"'{p}:{ts}'\"'\"')\"); present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in absent if t in Path(p).read_text()]; assert not missing and not unordered and not present, {'\"'\"'missing'\"'\"':missing,'\"'\"'unordered'\"'\"':unordered,'\"'\"'present'\"'\"':present}; print('\"'\"'OK: refund-approval::零金额退款不产生资金回款'\"'\"')'",
"literal_output": [
"OK: refund-approval::零金额退款不产生资金回款"
],
"exit_status": 0
}
},
{
"capability": "refund-approval",
"requirement": "非法退款金额仍被拒绝",
"spec": "openspec/specs/refund-approval/spec.md",
"entries": [
"POST /api/admin/refunds/{id}/approve"
],
"handler_consumer_job": [
"internal/handler/admin/refund.go:RefundHandler.ApproveBodyParser 后直接传 Service该方法体无 Validate/ValidateStruct 调用)",
"cmd/worker/main.go:ApprovalBusinessTypeRefund -> refund.Service.Handle"
],
"application_service_query": [
"internal/service/refund/service.go:Service.Approve/validateApprovedRefundAmount",
"internal/service/refund/approval_decision.go:Service.applyApprovedDecision"
],
"domain_state_amount": [
"Service 和企业微信受控审批决策路径可传递/处理 approvedAmount == 0公开 DTO 字段仍标记 omitempty,min=1"
],
"store_migration_config": [
"internal/model/refund.go:RefundRequest",
"migrations/archive/000093_create_refund_request.up.sql"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'internal/model/dto/refund_dto.go'\"'\"', '\"'\"'validate:\"omitempty,min=1\"'\"'\"'), ('\"'\"'internal/handler/admin/refund.go'\"'\"', '\"'\"'func (h *RefundHandler) Approve'\"'\"'), ('\"'\"'internal/handler/admin/refund.go'\"'\"', '\"'\"'if err := c.BodyParser(&req); err != nil'\"'\"'), ('\"'\"'internal/handler/admin/refund.go'\"'\"', '\"'\"'h.service.Approve(c.UserContext(), uint(id), &req)'\"'\"'), ('\"'\"'internal/service/refund/service.go'\"'\"', '\"'\"'if approvedAmount < 0'\"'\"'), ('\"'\"'internal/service/refund/approval_decision.go'\"'\"', '\"'\"'approvedAmount := refund.RequestedRefundAmount'\"'\"'), ('\"'\"'internal/service/refund/service.go'\"'\"', '\"'\"'审批退款金额不能大于申请退款金额'\"'\"'), ('\"'\"'internal/service/refund/service.go'\"'\"', '\"'\"'申请退款金额不能大于订单实收金额'\"'\"')]; ordered=[('\"'\"'internal/handler/admin/refund.go'\"'\"', ['\"'\"'func (h *RefundHandler) Approve'\"'\"', '\"'\"'if err := c.BodyParser(&req); err != nil'\"'\"', '\"'\"'h.service.Approve(c.UserContext(), uint(id), &req)'\"'\"'])]; absent=[('\"'\"'internal/handler/admin/refund.go'\"'\"', '\"'\"'ValidateStruct'\"'\"'), ('\"'\"'internal/handler/admin/refund.go'\"'\"', '\"'\"'.Validate('\"'\"')]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; unordered=[]; exec(\"for p,ts in ordered:\\n text=Path(p).read_text(); pos=-1; ok=True\\n for t in ts:\\n pos=text.find(t,pos+1)\\n if pos<0: ok=False; break\\n if not ok: unordered.append(f'\"'\"'{p}:{ts}'\"'\"')\"); present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in absent if t in Path(p).read_text()]; assert not missing and not unordered and not present, {'\"'\"'missing'\"'\"':missing,'\"'\"'unordered'\"'\"':unordered,'\"'\"'present'\"'\"':present}; print('\"'\"'OK: refund-approval::非法退款金额仍被拒绝'\"'\"')'",
"literal_output": [
"OK: refund-approval::非法退款金额仍被拒绝"
],
"exit_status": 0
}
},
{
"capability": "shop-bulk-import",
"requirement": "导入输入必须在生成 SQL 前完成全量预检",
"spec": "openspec/specs/shop-bulk-import/spec.md",
"entries": [
"scripts/migration/import_shops.py"
],
"handler_consumer_job": [
"scripts/migration/import_shops.py:main"
],
"application_service_query": [
"scripts/migration/lib/shop_import.py:load_config/load_plan"
],
"domain_state_amount": [
"scripts/migration/lib/shop_import.py:ShopImportPlan.errors"
],
"store_migration_config": [
"scripts/migration/config/shop_bulk_import.yaml"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'scripts/migration/import_shops.py'\"'\"', '\"'\"'plan = load_plan(csv_path, config)'\"'\"'), ('\"'\"'scripts/migration/import_shops.py'\"'\"', '\"'\"'if plan.errors:'\"'\"'), ('\"'\"'scripts/migration/lib/shop_import.py'\"'\"', '\"'\"'def load_plan'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: shop-bulk-import::导入输入必须在生成 SQL 前完成全量预检'\"'\"')'",
"literal_output": [
"OK: shop-bulk-import::导入输入必须在生成 SQL 前完成全量预检"
],
"exit_status": 0
}
},
{
"capability": "shop-bulk-import",
"requirement": "生成的 SQL 必须在写入前校验目标库状态",
"spec": "openspec/specs/shop-bulk-import/spec.md",
"entries": [
"scripts/migration/import_shops.py"
],
"handler_consumer_job": [
"scripts/migration/import_shops.py:main"
],
"application_service_query": [
"scripts/migration/lib/shop_sql.py:build_shop_sql/_preflight_sql"
],
"domain_state_amount": [
"scripts/migration/lib/shop_sql.py:DO $shop_import_guard$"
],
"store_migration_config": [
"scripts/migration/lib/shop_sql.py:tb_role/tb_account/tb_shop guards"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'scripts/migration/lib/shop_sql.py'\"'\"', '\"'\"'所有目标库守卫必须在业务写入前完成。'\"'\"'), ('\"'\"'scripts/migration/lib/shop_sql.py'\"'\"', '\"'\"'DO $shop_import_guard$'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: shop-bulk-import::生成的 SQL 必须在写入前校验目标库状态'\"'\"')'",
"literal_output": [
"OK: shop-bulk-import::生成的 SQL 必须在写入前校验目标库状态"
],
"exit_status": 0
}
},
{
"capability": "shop-bulk-import",
"requirement": "批量导入 SQL 必须创建完整的店铺初始事实",
"spec": "openspec/specs/shop-bulk-import/spec.md",
"entries": [
"scripts/migration/import_shops.py"
],
"handler_consumer_job": [
"scripts/migration/import_shops.py:main"
],
"application_service_query": [
"scripts/migration/lib/shop_sql.py:_row_sql/_audit_sql"
],
"domain_state_amount": [
"scripts/migration/lib/shop_import.py:initial_password_hash"
],
"store_migration_config": [
"scripts/migration/lib/shop_sql.py:tb_shop/tb_account/tb_account_role/tb_shop_role/tb_agent_wallet/tb_audit_event"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'scripts/migration/lib/shop_sql.py'\"'\"', '\"'\"'INSERT INTO tb_shop'\"'\"'), ('\"'\"'scripts/migration/lib/shop_sql.py'\"'\"', '\"'\"'INSERT INTO tb_account_role'\"'\"'), ('\"'\"'scripts/migration/lib/shop_sql.py'\"'\"', '\"'\"'INSERT INTO tb_agent_wallet'\"'\"'), ('\"'\"'scripts/migration/lib/shop_sql.py'\"'\"', '\"'\"'INSERT INTO tb_audit_event'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: shop-bulk-import::批量导入 SQL 必须创建完整的店铺初始事实'\"'\"')'",
"literal_output": [
"OK: shop-bulk-import::批量导入 SQL 必须创建完整的店铺初始事实"
],
"exit_status": 0
}
},
{
"capability": "shop-bulk-import",
"requirement": "导入执行必须由维护者审核后以单事务手工确认",
"spec": "openspec/specs/shop-bulk-import/spec.md",
"entries": [
"scripts/migration/import_shops.py"
],
"handler_consumer_job": [
"scripts/migration/import_shops.py:main"
],
"application_service_query": [
"scripts/migration/lib/shop_sql.py:build_shop_sql"
],
"domain_state_amount": [
"scripts/migration/lib/shop_sql.py:review before psql -1"
],
"store_migration_config": [
"scripts/migration/lib/shop_sql.py:psql -1"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'scripts/migration/lib/shop_sql.py'\"'\"', '\"'\"'执行前必须审核结果清单与摘要。'\"'\"'), ('\"'\"'scripts/migration/lib/shop_sql.py'\"'\"', '\"'\"'psql -1 -v ON_ERROR_STOP=1 -f <此文件>'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: shop-bulk-import::导入执行必须由维护者审核后以单事务手工确认'\"'\"')'",
"literal_output": [
"OK: shop-bulk-import::导入执行必须由维护者审核后以单事务手工确认"
],
"exit_status": 0
}
},
{
"capability": "shop-bulk-import",
"requirement": "导入结果必须可审核和重跑定位",
"spec": "openspec/specs/shop-bulk-import/spec.md",
"entries": [
"scripts/migration/import_shops.py"
],
"handler_consumer_job": [
"scripts/migration/import_shops.py:main"
],
"application_service_query": [
"scripts/migration/lib/shop_import.py:write_errors/write_results/write_summary"
],
"domain_state_amount": [
"scripts/migration/lib/shop_import.py:batch_id/ShopImportResult"
],
"store_migration_config": [
"scripts/migration/config/shop_bulk_import.yaml"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[('\"'\"'scripts/migration/import_shops.py'\"'\"', '\"'\"'write_results(result_path, plan.results)'\"'\"'), ('\"'\"'scripts/migration/import_shops.py'\"'\"', '\"'\"'write_summary(summary_path, plan, sql_path)'\"'\"'), ('\"'\"'scripts/migration/lib/shop_import.py'\"'\"', '\"'\"'batch_id={plan.config.batch_id}'\"'\"')]; negative=[]; missing=[f'\"'\"'{p}:{t}'\"'\"' for p,t in checks if t not in Path(p).read_text()]; present=[f'\"'\"'{p}:{t}'\"'\"' for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {'\"'\"'missing'\"'\"': missing, '\"'\"'present'\"'\"': present}; print('\"'\"'OK: shop-bulk-import::导入结果必须可审核和重跑定位'\"'\"')'",
"literal_output": [
"OK: shop-bulk-import::导入结果必须可审核和重跑定位"
],
"exit_status": 0
}
},
{
"capability": "employee-collection-bill",
"requirement": "员工代收款账单来源、金额与建账判据",
"spec": "openspec/specs/employee-collection-bill/spec.md",
"entries": [
"POST /api/admin/agent-recharges/{id}/offline-pay",
"POST /api/admin/orders",
"constants.OutboxEventTypeApprovalTerminalDecision"
],
"handler_consumer_job": [
"internal/service/order/service.go:Service.CreateAdminOrder",
"internal/service/agent_recharge/service.go:Service.OfflinePay",
"internal/application/agentrecharge/approval_decision.go:ApprovalDecisionHandler.Handle"
],
"application_service_query": [
"internal/application/employeecollection/bill_creation.go:BillCreationService.CreateFromOrderInTx",
"internal/application/employeecollection/bill_creation.go:BillCreationService.CreateFromRechargeInTx"
],
"domain_state_amount": [
"internal/domain/employeecollection/order_bill.go:ShouldCreateBillForOrder",
"internal/domain/employeecollection/order_bill.go:ShouldCreateBillForRecharge",
"internal/domain/employeecollection/order_bill.go:OrderBillSubjectFromOrder"
],
"store_migration_config": [
"internal/model/employee_collection.go:EmployeeCollectionBill",
"migrations/000212_add_employee_collection_bills.up.sql:tb_employee_collection_bill"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/domain/employeecollection/order_bill.go\", \"func ShouldCreateBillForOrder(subject OrderBillSubject) bool\"), (\"internal/domain/employeecollection/order_bill.go\", \"subject.PaymentMethod == model.PaymentMethodOffline\"), (\"internal/service/order/service.go\", \"s.billCreation.CreateFromOrderInTx(ctx, tx, order, hasGiftPackage)\"), (\"internal/service/agent_recharge/service.go\", \"s.billCreation.CreateFromRechargeInTx(ctx, tx, record)\"), (\"internal/application/agentrecharge/approval_decision.go\", \"h.billCreation.CreateFromRechargeInTx(ctx, tx, record)\"), (\"internal/application/employeecollection/bill_creation.go\", \"Columns: []clause.Column{{Name: \\\"source_key\\\"}}\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: employee-collection-bill::员工代收款账单来源、金额与建账判据\")'",
"literal_output": [
"OK: employee-collection-bill::员工代收款账单来源、金额与建账判据"
],
"exit_status": 0
}
},
{
"capability": "employee-collection-bill",
"requirement": "账单余额、状态与关闭",
"spec": "openspec/specs/employee-collection-bill/spec.md",
"entries": [
"GET /api/admin/employee-collection-bills",
"GET /api/admin/employee-collection-bills/statistics",
"GET /api/admin/employee-collection-bills/{id}",
"POST /api/admin/employee-collection-bills/{id}/close",
"GET /api/admin/employee-collection-applications",
"GET /api/admin/employee-collection-applications/{id}"
],
"handler_consumer_job": [
"internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.ListBills",
"internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.StatisticsBills",
"internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.GetBill",
"internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.CloseBill"
],
"application_service_query": [
"internal/query/employeecollection/bill.go:BillQuery.List/Statistics/Detail",
"internal/query/employeecollection/bill.go:applyBillVisibility",
"internal/query/employeecollection/application.go:applyApplicationVisibility",
"internal/application/employeecollection/bill_close.go:BillCloseService.Close"
],
"domain_state_amount": [
"internal/domain/employeecollection/bill.go:BillAmounts.Reserve/Release/Approve",
"internal/domain/employeecollection/bill.go:BillAmounts.DerivedStatus",
"internal/domain/employeecollection/bill.go:ValidateBillClose"
],
"store_migration_config": [
"internal/model/employee_collection.go:EmployeeCollectionBill",
"migrations/000212_add_employee_collection_bills.up.sql:tb_employee_collection_bill"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/domain/employeecollection/bill.go\", \"func (a BillAmounts) Reserve(amount int64) (BillAmounts, error)\"), (\"internal/domain/employeecollection/bill.go\", \"func (a BillAmounts) DerivedStatus() int\"), (\"internal/application/employeecollection/bill_close.go\", \"func (s *BillCloseService) Close(ctx context.Context, id uint, reason string)\"), (\"internal/query/employeecollection/bill.go\", \"func applyBillVisibility(ctx context.Context, query *gorm.DB) (*gorm.DB, error)\"), (\"internal/query/employeecollection/bill.go\", \"func (q *BillQuery) Statistics(\"), (\"internal/query/employeecollection/application.go\", \"func applyApplicationVisibility(ctx context.Context, query *gorm.DB) (*gorm.DB, error)\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: employee-collection-bill::账单余额、状态与关闭\")'",
"literal_output": [
"OK: employee-collection-bill::账单余额、状态与关闭"
],
"exit_status": 0
}
},
{
"capability": "employee-collection-bill",
"requirement": "外部付款核销申请与分摊",
"spec": "openspec/specs/employee-collection-bill/spec.md",
"entries": [
"POST /api/admin/employee-collection-applications",
"GET /api/admin/employee-collection-applications",
"GET /api/admin/employee-collection-applications/{id}"
],
"handler_consumer_job": [
"internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.CreateApplication",
"internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.ListApplications",
"internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.GetApplication",
"internal/handler/admin/employee_collection.go:bindApplicationRequest"
],
"application_service_query": [
"internal/application/employeecollection/application.go:ApplicationService.Create/submit",
"internal/application/employeecollection/application.go:lockBillsInAscendingOrder",
"internal/application/employeecollection/application.go:createAllocations",
"internal/query/employeecollection/application.go:ApplicationQuery.List/Detail"
],
"domain_state_amount": [
"internal/domain/employeecollection/allocation.go:ValidateAllocations",
"internal/domain/employeecollection/bill.go:BillAmounts.Reserve"
],
"store_migration_config": [
"migrations/000212_add_employee_collection_bills.up.sql:tb_employee_collection_application",
"migrations/000212_add_employee_collection_bills.up.sql:tb_employee_collection_application_allocation",
"internal/model/employee_collection.go:EmployeeCollectionApplicationAllocation"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/domain/employeecollection/allocation.go\", \"func ValidateAllocations(paidAmount int64, candidates []AllocationCandidate) error\"), (\"internal/application/employeecollection/application.go\", \"func (s *ApplicationService) Create(ctx context.Context, command SubmitApplicationCommand) (*ApplicationSubmitResult, error)\"), (\"internal/handler/admin/employee_collection.go\", \"func (h *EmployeeCollectionHandler) CreateApplication(c *fiber.Ctx) error\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: employee-collection-bill::外部付款核销申请与分摊\")'",
"literal_output": [
"OK: employee-collection-bill::外部付款核销申请与分摊"
],
"exit_status": 0
}
},
{
"capability": "employee-collection-bill",
"requirement": "核销申请审批、重提与幂等",
"spec": "openspec/specs/employee-collection-bill/spec.md",
"entries": [
"POST /api/callback/wecom/approval/{application_id}",
"constants.OutboxEventTypeApprovalSubmissionRequested",
"constants.OutboxEventTypeApprovalTerminalDecision",
"constants.TaskTypeWeComApprovalRecovery",
"constants.TaskTypeWeComApprovalSync",
"POST /api/admin/employee-collection-applications",
"PUT /api/admin/employee-collection-applications/{id}"
],
"handler_consumer_job": [
"internal/application/employeecollection/approval_decision.go:ApprovalDecisionHandler.Handle",
"internal/handler/callback/wecom_approval.go:WeComApprovalHandler.Receive",
"cmd/worker/main.go:ApprovalBusinessTypeEmployeeCollection -> employeecollectionApp.NewApprovalDecisionHandler",
"cmd/worker/main.go:TaskTypeWeComApprovalSync/TaskTypeWeComApprovalRecovery -> 既有查询与恢复机制"
],
"application_service_query": [
"internal/application/employeecollection/application.go:ApplicationService.Resubmit",
"internal/application/employeecollection/approval_decision.go:applyApproved/applyClosed/applyRevoked/revokeApproved/revokePending",
"internal/application/approval/create.go:CreationService.CreateInTx",
"internal/application/employeecollection/application.go:buildAttempt/nextAttemptNo"
],
"domain_state_amount": [
"internal/domain/employeecollection/application.go:ValidateApplicationResubmit",
"pkg/constants/approval.go:ApprovalBusinessTypeEmployeeCollection"
],
"store_migration_config": [
"migrations/000212_add_employee_collection_bills.up.sql:tb_employee_collection_application_attempt",
"internal/model/employee_collection.go:EmployeeCollectionApplicationAttempt"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/application/employeecollection/application.go\", \"func (s *ApplicationService) Resubmit(ctx context.Context, applicationID uint, command SubmitApplicationCommand) (*ApplicationSubmitResult, error)\"), (\"internal/application/employeecollection/approval_decision.go\", \"func (h *ApprovalDecisionHandler) Handle(ctx context.Context, event approvalapp.TerminalDecisionEvent) error\"), (\"internal/application/employeecollection/approval_decision.go\", \"func (h *ApprovalDecisionHandler) revokePending(\"), (\"pkg/constants/approval.go\", \"ApprovalBusinessTypeEmployeeCollection = \\\"employee_collection_approval\\\"\"), (\"cmd/worker/main.go\", \"constants.ApprovalBusinessTypeEmployeeCollection: employeecollectionApp.NewApprovalDecisionHandler(\"), (\"internal/handler/callback/wecom_approval.go\", \"func (h *WeComApprovalHandler) Receive(c *fiber.Ctx) error\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: employee-collection-bill::核销申请审批、重提与幂等\")'",
"literal_output": [
"OK: employee-collection-bill::核销申请审批、重提与幂等"
],
"exit_status": 0
}
},
{
"capability": "employee-collection-bill",
"requirement": "收款方式字典、退款联动与可追溯性",
"spec": "openspec/specs/employee-collection-bill/spec.md",
"entries": [
"constants.OutboxEventTypeApprovalTerminalDecision",
"GET /api/admin/employee-collection-payment-methods",
"POST /api/admin/employee-collection-payment-methods",
"PUT /api/admin/employee-collection-payment-methods/{id}",
"DELETE /api/admin/employee-collection-payment-methods/{id}"
],
"handler_consumer_job": [
"internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.ListPaymentMethods/CreatePaymentMethod/UpdatePaymentMethod/DeletePaymentMethod",
"internal/routes/employee_collection.go:requireEmployeeCollectionConfigAccess",
"internal/service/refund/approval_decision.go:Service.applyApprovedDecision"
],
"application_service_query": [
"internal/application/employeecollection/payment_method.go:PaymentMethodService.Create/Update/Delete",
"internal/application/employeecollection/refund_offset.go:RefundOffsetService.ApplyInTx"
],
"domain_state_amount": [
"internal/domain/employeecollection/bill.go:BillAmounts.ReduceReceivable",
"internal/domain/employeecollection/refund_offset.go:DecideRefundOffset"
],
"store_migration_config": [
"migrations/000212_add_employee_collection_bills.up.sql:tb_employee_collection_payment_method",
"migrations/000212_add_employee_collection_bills.up.sql:tb_employee_collection_bill_refund",
"internal/model/employee_collection.go:EmployeeCollectionPaymentMethod",
"internal/model/employee_collection.go:EmployeeCollectionBillRefund"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/application/employeecollection/payment_method.go\", \"func (s *PaymentMethodService) Delete(ctx context.Context, id uint) error\"), (\"internal/application/employeecollection/payment_method.go\", \"func countPaymentMethodReferences(\"), (\"internal/routes/employee_collection.go\", \"func requireEmployeeCollectionConfigAccess(handler fiber.Handler) fiber.Handler\"), (\"internal/application/employeecollection/refund_offset.go\", \"func (s *RefundOffsetService) ApplyInTx(ctx context.Context, tx *gorm.DB, source RefundOffsetSource) error\"), (\"internal/domain/employeecollection/refund_offset.go\", \"func DecideRefundOffset(bill BillAmounts, refundAmount int64) (RefundOffsetDecision, error)\"), (\"internal/domain/employeecollection/bill.go\", \"func (a BillAmounts) ReduceReceivable(amount int64) (BillAmounts, error)\"), (\"internal/service/refund/approval_decision.go\", \"s.refundOffset.ApplyInTx(ctx, tx, employeecollectionapp.RefundOffsetSource{\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: employee-collection-bill::收款方式字典、退款联动与可追溯性\")'",
"literal_output": [
"OK: employee-collection-bill::收款方式字典、退款联动与可追溯性"
],
"exit_status": 0
}
},
{
"capability": "employee-collection-bill",
"requirement": "员工账单列表查询与筛选",
"spec": "openspec/specs/employee-collection-bill/spec.md",
"entries": [
"GET /api/admin/employee-collection-bills"
],
"handler_consumer_job": [
"internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.ListBills旧参数名与旧格式拒绝"
],
"application_service_query": [
"internal/query/employeecollection/bill.go:BillQuery.List",
"internal/query/employeecollection/bill.go:applyBillFilters账单编号、模糊、时间与核销通过时间筛选"
],
"domain_state_amount": [
"internal/model/dto/employee_collection_dto.go:UnsettledAmount/RemainingAmount未核销与剩余可核销两个口径并列"
],
"store_migration_config": [
"internal/query/employeecollection/bill.go核销通过时间取已通过分摊关联申请表 decided_at 的相关子查询)"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/query/employeecollection/bill.go\", \"核销通过时间经「状态为已通过的分摊」关联到申请表的审批终态时间decided_at\"), (\"internal/query/employeecollection/bill.go\", \"decided_application.decided_at >= ?\"), (\"internal/query/employeecollection/bill.go\", \"func billKeyword(value *string, fieldName string)\"), (\"internal/query/employeecollection/bill.go\", \"ILIKE ? OR EXISTS (\"), (\"internal/query/employeecollection/bill.go\", \"func applyBillFilters(\"), (\"internal/model/dto/employee_collection_dto.go\", \"DecidedStartTime *string\"), (\"internal/model/dto/employee_collection_dto.go\", \"剩余可核销金额(分),已关闭账单为 0\"), (\"internal/handler/admin/employee_collection.go\", \"rejectLegacyBillTimeParams\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: employee-collection-bill::员工账单列表查询与筛选\")'",
"literal_output": [
"OK: employee-collection-bill::员工账单列表查询与筛选"
],
"exit_status": 0
}
},
{
"capability": "employee-collection-bill",
"requirement": "员工账单详情投影",
"spec": "openspec/specs/employee-collection-bill/spec.md",
"entries": [
"GET /api/admin/employee-collection-bills/{id}"
],
"handler_consumer_job": [
"internal/handler/admin/employee_collection.go:EmployeeCollectionHandler.GetBill"
],
"application_service_query": [
"internal/query/employeecollection/bill.go:BillQuery.Detail",
"internal/query/employeecollection/bill.go:BillQuery.billOperations账单维度既有审计事实投影",
"internal/query/employeecollection/bill.go:BillQuery.billSourceSnapshot来源订单或充值只读快照"
],
"domain_state_amount": [
"internal/model/dto/employee_collection_dto.go:EmployeeCollectionBillOperationLogResponse/EmployeeCollectionBillSourceSnapshotResponse"
],
"store_migration_config": [
"internal/query/employeecollection/bill.go来源不可读返回空值不阻断详情"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/query/employeecollection/bill.go\", \"func (q *BillQuery) Detail(\"), (\"internal/query/employeecollection/bill.go\", \"func (q *BillQuery) billOperations(\"), (\"internal/query/employeecollection/bill.go\", \"func (q *BillQuery) billSourceSnapshot(\"), (\"internal/query/employeecollection/bill.go\", \"不返回凭证内容与完整收款原文\"), (\"internal/model/dto/employee_collection_dto.go\", \"SourceOrder *EmployeeCollectionBillSourceSnapshotResponse\"), (\"internal/model/dto/employee_collection_dto.go\", \"来源不可读或字段缺失时对应字段为空值,不影响详情其余内容\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: employee-collection-bill::员工账单详情投影\")'",
"literal_output": [
"OK: employee-collection-bill::员工账单详情投影"
],
"exit_status": 0
}
},
{
"capability": "agent-funds-commission",
"requirement": "代理自充允许方式配置可审计",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"GET /api/admin/agent-self-recharge-payment-methods",
"PUT /api/admin/agent-self-recharge-payment-methods"
],
"handler_consumer_job": [
"internal/routes/agent_self_recharge_payment_method.go",
"internal/handler/admin/agent_recharge.go"
],
"application_service_query": [
"internal/application/agentrecharge/online_payment_policy.go"
],
"domain_state_amount": [
"pkg/constants/system_config.go"
],
"store_migration_config": [
"internal/bootstrap/payment_method_config.go"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"pkg/constants/system_config.go\", \"SystemConfigAgentSelfRechargeAllowedMethods\"), (\"pkg/constants/system_config.go\", \"AgentSelfRechargeAllowedBoth\"), (\"internal/bootstrap/payment_method_config.go\", \"DefaultValue: constants.AgentSelfRechargeAllowedBoth\"), (\"internal/bootstrap/payment_method_config.go\", \"EnumValues: []string{constants.AgentSelfRechargeAllowedWechatOnly\"), (\"internal/handler/admin/agent_recharge.go\", \"func (h *AgentRechargeHandler) UpdateSelfRechargePaymentMethods(\"), (\"internal/routes/agent_self_recharge_payment_method.go\", \"仅超级管理员可以修改代理自充允许范围\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: agent-funds-commission::代理自充允许方式配置可审计\")'",
"literal_output": [
"OK: agent-funds-commission::代理自充允许方式配置可审计"
],
"exit_status": 0
}
},
{
"capability": "agent-funds-commission",
"requirement": "线下预存款审批收款方式、其他凭证与交易流水号",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"POST /api/admin/agent-recharges"
],
"handler_consumer_job": [
"internal/handler/admin/agent_recharge.go",
"internal/application/agentrecharge/offline_creation.go"
],
"application_service_query": [
"internal/application/agentrecharge/offline_creation.go"
],
"domain_state_amount": [
"internal/model/agent_wallet.go"
],
"store_migration_config": [
"migrations/000213_add_agent_recharge_offline_fields.up.sql"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/application/agentrecharge/offline_creation.go\", \"func loadEnabledOfflinePaymentMethod(\"), (\"internal/application/agentrecharge/offline_creation.go\", \"func validateRechargeTransactionNo(\"), (\"internal/application/agentrecharge/offline_creation.go\", \"func validateVoucherKeys(\"), (\"migrations/000213_add_agent_recharge_offline_fields.up.sql\", \"external_transaction_no varchar(128)\"), (\"migrations/000213_add_agent_recharge_offline_fields.up.sql\", \"other_voucher_keys jsonb\"), (\"internal/model/agent_wallet.go\", \"column:external_transaction_no\"), (\"internal/model/agent_wallet.go\", \"column:offline_payment_method_code\"), (\"internal/application/wecom/scene.go\", \"constants.ApprovalFieldExternalTransactionNo\"), (\"internal/application/wecom/scene.go\", \"constants.ApprovalFieldOtherVoucherKey\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: agent-funds-commission::线下预存款审批收款方式、其他凭证与交易流水号\")'",
"literal_output": [
"OK: agent-funds-commission::线下预存款审批收款方式、其他凭证与交易流水号"
],
"exit_status": 0
}
},
{
"capability": "agent-funds-commission",
"requirement": "付款凭证识别仅作交易流水号预填",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"POST /api/admin/agent-recharges/payment-voucher-ocr"
],
"handler_consumer_job": [
"internal/handler/admin/agent_recharge.go",
"internal/routes/agent_recharge.go"
],
"application_service_query": [
"internal/application/agentrecharge/payment_voucher_ocr.go"
],
"domain_state_amount": [],
"store_migration_config": [
"internal/gateway/payment_voucher.go"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/gateway/payment_voucher.go\", \"func (c *Client) ExtractPaymentVoucherOrderNumber(\"), (\"internal/application/agentrecharge/payment_voucher_ocr.go\", \"func (s *PaymentVoucherOCRService) Recognize(\"), (\"internal/application/agentrecharge/payment_voucher_ocr.go\", \"ExternalTransactionNo string\"), (\"internal/handler/admin/agent_recharge.go\", \"func (h *AgentRechargeHandler) PaymentVoucherOCR(\"), (\"internal/routes/agent_recharge.go\", \"/payment-voucher-ocr\")]; negative=[(\"internal/application/agentrecharge/payment_voucher_ocr.go\", \"AmountCent\"), (\"internal/application/agentrecharge/payment_voucher_ocr.go\", \"Payee\"), (\"internal/application/agentrecharge/payment_voucher_ocr.go\", \"PaymentTime\")]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: agent-funds-commission::付款凭证识别仅作交易流水号预填\")'",
"literal_output": [
"OK: agent-funds-commission::付款凭证识别仅作交易流水号预填"
],
"exit_status": 0
}
},
{
"capability": "agent-funds-commission",
"requirement": "线下收款方式字典对代理充值引用的可见性",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"PUT /api/admin/employee-collection-payment-methods/{id}",
"DELETE /api/admin/employee-collection-payment-methods/{id}"
],
"handler_consumer_job": [
"internal/handler/admin/employee_collection.go"
],
"application_service_query": [
"internal/application/employeecollection/payment_method.go"
],
"domain_state_amount": [
"internal/domain/employeecollection/payment_method.go"
],
"store_migration_config": [
"internal/model/agent_wallet.go"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/application/employeecollection/payment_method.go\", \"func countPaymentMethodReferences(\"), (\"internal/application/employeecollection/payment_method.go\", \"model.AgentRechargeRecord{}\"), (\"internal/application/employeecollection/payment_method.go\", \"offline_payment_method_id = ?\"), (\"internal/application/employeecollection/payment_method.go\", \"线下收款方式已被核销申请或代理充值申请引用,不能修改稳定编码\")]; negative=[]; missing=[f\"{p}:{t}\" for p,t in checks if t not in Path(p).read_text()]; present=[f\"{p}:{t}\" for p,t in negative if t in Path(p).read_text()]; assert not missing and not present, {\"missing\": missing, \"present\": present}; print(\"OK: agent-funds-commission::线下收款方式字典对代理充值引用的可见性\")'",
"literal_output": [
"OK: agent-funds-commission::线下收款方式字典对代理充值引用的可见性"
],
"exit_status": 0
}
},
{
"capability": "agent-distribution-withdrawal",
"requirement": "分销码与待审批代理注册",
"spec": "openspec/specs/agent-distribution-withdrawal/spec.md",
"entries": [
"/api/c/v1/agent-distribution-registrations"
],
"handler_consumer_job": [
"internal/routes/agent_distribution_public.go",
"internal/handler/app/agent_distribution.go",
"internal/application/distributionwithdrawal/registration_approval.go",
"cmd/worker/main.go"
],
"application_service_query": [
"internal/application/distributionwithdrawal/registration.go"
],
"domain_state_amount": [
"internal/domain/distribution/distribution.go",
"pkg/constants/approval.go"
],
"store_migration_config": [
"migrations/000214_add_agent_distribution_registration.up.sql",
"migrations/000217_backfill_shop_distribution_code.up.sql"
],
"verification": {
"command": "grep -rn \"分销码无效\" internal/",
"literal_output": [
"internal/handler/app/agent_distribution.go:33:// 分销码无效、上级店铺停用、上级店铺缺少启用的主账号、短信验证码无效分别返回各自提示且不落库;",
"internal/application/distributionwithdrawal/registration.go:50:// 分销码无效、上级店铺停用、上级店铺缺少启用的主账号、短信验证码无效分别返回各自的错误码与提示。",
"internal/application/distributionwithdrawal/registration.go:244:\t\t\treturn nil, errors.New(errors.CodeInvalidParam, \"分销码无效\")",
"internal/routes/agent_distribution_public.go:19:\t\tDescription: \"公开接口,无需认证。请求必须携带有效分销码、短信已验证手机号与密码;分销码无效、分销码所属店铺已停用、上级店铺缺少启用的主账号、短信验证码无效或已被消费分别返回各自提示,且不创建注册记录、店铺或账号。手机号、用户名或店铺编号已被既有账号或店铺占用时按其字段返回已存在提示,已有其它待审批注册申请占用同一字段时返回资源冲突提示(已驳回或已通过的历史申请不阻塞重新注册)。短信验证码只在注册记录落库成功后消费,落库前的失败不消耗验证码,可用同一验证码直接重试。通过后仍需企业微信终审才会创建店铺与代理账号。\","
],
"exit_status": 0
}
},
{
"capability": "agent-distribution-withdrawal",
"requirement": "提现资料资格",
"spec": "openspec/specs/agent-distribution-withdrawal/spec.md",
"entries": [
"/api/admin/shops/{shop_id}/withdrawal-qualifications",
"/api/admin/withdrawal-qualifications/{id}/void"
],
"handler_consumer_job": [
"internal/routes/shop.go",
"internal/handler/admin/withdrawal_qualification.go",
"internal/application/distributionwithdrawal/qualification_approval.go",
"cmd/worker/main.go"
],
"application_service_query": [
"internal/application/distributionwithdrawal/qualification.go"
],
"domain_state_amount": [
"pkg/constants/withdrawal_qualification.go",
"pkg/constants/approval.go"
],
"store_migration_config": [
"migrations/000215_add_withdrawal_qualification_and_attempt.up.sql"
],
"verification": {
"command": "grep -rn \"uk_withdrawal_qualification_valid\" migrations/",
"literal_output": [
"migrations/000215_add_withdrawal_qualification_and_attempt.up.sql:50:CREATE UNIQUE INDEX uk_withdrawal_qualification_valid",
"migrations/000215_add_withdrawal_qualification_and_attempt.down.sql:38:DROP INDEX IF EXISTS uk_withdrawal_qualification_valid;"
],
"exit_status": 0
}
},
{
"capability": "agent-distribution-withdrawal",
"requirement": "提现冻结与企业微信终审",
"spec": "openspec/specs/agent-distribution-withdrawal/spec.md",
"entries": [
"/api/admin/shops/{shop_id}/withdrawal-requests"
],
"handler_consumer_job": [
"internal/routes/shop.go",
"internal/handler/admin/shop_commission.go",
"internal/application/distributionwithdrawal/withdrawal.go",
"internal/application/distributionwithdrawal/withdrawal_approval.go",
"cmd/worker/main.go"
],
"application_service_query": [
"internal/application/distributionwithdrawal/withdrawal.go",
"internal/application/distributionwithdrawal/withdrawal.go:提现提交冻结资格版本与校验结果进当次审批尝试快照"
],
"domain_state_amount": [
"internal/domain/wallet/wallet.go",
"pkg/constants/commission_withdrawal.go",
"pkg/constants/approval.go",
"internal/model/financial.go:CommissionWithdrawalRequestAttempt.QualificationVersionID/QualificationCheckedAt/QualificationPassed/QualificationFailureReason",
"pkg/constants/withdrawal_qualification.go:WithdrawalQualificationFailureMissing/Invalidated/ShopDisabled/NotApproved"
],
"store_migration_config": [
"internal/store/postgres/commission_withdrawal_request_store.go",
"migrations/000215_add_withdrawal_qualification_and_attempt.up.sql",
"migrations/000237_extend_withdrawal_attempt_qualification.up.sql:tb_commission_withdrawal_request_attempt 资格校验列"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/application/distributionwithdrawal/withdrawal_approval.go\", \"constants.WithdrawalStatusApproved,\"), (\"internal/application/distributionwithdrawal/withdrawal_approval.go\", \"禁止使用已到账状态值 4\"), (\"internal/application/distributionwithdrawal/withdrawal_approval.go\", \"paid_at IS NULL\"), (\"internal/application/distributionwithdrawal/withdrawal.go\", \"QualificationVersionID: qualification.VersionID,\"), (\"internal/application/distributionwithdrawal/withdrawal.go\", \"qualification_version_id\"), (\"pkg/constants/withdrawal_qualification.go\", \"WithdrawalQualificationFailureInvalidated = \\\"qualification_invalidated\\\"\"), (\"pkg/constants/withdrawal_qualification.go\", \"WithdrawalQualificationCheckPassed = 1\"), (\"migrations/000237_extend_withdrawal_attempt_qualification.up.sql\", \"ADD COLUMN IF NOT EXISTS qualification_version_id\"), (\"internal/model/financial.go\", \"资料资格校验留痕:本次提交依据的有效资格版本与当次校验结果,随尝试冻结,历史尝试不被改写。\"), (\"internal/application/distributionwithdrawal/withdrawal.go\", \"当次资料资格校验结果随尝试冻结\"), (\"internal/application/distributionwithdrawal/withdrawal.go\", \"QualificationPassed: qualificationPassedFlag(qualification.Passed),\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: agent-distribution-withdrawal::提现冻结与企业微信终审\")'",
"literal_output": [
"OK: agent-distribution-withdrawal::提现冻结与企业微信终审"
],
"exit_status": 0
}
},
{
"capability": "agent-distribution-withdrawal",
"requirement": "提现本地人工终审边界",
"spec": "openspec/specs/agent-distribution-withdrawal/spec.md",
"entries": [
"/api/admin/commission/withdrawal-requests/{id}/approve",
"/api/admin/commission/withdrawal-requests/{id}/reject"
],
"handler_consumer_job": [
"internal/routes/commission.go",
"internal/handler/admin/commission_withdrawal.go"
],
"application_service_query": [
"internal/service/commission_withdrawal/service.go"
],
"domain_state_amount": [
"pkg/constants/commission_withdrawal.go"
],
"store_migration_config": [
"internal/store/postgres/commission_withdrawal_request_store.go",
"migrations/000215_add_withdrawal_qualification_and_attempt.up.sql"
],
"verification": {
"command": "grep -rn \"提现申请已接入企业微信终审\" internal/",
"literal_output": [
"internal/service/commission_withdrawal/service.go:154:\t\tbusinessErr := errors.New(errors.CodeConflict, \"提现申请已接入企业微信终审,不支持本地人工处理\")",
"internal/service/commission_withdrawal/service.go:281:\t\tbusinessErr := errors.New(errors.CodeConflict, \"提现申请已接入企业微信终审,不支持本地人工处理\")"
],
"exit_status": 0
}
},
{
"capability": "agent-distribution-withdrawal",
"requirement": "待审提现与佣金回溯的释放接缝",
"spec": "openspec/specs/agent-distribution-withdrawal/spec.md",
"entries": [],
"handler_consumer_job": [
"internal/application/distributionwithdrawal/withdrawal.go",
"internal/service/refund/service.go"
],
"application_service_query": [
"internal/application/distributionwithdrawal/withdrawal.go"
],
"domain_state_amount": [
"internal/model/financial.go",
"pkg/constants/commission_withdrawal.go"
],
"store_migration_config": [
"internal/store/postgres/withdrawal_attempt_release.go",
"migrations/000215_add_withdrawal_qualification_and_attempt.up.sql"
],
"verification": {
"command": "grep -n \"released_at IS NULL\" internal/store/postgres/withdrawal_attempt_release.go",
"literal_output": [
"16:// 语义:对每个申请,按 `released_at IS NULL` 条件更新写入 released_at并返回各申请",
"41:\t\tWhere(\"request_id IN ? AND released_at IS NULL\", requestIDs).",
"47:\t\t// 行已由上面的 FOR UPDATE 锁定且筛选条件为 released_at IS NULL",
"50:\t\t\tWhere(\"id = ? AND released_at IS NULL\", attempt.ID)."
],
"exit_status": 0
}
},
{
"capability": "agent-distribution-withdrawal",
"requirement": "注册审批材料带出业务员",
"spec": "openspec/specs/agent-distribution-withdrawal/spec.md",
"entries": [
"POST /api/c/v1/agent-distribution-registrations"
],
"handler_consumer_job": [
"internal/application/distributionwithdrawal/registration.go:上级店铺业务员名称快照与无业务员标记的组装"
],
"application_service_query": [
"internal/application/wecom/scene.go代理注册审批场景新增业务员与无业务员标记字段RequiredMapping",
"internal/infrastructure/wecom/approval_form.go:buildApprovalForm缺控件即明确失败"
],
"domain_state_amount": [
"pkg/constants/approval.go:ApprovalFieldDistributionBusinessOwner/ApprovalFieldDistributionBusinessOwnerAbsence"
],
"store_migration_config": [
"internal/application/wecom/scene.go:RequiredSceneFields本次新增字段的唯一必须映射来源"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/application/distributionwithdrawal/registration.go\", \"parentBusinessOwnerName\"), (\"internal/application/distributionwithdrawal/registration.go\", \"agentDistributionBusinessOwnerAbsenceMark\"), (\"internal/application/distributionwithdrawal/registration.go\", \"业务员取上级店铺当前业务员的名称快照\"), (\"pkg/constants/approval.go\", \"ApprovalFieldDistributionBusinessOwnerAbsence\"), (\"internal/application/wecom/scene.go\", \"ApprovalFieldDistributionBusinessOwner\"), (\"internal/infrastructure/wecom/approval_form.go\", \"企业微信审批场景缺少必须配置的控件映射\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: agent-distribution-withdrawal::注册审批材料带出业务员\")'",
"literal_output": [
"OK: agent-distribution-withdrawal::注册审批材料带出业务员"
],
"exit_status": 0
}
},
{
"capability": "agent-distribution-withdrawal",
"requirement": "店铺下级代理数量投影",
"spec": "openspec/specs/agent-distribution-withdrawal/spec.md",
"entries": [
"GET /api/admin/shops",
"GET /api/admin/shops/{id}"
],
"handler_consumer_job": [
"internal/handler/admin/shop.go店铺列表与详情复用同一分页批量投影"
],
"application_service_query": [
"internal/query/shop/business_owner.go:loadDownstreamAgentCounts按当页店铺集合一次批量聚合"
],
"domain_state_amount": [
"internal/model/dto/shop_dto.go:DownstreamAgentCount只读无下级返回 0"
],
"store_migration_config": [
"internal/query/shop/business_owner.go:loadDownstreamAgentCounts数据范围由调用方列表与详情过滤保证"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/query/shop/business_owner.go\", \"无下级的店铺返回 0map 缺省零值),不使用空值\"), (\"internal/query/shop/business_owner.go\", \"该数量只读,不影响上下级关系、佣金关系、通知范围或任何既有写入语义\"), (\"internal/model/dto/shop_dto.go\", \"DownstreamAgentCount int64\"), (\"internal/query/shop/business_owner.go\", \"func (q *BusinessOwnerQuery) loadDownstreamAgentCounts(\"), (\"internal/model/dto/shop_dto.go\", \"直接下级代理数量(未删除且上级店铺标识等于该店铺的店铺数),无下级返回 0\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: agent-distribution-withdrawal::店铺下级代理数量投影\")'",
"literal_output": [
"OK: agent-distribution-withdrawal::店铺下级代理数量投影"
],
"exit_status": 0
}
},
{
"capability": "personal-customer",
"requirement": "资产信息展示当前可用或已用完主套餐",
"spec": "openspec/specs/personal-customer/spec.md",
"entries": [
"/api/c/v1/asset/info"
],
"handler_consumer_job": [
"internal/routes/personal.go",
"internal/handler/app/client_asset.go"
],
"application_service_query": [
"internal/service/asset/service.go"
],
"domain_state_amount": [
"pkg/constants/constants.go"
],
"store_migration_config": [
"internal/store/postgres/package_usage_store.go",
"internal/model/package.go"
],
"verification": {
"command": "grep -n \"PackageUsageStatusDepleted\" internal/store/postgres/package_usage_store.go",
"literal_output": [
"internal/store/postgres/package_usage_store.go:66:.Where(\"status IN (?, ?)\", constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted)."
],
"exit_status": 0
}
},
{
"capability": "order-refund-exchange",
"requirement": "退款实收金额与方式矩阵",
"spec": "openspec/specs/order-refund-exchange/spec.md",
"entries": [
"/api/admin/refunds",
"GET /api/admin/refunds/order-options"
],
"handler_consumer_job": [
"internal/routes/refund.go",
"internal/handler/admin/refund.go",
"internal/handler/admin/refund.go:RefundHandler.OrderOptions"
],
"application_service_query": [
"internal/service/refund/method.go",
"internal/service/refund/settlement.go:Service.OrderOptions只读复用同一方式判定与原路凭证判定"
],
"domain_state_amount": [
"internal/model/refund.go",
"internal/model/payment.go",
"pkg/constants/refund.go"
],
"store_migration_config": [
"migrations/000218_add_refund_attempt_and_channel_refund.up.sql"
],
"verification": {
"command": "grep -n 'func (s \\*Service) decideRefundMethods' internal/service/refund/method.go",
"literal_output": [
"40:func (s *Service) decideRefundMethods(ctx context.Context, order *model.Order) (*refundMethodDecision, error) {"
],
"exit_status": 0
}
},
{
"capability": "order-refund-exchange",
"requirement": "企业微信唯一终审与审批尝试重提",
"spec": "openspec/specs/order-refund-exchange/spec.md",
"entries": [
"/api/admin/refunds/{id}/resubmit",
"/api/callback/wecom/approval/{application_id}"
],
"handler_consumer_job": [
"internal/routes/refund.go",
"internal/handler/admin/refund.go",
"internal/application/refundapproval/creation.go",
"cmd/worker/main.go"
],
"application_service_query": [
"internal/application/refundapproval/resolve.go",
"internal/service/refund/service.go"
],
"domain_state_amount": [
"pkg/constants/approval.go",
"internal/model/refund.go"
],
"store_migration_config": [
"migrations/000218_add_refund_attempt_and_channel_refund.up.sql"
],
"verification": {
"command": "grep -n 'BusinessID: attempt.ID' internal/application/refundapproval/creation.go",
"literal_output": [
"171:\t\t\tBusinessID: attempt.ID, SubmitterAccountID: current.Creator,",
"260:\t\t\tBusinessID: attempt.ID, SubmitterAccountID: command.SubmitterAccountID,",
"403:\t\t\tBusinessID: attempt.ID, SubmitterAccountID: current.Creator,"
],
"exit_status": 0
}
},
{
"capability": "order-refund-exchange",
"requirement": "原路退款渠道能力与执行",
"spec": "openspec/specs/order-refund-exchange/spec.md",
"entries": [
"constants.TaskTypeRefundChannelRecovery"
],
"handler_consumer_job": [
"internal/infrastructure/payment/refund_channel_recovery_task.go",
"internal/infrastructure/payment/refund_adapter.go",
"internal/application/refundchannel/event.go",
"cmd/worker/main.go"
],
"application_service_query": [
"internal/application/refundchannel/service.go",
"internal/service/refund/payment_merchant.go"
],
"domain_state_amount": [
"pkg/constants/refund.go",
"pkg/wechat/payment_v2_refund.go",
"pkg/fuiou/refund.go",
"pkg/alipay/refund.go"
],
"store_migration_config": [
"migrations/000218_add_refund_attempt_and_channel_refund.up.sql",
"migrations/000219_add_wechat_v2_client_cert_credentials.up.sql"
],
"verification": {
"command": "grep -n 'func RefundCredentialIssue' internal/application/refundchannel/service.go",
"literal_output": [
"452:func RefundCredentialIssue(providerType string, config *model.WechatConfig) string {"
],
"exit_status": 0
}
},
{
"capability": "order-refund-exchange",
"requirement": "退款权益与订单状态时点",
"spec": "openspec/specs/order-refund-exchange/spec.md",
"entries": [
"/api/callback/wecom/approval/{application_id}",
"/api/admin/refunds/{id}/approve"
],
"handler_consumer_job": [
"internal/service/refund/approval_decision.go",
"internal/application/refundchannel/service.go",
"cmd/worker/main.go"
],
"application_service_query": [
"internal/service/refund/service.go",
"internal/service/package/activation_service.go"
],
"domain_state_amount": [
"internal/model/order.go",
"internal/model/refund.go"
],
"store_migration_config": [
"migrations/000218_add_refund_attempt_and_channel_refund.up.sql"
],
"verification": {
"command": "grep -n 'originalRoute := refund.Method' internal/service/refund/approval_decision.go",
"literal_output": [
"120:\t\toriginalRoute := refund.Method == constants.RefundMethodOriginalRoute"
],
"exit_status": 0
}
},
{
"capability": "order-refund-exchange",
"requirement": "退款终态事实与失败分类",
"spec": "openspec/specs/order-refund-exchange/spec.md",
"entries": [
"/api/admin/refunds/{id}",
"constants.TaskTypeRefundChannelRecovery"
],
"handler_consumer_job": [
"internal/routes/refund.go",
"internal/handler/admin/refund.go",
"internal/infrastructure/audit/refund_channel.go"
],
"application_service_query": [
"internal/service/refund/attempt_query.go",
"internal/exporter/refund_scene.go"
],
"domain_state_amount": [
"pkg/constants/refund.go",
"internal/model/refund.go",
"internal/infrastructure/commissiondelivery/event.go"
],
"store_migration_config": [
"migrations/000218_add_refund_attempt_and_channel_refund.up.sql"
],
"verification": {
"command": "grep -n 'func RefundFailureReasonIsDefinitive' pkg/constants/refund.go",
"literal_output": [
"99:func RefundFailureReasonIsDefinitive(reason string) bool {"
],
"exit_status": 0
}
},
{
"capability": "order-refund-exchange",
"requirement": "退款展示当前退款套餐用量",
"spec": "openspec/specs/order-refund-exchange/spec.md",
"entries": [
"/api/admin/refunds",
"/api/admin/refunds/{id}"
],
"handler_consumer_job": [
"internal/routes/refund.go",
"internal/handler/admin/refund.go",
"internal/exporter/refund_scene.go"
],
"application_service_query": [
"internal/service/refund/package_usage.go"
],
"domain_state_amount": [
"internal/model/dto/refund_dto.go",
"internal/model/package.go"
],
"store_migration_config": [
"internal/model/package.go"
],
"verification": {
"command": "grep -n 'func (s \\*Service) loadRefundPackageUsages' internal/service/refund/package_usage.go",
"literal_output": [
"33:func (s *Service) loadRefundPackageUsages(ctx context.Context, refunds []*model.RefundRequest) (map[uint]refundPackageUsage, error) {"
],
"exit_status": 0
}
},
{
"capability": "agent-funds-commission",
"requirement": "套餐退款佣金回溯",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"constants.TaskTypeRefundCommissionRecovery"
],
"handler_consumer_job": [
"internal/service/refund/approval_decision.go",
"internal/infrastructure/commissiondelivery/event.go",
"cmd/worker/main.go"
],
"application_service_query": [
"internal/service/refund/clawback.go"
],
"domain_state_amount": [
"internal/model/commission_clawback.go",
"pkg/constants/iot.go"
],
"store_migration_config": [
"internal/store/postgres/commission_record_store.go",
"migrations/000220_add_commission_clawback_record.up.sql"
],
"verification": {
"command": "grep -nE 'uk_commission_clawback_refund_original|chk_commission_clawback_amount|chk_commission_clawback_withdrawable|chk_commission_clawback_status' migrations/000220_add_commission_clawback_record.up.sql",
"literal_output": [
"23: CONSTRAINT uk_commission_clawback_refund_original UNIQUE (refund_id, original_commission_id),",
"25: CONSTRAINT chk_commission_clawback_amount CHECK (amount < 0),",
"26: CONSTRAINT chk_commission_clawback_withdrawable CHECK (withdrawable = FALSE),",
"27: CONSTRAINT chk_commission_clawback_status CHECK (status = 5)"
],
"exit_status": 0
}
},
{
"capability": "agent-funds-commission",
"requirement": "回溯明细关联查询与导出",
"spec": "openspec/specs/agent-funds-commission/spec.md",
"entries": [
"/api/admin/shops/{shop_id}/commission-records",
"/api/admin/shops/{shop_id}/commission-records/{id}"
],
"handler_consumer_job": [
"internal/routes/shop.go",
"internal/handler/admin/shop_commission.go"
],
"application_service_query": [
"internal/service/shop_commission/service.go"
],
"domain_state_amount": [
"internal/model/dto/shop_commission_dto.go"
],
"store_migration_config": [
"internal/store/postgres/commission_record_store.go",
"internal/exporter/commission_record_scene.go"
],
"verification": {
"command": "grep -n 'ledger_clawback' internal/store/postgres/commission_record_store.go internal/exporter/commission_record_scene.go",
"literal_output": [
"internal/store/postgres/commission_record_store.go:118:\t\tRaw(\"SELECT * FROM (?) AS ledger_original UNION ALL SELECT * FROM (?) AS ledger_clawback\", original, clawback)",
"internal/exporter/commission_record_scene.go:60:\t\tRaw(\"SELECT * FROM (?) AS ledger_original UNION ALL SELECT * FROM (?) AS ledger_clawback\","
],
"exit_status": 0
}
},
{
"capability": "business-user-group",
"requirement": "业务用户组生命周期与成员归属",
"spec": "openspec/specs/business-user-group/spec.md",
"entries": [
"/api/admin/business-user-groups",
"/api/admin/business-user-groups/{id}",
"/api/admin/business-user-groups/{id}/members",
"/api/admin/business-user-groups/members"
],
"handler_consumer_job": [
"internal/routes/business_user_group.go",
"internal/handler/admin/business_user_group.go"
],
"application_service_query": [
"internal/application/businessusergroup/service.go"
],
"domain_state_amount": [
"internal/model/business_user_group.go",
"pkg/constants/business_user_group.go"
],
"store_migration_config": [
"internal/store/postgres/business_user_group_store.go",
"migrations/000221_add_business_user_group_and_shop_owner_import.up.sql"
],
"verification": {
"command": "grep -nE '用户组仍有成员|目标用户组已停用|uk_business_user_group_code|IsAvailablePlatformBusinessOwner' internal/application/businessusergroup/service.go",
"literal_output": [
"170:\t\t\treturn errors.New(errors.CodeInvalidStatus, \"用户组仍有成员,只能停用或先移走成员\")",
"202:\t\t\treturn errors.New(errors.CodeInvalidStatus, \"目标用户组已停用,不能作为成员归属目标\")",
"316:\t\tif constants.IsAvailablePlatformBusinessOwner(account.UserType, account.Status, account.DeletedAt.Valid) {",
"338:\tif strings.Contains(strings.ToLower(err.Error()), \"uk_business_user_group_code\") {"
],
"exit_status": 0
}
},
{
"capability": "business-user-group",
"requirement": "店铺负责人和所属组实时推导",
"spec": "openspec/specs/business-user-group/spec.md",
"entries": [
"/api/admin/shops",
"/api/admin/shops/{id}"
],
"handler_consumer_job": [
"internal/routes/shop.go",
"internal/query/shop/business_owner.go"
],
"application_service_query": [
"internal/query/shop/business_owner.go"
],
"domain_state_amount": [
"internal/model/dto/shop_dto.go",
"internal/model/business_user_group.go"
],
"store_migration_config": [
"migrations/000221_add_business_user_group_and_shop_owner_import.up.sql"
],
"verification": {
"command": "grep -nE 'EXISTS \\(SELECT 1 FROM tb_business_user_group_member|ungroupedCondition' internal/query/shop/business_owner.go",
"literal_output": [
"126:\t\t\t\"EXISTS (SELECT 1 FROM tb_business_user_group_member m WHERE m.account_id = tb_shop.business_owner_account_id AND m.deleted_at IS NULL AND m.business_user_group_id = ?)\",",
"131:\t\t\t\"EXISTS (SELECT 1 FROM tb_business_user_group_member m WHERE m.account_id = tb_shop.business_owner_account_id AND m.deleted_at IS NULL AND EXISTS (SELECT 1 FROM tb_business_user_group g WHERE g.id = m.business_user_group_id AND g.deleted_at IS NULL AND g.business_line = ?))\",",
"136:\t\tconst ungroupedCondition = \"(tb_shop.business_owner_account_id IS NULL OR NOT EXISTS (SELECT 1 FROM tb_business_user_group_member m WHERE m.account_id = tb_shop.business_owner_account_id AND m.deleted_at IS NULL))\"",
"138:\t\t\tdb = db.Where(ungroupedCondition)",
"140:\t\t\tdb = db.Where(\"NOT \" + ungroupedCondition)"
],
"exit_status": 0
}
},
{
"capability": "business-user-group",
"requirement": "店铺负责人批量交接",
"spec": "openspec/specs/business-user-group/spec.md",
"entries": [
"/api/admin/shops/business-owner/batch"
],
"handler_consumer_job": [
"internal/routes/business_user_group.go",
"internal/handler/admin/business_user_group.go"
],
"application_service_query": [
"internal/application/shop/batch_business_owner.go"
],
"domain_state_amount": [
"internal/model/dto/shop_dto.go"
],
"store_migration_config": [
"internal/infrastructure/audit/business_user_group.go"
],
"verification": {
"command": "grep -nE 'batchBusinessOwnerFailureMessage' internal/application/shop/batch_business_owner.go",
"literal_output": [
"102:\t\t\treturn errors.New(errors.CodeForbidden, batchBusinessOwnerFailureMessage)",
"122:\t\t\treturn errors.New(errors.CodeForbidden, batchBusinessOwnerFailureMessage)",
"203:// batchBusinessOwnerFailureMessage 复用平台维护入口的统一失败文案,不区分无权、不存在与已删除。",
"204:const batchBusinessOwnerFailureMessage = constants.PlatformManagementForbiddenMessage",
"243:\t\treturn nil, errors.New(errors.CodeForbidden, batchBusinessOwnerFailureMessage)",
"249:\t\treturn nil, errors.New(errors.CodeForbidden, batchBusinessOwnerFailureMessage)"
],
"exit_status": 0
}
},
{
"capability": "business-user-group",
"requirement": "店铺负责人 CSV 导入",
"spec": "openspec/specs/business-user-group/spec.md",
"entries": [
"/api/admin/shops/business-owner-imports",
"/api/admin/shops/business-owner-imports/{id}",
"constants.TaskTypeShopBusinessOwnerImport"
],
"handler_consumer_job": [
"internal/routes/business_user_group.go",
"internal/handler/admin/shop_business_owner_import.go"
],
"application_service_query": [
"internal/task/shop_business_owner_import.go",
"pkg/utils/encoding.go"
],
"domain_state_amount": [
"internal/model/shop_business_owner_import_task.go",
"pkg/constants/shop_business_owner_import.go"
],
"store_migration_config": [
"internal/store/postgres/shop_business_owner_import_task_store.go",
"migrations/000221_add_business_user_group_and_shop_owner_import.up.sql"
],
"verification": {
"command": "grep -nE 'ShopBusinessOwnerImportColumns|ShopBusinessOwnerImportErrorFileFormat|ShopBusinessOwnerImportRowErrorOwnerForbidden|ShopOwnerImportAccessDescription' pkg/constants/shop_business_owner_import.go",
"literal_output": [
"29:// ShopBusinessOwnerImportColumns 表示导入文件首行必须完全一致的固定列序。",
"30:var ShopBusinessOwnerImportColumns = []string{\"店铺编码\", \"操作类型\", \"业务员登录账号\", \"备注\"}",
"34:\t// ShopBusinessOwnerImportErrorFileFormat 表示文件格式或表头与固定列序不符。",
"35:\tShopBusinessOwnerImportErrorFileFormat = \"文件格式或表头与固定列序不符\"",
"50:\t// ShopBusinessOwnerImportRowErrorOwnerForbidden 表示清空却填写了业务员登录账号。",
"51:\tShopBusinessOwnerImportRowErrorOwnerForbidden = \"清空不得填写业务员登录账号\"",
"59:\t// ShopOwnerImportAccessDescription 表示店铺负责人导入入口的角色说明。",
"60:\tShopOwnerImportAccessDescription = \"仅超级管理员和平台账号可访问,代理与企业账号返回 403。\""
],
"exit_status": 0
}
},
{
"capability": "order-refund-exchange",
"requirement": "换货业务数据迁移状态与失败恢复",
"spec": "openspec/specs/order-refund-exchange/spec.md",
"entries": [
"/api/admin/exchanges",
"/api/admin/exchanges/{id}",
"/api/admin/exchanges/{id}/ship",
"/api/admin/exchanges/{id}/complete"
],
"handler_consumer_job": [
"internal/routes/exchange.go",
"internal/handler/admin/exchange.go"
],
"application_service_query": [
"internal/service/exchange/service.go",
"internal/service/exchange/migration_failure.go",
"internal/query/exchange/list.go"
],
"domain_state_amount": [
"internal/model/exchange_order.go",
"pkg/constants/constants.go"
],
"store_migration_config": [
"migrations/000222_add_exchange_migration_status.up.sql",
"migrations/000222_add_exchange_migration_status.down.sql",
"internal/store/postgres/exchange_order_store.go"
],
"verification": {
"command": "grep -n '仅超级管理员或平台用户可重试迁移失败的换货单' internal/service/exchange/service.go",
"literal_output": [
"254:\t\terr = errors.New(errors.CodeForbidden, \"仅超级管理员或平台用户可重试迁移失败的换货单\")",
"271:\t\t\treturn errors.New(errors.CodeForbidden, \"仅超级管理员或平台用户可重试迁移失败的换货单\")"
],
"exit_status": 0
}
},
{
"capability": "order-refund-exchange",
"requirement": "换货业务数据迁移范围",
"spec": "openspec/specs/order-refund-exchange/spec.md",
"entries": [
"/api/admin/exchanges/{id}/complete"
],
"handler_consumer_job": [
"internal/routes/exchange.go",
"internal/handler/admin/exchange.go"
],
"application_service_query": [
"internal/service/exchange/service.go",
"internal/service/exchange/migration.go",
"internal/service/customer_binding/service.go"
],
"domain_state_amount": [
"internal/model/exchange_order.go",
"internal/model/personal_customer_phone.go"
],
"store_migration_config": [
"migrations/000222_add_exchange_migration_status.up.sql"
],
"verification": {
"command": "grep -n 'errors.Wrap(errors.CodeExchangeMigrationFailed' internal/service/exchange/migration.go",
"literal_output": [
"23:\t\treturn nil, errors.Wrap(errors.CodeExchangeMigrationFailed, err, \"执行钱包迁移失败\")",
"27:\t\treturn nil, errors.Wrap(errors.CodeExchangeMigrationFailed, err, \"迁移套餐使用记录失败\")",
"30:\t\treturn nil, errors.Wrap(errors.CodeExchangeMigrationFailed, err, \"复制累计充值字段失败\")",
"33:\t\treturn nil, errors.Wrap(errors.CodeExchangeMigrationFailed, err, \"复制资产标签失败\")"
],
"exit_status": 0
}
},
{
"capability": "order-refund-exchange",
"requirement": "换货导出业务数据迁移状态",
"spec": "openspec/specs/order-refund-exchange/spec.md",
"entries": [
"/api/admin/export-tasks"
],
"handler_consumer_job": [
"internal/exporter/exchange_scene.go"
],
"application_service_query": [
"internal/service/export_task/service.go"
],
"domain_state_amount": [
"pkg/constants/constants.go"
],
"store_migration_config": [],
"verification": {
"command": "grep -n '\"迁移状态\"' internal/exporter/exchange_scene.go",
"literal_output": [
"44:\t\t\"收货人姓名\", \"收货人电话\", \"收货地址\", \"快递公司\", \"快递单号\", \"状态\", \"迁移状态\", \"创建人\", \"创建时间\","
],
"exit_status": 0
}
},
{
"capability": "order-refund-exchange",
"requirement": "退款原因必填与申请人备注",
"spec": "openspec/specs/order-refund-exchange/spec.md",
"entries": [
"POST /api/admin/refunds",
"POST /api/admin/refunds/{id}/resubmit"
],
"handler_consumer_job": [
"internal/handler/admin/refund.go:RefundHandler.Create/Resubmit退款原因必填与申请人备注入口"
],
"application_service_query": [
"internal/service/refund/service.go:Service.Create退款原因去空白后必填校验只作用于创建与重提用例",
"internal/service/refund/service.go:Service.Resubmit重提同样强制原因必填"
],
"domain_state_amount": [
"internal/application/refundapproval/creation.go:CreateCommand.ApplicantRemark冻结进当次审批尝试快照",
"internal/model/refund.go:RefundRequestAttempt.Remark申请人备注逐次冻结与主表审批备注不是同一字段"
],
"store_migration_config": [
"migrations/000233_extend_refund_settlement_fields.up.sql:tb_refund_request_attempt.remark"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/service/refund/service.go\", \"退款原因必填只在创建与重提用例校验\"), (\"internal/service/refund/service.go\", \"退款原因不能为空\"), (\"internal/model/dto/refund_dto.go\", \"申请人备注(可选,随本次提交冻结进审批尝试快照,不改变审批备注)\"), (\"internal/model/refund.go\", \"是本次提交的申请人备注,逐次冻结\"), (\"internal/application/refundapproval/creation.go\", \"ApplicantRemark string\"), (\"migrations/000233_extend_refund_settlement_fields.up.sql\", \"ADD COLUMN IF NOT EXISTS remark TEXT NOT NULL DEFAULT ''\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: order-refund-exchange::退款原因必填与申请人备注\")'",
"literal_output": [
"OK: order-refund-exchange::退款原因必填与申请人备注"
],
"exit_status": 0
}
},
{
"capability": "order-refund-exchange",
"requirement": "退款渠道标识与线下处理流水号",
"spec": "openspec/specs/order-refund-exchange/spec.md",
"entries": [
"POST /api/admin/refunds",
"GET /api/admin/refunds",
"GET /api/admin/refunds/{id}",
"POST /api/admin/refunds/{id}/offline-settlement"
],
"handler_consumer_job": [
"internal/handler/admin/refund.go:RefundHandler.RegisterOfflineSettlement登记或更正线下处理流水号"
],
"application_service_query": [
"internal/service/refund/settlement.go:Service.RegisterOfflineSettlement写审计前置值与后值不改状态与金额",
"internal/service/refund/service.go:Service.Create创建时冻结来源支付单号与原渠道交易流水号"
],
"domain_state_amount": [
"internal/model/refund.go:RefundRequest.SourcePaymentNo/OriginalChannelTradeNo/OfflineSettlementNo/OfflineSettledAt/OfflineSettledBy",
"internal/exporter/refund_scene.go列表、详情与导出一致返回三个流水号列"
],
"store_migration_config": [
"migrations/000233_extend_refund_settlement_fields.up.sql:tb_refund_request 新增来源支付事实与线下登记列"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/service/refund/settlement.go\", \"func (s *Service) RegisterOfflineSettlement(\"), (\"internal/service/refund/settlement.go\", \"仅客户收款信息退款方式可登记线下退款处理流水号\"), (\"internal/service/refund/settlement.go\", \"constants.AuditActionRefundOfflineSettled\"), (\"internal/service/refund/settlement.go\", \"MUST NOT 夹带状态、实收金额等未改动事实\"), (\"internal/model/refund.go\", \"OfflineSettlementNo\"), (\"internal/model/refund.go\", \"冻结的来源支付单号,线下订单为空\"), (\"migrations/000233_extend_refund_settlement_fields.up.sql\", \"ADD COLUMN IF NOT EXISTS offline_settlement_no\"), (\"internal/exporter/refund_scene.go\", \"来源支付单号\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: order-refund-exchange::退款渠道标识与线下处理流水号\")'",
"literal_output": [
"OK: order-refund-exchange::退款渠道标识与线下处理流水号"
],
"exit_status": 0
}
},
{
"capability": "order-refund-exchange",
"requirement": "企业微信退款审批材料字段",
"spec": "openspec/specs/order-refund-exchange/spec.md",
"entries": [
"POST /api/admin/refunds",
"POST /api/admin/refunds/{id}/resubmit",
"POST /api/admin/refunds/{id}/trigger-approval"
],
"handler_consumer_job": [
"internal/application/refundapproval/creation.go:CreationService.Create/Resubmit/TriggerHistorical材料冻结进尝试快照",
"internal/infrastructure/wecom/approval_form.go:buildApprovalForm本次新增字段缺失控件即明确失败"
],
"application_service_query": [
"internal/service/refund/material.go:Service.loadRefundMaterial套餐用量取既有展示解析规则与真流量口径",
"internal/application/wecom/scene.go:RequiredSceneFields本次新增字段标注必须映射"
],
"domain_state_amount": [
"internal/application/refundapproval/creation.go:RefundMaterial.AssetType/DeviceType/DeviceModel/PackageUsedMB/PackageTotalMB/OriginalChannelTradeNo",
"pkg/constants/approval.go:ApprovalFieldRefundAssetType/PackageUsedMB/PackageTotalMB/ChannelTradeNo"
],
"store_migration_config": [
"openspec/specs/order-refund-exchange/spec.md材料字段与控件强制范围的行为契约"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/application/refundapproval/creation.go\", \"PackageUsedMB int64\"), (\"internal/application/refundapproval/creation.go\", \"OriginalChannelTradeNo string\"), (\"internal/service/refund/material.go\", \"func (s *Service) loadRefundMaterial(\"), (\"internal/application/wecom/scene.go\", \"ApprovalFieldRefundPackageTotalMB\"), (\"internal/application/wecom/scene.go\", \"ApprovalFieldRefundChannelTradeNo\"), (\"internal/infrastructure/wecom/approval_form.go\", \"企业微信审批场景缺少必须配置的控件映射\"), (\"internal/infrastructure/wecom/approval_form.go\", \"审批业务快照缺少必须提交的字段\"), (\"pkg/constants/approval.go\", \"ApprovalFieldRefundDeviceModel\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: order-refund-exchange::企业微信退款审批材料字段\")'",
"literal_output": [
"OK: order-refund-exchange::企业微信退款审批材料字段"
],
"exit_status": 0
}
},
{
"capability": "phone-asset-association",
"requirement": "H5 验证建立关联与数量上限",
"spec": "openspec/specs/phone-asset-association/spec.md",
"entries": [
"POST /api/c/v1/auth/bind-phone"
],
"handler_consumer_job": [
"internal/handler/app/client_auth.go",
"internal/middleware/personal_auth.go"
],
"application_service_query": [
"internal/service/client_auth/phone_asset_association.go",
"internal/service/phone_asset_association/association_write.go"
],
"domain_state_amount": [
"pkg/constants/phone_asset_association.go"
],
"store_migration_config": [
"internal/store/postgres/phone_asset_association_store.go",
"migrations/000223_add_phone_asset_association.up.sql"
],
"verification": {
"command": "代码中检索 PhoneAssetAssociationMaxValidPerPhone",
"literal_output": [
"pkg/constants/phone_asset_association.go:32:\tPhoneAssetAssociationMaxValidPerPhone = 10"
],
"exit_status": 0
}
},
{
"capability": "phone-asset-association",
"requirement": "登录提示判定",
"spec": "openspec/specs/phone-asset-association/spec.md",
"entries": [
"POST /api/c/v1/auth/verify-asset"
],
"handler_consumer_job": [
"internal/middleware/personal_auth.go"
],
"application_service_query": [
"internal/service/client_auth/service.go"
],
"domain_state_amount": [
"pkg/constants/phone_asset_association.go"
],
"store_migration_config": [
"internal/store/postgres/phone_asset_association_store.go"
],
"verification": {
"command": "代码中检索 needBindPhone = !associated",
"literal_output": [
"internal/service/client_auth/service.go:1042:\t\t\tneedBindPhone = !associated"
],
"exit_status": 0
}
},
{
"capability": "phone-asset-association",
"requirement": "H5 绑定与换绑的关联写入",
"spec": "openspec/specs/phone-asset-association/spec.md",
"entries": [
"POST /api/c/v1/auth/bind-phone",
"POST /api/c/v1/auth/change-phone"
],
"handler_consumer_job": [
"internal/handler/app/client_auth.go"
],
"application_service_query": [
"internal/service/phone_asset_association/association_write.go"
],
"domain_state_amount": [
"pkg/constants/phone_asset_association.go"
],
"store_migration_config": [
"internal/store/postgres/phone_asset_association_store.go"
],
"verification": {
"command": "代码中检索 换绑已回滚",
"literal_output": [
"internal/service/phone_asset_association/association_write.go:140:\t\t\treturn nil, errors.New(errors.CodeInvalidStatus, \"新手机号已存在与待迁移资产相同的有效关联,换绑已回滚\")"
],
"exit_status": 0
}
},
{
"capability": "phone-asset-association",
"requirement": "后台查看关联",
"spec": "openspec/specs/phone-asset-association/spec.md",
"entries": [
"GET /api/admin/phone-asset-associations"
],
"handler_consumer_job": [
"internal/handler/admin/phone_asset_association.go",
"internal/routes/phone_asset_association.go"
],
"application_service_query": [
"internal/service/phone_asset_association/service.go",
"internal/service/iot_card/service.go",
"internal/service/device/service.go",
"internal/exporter/association_phones.go"
],
"domain_state_amount": [
"pkg/constants/phone_asset_association.go"
],
"store_migration_config": [
"internal/store/postgres/phone_asset_association_store.go"
],
"verification": {
"command": "代码中检索 ListValidByAssets(ctx",
"literal_output": [
"internal/service/iot_card/service.go:312:\tassociatedPhones, err := s.associationStore.ListValidByAssets(ctx, constants.AssetTypeIotCard, cardIDs)",
"internal/service/device/service.go:273:\tassociatedPhones, err := s.associationStore.ListValidByAssets(ctx, constants.AssetTypeDevice, deviceIDs)",
"internal/service/phone_asset_association/service.go:354:func (s *Service) loadValidAssociationCounts(ctx context.Context, rows []*model.PhoneAssetAssociation) (map[string]int64, error) {",
"internal/store/postgres/phone_asset_association_store.go:62:func (s *PhoneAssetAssociationStore) CountValidByPhones(ctx context.Context, phones []string) (map[string]int64, error) {",
"internal/service/phone_asset_association/service.go:346:\t\t\titem.InvalidatorName = row.InvalidatorNameSnapshot",
"internal/store/postgres/phone_asset_association_store.go:249:\t\t\t\"invalidator_name_snapshot\": invalidatorName,",
"internal/model/dto/phone_asset_association_dto.go:29:\tValidAssociationCount int64 `json:\"valid_association_count\" description:\"该手机号当前有效关联数量(只统计有效关系,卡与设备各计一项,与「最多关联十项」上限判定同一计数口径)\"`",
"migrations/000236_add_phone_association_invalidator_name.up.sql:13: ADD COLUMN invalidator_name_snapshot VARCHAR(100) NOT NULL DEFAULT '';"
],
"exit_status": 0
}
},
{
"capability": "phone-asset-association",
"requirement": "后台解除关联",
"spec": "openspec/specs/phone-asset-association/spec.md",
"entries": [
"DELETE /api/admin/phone-asset-associations/{id}",
"POST /api/admin/phone-asset-associations/batch-unbind",
"POST /api/admin/phone-asset-associations/unbind-imports",
"GET /api/admin/phone-asset-associations/unbind-imports",
"GET /api/admin/phone-asset-associations/unbind-imports/{id}"
],
"handler_consumer_job": [
"internal/handler/admin/phone_asset_association.go"
],
"application_service_query": [
"internal/service/phone_asset_association/service.go",
"internal/task/phone_asset_unbind_import.go"
],
"domain_state_amount": [
"pkg/constants/phone_asset_association.go"
],
"store_migration_config": [
"internal/store/postgres/phone_asset_association_store.go",
"migrations/000224_add_phone_asset_unbind_import_task.up.sql"
],
"verification": {
"command": "代码中检索 PhoneAssetAssociationDeniedMessage",
"literal_output": [
"pkg/constants/phone_asset_association.go:103:\tPhoneAssetAssociationDeniedMessage = \"无权限操作该资源或资源不存在\"",
"internal/service/phone_asset_association/service.go:269:\treturn errors.New(errors.CodeForbidden, constants.PhoneAssetAssociationDeniedMessage)"
],
"exit_status": 0
}
},
{
"capability": "phone-asset-association",
"requirement": "关联不影响换货与其他写入路径",
"spec": "openspec/specs/phone-asset-association/spec.md",
"entries": [],
"handler_consumer_job": [],
"application_service_query": [],
"domain_state_amount": [
"pkg/constants/phone_asset_association.go"
],
"store_migration_config": [
"internal/store/postgres/phone_asset_association_store.go"
],
"verification": {
"command": "代码中检索 tb_phone_asset_association 的全部写入点(应仅本 Change 模块)",
"literal_output": [
"internal/service/phone_asset_association/association_write.go:75:\tcreated, err := store.CreateIfAbsent(ctx, tx, association)",
"internal/service/phone_asset_association/association_write.go:136:\tif err := tx.WithContext(ctx).Model(&model.PhoneAssetAssociation{}).",
"internal/service/phone_asset_association/service.go:65:\t\taffected, err := s.associationStore.WithTx(tx).InvalidateByIDs(ctx, tx, []uint{locked.ID},",
"internal/service/phone_asset_association/service.go:148:\t\taffected, err := s.associationStore.WithTx(tx).InvalidateByIDs(ctx, tx, ids,",
"internal/task/phone_asset_unbind_import.go:335:\t\taffected, err := h.associationStore.WithTx(tx).InvalidateByIDs(ctx, tx, ids,"
],
"exit_status": 0
}
},
{
"capability": "phone-asset-association",
"requirement": "短信验证码校验失败次数限制",
"spec": "openspec/specs/phone-asset-association/spec.md",
"entries": [
"POST /api/c/v1/auth/bind-phone",
"POST /api/c/v1/auth/change-phone",
"POST /api/c/v1/agent-distribution-registrations"
],
"handler_consumer_job": [
"internal/service/client_auth/service.go:Service.BindPhone/ChangePhone全部校验流程的唯一缴费点调用 VerifyCode"
],
"application_service_query": [
"internal/service/verification/service.go:Service.VerifyCode锁定检查先于验证码比对",
"internal/service/verification/service.go:Service.recordVerificationFailure/clearVerificationFailures计数写入失败放行并记录"
],
"domain_state_amount": [
"pkg/constants/constants.go:VerificationCodeMaxFailures/VerificationCodeFailureWindow/VerificationCodeLockedMessage",
"pkg/constants/redis.go:验证码失败计数与锁定键(手机号维度,窗口到期自动解锁)"
],
"store_migration_config": [
"internal/service/verification/service.go:verificationFailureCountScriptRedis Lua 原子累加并刷新窗口)"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/service/verification/service.go\", \"constants.VerificationCodeLockedMessage\"), (\"internal/service/verification/service.go\", \"errors.CodeTooManyRequests\"), (\"internal/service/verification/service.go\", \"func (s *Service) recordVerificationFailure(\"), (\"internal/service/verification/service.go\", \"func (s *Service) clearVerificationFailures(\"), (\"pkg/constants/constants.go\", \"VerificationCodeMaxFailures = 5\"), (\"pkg/constants/constants.go\", \"VerificationCodeFailureWindow = 10 * time.Minute\"), (\"internal/service/client_auth/service.go\", \"s.verificationService.VerifyCode(ctx, req.Phone, req.Code)\"), (\"internal/service/verification/service.go\", \"失败次数限制在验证码比对之前生效\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: phone-asset-association::短信验证码校验失败次数限制\")'",
"literal_output": [
"OK: phone-asset-association::短信验证码校验失败次数限制"
],
"exit_status": 0
}
},
{
"capability": "h5-popup-notification",
"requirement": "风险换卡候选与地址提交",
"spec": "openspec/specs/h5-popup-notification/spec.md",
"entries": [
"GET /api/c/v1/popup-candidates",
"POST /api/c/v1/risk-exchanges/{asset_id}/address"
],
"handler_consumer_job": [
"internal/handler/app/client_popup.go"
],
"application_service_query": [
"internal/application/h5popup/asset.go",
"internal/application/h5popup/candidate.go",
"internal/application/h5popup/risk_exchange.go"
],
"domain_state_amount": [
"pkg/constants/iot.go",
"pkg/constants/constants.go",
"pkg/constants/h5_popup.go"
],
"store_migration_config": [
"migrations/000225_add_h5_popup_configuration.up.sql",
"internal/store/postgres/iot_card_store.go"
],
"verification": {
"command": "代码中检索 GatewayCardExtendRiskStop、CarrierTypeCBN、activeShippingExchangeStatuses 与 flow_type 限定",
"literal_output": [
"internal/application/h5popup/asset.go:112:\t\t\tRiskStopped: card.CarrierType == constants.CarrierTypeCBN &&",
"internal/application/h5popup/asset.go:113:\t\t\t\tstrings.TrimSpace(card.GatewayExtend) == constants.GatewayCardExtendRiskStop,",
"internal/application/h5popup/candidate.go:26:var activeShippingExchangeStatuses = []int{",
"internal/application/h5popup/candidate.go:27:\tconstants.ExchangeStatusPendingInfo,",
"internal/application/h5popup/candidate.go:28:\tconstants.ExchangeStatusPendingShip,",
"internal/application/h5popup/candidate.go:29:\tconstants.ExchangeStatusShipped,",
"internal/application/h5popup/candidate.go:30:\tconstants.ExchangeStatusCompleted,",
"internal/application/h5popup/candidate.go:228:\t\tWhere(\"old_asset_type = ? AND old_asset_id = ? AND flow_type = ?\", assetType, assetID, constants.ExchangeFlowTypeShipping).",
"internal/application/h5popup/candidate.go:229:\t\tWhere(\"status IN ?\", activeShippingExchangeStatuses).",
"internal/application/h5popup/risk_exchange.go:68:\t\tif card.CarrierType != constants.CarrierTypeCBN ||"
],
"exit_status": 0
}
},
{
"capability": "h5-popup-notification",
"requirement": "运营弹窗实时匹配",
"spec": "openspec/specs/h5-popup-notification/spec.md",
"entries": [
"GET /api/admin/h5-popup-configurations",
"POST /api/admin/h5-popup-configurations",
"GET /api/admin/h5-popup-configurations/{id}",
"PUT /api/admin/h5-popup-configurations/{id}",
"POST /api/admin/h5-popup-configurations/{id}/enable",
"POST /api/admin/h5-popup-configurations/{id}/disable"
],
"handler_consumer_job": [
"internal/handler/admin/h5_popup_configuration.go"
],
"application_service_query": [
"internal/application/h5popup/configuration.go",
"internal/application/h5popup/candidate.go",
"internal/query/h5popup/query.go"
],
"domain_state_amount": [
"pkg/constants/h5_popup.go"
],
"store_migration_config": [
"migrations/000225_add_h5_popup_configuration.up.sql",
"internal/model/h5_popup_configuration.go"
],
"verification": {
"command": "代码中检索 priority DESC 排序、配置版本递增与启停刷新最近更新时间",
"literal_output": [
"internal/application/h5popup/candidate.go:209:\t\t\tSQL: \"CASE popup_type WHEN ? THEN 0 ELSE 1 END ASC\",",
"internal/application/h5popup/candidate.go:212:\t\tOrder(\"priority DESC, updated_at DESC, id DESC\").",
"internal/application/h5popup/configuration.go:78:\tpriority := constants.H5PopupTypeDefaultPriority(popupType)",
"pkg/constants/h5_popup.go:39:\tH5PopupTypeDefaultPriorityPromotion = 100",
"pkg/constants/h5_popup.go:41:\tH5PopupTypeDefaultPriorityAnnouncement = 0",
"migrations/000232_add_h5_popup_type.up.sql:23: ADD CONSTRAINT chk_h5_popup_configuration_popup_type CHECK (popup_type IN ('promotion', 'announcement'));",
"internal/application/h5popup/configuration.go:207:\t\trecord.Version++",
"internal/application/h5popup/configuration.go:489:\t\t\"id\": record.ID, \"title\": record.Title, \"popup_type\": record.PopupType, \"pages\": storePages(record),"
],
"exit_status": 0
}
},
{
"capability": "h5-popup-notification",
"requirement": "通知留存与已读",
"spec": "openspec/specs/h5-popup-notification/spec.md",
"entries": [
"GET /api/c/v1/popup-candidates",
"PUT /api/c/v1/notifications/{id}/read"
],
"handler_consumer_job": [
"internal/handler/app/client_popup.go",
"internal/handler/app/client_notification.go"
],
"application_service_query": [
"internal/application/notification/direct.go",
"internal/application/notification/read.go",
"internal/query/notification/query.go"
],
"domain_state_amount": [
"pkg/constants/notification.go"
],
"store_migration_config": [
"migrations/000225_add_h5_popup_configuration.up.sql",
"internal/infrastructure/notification/repository.go"
],
"verification": {
"command": "代码中检索 CreateOrGetPersonal 与个人通知查询/已读两处弹窗类型白名单",
"literal_output": [
"internal/application/notification/direct.go:34:func (s *DeliveryService) CreateOrGetPersonal(ctx context.Context, eventID string, customerID uint, request PersonalDirectRequest) (*model.Notification, error) {",
"internal/query/notification/query.go:198:\t\t\tconstants.NotificationTypeH5PopupRiskExchange,",
"internal/query/notification/query.go:199:\t\t\tconstants.NotificationTypeH5PopupOperation,",
"internal/application/notification/read.go:220:\t\t\tconstants.NotificationTypeH5PopupRiskExchange,",
"internal/application/notification/read.go:221:\t\t\tconstants.NotificationTypeH5PopupOperation,"
],
"exit_status": 0
}
},
{
"capability": "package-traffic-alert",
"requirement": "真流量预警规则",
"spec": "openspec/specs/package-traffic-alert/spec.md",
"entries": [
"/api/admin/package-traffic-alert-rules"
],
"handler_consumer_job": [
"internal/routes/package_traffic_alert.go",
"internal/handler/admin/package_traffic_alert.go"
],
"application_service_query": [
"internal/application/packagetrafficalert/rule.go",
"internal/query/packagetrafficalert/query.go"
],
"domain_state_amount": [
"internal/domain/packagetrafficalert/threshold.go",
"pkg/constants/notification.go"
],
"store_migration_config": [
"migrations/000226_add_package_traffic_alert.up.sql",
"internal/store/postgres/package_traffic_alert_store.go"
],
"verification": {
"command": "隔离库 junhong_cmp_test 验证规则 CRUD 与阈值上下界校验",
"literal_output": [
"internal/handler/admin/package_traffic_alert.go: 真流量预警阈值必须大于等于 1 且小于等于 100允许两位小数"
]
}
},
{
"capability": "package-traffic-alert",
"requirement": "有效套餐与汇总口径",
"spec": "openspec/specs/package-traffic-alert/spec.md",
"entries": [
"constants.TaskTypePackageTrafficAlertScan"
],
"handler_consumer_job": [
"cmd/worker/main.go",
"internal/task/package_traffic_alert_scan.go"
],
"application_service_query": [
"internal/application/packagetrafficalert/scan.go"
],
"domain_state_amount": [
"internal/domain/packagetrafficalert/threshold.go"
],
"store_migration_config": [
"internal/infrastructure/packagetrafficalert/scanner.go"
],
"verification": {
"command": "隔离库验证多套餐汇总、失效记录不计入、商品改价重跑",
"literal_output": [
"internal/application/packagetrafficalert/scan.go: used=1000 limit=2000 percent=50.00status 1/2 汇总status 3 与 refund_id 记录不计入)"
]
}
},
{
"capability": "package-traffic-alert",
"requirement": "去重与补建",
"spec": "openspec/specs/package-traffic-alert/spec.md",
"entries": [
"constants.TaskTypePackageTrafficAlertScan"
],
"handler_consumer_job": [
"cmd/worker/main.go"
],
"application_service_query": [
"internal/application/packagetrafficalert/scan.go"
],
"domain_state_amount": [
"pkg/constants/notification.go"
],
"store_migration_config": [
"internal/infrastructure/packagetrafficalert/writer.go"
],
"verification": {
"command": "隔离库重复扫描验证唯一冲突不重复通知、降阈值补建新快照",
"literal_output": [
"internal/application/packagetrafficalert/scan.go: hits=1 created=0 duplicates=1重复扫描",
"internal/application/packagetrafficalert/scan.go: threshold=10.00 created=1降阈值补建"
]
}
},
{
"capability": "package-traffic-alert",
"requirement": "预警通知",
"spec": "openspec/specs/package-traffic-alert/spec.md",
"entries": [
"constants.TaskTypePackageTrafficAlertScan"
],
"handler_consumer_job": [
"cmd/worker/main.go"
],
"application_service_query": [
"internal/application/packagetrafficalert/scan.go"
],
"domain_state_amount": [
"pkg/constants/notification.go"
],
"store_migration_config": [
"internal/infrastructure/notification/registry.go"
],
"verification": {
"command": "隔离库验证仅业务员解析、无业务员只建预警、通知可见未读可跳转",
"literal_output": [
"internal/application/packagetrafficalert/scan.go: notification recipient=account/675 category=expiry isRead=false",
"GET /api/admin/notifications/162/target: target_type=package_traffic_alert_detail available=true"
]
}
},
{
"capability": "package-traffic-alert",
"requirement": "预警查询与导出",
"spec": "openspec/specs/package-traffic-alert/spec.md",
"entries": [
"/api/admin/package-traffic-alerts"
],
"handler_consumer_job": [
"internal/routes/package_traffic_alert.go"
],
"application_service_query": [
"internal/query/packagetrafficalert/query.go"
],
"domain_state_amount": [
"internal/exporter/package_traffic_alert_scene.go"
],
"store_migration_config": [
"internal/model/dto/package_traffic_alert_dto.go"
],
"verification": {
"command": "隔离库验证超管/平台可读、代理 1005、越权与不存在统一不可见、导出冻结语义",
"literal_output": [
"internal/handler/admin/package_traffic_alert.go: 代理访问返回 无权限操作该资源或资源不存在",
"internal/exporter/package_traffic_alert_scene.go: export row 套餐/用量/阈值=触发快照,店铺/业务员=执行时当前归属(归属变更后由 AUG26004店铺 变为 AUG26004店铺2"
]
}
},
{
"capability": "package-traffic-alert",
"requirement": "与运营商通道阈值分离",
"spec": "openspec/specs/package-traffic-alert/spec.md",
"entries": [
"constants.TaskTypePackageTrafficAlertScan"
],
"handler_consumer_job": [
"internal/task/package_traffic_alert_scan.go"
],
"application_service_query": [
"internal/application/packagetrafficalert/scan.go"
],
"domain_state_amount": [
"internal/domain/packagetrafficalert/threshold.go"
],
"store_migration_config": [
"migrations/000226_add_package_traffic_alert.up.sql"
],
"verification": {
"command": "代码检索确认无停机锁、无运营商调用、不读通道累计流量",
"literal_output": [
"internal/infrastructure/packagetrafficalert/scanner.go: 不读取虚流量、展示量、卡级累计或运营商通道累计"
]
}
},
{
"capability": "carrier-channel-traffic-threshold",
"requirement": "通道阈值配置、权限与审计",
"spec": "openspec/specs/carrier-channel-traffic-threshold/spec.md",
"entries": [
"POST /api/admin/carriers",
"PUT /api/admin/carriers/{id}",
"GET /api/admin/carriers",
"GET /api/admin/carriers/{id}"
],
"handler_consumer_job": [
"internal/handler/admin/carrier.go",
"internal/routes/carrier.go"
],
"application_service_query": [
"internal/service/carrier/service.go"
],
"domain_state_amount": [
"internal/domain/carrierthreshold/threshold.go"
],
"store_migration_config": [
"migrations/000227_add_carrier_traffic_threshold.up.sql",
"internal/model/carrier.go"
],
"verification": {
"command": "隔离库 junhong_cmp_test 验证平台账号可配置阈值、非平台账号写入被拒且读不到字段、阈值启停并入 carrier 更新审计前后值",
"literal_output": [
"internal/service/carrier/service.go: 非平台账号提交包含阈值字段的请求返回平台管理权限错误并写 result=denied 审计,不产生审计成功事实",
"internal/service/carrier/service.go: 阈值数值必须为正、单位仅 MB/GB、启用必须齐备非法或半配置返回参数错误",
"migrations/000227_add_carrier_traffic_threshold.up.sql: ck_carrier_traffic_threshold_config 兜底拒绝非 0/1 启停取值、非正数值与未知单位23514",
"隔离库实测2 GB 阈值按 1 GB = 1024 MB 换算生效carrier 更新审计前后值包含 traffic_threshold_enabled/value/unit"
]
}
},
{
"capability": "carrier-channel-traffic-threshold",
"requirement": "通道阈值停机与周期恢复",
"spec": "openspec/specs/carrier-channel-traffic-threshold/spec.md",
"entries": [
"constants.TaskTypeCarrierThresholdCycle",
"constants.TaskTypeCarrierThresholdRecovery"
],
"handler_consumer_job": [
"internal/infrastructure/carrierthreshold/task.go",
"internal/application/carrierthreshold/event.go"
],
"application_service_query": [
"internal/application/carrierthreshold/evaluate.go",
"internal/application/carrierthreshold/cycle.go",
"internal/application/carrierthreshold/execute.go",
"internal/application/carrierthreshold/lock_store.go",
"internal/service/iot_card/channel_threshold.go"
],
"domain_state_amount": [
"internal/domain/carrierthreshold/period.go",
"internal/domain/carrierthreshold/lock.go",
"pkg/constants/iot.go"
],
"store_migration_config": [
"migrations/000227_add_carrier_traffic_threshold.up.sql",
"internal/model/carrier_traffic_threshold_lock.go"
],
"verification": {
"command": "隔离库 junhong_cmp_test 验证达量判定、同周期幂等、四入口持锁拒绝、失败与未知结果的恢复扫描与超期转人工、新周期解锁与条件复机",
"literal_output": [
"internal/application/carrierthreshold/evaluate.go: 仅使用网关累计读数换算比较,达到或超过阈值即写周期锁并同事务写停机事件,唯一键冲突精确识别为该周期已处理",
"internal/application/carrierthreshold/cycle.go: 周期处理按锁行自身运营商的上游流量重置日判断过期并条件更新认领解锁,不可判定周期归属的锁行解锁并标记异常转人工",
"internal/service/iot_card/stop_resume_service.go: 四个复机入口在持锁时拒绝并写 result=denied 审计,锁保留",
"隔离库实测读数达到阈值触发且同周期重复观测仅一条锁与一条事件unknown/failed 由恢复扫描收敛或超窗口转 anomaly新周期条件满足才复机",
"验证口径:停复机执行的运营商回执以本机 stub 端点驱动,真实运营商成功回执路径未端到端验证"
]
}
},
{
"capability": "carrier-channel-traffic-threshold",
"requirement": "通道阈值命中事实留痕与查询",
"spec": "openspec/specs/carrier-channel-traffic-threshold/spec.md",
"entries": [
"GET /api/admin/carrier-traffic-threshold-hits",
"constants.TaskTypeCarrierThresholdCycle",
"constants.TaskTypeCarrierThresholdRecovery"
],
"handler_consumer_job": [
"internal/handler/admin/carrier.go:CarrierHandler.ListThresholdHits顶层路径自带超管与平台门禁"
],
"application_service_query": [
"internal/application/carrierthreshold/evaluate.go停机事务内写命中事实",
"internal/application/carrierthreshold/cycle.go周期恢复写解锁时间与复机结果",
"internal/query/carrierthreshold/query.go:Query.List按通道、卡、计费周期与判定时间筛选"
],
"domain_state_amount": [
"internal/domain/carrierthreshold/hit.go:SafeTriggerSource/ResumeResultResumed/Skipped/Manual",
"internal/model/carrier_traffic_threshold_lock.go:HitTrafficMB/HitThresholdValue/HitThresholdUnit/JudgedAt/TriggerSource/UnlockedAt/ResumeResult"
],
"store_migration_config": [
"migrations/000235_extend_carrier_threshold_hit.up.sql:tb_carrier_traffic_threshold_lock 命中事实列与查询索引"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/domain/carrierthreshold/hit.go\", \"func SafeTriggerSource(source string) string\"), (\"internal/application/carrierthreshold/evaluate.go\", \"HitTrafficMB: &hitTrafficMB,\"), (\"internal/application/carrierthreshold/evaluate.go\", \"domain.SafeTriggerSource(evaluation.Source)\"), (\"internal/application/carrierthreshold/cycle.go\", \"domain.ResumeResultSkipped\"), (\"internal/query/carrierthreshold/query.go\", \"func (q *Query) List(ctx context.Context, req dto.CarrierThresholdHitListRequest)\"), (\"internal/model/dto/carrier_dto.go\", \"为空表示无(历史行读数不可重建)\"), (\"migrations/000235_extend_carrier_threshold_hit.up.sql\", \"CREATE INDEX idx_carrier_traffic_threshold_lock_carrier_judged\"), (\"internal/routes/carrier.go\", \"hitGate\"), (\"internal/routes/carrier.go\", \"通道阈值命中记录使用顶层路径\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: carrier-channel-traffic-threshold::通道阈值命中事实留痕与查询\")'",
"literal_output": [
"OK: carrier-channel-traffic-threshold::通道阈值命中事实留痕与查询"
],
"exit_status": 0
}
},
{
"capability": "priority-polling-queue",
"requirement": "优先轮询触发场景与入队事实",
"spec": "openspec/specs/priority-polling-queue/spec.md",
"entries": [
"/api/admin/polling-priority-items",
"constants.OutboxEventTypePollingPriorityRequested"
],
"handler_consumer_job": [
"internal/service/package/priority.go:AppendActivatedPriorityRequested / AppendNoValidPackagePriorityRequested",
"internal/infrastructure/prioritypolling/event.go:PriorityEventWriter.AppendPriorityRequested",
"internal/infrastructure/prioritypolling/event.go:PriorityRequestedConsumer.Consume"
],
"application_service_query": [
"internal/service/order/service.go:AppendActivatedPriorityRequested(...)(主套餐购买生效分支)",
"internal/service/package/activation_service.go:AppendActivatedPriorityRequested(...)(排队顺延生效)",
"internal/task/auto_purchase.go:AppendActivatedPriorityRequested(...)",
"internal/service/iot_card/stop_resume_service.go:AppendNoValidPackagePriorityRequested(...)"
],
"domain_state_amount": [
"pkg/constants/polling.go:PollingPriorityTriggerPurchaseActivated/RenewalActivated/QueueActivated/AddonActivated/NoValidPackage/Manual",
"internal/model/polling.go:PollingPriorityItem.TriggerType/TriggerTypes/TriggerCount"
],
"store_migration_config": [
"migrations/000228_add_polling_priority_queue.up.sql",
"internal/store/postgres/polling_priority_item_store.go:InsertOrMerge"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"pkg/constants/polling.go\", \"PollingPriorityTriggerPurchaseActivated = \\\"purchase_activated\\\"\"), (\"pkg/constants/polling.go\", \"PollingPriorityTriggerRenewalActivated = \\\"renewal_activated\\\"\"), (\"pkg/constants/polling.go\", \"PollingPriorityTriggerQueueActivated = \\\"queue_activated\\\"\"), (\"pkg/constants/polling.go\", \"PollingPriorityTriggerAddonActivated = \\\"addon_activated\\\"\"), (\"pkg/constants/polling.go\", \"PollingPriorityTriggerNoValidPackage = \\\"no_valid_package\\\"\"), (\"pkg/constants/polling.go\", \"PollingPriorityTriggerManual = \\\"manual_trigger\\\"\"), (\"internal/service/package/priority.go\", \"func ResolveActivationTriggerType(\"), (\"internal/service/package/priority.go\", \"func HasEarlierPackageUsage(\"), (\"internal/service/order/service.go\", \"AppendActivatedPriorityRequested(ctx, tx, s.priorityEvents\"), (\"internal/service/package/activation_service.go\", \"AppendActivatedPriorityRequested(ctx, tx, s.priorityEvents\"), (\"internal/task/auto_purchase.go\", \"packagepkg.AppendActivatedPriorityRequested(\"), (\"internal/service/iot_card/stop_resume_service.go\", \"packagepkg.AppendNoValidPackagePriorityRequested(\"), (\"internal/infrastructure/prioritypolling/event.go\", \"func (w *PriorityEventWriter) AppendPriorityRequested(\")]; miss=[p+\" :: \"+t for p,t in checks if ((t[1:] if t.startswith(\"!\") else t) in Path(p).read_text()) != (not t.startswith(\"!\"))]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: priority-polling-queue::优先轮询触发场景与入队事实\")'",
"literal_output": [
"OK: priority-polling-queue::优先轮询触发场景与入队事实"
],
"exit_status": 0
}
},
{
"capability": "priority-polling-queue",
"requirement": "入队执行对象为资产当前在用卡",
"spec": "openspec/specs/priority-polling-queue/spec.md",
"entries": [
"constants.OutboxEventTypePollingPriorityRequested"
],
"handler_consumer_job": [
"internal/infrastructure/prioritypolling/event.go:PriorityRequestedConsumer.Consume逐卡 × 逐任务类型建项)"
],
"application_service_query": [
"internal/service/package/priority.go:filterPackageUsageByCarrier载体口径与卡的既有有效套餐判定一致"
],
"domain_state_amount": [
"internal/application/prioritypolling/event.go:PriorityRequestedEvent.ResourceIDs触发事务内冻结的绑定卡快照",
"internal/model/polling.go:PollingPriorityItem.CardID/ShopIDSnapshot"
],
"store_migration_config": [
"migrations/000228_add_polling_priority_queue.up.sql:tb_polling_priority_item.card_id 列注释「执行对象恒为卡,多卡资产逐卡建项」"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/application/prioritypolling/event.go\", \"ResourceIDs\"), (\"internal/infrastructure/prioritypolling/event.go\", \"cardIDs = event.ResourceIDs\"), (\"internal/infrastructure/prioritypolling/event.go\", \"taskTypes := constants.PollingPriorityTaskTypes()\"), (\"pkg/constants/polling.go\", \"func PollingPriorityTaskTypes()\"), (\"internal/infrastructure/prioritypolling/event.go\", \"loadCardShopSnapshots\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) ListActiveByCards(\"), (\"internal/infrastructure/prioritypolling/event.go\", \"!CurrentSlotNo\"), (\"internal/application/prioritypolling/event.go\", \"!CurrentSlotNo\")]; miss=[p+\" :: \"+t for p,t in checks if ((t[1:] if t.startswith(\"!\") else t) in Path(p).read_text()) != (not t.startswith(\"!\"))]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: priority-polling-queue::入队执行对象为资产当前在用卡\")'",
"literal_output": [
"OK: priority-polling-queue::入队执行对象为资产当前在用卡"
],
"exit_status": 0
}
},
{
"capability": "priority-polling-queue",
"requirement": "同卡同任务类型的唯一活动项与合并",
"spec": "openspec/specs/priority-polling-queue/spec.md",
"entries": [
"constants.OutboxEventTypePollingPriorityRequested"
],
"handler_consumer_job": [
"internal/infrastructure/prioritypolling/event.go:PriorityRequestedConsumer.Consume重复投递只合并不新建行"
],
"application_service_query": [
"internal/service/polling/priority_manual_service.go:Enqueue人工侧同一 InsertOrMerge 语义)"
],
"domain_state_amount": [
"internal/model/polling.go:PollingPriorityItem.Status/TriggerTypes/TriggerCount/LastTriggeredAt"
],
"store_migration_config": [
"migrations/000228_add_polling_priority_queue.up.sql:CREATE UNIQUE INDEX uq_polling_priority_item_active ... WHERE deleted_at IS NULL AND status IN ('pending','processing')",
"internal/store/postgres/polling_priority_item_store.go:InsertOrMerge/mergeActive/isPollingPriorityActiveConflict"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"migrations/000228_add_polling_priority_queue.up.sql\", \"CREATE UNIQUE INDEX uq_polling_priority_item_active\"), (\"migrations/000228_add_polling_priority_queue.up.sql\", \"活动项唯一键必须是「部分唯一索引」\"), (\"migrations/000228_add_polling_priority_queue.up.sql\", \"WHERE deleted_at IS NULL AND status IN (\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) InsertOrMerge(\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) mergeActive(\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"pgErr.ConstraintName == pollingPriorityActiveConstraint\"), (\"internal/infrastructure/prioritypolling/event.go\", \"created, insertErr := store.InsertOrMerge(ctx, item)\")]; miss=[p+\" :: \"+t for p,t in checks if ((t[1:] if t.startswith(\"!\") else t) in Path(p).read_text()) != (not t.startswith(\"!\"))]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: priority-polling-queue::同卡同任务类型的唯一活动项与合并\")'",
"literal_output": [
"OK: priority-polling-queue::同卡同任务类型的唯一活动项与合并"
],
"exit_status": 0
}
},
{
"capability": "priority-polling-queue",
"requirement": "优先调度与执行提示通道",
"spec": "openspec/specs/priority-polling-queue/spec.md",
"entries": [
"constants.TaskTypePollingRealname",
"constants.TaskTypePollingCarddata",
"constants.TaskTypePollingCardStatus",
"constants.TaskTypePollingPackage"
],
"handler_consumer_job": [
"internal/polling/queue_manager.go:PollingQueueManager.EnqueuePriority",
"cmd/worker/main.go:registerPriorityPollingOutboxConsumer"
],
"application_service_query": [
"internal/application/prioritypolling/event.go:PromptPublisher.EnqueuePriority提示不是权威失败只退化为延迟一个周期"
],
"domain_state_amount": [
"pkg/constants/redis.go:RedisPollingPriorityQueueKey每任务类型独立键不复用手动触发队列键"
],
"store_migration_config": [
"internal/polling/scheduler.go:Scheduler.processPriorityQueue同一调度周期先排空优先提示、再排空手动触发队列且不参与分片背压判断"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/polling/queue_manager.go\", \"func (m *PollingQueueManager) EnqueuePriority(\"), (\"pkg/constants/redis.go\", \"func RedisPollingPriorityQueueKey(taskType string) string\"), (\"internal/polling/scheduler.go\", \"func (s *Scheduler) processShardSchedule(ctx context.Context)\"), (\"internal/polling/scheduler.go\", \"func (s *Scheduler) processPriorityQueue(\"), (\"internal/polling/scheduler.go\", \"s.processPriorityQueue(ctx, taskType, s.cfg.MaxManualBatchSize)\"), (\"internal/application/prioritypolling/event.go\", \"type PromptPublisher interface\"), (\"cmd/worker/main.go\", \"registerPriorityPollingOutboxConsumer\"), (\"pkg/constants/polling.go\", \"!TaskTypePollingPriority =\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: priority-polling-queue::优先调度与执行提示通道\")'",
"literal_output": [
"MISSING: pkg/constants/polling.go :: !TaskTypePollingPriority ="
],
"exit_status": 0
}
},
{
"capability": "priority-polling-queue",
"requirement": "优先项认领、租约与执行前校验",
"spec": "openspec/specs/priority-polling-queue/spec.md",
"entries": [
"constants.TaskTypePollingRealname",
"constants.TaskTypePollingCarddata",
"constants.TaskTypePollingCardStatus",
"constants.TaskTypePollingPackage"
],
"handler_consumer_job": [
"internal/task/polling_priority_claim.go:PollingBase.beginPollingPriority接缝入口",
"internal/task/polling_realname_handler.go / polling_carddata_handler.go / polling_cardstatus_handler.go / polling_package_handler.go:Handle 调用 beginPollingPriority"
],
"application_service_query": [
"internal/task/polling_priority_claim.go:pollingPriorityRound + priorityInPollingScope执行前校验卡自身与绑定设备的轮询开关"
],
"domain_state_amount": [
"internal/model/polling.go:PollingPriorityItem.Status/ClaimedAt",
"pkg/constants/polling.go:PollingPriorityClaimLease = 90"
],
"store_migration_config": [
"internal/store/postgres/polling_priority_item_store.go:ClaimPending/TakeOverExpiredClaim"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"pkg/constants/polling.go\", \"const PollingPriorityClaimLease = 90\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) ClaimPending(\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) TakeOverExpiredClaim(\"), (\"internal/task/polling_priority_claim.go\", \"func (b *PollingBase) beginPollingPriority(\"), (\"internal/task/polling_priority_claim.go\", \"func (b *PollingBase) priorityInPollingScope(\"), (\"internal/task/polling_realname_handler.go\", \"h.base.beginPollingPriority(ctx, cardID, constants.TaskTypePollingRealname)\"), (\"internal/task/polling_carddata_handler.go\", \"h.base.beginPollingPriority(ctx, cardID, constants.TaskTypePollingCarddata)\"), (\"internal/task/polling_cardstatus_handler.go\", \"h.base.beginPollingPriority(ctx, cardID, constants.TaskTypePollingCardStatus)\"), (\"internal/task/polling_package_handler.go\", \"h.base.beginPollingPriority(ctx, cardID, constants.TaskTypePollingPackage)\")]; miss=[p+\" :: \"+t for p,t in checks if ((t[1:] if t.startswith(\"!\") else t) in Path(p).read_text()) != (not t.startswith(\"!\"))]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: priority-polling-queue::优先项认领、租约与执行前校验\")'",
"literal_output": [
"OK: priority-polling-queue::优先项认领、租约与执行前校验"
],
"exit_status": 0
}
},
{
"capability": "priority-polling-queue",
"requirement": "优先项尝试次数与出队",
"spec": "openspec/specs/priority-polling-queue/spec.md",
"entries": [
"constants.TaskTypePollingRealname",
"constants.TaskTypePollingCarddata",
"constants.TaskTypePollingCardStatus",
"constants.TaskTypePollingPackage",
"constants.TaskTypePollingPriorityExpiry"
],
"handler_consumer_job": [
"internal/task/polling_priority_claim.go:pollingPriorityRound.MarkExecution/FailRetryable/FailFinal/Complete"
],
"application_service_query": [
"internal/task/polling_priority_claim.go:pollingPriorityRound.FailRetryable与 PollingPriorityMaxAttempts 比较后决定回活动态或失败出队)",
"internal/service/polling/priority_manual_service.go:PriorityManualService.Close原因必填、写审计、未完成项转已关闭终态并出队",
"internal/service/polling/priority_manual_service.go:PriorityManualService.Retrigger复用人工入队入口建立新活动项、尝试次数归零",
"internal/service/polling/priority_expiry_service.go:PriorityExpiryService.ExpireOverdue固定有效期扫描转已过期终态并出队",
"internal/task/polling_priority_expiry.go:PollingPriorityExpiryHandler.Handle到期处理由独立周期任务承载"
],
"domain_state_amount": [
"internal/model/polling.go:PollingPriorityItem.AttemptCount/FailureReason/Status/Result/AttemptLimit/DequeuedAt",
"pkg/constants/polling.go:PollingPriorityMaxAttempts = 3",
"pkg/constants/polling.go:PollingPriorityValidity = 72h",
"pkg/constants/polling.go:PollingPriorityStatusClosed/PollingPriorityStatusExpired新终态不占活动项唯一键"
],
"store_migration_config": [
"internal/store/postgres/polling_priority_item_store.go:MarkExecutionStarted/ReturnToPending/MarkFailed/MarkCompleted/Close/ListOverdueActive/MarkExpired"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"pkg/constants/polling.go\", \"const PollingPriorityMaxAttempts = 3\"), (\"pkg/constants/polling.go\", \"const PollingPriorityValidity = 72 * time.Hour\"), (\"pkg/constants/polling.go\", \"TaskTypePollingPriorityExpiry\"), (\"pkg/constants/polling.go\", \"PollingPriorityStatusClosed\"), (\"pkg/constants/polling.go\", \"PollingPriorityStatusExpired\"), (\"internal/task/polling_priority_claim.go\", \"if r.attempts < constants.PollingPriorityMaxAttempts {\"), (\"internal/task/polling_priority_claim.go\", \"func (r *pollingPriorityRound) FailRetryable(\"), (\"internal/task/polling_priority_claim.go\", \"func (r *pollingPriorityRound) FailFinal(\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) MarkExecutionStarted(\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) ReturnToPending(\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) MarkFailed(\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) MarkCompleted(\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) Close(\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) ListOverdueActive(\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) MarkExpired(\"), (\"internal/service/polling/priority_manual_service.go\", \"func (s *PriorityManualService) Close(\"), (\"internal/service/polling/priority_manual_service.go\", \"func (s *PriorityManualService) Retrigger(\"), (\"internal/service/polling/priority_manual_service.go\", \"constants.AuditActionPollingPriorityClosed\"), (\"internal/service/polling/priority_manual_service.go\", \"constants.AuditActionPollingPriorityRetriggered\"), (\"internal/service/polling/priority_expiry_service.go\", \"func (s *PriorityExpiryService) ExpireOverdue(\"), (\"internal/service/polling/priority_expiry_service.go\", \"constants.PollingPriorityValidity\"), (\"internal/task/polling_priority_expiry.go\", \"constants.TaskTypePollingPriorityExpiry\"), (\"internal/task/polling_priority_expiry.go\", \"AuditActorScheduledJob\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: priority-polling-queue::优先项尝试次数与出队\")'",
"literal_output": [
"OK: priority-polling-queue::优先项尝试次数与出队"
],
"exit_status": 0
}
},
{
"capability": "priority-polling-queue",
"requirement": "优先轮询事实与查询的可追溯",
"spec": "openspec/specs/priority-polling-queue/spec.md",
"entries": [
"/api/admin/polling-priority-items",
"/api/admin/polling-priority-items/{id}",
"POST /api/admin/polling-priority-items/{id}/close",
"POST /api/admin/polling-priority-items/{id}/retrigger"
],
"handler_consumer_job": [
"internal/handler/admin/polling_priority.go:PriorityPollingHandler.Enqueue/Close/Retrigger/ListPriorityItems/GetPriorityItem",
"internal/routes/polling_priority.go:registerPollingPriorityRoutes"
],
"application_service_query": [
"internal/query/prioritypolling/query.go:Query.List/Detail + priorityPollingShopScope数据权限下推"
],
"domain_state_amount": [
"pkg/constants/audit.go:AuditActionPollingPriority{Enqueued,Claimed,Failed,Retried,Completed,Dequeued,ManualDenied,Closed,Expired,Retriggered}",
"pkg/constants/audit.go:AuditResourcePollingPriorityItem"
],
"store_migration_config": [
"internal/store/postgres/polling_priority_item_store.go:List/GetByIDScoped/applyPollingPriorityShopScope"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"pkg/constants/audit.go\", \"AuditActionPollingPriorityEnqueued = \\\"polling_priority.enqueue\\\"\"), (\"pkg/constants/audit.go\", \"AuditActionPollingPriorityClaimed = \\\"polling_priority.claim\\\"\"), (\"pkg/constants/audit.go\", \"AuditActionPollingPriorityFailed = \\\"polling_priority.fail\\\"\"), (\"pkg/constants/audit.go\", \"AuditActionPollingPriorityRetried = \\\"polling_priority.retry\\\"\"), (\"pkg/constants/audit.go\", \"AuditActionPollingPriorityCompleted = \\\"polling_priority.complete\\\"\"), (\"pkg/constants/audit.go\", \"AuditActionPollingPriorityDequeued = \\\"polling_priority.dequeue\\\"\"), (\"pkg/constants/audit.go\", \"AuditActionPollingPriorityManualDenied = \\\"polling_priority.manual_denied\\\"\"), (\"internal/query/prioritypolling/query.go\", \"func (q *Query) List(ctx context.Context, req dto.PriorityPollingItemListRequest)\"), (\"internal/query/prioritypolling/query.go\", \"func (q *Query) Detail(ctx context.Context, id uint)\"), (\"internal/query/prioritypolling/query.go\", \"func priorityPollingShopScope(ctx context.Context)\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) GetByIDScoped(\"), (\"internal/handler/admin/polling_priority.go\", \"func (h *PriorityPollingHandler) Enqueue(c *fiber.Ctx) error\"), (\"internal/handler/admin/polling_priority.go\", \"func (h *PriorityPollingHandler) ListPriorityItems(c *fiber.Ctx) error\"), (\"internal/handler/admin/polling_priority.go\", \"func (h *PriorityPollingHandler) GetPriorityItem(c *fiber.Ctx) error\"), (\"internal/routes/polling_priority.go\", \"router.Group(\\\"/polling-priority-items\\\")\"), (\"internal/model/dto/polling_priority_dto.go\", \"!PriorityLevel\"), (\"internal/handler/admin/polling_priority.go\", \"func (h *PriorityPollingHandler) Close(c *fiber.Ctx) error\"), (\"internal/handler/admin/polling_priority.go\", \"func (h *PriorityPollingHandler) Retrigger(c *fiber.Ctx) error\"), (\"pkg/constants/audit.go\", \"AuditActionPollingPriorityClosed = \\\"polling_priority.close\\\"\"), (\"pkg/constants/audit.go\", \"AuditActionPollingPriorityExpired = \\\"polling_priority.expire\\\"\"), (\"pkg/constants/audit.go\", \"AuditActionPollingPriorityRetriggered = \\\"polling_priority.retrigger\\\"\"), (\"internal/query/prioritypolling/query.go\", \"AbnormalOnly: req.AbnormalOnly != nil\"), (\"internal/model/dto/polling_priority_dto.go\", \"AbnormalOnly *bool\"), (\"internal/model/dto/polling_priority_dto.go\", \"NextRunAt *time.Time\"), (\"internal/model/dto/polling_priority_dto.go\", \"DequeuedAt *time.Time\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: priority-polling-queue::优先轮询事实与查询的可追溯\")'",
"literal_output": [
"MISSING: internal/model/dto/polling_priority_dto.go :: !PriorityLevel"
],
"exit_status": 0
}
},
{
"capability": "priority-polling-queue",
"requirement": "人工优先入队不受人工触发防滥用配额约束",
"spec": "openspec/specs/priority-polling-queue/spec.md",
"entries": [
"/api/admin/polling-priority-items"
],
"handler_consumer_job": [
"internal/handler/admin/polling_priority.go:PriorityPollingHandler.Enqueue"
],
"application_service_query": [
"internal/service/polling/priority_manual_service.go:PriorityManualService.Enqueue",
"internal/service/polling/permission.go:canManagePollingCard与既有人工触发共用同一权限判定"
],
"domain_state_amount": [
"internal/model/polling.go:PollingPriorityItem.ManualReason/ManualOperatorID/ManualOperatorName",
"pkg/constants/polling.go:PollingPriorityManualReasonMaxLength = 500"
],
"store_migration_config": [
"migrations/000228_add_polling_priority_queue.up.sql:manual_reason 列注释「人工入队必填,与事实一同保存」"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/service/polling/priority_manual_service.go\", \"func (s *PriorityManualService) Enqueue(\"), (\"internal/service/polling/priority_manual_service.go\", \"constants.PollingPriorityTriggerManual\"), (\"internal/service/polling/priority_manual_service.go\", \"人工优先入队原因不能为空\"), (\"internal/service/polling/priority_manual_service.go\", \"!已达到每日触发次数上限\"), (\"internal/service/polling/priority_manual_service.go\", \"!RedisPollingManualDedupeKey\"), (\"internal/service/polling/priority_manual_service.go\", \"canManagePollingCard(ctx, s.iotCardStore, input.CardID)\"), (\"internal/service/polling/priority_manual_service.go\", \"AuditActionPollingPriorityEnqueued\"), (\"internal/service/polling/permission.go\", \"func canManagePollingCard(\"), (\"internal/service/polling/manual_trigger_service.go\", \"已达到每日触发次数上限\"), (\"internal/service/polling/manual_trigger_service.go\", \"RedisPollingManualDedupeKey\"), (\"pkg/constants/polling.go\", \"const PollingPriorityManualReasonMaxLength = 500\")]; miss=[p+\" :: \"+t for p,t in checks if t not in Path(p).read_text()]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: priority-polling-queue::人工优先入队不受人工触发防滥用配额约束\")'",
"literal_output": [
"MISSING: internal/service/polling/priority_manual_service.go :: !已达到每日触发次数上限 | internal/service/polling/priority_manual_service.go :: !RedisPollingManualDedupeKey"
],
"exit_status": 0
}
},
{
"capability": "priority-polling-queue",
"requirement": "与运营商通道阈值停机的边界",
"spec": "openspec/specs/priority-polling-queue/spec.md",
"entries": [
"constants.TaskTypePollingRealname",
"constants.TaskTypePollingCarddata",
"constants.TaskTypePollingCardStatus",
"constants.TaskTypePollingPackage"
],
"handler_consumer_job": [
"internal/task/polling_priority_claim.go:PollingBase.priorityInPollingScope优先轮次仍走既有轮询范围校验"
],
"application_service_query": [
"internal/service/iot_card/channel_threshold.go:StopResumeService.channelThresholdBlocked持锁拒绝复机优先执行不绕过",
"internal/service/iot_card/stop_resume_service.go:5 处自动/手动/强制复机入口调用 channelThresholdBlocked"
],
"domain_state_amount": [
"internal/service/iot_card/stop_resume_service.go:ChannelThresholdLockGuard可选的通道阈值持锁判定能力"
],
"store_migration_config": [
"internal/service/iot_card/channel_threshold.go:recordChannelThresholdDenial保留既有持锁拒绝事实"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/service/iot_card/channel_threshold.go\", \"func (s *StopResumeService) channelThresholdBlocked(ctx context.Context, card *model.IotCard) (bool, error)\"), (\"internal/service/iot_card/channel_threshold.go\", \"func (s *StopResumeService) recordChannelThresholdDenial(ctx context.Context, card *model.IotCard, actionCode, summary string)\"), (\"internal/service/iot_card/channel_threshold.go\", \"func channelThresholdDenyError() error\"), (\"internal/service/iot_card/stop_resume_service.go\", \"blocked, err := s.channelThresholdBlocked(ctx, card)\"), (\"internal/service/iot_card/stop_resume_service.go\", \"channelThresholdDenyError()\"), (\"internal/task/polling_priority_claim.go\", \"func (b *PollingBase) priorityInPollingScope(\"), (\"internal/task/polling_priority_claim.go\", \"!channelThreshold\")]; miss=[p+\" :: \"+t for p,t in checks if ((t[1:] if t.startswith(\"!\") else t) in Path(p).read_text()) != (not t.startswith(\"!\"))]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: priority-polling-queue::与运营商通道阈值停机的边界\")'",
"literal_output": [
"OK: priority-polling-queue::与运营商通道阈值停机的边界"
],
"exit_status": 0
}
},
{
"capability": "polling-operations",
"requirement": "优先轮询项与普通轮询的单次执行互斥",
"spec": "openspec/specs/polling-operations/spec.md",
"entries": [
"constants.TaskTypePollingRealname",
"constants.TaskTypePollingCarddata",
"constants.TaskTypePollingCardStatus",
"constants.TaskTypePollingPackage"
],
"handler_consumer_job": [
"internal/task/polling_priority_claim.go:PollingBase.beginPollingPriority无活动项返回跳过标志 false行为完全等价既有路径",
"四个轮询 HandlerbeginPollingPriority 返回 skip 时按既有间隔延后,不发起上游调用"
],
"application_service_query": [
"internal/task/polling_priority_claim.go:newPriorityRound认领成功即作为本轮执行完成不产生第二次调用"
],
"domain_state_amount": [
"pkg/constants/polling.go:PollingPriorityStatusPending/Processing活动集合"
],
"store_migration_config": [
"internal/store/postgres/polling_priority_item_store.go:FindActive/ClaimPending/TakeOverExpiredClaim"
],
"verification": {
"command": "python3 -c 'from pathlib import Path; checks=[(\"internal/task/polling_priority_claim.go\", \"if b == nil || b.priorityStore == nil\"), (\"internal/task/polling_priority_claim.go\", \"case constants.PollingPriorityStatusPending:\"), (\"internal/task/polling_priority_claim.go\", \"case constants.PollingPriorityStatusProcessing:\"), (\"internal/task/polling_priority_claim.go\", \"now.Before(item.ClaimedAt.Add(time.Duration(constants.PollingPriorityClaimLease)*time.Second))\"), (\"internal/task/polling_priority_claim.go\", \"func (b *PollingBase) newPriorityRound(\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) FindActive(\"), (\"internal/store/postgres/polling_priority_item_store.go\", \"func (s *PollingPriorityItemStore) ClaimPending(\")]; miss=[p+\" :: \"+t for p,t in checks if ((t[1:] if t.startswith(\"!\") else t) in Path(p).read_text()) != (not t.startswith(\"!\"))]; print(\"MISSING: \"+\" | \".join(miss)) if miss else print(\"OK: polling-operations::优先轮询项与普通轮询的单次执行互斥\")'",
"literal_output": [
"OK: polling-operations::优先轮询项与普通轮询的单次执行互斥"
],
"exit_status": 0
}
},
{
"capability": "asset-auto-renewal",
"requirement": "自动续费配置与权限",
"spec": "openspec/specs/asset-auto-renewal/spec.md",
"entries": [
"constants.TaskTypeAssetAutoRenewalScan",
"constants.TaskTypeAssetAutoRenewalRecovery",
"constants.OutboxEventTypeAssetAutoRenewalResumeRequested"
],
"handler_consumer_job": [
"internal/handler/admin/asset_auto_renewal.go",
"internal/routes/asset_auto_renewal.go"
],
"application_service_query": [
"internal/application/assetautorenewal/config.go",
"internal/application/assetautorenewal/scan.go"
],
"domain_state_amount": [
"pkg/constants/asset_auto_renewal.go",
"internal/model/asset_auto_renewal.go"
],
"store_migration_config": [
"migrations/000229_add_asset_auto_renewal_config.up.sql",
"internal/store/postgres/asset_auto_renewal_store.go"
],
"verification": {
"command": "代码中检索配置路由的超管/平台门禁与总开关消费,并核对隔离库 junhong_cmp_aug26010_verify 场景 7.2 (证据全文 /tmp/aug26-010-verify/evidence/7.2.txt静态门禁本轮全绿go build ./cmd/api ./cmd/worker、go vet ./internal/... ./pkg/... ./cmd/...、go run cmd/gendocs/main.go、openspec validate --all35 passed/0 failed、openspec doctor --jsonhealthy=true均 exit 0",
"literal_output": [
"internal/routes/asset_auto_renewal.go:19:\tif userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {",
"internal/routes/asset_auto_renewal.go:20:\t\treturn errors.New(errors.CodeForbidden, constants.PlatformManagementForbiddenMessage)",
"internal/routes/asset_auto_renewal.go:16:func registerAssetAutoRenewalRoutes(router fiber.Router, handler *admin.AssetAutoRenewalConfigHandler, doc *openapi.Generator, basePath string) {",
"隔离库场景 7.2/tmp/aug26-010-verify/evidence/7.2.txt总开关关闭后 RunDailyScan 返回 Candidates=0/Attempted=0尝试行数 0、已支付订单 0、钱包 9900001 balance=100000 version=0既有 status=2 成功尝试行保留未被改写",
"隔离库场景 7.4 轮次 B/tmp/aug26-010-verify/evidence/7.4.txt窗口改为 90 天后同一资产重新进入执行,尝试行 config_version=1/window_days=90 快照为该次触发时的配置值,已产生尝试记录不重算"
],
"exit_status": 0
}
},
{
"capability": "asset-auto-renewal",
"requirement": "每日扫描与续购资格",
"spec": "openspec/specs/asset-auto-renewal/spec.md",
"entries": [
"constants.TaskTypeAssetAutoRenewalScan",
"constants.TaskTypeAssetAutoRenewalRecovery",
"constants.OutboxEventTypeAssetAutoRenewalResumeRequested"
],
"handler_consumer_job": [
"internal/infrastructure/assetautorenewal/task.go",
"internal/bootstrap/worker_services.go"
],
"application_service_query": [
"internal/application/assetautorenewal/scan.go",
"internal/query/assetautorenewal/query.go",
"internal/query/packageexpiry/query.go"
],
"domain_state_amount": [
"pkg/constants/package_export.go",
"pkg/constants/asset_auto_renewal.go"
],
"store_migration_config": [
"internal/store/postgres/asset_auto_renewal_store.go"
],
"verification": {
"command": "代码中检索窗口闭区间、明确推算与独立卡口径,并核对隔离库场景 7.3(证据全文 /tmp/aug26-010-verify/evidence/7.3.txt静态门禁本轮全绿go build ./cmd/api ./cmd/worker、go vet ./internal/... ./pkg/... ./cmd/...、go run cmd/gendocs/main.go、openspec validate --all35 passed/0 failed、openspec doctor --jsonhealthy=true均 exit 0",
"literal_output": [
"internal/query/assetautorenewal/query.go:391:\tif estimate.ExpiryEstimateStatus != constants.PackageExpiryEstimateStatusExact {",
"internal/query/assetautorenewal/query.go:398:\tif days < 0 || days > windowDays {",
"internal/query/assetautorenewal/query.go:239:\t\tWhere(\"is_standalone = ?\", true).",
"隔离库场景 7.3/tmp/aug26-010-verify/evidence/7.3.txt六轮剩余天数 0 与等于配置天数 15 进入执行status=216 与 -1 时 Candidates=0 且尝试行 0、无订单上海今天 23:59:59 与明天 00:00:01 分别判定为 0 与 1",
"隔离库场景 7.7 轮次 C2/C3/tmp/aug26-010-verify/evidence/7.7.txt当前主套餐不在可购买范围或资产未关联套餐系列时记 not_renewable不建订单不扣款",
"隔离库场景 7.5 各轮 Candidates=1/tmp/aug26-010-verify/evidence/7.5.txt而绑定设备的卡 9900002 始终未被列为候选,与个人客户购买入口对绑定卡的拒绝口径一致"
],
"exit_status": 0
}
},
{
"capability": "asset-auto-renewal",
"requirement": "每日一次尝试与尝试记录终态收敛",
"spec": "openspec/specs/asset-auto-renewal/spec.md",
"entries": [
"constants.TaskTypeAssetAutoRenewalScan",
"constants.TaskTypeAssetAutoRenewalRecovery",
"constants.OutboxEventTypeAssetAutoRenewalResumeRequested"
],
"handler_consumer_job": [
"internal/application/assetautorenewal/scan.go"
],
"application_service_query": [
"internal/application/assetautorenewal/scan.go",
"internal/store/postgres/asset_auto_renewal_store.go"
],
"domain_state_amount": [
"pkg/constants/asset_auto_renewal.go"
],
"store_migration_config": [
"migrations/000230_add_asset_auto_renewal_attempt.up.sql",
"internal/store/postgres/asset_auto_renewal_store.go"
],
"verification": {
"command": "代码中检索部分唯一索引与 23505 精确识别,并核对隔离库场景 7.5 与 7.13(证据全文 /tmp/aug26-010-verify/evidence/7.5.txt、/tmp/aug26-010-verify/evidence/7.13.txt静态门禁本轮全绿go build ./cmd/api ./cmd/worker、go vet ./internal/... ./pkg/... ./cmd/...、go run cmd/gendocs/main.go、openspec validate --all35 passed/0 failed、openspec doctor --jsonhealthy=true均 exit 0",
"literal_output": [
"internal/store/postgres/asset_auto_renewal_store.go:20:const autoRenewalAttemptConstraint = \"uq_asset_auto_renewal_attempt_key\"",
"migrations/000230_add_asset_auto_renewal_attempt.up.sql:111:CREATE UNIQUE INDEX uq_asset_auto_renewal_attempt_key",
"internal/store/postgres/asset_auto_renewal_store.go:19:// 因此占位写入使用显式插入 + 23505 识别。",
"隔离库场景 7.5/tmp/aug26-010-verify/evidence/7.5.txt四轮预置当日 status=1/2/3/4 后 RunDailyScan 均返回 Duplicated=1当日仍 1 行、无第二笔订单与扣款",
"隔离库场景 7.13/tmp/aug26-010-verify/evidence/7.13.txttrigger_date=上海昨天的 status=1 收敛为 status=3/failure_reason=interrupted 且不发通知;同时存在今天 status=1 时只收敛昨天一行"
],
"exit_status": 0
}
},
{
"capability": "asset-auto-renewal",
"requirement": "资金、价格与单事务闭合",
"spec": "openspec/specs/asset-auto-renewal/spec.md",
"entries": [
"constants.TaskTypeAssetAutoRenewalScan",
"constants.TaskTypeAssetAutoRenewalRecovery",
"constants.OutboxEventTypeAssetAutoRenewalResumeRequested"
],
"handler_consumer_job": [
"internal/infrastructure/assetautorenewal/task.go"
],
"application_service_query": [
"internal/application/assetautorenewal/renew.go",
"internal/service/purchase_validation/service.go"
],
"domain_state_amount": [
"internal/store/postgres/asset_wallet_store.go",
"internal/model/asset_wallet.go",
"pkg/constants/wallet.go"
],
"store_migration_config": [
"internal/store/postgres/asset_wallet_transaction_store.go",
"internal/store/postgres/asset_auto_renewal_store.go"
],
"verification": {
"command": "代码中检索单事务闭合与可用余额扣款,并核对隔离库场景 7.6、7.7 与 7.10(证据全文 /tmp/aug26-010-verify/evidence/7.10.txt、/tmp/aug26-010-verify/evidence/7.6.txt、/tmp/aug26-010-verify/evidence/7.7.txt静态门禁本轮全绿go build ./cmd/api ./cmd/worker、go vet ./internal/... ./pkg/... ./cmd/...、go run cmd/gendocs/main.go、openspec validate --all35 passed/0 failed、openspec doctor --jsonhealthy=true均 exit 0",
"literal_output": [
"internal/application/assetautorenewal/renew.go:74:\terr = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {",
"internal/application/assetautorenewal/renew.go:195:\tif wallet.GetAvailableBalance() < price {",
"internal/store/postgres/asset_wallet_store.go:78:func (s *AssetWalletStore) DeductBalanceWithTx(ctx context.Context, tx *gorm.DB, walletID uint, amount int64, version int) error",
"internal/model/asset_wallet.go:32:// GetAvailableBalance 获取可用余额 = balance - frozen_balance",
"隔离库场景 7.10/tmp/aug26-010-verify/evidence/7.10.txt尝试行 renew_price=deduct_amount=3000、balance 100000→97000订单 payment_status=2/total_amount=3000、明细 unit_price=3000、支付 status=2/amount=3000、流水 transaction_type=deduct/amount=-3000/reference_no=order_no 与套餐使用记录同事务提交后同时可见;无关钱包 9900003 保持 100000/version 0",
"隔离库场景 7.6 与 7.7/tmp/aug26-010-verify/evidence/7.6.txt、/tmp/aug26-010-verify/evidence/7.7.txt余额不足 2000<3000、冻结占用 5000/3000、套餐不可续费四类来源均不建订单不扣款钱包 version 保持 0冻结轮 balance 保持 5000 不侵占冻结额"
],
"exit_status": 0
}
},
{
"capability": "asset-auto-renewal",
"requirement": "失败通知与接收人",
"spec": "openspec/specs/asset-auto-renewal/spec.md",
"entries": [
"constants.TaskTypeAssetAutoRenewalScan",
"constants.TaskTypeAssetAutoRenewalRecovery",
"constants.OutboxEventTypeAssetAutoRenewalResumeRequested"
],
"handler_consumer_job": [
"internal/infrastructure/notification/registry.go",
"internal/infrastructure/notification/delivery.go"
],
"application_service_query": [
"internal/application/assetautorenewal/notify.go",
"internal/application/notification/direct.go",
"internal/application/notification/read.go"
],
"domain_state_amount": [
"pkg/constants/asset_auto_renewal.go",
"pkg/constants/notification.go"
],
"store_migration_config": [
"internal/infrastructure/messaging/outbox",
"internal/query/notification/query.go"
],
"verification": {
"command": "代码中检索通知类型注册与可通知原因白名单,并核对隔离库场景 7.6 的通知落地(证据全文 /tmp/aug26-010-verify/evidence/7.6.txt静态门禁本轮全绿go build ./cmd/api ./cmd/worker、go vet ./internal/... ./pkg/... ./cmd/...、go run cmd/gendocs/main.go、openspec validate --all35 passed/0 failed、openspec doctor --jsonhealthy=true均 exit 0",
"literal_output": [
"internal/infrastructure/notification/registry.go:162:\t\tconstants.NotificationTypeAssetAutoRenewalFailed: {",
"internal/infrastructure/notification/registry.go:163:\t\t\tType: constants.NotificationTypeAssetAutoRenewalFailed, Category: constants.NotificationCategoryExpiry,",
"internal/application/assetautorenewal/notify.go:43:\tif !constants.IsAssetAutoRenewalNotifiableFailureReason(request.Reason) {",
"隔离库场景 7.6/tmp/aug26-010-verify/evidence/7.6.txt可用余额 2000<3000 记 insufficient_balance 后落地 3 条通知 —— personal_customer/9900001 与 account/9900001店铺业务员、account/9900002店铺账号均 type=asset.auto_renewal.failed、category=expiry、severity=warning、ref_type=iot_card、ref_id=9900001 且 expires_at 非空;当日重复扫描仍为 3 条",
"隔离库场景 7.13/tmp/aug26-010-verify/evidence/7.13.txt收敛产生的 interrupted 不属于通知口径,收敛前后通知数为 0"
],
"exit_status": 0
}
},
{
"capability": "asset-auto-renewal",
"requirement": "成功后的可靠复机",
"spec": "openspec/specs/asset-auto-renewal/spec.md",
"entries": [
"constants.TaskTypeAssetAutoRenewalScan",
"constants.TaskTypeAssetAutoRenewalRecovery",
"constants.OutboxEventTypeAssetAutoRenewalResumeRequested"
],
"handler_consumer_job": [
"internal/infrastructure/assetautorenewal/task.go",
"internal/infrastructure/assetautorenewal/commander.go",
"cmd/worker/main.go"
],
"application_service_query": [
"internal/application/assetautorenewal/recovery.go",
"internal/application/assetautorenewal/event.go",
"internal/application/assetautorenewal/renew.go"
],
"domain_state_amount": [
"internal/service/iot_card/auto_renewal.go",
"internal/service/iot_card/stop_resume_service.go",
"pkg/constants/asset_auto_renewal.go"
],
"store_migration_config": [
"migrations/000230_add_asset_auto_renewal_attempt.up.sql",
"internal/store/postgres/asset_auto_renewal_store.go"
],
"verification": {
"command": "代码中检索复机 Outbox 事件、消费者与恢复扫描口径,并核对隔离库场景 7.11 与 7.12(含本机 stub 网关真实调用链)(证据全文 /tmp/aug26-010-verify/evidence/7.11.txt、/tmp/aug26-010-verify/evidence/7.12.txt静态门禁本轮全绿go build ./cmd/api ./cmd/worker、go vet ./internal/... ./pkg/... ./cmd/...、go run cmd/gendocs/main.go、openspec validate --all35 passed/0 failed、openspec doctor --jsonhealthy=true均 exit 0",
"literal_output": [
"internal/application/assetautorenewal/recovery.go:26:// RecoverResumeResults 扫描未收敛的复机子结果:只查询运营商状态回填,绝不重复发起复机调用。",
"internal/application/assetautorenewal/recovery.go:65:\tonline, known, integrationID, err := s.resume.QueryAutoRenewalResumeState(ctx, attempt.AssetType, attempt.AssetID)",
"internal/infrastructure/assetautorenewal/task.go:51:func NewRecoveryTaskHandler(service *assetAutoRenewalApp.Service) *RecoveryTaskHandler {",
"隔离库场景 7.11/tmp/aug26-010-verify/evidence/7.11.txt复机条件不成立时 resume_status=1跳过、无 asset_auto_renewal.resume.requested 事件、通知 0订单/支付/流水事实完好",
"隔离库场景 7.12/tmp/aug26-010-verify/evidence/7.12.txt进程内 127.0.0.1 stub 网关按 Gateway 契约返回「正常」时 RecoveryResult Confirmed=1 且集成日志记 query_card_status/trigger_scene=asset_auto_renewal_resume_recovery/trigger_source=scheduler网关不可达或返回「停机」时 Pending=1、Anomaly=1、resume_integration_id 非空、通知 0恢复前后 resume 投递事件数不变"
],
"exit_status": 0
}
},
{
"capability": "asset-auto-renewal",
"requirement": "尝试记录与可追溯字段",
"spec": "openspec/specs/asset-auto-renewal/spec.md",
"entries": [
"constants.TaskTypeAssetAutoRenewalScan",
"constants.TaskTypeAssetAutoRenewalRecovery",
"constants.OutboxEventTypeAssetAutoRenewalResumeRequested"
],
"handler_consumer_job": [
"internal/application/assetautorenewal/scan.go"
],
"application_service_query": [
"internal/application/assetautorenewal/scan.go",
"internal/application/assetautorenewal/renew.go"
],
"domain_state_amount": [
"internal/model/asset_auto_renewal.go",
"pkg/constants/asset_auto_renewal.go"
],
"store_migration_config": [
"migrations/000230_add_asset_auto_renewal_attempt.up.sql"
],
"verification": {
"command": "代码中检索尝试记录可追溯字段定义,并核对隔离库场景 7.10 与 7.5 的实际列取值(证据全文 /tmp/aug26-010-verify/evidence/7.10.txt、/tmp/aug26-010-verify/evidence/7.5.txt静态门禁本轮全绿go build ./cmd/api ./cmd/worker、go vet ./internal/... ./pkg/... ./cmd/...、go run cmd/gendocs/main.go、openspec validate --all35 passed/0 failed、openspec doctor --jsonhealthy=true均 exit 0",
"literal_output": [
"internal/model/asset_auto_renewal.go:65:\tRenewPrice int64 `gorm:\"column:renew_price;type:bigint;not null;default:0;comment:执行时当前可售续费价(分)\" json:\"renew_price\"`",
"internal/model/asset_auto_renewal.go:70:\tWalletTransactionID uint `gorm:\"column:wallet_transaction_id;type:bigint;not null;default:0;comment:资产钱包流水标识tb_asset_wallet_transaction.id\"`",
"internal/model/asset_auto_renewal.go:84:\tResumeIntegrationID string `gorm:\"column:resume_integration_id;type:varchar(64);not null;default:'';comment:复机外部交互标识Integration Log 标识)\"`",
"internal/model/asset_auto_renewal.go:93:\tAttemptSeq int `gorm:\"column:attempt_seq;type:integer;not null;default:1;comment:跨日尝试次数\" json:\"attempt_seq\"`",
"隔离库场景 7.10/tmp/aug26-010-verify/evidence/7.10.txt成功尝试行含 renew_price=3000、deduct_amount=3000、wallet_id=9900001、wallet_transaction_id>0、balance_before=100000、balance_after=97000、order_id>0、order_no=ORD…、current_usage_id/current_package_id、customer_id=9900001、shop_id=9900001、config_version=1、window_days=15、attempt_seq",
"隔离库场景 7.5 与 7.8/tmp/aug26-010-verify/evidence/7.5.txt、/tmp/aug26-010-verify/evidence/7.8.txt跳过尝试行只含 skip_reason 且 order_id=0、order_no 为空、deduct_amount=0"
],
"exit_status": 0
}
},
{
"capability": "asset-auto-renewal",
"requirement": "手动续购优先",
"spec": "openspec/specs/asset-auto-renewal/spec.md",
"entries": [
"constants.TaskTypeAssetAutoRenewalScan",
"constants.TaskTypeAssetAutoRenewalRecovery",
"constants.OutboxEventTypeAssetAutoRenewalResumeRequested"
],
"handler_consumer_job": [
"internal/application/assetautorenewal/scan.go"
],
"application_service_query": [
"internal/application/assetautorenewal/renew.go",
"internal/query/assetautorenewal/query.go"
],
"domain_state_amount": [
"pkg/constants/asset_auto_renewal.go"
],
"store_migration_config": [
"internal/store/postgres/asset_auto_renewal_store.go",
"migrations/000230_add_asset_auto_renewal_attempt.up.sql"
],
"verification": {
"command": "代码中检索跳过原因常量与锁后重读的资格不变式,并核对隔离库场景 7.8 与 7.9(证据全文 /tmp/aug26-010-verify/evidence/7.8.txt、/tmp/aug26-010-verify/evidence/7.9.txt静态门禁本轮全绿go build ./cmd/api ./cmd/worker、go vet ./internal/... ./pkg/... ./cmd/...、go run cmd/gendocs/main.go、openspec validate --all35 passed/0 failed、openspec doctor --jsonhealthy=true均 exit 0",
"literal_output": [
"pkg/constants/asset_auto_renewal.go:80:\tAssetAutoRenewalSkipManualRenewed = \"manual_renewed\"",
"pkg/constants/asset_auto_renewal.go:82:\tAssetAutoRenewalSkipManualOrderPending = \"manual_order_pending\"",
"internal/application/assetautorenewal/renew.go:// 1. 先判资格不变式——已存在待生效主套餐即「人工已完成续购 / 不叠加周期」,无论最终到期被推到多远",
"隔离库场景 7.8/tmp/aug26-010-verify/evidence/7.8.txt存在待生效主套餐时 status=4/skip_reason=manual_renewed/failure_detail=锁后重读发现该资产已存在待生效主套餐paid_orders=0、deduct_amount=0、待生效仍 1 条;含占位写入后原子出现待生效的并发形态轮次",
"隔离库场景 7.9/tmp/aug26-010-verify/evidence/7.9.txt未关闭待支付个人资产钱包主套餐订单在场时 status=4/skip_reason=manual_order_pending、通知 0、订单保持 payment_status=1关闭订单后同一资产转为 status=2 成功",
"隔离库场景 7.14/tmp/aug26-010-verify/evidence/7.14.txt3 个独立进程并发触发同一资产仅 1 笔已支付续购paid_orders=1、flow_rows=1、balance 97000、version=1"
],
"exit_status": 0
}
},
{
"capability": "asset-auto-renewal",
"requirement": "不得因钱包余额跳过停机判定",
"spec": "openspec/specs/asset-auto-renewal/spec.md",
"entries": [
"constants.TaskTypeAssetAutoRenewalScan",
"constants.TaskTypeAssetAutoRenewalRecovery",
"constants.OutboxEventTypeAssetAutoRenewalResumeRequested"
],
"handler_consumer_job": [
"internal/infrastructure/assetautorenewal/commander.go"
],
"application_service_query": [
"internal/service/iot_card/auto_renewal.go",
"internal/service/iot_card/stop_resume_service.go"
],
"domain_state_amount": [
"internal/domain/cardobservation/network.go",
"pkg/constants/iot.go"
],
"store_migration_config": [
"pkg/config/defaults/config.yaml"
],
"verification": {
"command": "代码中检索复机判定仅只读复用既有停复机单一事实源且未新增运行期开关,并核对隔离库场景 7.11(证据全文 /tmp/aug26-010-verify/evidence/7.11.txt静态门禁本轮全绿go build ./cmd/api ./cmd/worker、go vet ./internal/... ./pkg/... ./cmd/...、go run cmd/gendocs/main.go、openspec validate --all35 passed/0 failed、openspec doctor --jsonhealthy=true均 exit 0",
"literal_output": [
"internal/service/iot_card/auto_renewal.go:38:func (s *StopResumeService) AutoRenewalResumeReady(ctx context.Context, assetType string, assetID uint) (bool, string, error) {",
"internal/service/iot_card/auto_renewal.go:98:// 判定不成立时返回 Applied=false 且不发起任何调用;判定成立但调用失败或结果未知时,续费事实不回滚,结果交可靠任务与恢复扫描收敛。",
"rg -c 'auto_renewal|AutoRenewal' pkg/config/defaults/config.yaml → 输出 0无匹配exit 1未新增任何自动续费运行期开关",
"隔离库场景 7.11/tmp/aug26-010-verify/evidence/7.11.txt续购成功但新主套餐按既有规则为待生效时未发起复机resume_status=1 跳过、无 resume 投递事件、通知 0停机与复机判定未被本能力改动"
],
"exit_status": 0
}
},
{
"capability": "export-time-filter",
"requirement": "统一时间筛选参数与解析契约",
"spec": "openspec/specs/export-time-filter/spec.md",
"entries": [
"/api/admin/iot-cards/import-tasks",
"/api/admin/devices/import/tasks",
"/api/admin/export-tasks",
"/api/admin/orders",
"/api/admin/exchanges",
"/api/admin/asset-allocation-records",
"/api/admin/agent-recharges",
"/api/admin/shops/{shop_id}/commission-records",
"/api/admin/commission/withdrawal-requests",
"/api/admin/shops/{shop_id}/withdrawal-requests",
"/api/admin/authorizations",
"/api/admin/expiring-assets"
],
"handler_consumer_job": [
"internal/handler/admin/exchange.go",
"internal/handler/admin/asset.go",
"internal/handler/admin/authorization.go",
"internal/model/dto/export_task_dto.go"
],
"application_service_query": [
"internal/query/packageexpiry/list.go",
"internal/query/exchange/list.go"
],
"domain_state_amount": [
"pkg/utils/time_range.go"
],
"store_migration_config": [
"internal/store/postgres/commission_record_store.go"
],
"verification": {
"command": "rg -n --sort path 'timeFilterLayout|timeFilterPattern|func ParseTimeRange|FormatTimeFilterValue|TimeFilterFormatError|timeFilterFormatMessageSuffix|timeFilterOrderMessage' pkg/utils/time_range.go",
"literal_output": [
"19:// timeFilterLayout 统一时间筛选唯一接受的格式:带显式时区的秒级 RFC3339。",
"21:const timeFilterLayout = \"2006-01-02T15:04:05Z07:00\"",
"23:// timeFilterFormatMessageSuffix 格式非法时的固定中文消息后缀,前缀为字段名。",
"24:const timeFilterFormatMessageSuffix = \" 时间格式不合法,必须为带时区的 RFC3339 秒级时间,例如 2026-09-01T00:00:00+08:00\"",
"26:// timeFilterOrderMessage 开始时间晚于结束时间时的固定中文消息。",
"27:const timeFilterOrderMessage = \"start_time 不能晚于 end_time\"",
"34:func ParseTimeRange(start, end string) (*time.Time, *time.Time, error) {",
"44:\t\treturn nil, nil, errors.New(errors.CodeInvalidParam, timeFilterOrderMessage)",
"49:// FormatTimeFilterValue 把时间边界规范化为创建期冻结用的 UTC RFC3339 秒级字符串。",
"50:func FormatTimeFilterValue(value time.Time) string {",
"51:\treturn value.UTC().Format(timeFilterLayout)",
"54:// TimeFilterFormatError 返回指明字段名的统一时间格式错误,供导出执行期校验冻结值时复用同一消息。",
"55:func TimeFilterFormatError(field string) error {",
"56:\treturn errors.New(errors.CodeInvalidParam, field+timeFilterFormatMessageSuffix)",
"59:// timeFilterPattern 锁定统一时间筛选的完整 RFC3339 形状:日期与时间全部补零、秒级、带显式时区。",
"63:var timeFilterPattern = regexp.MustCompile(`^\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\\d|3[01])T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)$`)",
"71:\t// 允许集合的完整形状由 timeFilterPattern 再次锁定。",
"73:\t\treturn nil, TimeFilterFormatError(field)",
"75:\tif !timeFilterPattern.MatchString(value) {",
"76:\t\treturn nil, TimeFilterFormatError(field)",
"78:\tparsed, err := time.Parse(timeFilterLayout, value)",
"80:\t\treturn nil, TimeFilterFormatError(field)"
],
"exit_status": 0
}
},
{
"capability": "export-time-filter",
"requirement": "受影响端点与旧格式替换",
"spec": "openspec/specs/export-time-filter/spec.md",
"entries": [
"/api/admin/exchanges",
"/api/admin/asset-allocation-records",
"/api/admin/agent-recharges",
"/api/admin/shops/{shop_id}/commission-records",
"/api/admin/commission/withdrawal-requests",
"/api/admin/shops/{shop_id}/withdrawal-requests",
"/api/admin/authorizations",
"/api/admin/expiring-assets",
"/api/admin/employee-collection-bills",
"/api/admin/polling-priority-items",
"/api/admin/carrier-traffic-threshold-hits"
],
"handler_consumer_job": [
"internal/handler/admin/exchange.go",
"internal/handler/admin/agent_recharge.go",
"internal/handler/admin/authorization.go",
"internal/handler/admin/asset.go"
],
"application_service_query": [
"internal/query/exchange/list.go",
"internal/service/agent_recharge/service.go",
"internal/service/commission_withdrawal/service.go",
"internal/service/shop_commission/service.go",
"internal/query/packageexpiry/list.go"
],
"domain_state_amount": [
"internal/exporter/time_filters.go"
],
"store_migration_config": [
"internal/store/postgres/enterprise_card_authorization_store.go",
"internal/store/postgres/asset_allocation_record_store.go"
],
"verification": {
"command": "rg -n --sort path -e 'start_time' -e 'strictTimeRange' -e 'authorized_at <= ?' -e '闭区间' internal/model/dto/agent_recharge_dto.go internal/model/dto/exchange_dto.go internal/service/commission_withdrawal/service.go internal/service/shop_commission/service.go internal/exporter/exchange_scene.go internal/exporter/agent_recharge_scene.go internal/exporter/order_scene.go internal/query/packageexpiry/list.go internal/store/postgres/enterprise_card_authorization_store.go internal/store/postgres/asset_allocation_record_store.go",
"literal_output": [
"internal/model/dto/agent_recharge_dto.go:140:\tStartTime string `json:\"start_time\" query:\"start_time\" description:\"创建时间起始(带时区的 RFC3339 秒级时间,闭区间含该时刻,如 2026-09-01T00:00:00+08:00\"`",
"internal/model/dto/agent_recharge_dto.go:141:\tEndTime string `json:\"end_time\" query:\"end_time\" description:\"创建时间结束(带时区的 RFC3339 秒级时间,闭区间含该时刻,如 2026-09-30T23:59:59+08:00\"`",
"internal/model/dto/exchange_dto.go:24:\tStartTime string `json:\"start_time\" query:\"start_time\" description:\"创建时间起始(带时区的 RFC3339 秒级时间,闭区间含该时刻,如 2026-09-01T00:00:00+08:00\"`",
"internal/model/dto/exchange_dto.go:25:\tEndTime string `json:\"end_time\" query:\"end_time\" description:\"创建时间结束(带时区的 RFC3339 秒级时间,闭区间含该时刻,如 2026-09-30T23:59:59+08:00\"`",
"internal/exporter/exchange_scene.go:124:\tstart, end, err := strictTimeRange(params.Filters)",
"internal/exporter/agent_recharge_scene.go:139:\tstart, end, err := strictTimeRange(params.Filters)",
"internal/exporter/order_scene.go:151:\tstart, end, err := strictTimeRange(params.Filters)",
"internal/query/packageexpiry/list.go:36:// 时间边界由共享严格解析器解析为 UTC 瞬时,按最终到期时刻闭区间比较。",
"internal/query/packageexpiry/list.go:53:// 时间边界由调用方用共享严格解析器解析后传入,按最终到期时刻闭区间比较。",
"internal/query/packageexpiry/list.go:241:\t\t// 按当前生效主套餐的最终到期时刻做闭区间比较,含两端。",
"internal/store/postgres/enterprise_card_authorization_store.go:347:\t\tbaseQuery += \" AND a.authorized_at <= ?\"",
"internal/store/postgres/asset_allocation_record_store.go:81:\tif createdAtStart, ok := filters[\"start_time\"].(time.Time); ok {"
],
"exit_status": 0
}
},
{
"capability": "export-time-filter",
"requirement": "临期导出",
"spec": "openspec/specs/export-time-filter/spec.md",
"entries": [
"/api/admin/expiring-assets",
"/api/admin/expiring-assets/export",
"/api/admin/export-tasks"
],
"handler_consumer_job": [
"internal/handler/admin/asset.go",
"internal/routes/package_expiry.go"
],
"application_service_query": [
"internal/query/packageexpiry/list.go",
"internal/exporter/expiring_asset_scene.go"
],
"domain_state_amount": [
"internal/exporter/ownership_columns.go"
],
"store_migration_config": [
"internal/exporter/registry.go"
],
"verification": {
"command": "rg -n --sort path -e 'ExportTaskSceneExpiringAsset' -e 'ListAllWithScope' -e 'expiring-assets/export' -e 'rejectLegacyTimeParams' pkg/constants/constants.go internal/exporter/expiring_asset_scene.go internal/query/packageexpiry/list.go internal/routes/package_expiry.go internal/exporter/registry.go internal/handler/admin/asset.go internal/handler/admin/time_params.go",
"literal_output": [
"pkg/constants/constants.go:406:\t// ExportTaskSceneExpiringAsset 表示临期资产导出场景,一行对应一项资产,取当前生效主套餐最终到期时间。",
"pkg/constants/constants.go:407:\tExportTaskSceneExpiringAsset = \"expiring_asset\"",
"internal/exporter/expiring_asset_scene.go:32:\treturn constants.ExportTaskSceneExpiringAsset",
"internal/exporter/expiring_asset_scene.go:144:\treturn s.expiryQ.ListAllWithScope(ctx, scope, filter)",
"internal/query/packageexpiry/list.go:92:// ListAllWithScope 按调用方给定的数据范围与筛选返回全部临期资产。",
"internal/query/packageexpiry/list.go:94:func (q *Query) ListAllWithScope(ctx context.Context, scope ScopeApplier, filter ListFilter) ([]dto.ExpiringAssetItem, error) {",
"internal/routes/package_expiry.go:22:\tRegister(router, doc, basePath, \"POST\", \"/expiring-assets/export\", handler.ExportExpiring, RouteSpec{",
"internal/exporter/registry.go:82:\t\tconstants.ExportTaskSceneExpiringAsset,",
"internal/handler/admin/asset.go:171:\tif err := rejectLegacyTimeParams(c, \"expires_from\", \"expires_to\"); err != nil {",
"internal/handler/admin/asset.go:196:// POST /api/admin/expiring-assets/export",
"internal/handler/admin/asset.go:214:\t\tScene: constants.ExportTaskSceneExpiringAsset,",
"internal/handler/admin/time_params.go:9:// rejectLegacyTimeParams 拒绝受影响端点上已废弃的旧时间参数名。",
"internal/handler/admin/time_params.go:12:func rejectLegacyTimeParams(c *fiber.Ctx, keys ...string) error {"
],
"exit_status": 0
}
},
{
"capability": "export-time-filter",
"requirement": "三类异步导出的创建期冻结与不扩大范围",
"spec": "openspec/specs/export-time-filter/spec.md",
"entries": [
"/api/admin/export-tasks",
"/api/admin/expiring-assets/export",
"/api/admin/package-traffic-alerts/export"
],
"handler_consumer_job": [
"internal/handler/admin/export_task.go",
"internal/handler/admin/package_traffic_alert.go"
],
"application_service_query": [
"internal/service/export_task/service.go",
"internal/exporter/time_filters.go"
],
"domain_state_amount": [
"internal/task/export_dispatch.go"
],
"store_migration_config": [
"internal/store/postgres/export_task_store.go"
],
"verification": {
"command": "rg -n --sort path -e 'NormalizeTaskTimeFilters' -e 'ValidateTaskTimeFilters' -e 'ExportTaskInvalidTimeFilterMessage' -e '当前账号无可导出的数据范围' -e 'strictTimeRange' internal/service/export_task/service.go internal/exporter/time_filters.go internal/task/export_dispatch.go pkg/constants/constants.go internal/exporter/package_traffic_alert_scene.go",
"literal_output": [
"internal/service/export_task/service.go:78:\tif err := exporter.NormalizeTaskTimeFilters(req.Scene, req.Query); err != nil {",
"internal/service/export_task/service.go:115:\t\t\treturn nil, errors.New(errors.CodeForbidden, \"当前账号无可导出的数据范围\")",
"internal/exporter/time_filters.go:40:// NormalizeTaskTimeFilters 在创建导出任务时按场景校验并规范化时间边界。",
"internal/exporter/time_filters.go:43:func NormalizeTaskTimeFilters(scene string, query map[string]interface{}) error {",
"internal/exporter/time_filters.go:67:// ValidateTaskTimeFilters 校验导出任务筛选快照中的时间边界,创建期与执行期共用。",
"internal/exporter/time_filters.go:70:func ValidateTaskTimeFilters(scene string, filters map[string]any) error {",
"internal/exporter/time_filters.go:81:// strictTimeRange 解析任务冻结的 start_time/end_time 闭区间,供受影响场景构造执行期筛选。",
"internal/exporter/time_filters.go:83:func strictTimeRange(filters map[string]any) (*time.Time, *time.Time, error) {",
"internal/task/export_dispatch.go:123:\tif err := exporter.ValidateTaskTimeFilters(exportTask.Scene, params.Filters); err != nil {",
"internal/task/export_dispatch.go:125:\t\t_ = h.taskStore.MarkFailed(ctx, exportTask.ID, updater, constants.ExportTaskInvalidTimeFilterMessage)",
"pkg/constants/constants.go:414:// ExportTaskInvalidTimeFilterMessage 是导出任务冻结的时间边界非法时写入 error_message 的安全失败摘要。",
"pkg/constants/constants.go:416:const ExportTaskInvalidTimeFilterMessage = \"导出筛选的时间边界非法,任务已终止\"",
"internal/exporter/package_traffic_alert_scene.go:179:\tstartTime, endTime, err := strictTimeRange(params.Filters)"
],
"exit_status": 0
}
},
{
"capability": "operations-report",
"requirement": "每日报表快照与不回填历史",
"spec": "openspec/specs/operations-report/spec.md",
"entries": [
"constants.TaskTypeOperationsReportSnapshot"
],
"handler_consumer_job": [
"internal/task/operations_report_snapshot.go",
"pkg/queue/handler.go",
"cmd/worker/main.go"
],
"application_service_query": [
"internal/application/operationsreport/generate.go"
],
"domain_state_amount": [
"internal/domain/operationsreport/metrics.go"
],
"store_migration_config": [
"migrations/000231_create_operations_report_snapshot.up.sql",
"migrations/000231_create_operations_report_snapshot.down.sql",
"internal/infrastructure/operationsreport/snapshot_store.go"
],
"verification": {
"command": "rg -n --sort path \"TaskTypeOperationsReportSnapshot\" pkg/constants/constants.go pkg/queue/handler.go cmd/worker/main.go internal/task/operations_report_snapshot.go",
"literal_output": [
"pkg/constants/constants.go:100:\tTaskTypeOperationsReportSnapshot = \"operations:report:snapshot\" // 每日运营报表日报快照生成",
"pkg/constants/constants.go:323:\tcase TaskTypeOperationsReportSnapshot:",
"pkg/queue/handler.go:404:\th.mux.HandleFunc(constants.TaskTypeOperationsReportSnapshot, handler.Handle)",
"pkg/queue/handler.go:405:\th.logger.Info(\"注册每日运营报表日报快照生成任务处理器\", zap.String(\"task_type\", constants.TaskTypeOperationsReportSnapshot))",
"cmd/worker/main.go:1052:\t\tconstants.TaskTypeOperationsReportSnapshot,",
"cmd/worker/main.go:1057:\t\tasynq.Queue(constants.QueueForTaskType(constants.TaskTypeOperationsReportSnapshot)),"
],
"exit_status": 0
}
},
{
"capability": "operations-report",
"requirement": "激活情况指标口径",
"spec": "openspec/specs/operations-report/spec.md",
"entries": [
"/api/admin/operations-reports/activation-summary",
"/api/admin/operations-reports/activation-summary/export"
],
"handler_consumer_job": [
"internal/handler/admin/operations_report.go",
"internal/exporter/operations_report_scene.go"
],
"application_service_query": [
"internal/application/operationsreport/generate.go",
"internal/query/operationsreport/aggregate.go"
],
"domain_state_amount": [
"internal/domain/operationsreport/metrics.go",
"internal/domain/operationsreport/purchase.go"
],
"store_migration_config": [
"internal/infrastructure/operationsreport/source.go",
"migrations/000231_create_operations_report_snapshot.up.sql"
],
"verification": {
"command": "rg -n --sort path \"MBPerGB|func Ratio|func CardAverageGB|func ForecastCardAverageGB|func PurchaseCountAsOf|ValidMainPackageStatuses\" internal/domain/operationsreport/metrics.go internal/domain/operationsreport/purchase.go",
"literal_output": [
"internal/domain/operationsreport/metrics.go:13:// MBPerGB 是报表域自持的流量换算常量1 GB = 1024 MB。",
"internal/domain/operationsreport/metrics.go:15:const MBPerGB = 1024",
"internal/domain/operationsreport/metrics.go:100:func Ratio(numerator, denominator int64) (float64, bool) {",
"internal/domain/operationsreport/metrics.go:109:func CardAverageGB(totalRealTrafficMB float64, denominator int64) (float64, bool) {",
"internal/domain/operationsreport/metrics.go:113:\taverage := totalRealTrafficMB / MBPerGB / float64(denominator)",
"internal/domain/operationsreport/metrics.go:120:func ForecastCardAverageGB(totalRealTrafficMB float64, denominator int64, endDate time.Time) (float64, bool) {",
"internal/domain/operationsreport/purchase.go:26:func PurchaseCountAsOf(ctx context.Context, source PurchaseCountSource, snapshotDate time.Time) (int64, error) {",
"internal/domain/operationsreport/purchase.go:30:// ValidMainPackageStatuses 是「有效主套餐」的状态集合:生效中与已用完。",
"internal/domain/operationsreport/purchase.go:34:var ValidMainPackageStatuses = []int{"
],
"exit_status": 0
}
},
{
"capability": "operations-report",
"requirement": "套餐续费指标口径",
"spec": "openspec/specs/operations-report/spec.md",
"entries": [
"/api/admin/operations-reports/package-renewal-summary",
"/api/admin/operations-reports/package-renewal-summary/export"
],
"handler_consumer_job": [
"internal/handler/admin/operations_report.go",
"internal/exporter/operations_report_scene.go"
],
"application_service_query": [
"internal/application/operationsreport/generate.go",
"internal/query/operationsreport/aggregate.go"
],
"domain_state_amount": [
"internal/domain/operationsreport/renewal.go",
"internal/domain/operationsreport/metrics.go"
],
"store_migration_config": [
"internal/infrastructure/operationsreport/source.go",
"migrations/000231_create_operations_report_snapshot.up.sql"
],
"verification": {
"command": "rg -n --sort path \"func IsRenewed|RenewalRate|RenewalDueAssetCount|RenewalRenewedAssetCount|due_assets\" internal/domain/operationsreport/renewal.go internal/domain/operationsreport/metrics.go internal/application/operationsreport/generate.go internal/query/operationsreport/aggregate.go",
"literal_output": [
"internal/domain/operationsreport/renewal.go:30:func IsRenewed(expired ExpiredMainUsage, candidates []MainUsageCandidate) bool {",
"internal/domain/operationsreport/metrics.go:145:// RenewalRate 计算续费率:续费资产数除以到期资产数。",
"internal/domain/operationsreport/metrics.go:148:func RenewalRate(renewed, due int64) (float64, bool) {",
"internal/application/operationsreport/generate.go:140:\t\t\tzap.Int64(\"renewal_due_asset_count\", snapshot.RenewalDueAssetCount),",
"internal/application/operationsreport/generate.go:141:\t\t\tzap.Int64(\"renewal_renewed_asset_count\", snapshot.RenewalRenewedAssetCount),",
"internal/application/operationsreport/generate.go:236:\tsnapshot.RenewalDueAssetCount = int64(len(dueAssets))",
"internal/application/operationsreport/generate.go:237:\tsnapshot.RenewalRenewedAssetCount = int64(len(renewedAssets))",
"internal/query/operationsreport/aggregate.go:95:\tif rate, ok := domainreport.RenewalRate(renewed, due); ok {",
"internal/query/operationsreport/aggregate.go:96:\t\titem.RenewalRate = &rate",
"internal/query/operationsreport/aggregate.go:103:\treturn renewalMetrics{DueAssets: head.RenewalDueAssetCount, RenewedAssets: head.RenewalRenewedAssetCount}",
"internal/query/operationsreport/aggregate.go:132:\tDueAssets int64 `gorm:\"column:due_assets\"`",
"internal/query/operationsreport/aggregate.go:304:\t\t\"COUNT(DISTINCT asset_type || ':' || asset_id) AS due_assets\","
],
"exit_status": 0
}
},
{
"capability": "operations-report",
"requirement": "报表维度分组与归属冻结",
"spec": "openspec/specs/operations-report/spec.md",
"entries": [
"/api/admin/operations-reports/activation-summary",
"/api/admin/operations-reports/activation-trend",
"/api/admin/operations-reports/package-renewal-summary",
"/api/admin/operations-reports/package-renewal-trend"
],
"handler_consumer_job": [
"internal/handler/admin/operations_report.go"
],
"application_service_query": [
"internal/query/operationsreport/aggregate.go"
],
"domain_state_amount": [
"internal/domain/operationsreport/dimension.go"
],
"store_migration_config": [
"internal/infrastructure/operationsreport/source.go",
"migrations/000231_create_operations_report_snapshot.up.sql"
],
"verification": {
"command": "rg -n --sort path \"DimensionAll =|PlaceholderUnset =|RootShopName|AgentAccountID\" internal/domain/operationsreport/dimension.go internal/infrastructure/operationsreport/source.go",
"literal_output": [
"internal/domain/operationsreport/dimension.go:27:const DimensionAll = \"全部\"",
"internal/domain/operationsreport/dimension.go:50:const PlaceholderUnset = \"未设置\"",
"internal/infrastructure/operationsreport/source.go:189:\t\t\tRootShopName: attribute.RootShopName,",
"internal/infrastructure/operationsreport/source.go:190:\t\t\tAgentAccountID: attribute.AgentAccountID,",
"internal/infrastructure/operationsreport/source.go:232:\t\t\tRootShopName: attribute.RootShopName,",
"internal/infrastructure/operationsreport/source.go:233:\t\t\tAgentAccountID: attribute.AgentAccountID,",
"internal/infrastructure/operationsreport/source.go:359:\tRootShopName string",
"internal/infrastructure/operationsreport/source.go:360:\tAgentAccountID *uint",
"internal/infrastructure/operationsreport/source.go:380:\t\tattribute.RootShopName = o.shops[rootID].ShopName",
"internal/infrastructure/operationsreport/source.go:384:\t\t\tattribute.AgentAccountID = &agentID"
],
"exit_status": 0
}
},
{
"capability": "operations-report",
"requirement": "报表查询与趋势",
"spec": "openspec/specs/operations-report/spec.md",
"entries": [
"/api/admin/operations-reports/activation-summary",
"/api/admin/operations-reports/activation-trend",
"/api/admin/operations-reports/package-renewal-summary",
"/api/admin/operations-reports/package-renewal-trend"
],
"handler_consumer_job": [
"internal/handler/admin/operations_report.go"
],
"application_service_query": [
"internal/query/operationsreport/query.go",
"internal/query/operationsreport/periods.go"
],
"domain_state_amount": [
"internal/domain/operationsreport/metrics.go"
],
"store_migration_config": [
"internal/model/dto/operations_report_dto.go"
],
"verification": {
"command": "rg -n --sort path \"utils.ParseTimeRange|granularity 只能为 day 或 month|has_snapshot|snapshot_dates\" internal/query/operationsreport/query.go internal/model/dto/operations_report_dto.go",
"literal_output": [
"internal/query/operationsreport/query.go:43:\tstart, end, err := utils.ParseTimeRange(request.StartTime, request.EndTime)",
"internal/query/operationsreport/query.go:138:\t\treturn nil, errors.New(errors.CodeInvalidParam, \"granularity 只能为 day 或 month\")",
"internal/query/operationsreport/query.go:140:\tstart, end, err := utils.ParseTimeRange(request.StartTime, request.EndTime)",
"internal/query/operationsreport/query.go:232:\tstart, end, err := utils.ParseTimeRange(request.StartTime, request.EndTime)",
"internal/query/operationsreport/query.go:304:\t\treturn nil, errors.New(errors.CodeInvalidParam, \"granularity 只能为 day 或 month\")",
"internal/query/operationsreport/query.go:306:\tstart, end, err := utils.ParseTimeRange(request.StartTime, request.EndTime)",
"internal/model/dto/operations_report_dto.go:54:\tHasSnapshot bool `json:\"has_snapshot\" description:\"结束日是否存在快照;为 false 时累计类与派生指标为空且分组行为空集\"`",
"internal/model/dto/operations_report_dto.go:55:\tSnapshotDates []string `json:\"snapshot_dates\" description:\"区间内实际命中的快照日期yyyy-MM-dd\"`",
"internal/model/dto/operations_report_dto.go:90:\tHasSnapshot bool `json:\"has_snapshot\" description:\"结束日是否存在快照;为 false 时全部指标为空且分组行为空集\"`",
"internal/model/dto/operations_report_dto.go:91:\tSnapshotDates []string `json:\"snapshot_dates\" description:\"区间内实际命中的快照日期yyyy-MM-dd\"`"
],
"exit_status": 0
}
},
{
"capability": "operations-report",
"requirement": "报表权限、数据范围与导出冻结",
"spec": "openspec/specs/operations-report/spec.md",
"entries": [
"/api/admin/operations-reports/activation-summary/export",
"/api/admin/operations-reports/package-renewal-summary/export"
],
"handler_consumer_job": [
"internal/routes/operations_report.go",
"internal/handler/admin/operations_report.go"
],
"application_service_query": [
"internal/service/export_task/service.go"
],
"domain_state_amount": [
"internal/exporter/time_filters.go"
],
"store_migration_config": [
"internal/exporter/registry.go",
"internal/exporter/operations_report_scene.go"
],
"verification": {
"command": "rg -n --sort path \"ensureOperationsReportExportAllowed|frozenQueryContext|PlatformManagementForbiddenMessage\" internal/exporter/operations_report_scene.go internal/routes/operations_report.go internal/query/operationsreport/query.go",
"literal_output": [
"internal/exporter/operations_report_scene.go:76:\tif err := ensureOperationsReportExportAllowed(params); err != nil {",
"internal/exporter/operations_report_scene.go:83:\tresponse, err := s.query.ActivationSummary(frozenQueryContext(ctx, params), request)",
"internal/exporter/operations_report_scene.go:178:\tif err := ensureOperationsReportExportAllowed(params); err != nil {",
"internal/exporter/operations_report_scene.go:185:\tresponse, err := s.query.RenewalSummary(frozenQueryContext(ctx, params), request)",
"internal/exporter/operations_report_scene.go:298:// frozenQueryContext 以任务内冻结的账号类型与可见店铺范围构造查询上下文。",
"internal/exporter/operations_report_scene.go:300:func frozenQueryContext(ctx context.Context, params ExportParams) context.Context {",
"internal/exporter/operations_report_scene.go:307:// ensureOperationsReportExportAllowed 只允许超级管理员与平台账号使用运营报表导出场景。",
"internal/exporter/operations_report_scene.go:309:func ensureOperationsReportExportAllowed(params ExportParams) error {",
"internal/exporter/operations_report_scene.go:313:\treturn errors.New(errors.CodeForbidden, constants.PlatformManagementForbiddenMessage)",
"internal/routes/operations_report.go:24:\t\t\treturn errors.New(errors.CodeForbidden, constants.PlatformManagementForbiddenMessage)",
"internal/query/operationsreport/query.go:353:\t\treturn errors.New(errors.CodeForbidden, constants.PlatformManagementForbiddenMessage)"
],
"exit_status": 0
}
}
]